Skip to content

Commit

Permalink
Merge pull request #7 from minos-framework/0.2.0
Browse files Browse the repository at this point in the history
v0.2.0
  • Loading branch information
vladyslav-fenchak authored Feb 16, 2022
2 parents f5b65bf + a7f793c commit 47d475a
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 15 deletions.
7 changes: 7 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@
## 0.1.0 (2022-02-03)

* First release on PyPI.

## 0.2.0 (2022-02-16)

* Set roles on config file.
* Set default role on config file.
* Populate roles into database.
* On auth creation assign default role (defined in config file).
2 changes: 1 addition & 1 deletion minos/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
__author__ = """Clariteia Devs"""
__email__ = "devs@clariteia.com"
__version__ = "0.1.0"
__version__ = "0.2.0"

from .config import (
AuthConfig,
Expand Down
22 changes: 22 additions & 0 deletions minos/auth/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
from pathlib import (
Path,
)
from typing import (
Any,
)

import yaml

Expand All @@ -24,6 +27,8 @@
USER_SERVICE = collections.namedtuple("UserService", "host port path")
CREDENTIAL_SERVICE = collections.namedtuple("CredentialService", "host port path create_token")
TOKEN_SERVICE = collections.namedtuple("TokenService", "host port path create_token")
ROLES = collections.namedtuple("Roles", "roles default")
ROLE = collections.namedtuple("Role", "code name")

_ENVIRONMENT_MAPPER = {
"rest.host": "AUTH_REST_HOST",
Expand Down Expand Up @@ -168,3 +173,20 @@ def token_service(self) -> TOKEN_SERVICE:
path=str(self._get("token-service.path")),
create_token=self._get("token-service.create-token"),
)

@property
def roles(self) -> ROLES:
"""Get the rest config.
:return: A ``REST`` NamedTuple instance.
"""
return ROLES(roles=self._roles, default=str(self._get("roles.default")),)

@property
def _roles(self) -> list[ROLE]:
info = self._get("roles.roles")
roles = [self._role_entry(role) for role in info]
return roles

def _role_entry(self, service: dict[str, Any]) -> ROLE:
return ROLE(name=service["name"], code=service["code"],)
36 changes: 36 additions & 0 deletions minos/auth/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from sqlalchemy import (
TIMESTAMP,
Column,
ForeignKey,
Integer,
String,
)
Expand All @@ -15,6 +16,9 @@
from sqlalchemy.ext.declarative import (
declarative_base,
)
from sqlalchemy.orm import (
relationship,
)

Base = declarative_base()

Expand All @@ -32,6 +36,8 @@ class Authentication(Base):
user_uuid = Column(String)
user_id = Column(String)
token = Column(String)
role_code = Column(Integer, ForeignKey("roles.code"))
role = relationship("Role", backref="parents")
created_at = Column(TIMESTAMP)
updated_at = Column(TIMESTAMP)

Expand All @@ -42,3 +48,33 @@ def __repr__(self):
self.uuid, AuthType(self.auth_type), self.auth_uuid, self.created_at, self.updated_at
)
)

def to_serializable_dict(self):
return {
"uuid": str(self.uuid),
"auth_type": AuthType(self.auth_type).value,
"auth_name": AuthType(self.auth_type).name,
"auth_uuid": self.auth_uuid,
"user_uuid": self.user_uuid,
"user_id": self.user_id,
"token": self.token,
"role": self.role.to_serializable_dict(),
"created_at": str(self.created_at),
"updated_at": str(self.updated_at),
}


class Role(Base):
__tablename__ = "roles"
code = Column(Integer, primary_key=True)
role_name = Column(String)
created_at = Column(TIMESTAMP)
updated_at = Column(TIMESTAMP)

def to_serializable_dict(self):
return {
"code": self.code,
"role_name": self.role_name,
"created_at": str(self.created_at),
"updated_at": str(self.updated_at),
}
68 changes: 59 additions & 9 deletions minos/auth/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .database.models import (
Authentication,
AuthType,
Role,
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -59,7 +60,7 @@ async def register_credentials(request: web.Request) -> web.Response:
request, token, content["username"], user_uuid, credential_uuid, AuthType.CREDENTIAL.value
)
else:
return credentials_response
return credentials_response # pragma: no cover

return user_creation

Expand Down Expand Up @@ -94,7 +95,7 @@ async def register_token(request: web.Request) -> web.Response:
)
return token_response

return user_creation
return user_creation # pragma: no cover


async def credentials_login(request: web.Request) -> web.Response:
Expand Down Expand Up @@ -141,7 +142,7 @@ async def validate_credentials(request: web.Request):
token = await get_credential_token(request, credential_uuid)
return web.json_response({"token": token}), token

return response, None
return response, None # pragma: no cover


async def get_credential_token(request: web.Request, credential_uuid: str):
Expand Down Expand Up @@ -177,22 +178,28 @@ async def get_token_user(request: web.Request, token: str, auth_type: AuthType):
s = session()

r = s.query(Authentication).filter(Authentication.token == token).order_by(desc(Authentication.updated_at)).first()
role = r.role.code
s.close()

if r is not None:
if r.auth_type == auth_type.value:
user_call_response = await get_user_call(request, r.user_uuid)
return user_call_response
response = await get_user_call(request, r.user_uuid)

if response.status == 200:
resp_json = json.loads(response.text)
resp_json["role"] = role
return web.json_response(resp_json)
return response # pragma: no cover

return web.HTTPBadRequest(text="Please provide correct Token.")
return web.HTTPBadRequest(text="Please provide correct Token.") # pragma: no cover


async def get_user_from_credentials(request: web.Request) -> web.Response:
resp, token = await validate_credentials(request)

if resp.status == 200:
return await get_token_user(request, token, AuthType.CREDENTIAL)
return resp
return resp # pragma: no cover


async def validate_token(request: web.Request) -> web.Response:
Expand All @@ -207,21 +214,35 @@ async def validate_token(request: web.Request) -> web.Response:
s = session()

r = s.query(Authentication).filter(Authentication.token == token).order_by(desc(Authentication.updated_at)).first()
role = None
if r is not None:
role = r.role.code
s.close()

if r is not None:

if r.auth_type == AuthType.TOKEN.value:
token_resp = await validate_token_call(request)

if token_resp.status == 200:
return await get_user_call(request, r.user_uuid)
return await user_call(request, r.user_uuid, role)

if r.auth_type == AuthType.CREDENTIAL.value:
return await get_user_call(request, r.user_uuid)
return await user_call(request, r.user_uuid, role)

return web.json_response({"error": "Please provide correct Token."}, status=400)


async def user_call(request: web.Request, user_uuid, role):
response = await get_user_call(request, user_uuid)

if response.status == 200:
resp_json = json.loads(response.text)
resp_json["role"] = role
return web.json_response(resp_json)
return response # pragma: no cover


async def get_user_call(request: web.Request, user_uuid: str) -> web.Response:
""" Get User by Session token """
user_host = request.app["config"].user_service.host
Expand Down Expand Up @@ -339,6 +360,7 @@ async def create_authentication(
user_id=user_id,
token=token,
auth_type=auth_type,
role_code=int(request.app["config"].roles.default),
created_at=now,
updated_at=now,
)
Expand All @@ -359,3 +381,31 @@ async def _get_authorization_token(request: web.Request):
raise Exception
except Exception as e:
raise e


class RoleRest:
@staticmethod
async def get_roles(request: web.Request):
session = sessionmaker(bind=request.app["db_engine"])

s = session()
records = s.query(Role).all()
res = list()
for record in records:
res.append(record.to_serializable_dict())
s.close()
return web.json_response(res)


class AuthenticationRest:
@staticmethod
async def get_all(request: web.Request):
session = sessionmaker(bind=request.app["db_engine"])

s = session()
records = s.query(Authentication).all()
res = list()
for record in records:
res.append(record.to_serializable_dict())
s.close()
return web.json_response(res)
27 changes: 27 additions & 0 deletions minos/auth/service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import logging
from datetime import (
datetime,
)

from aiohttp import (
web,
Expand All @@ -9,14 +12,20 @@
from sqlalchemy import (
create_engine,
)
from sqlalchemy.orm import (
sessionmaker,
)

from .config import (
AuthConfig,
)
from .database.models import (
Base,
Role,
)
from .handler import (
AuthenticationRest,
RoleRest,
credentials_login,
get_user_from_credentials,
get_user_from_token,
Expand All @@ -41,6 +50,7 @@ async def create_application(self) -> web.Application:
app["config"] = self.config
self.engine = await self.create_engine()
await self.create_database()
await self.populate_database()

app["db_engine"] = self.engine

Expand All @@ -54,6 +64,10 @@ async def create_application(self) -> web.Application:

app.router.add_route("POST", "/auth/validate-token", validate_token)

app.router.add_route("GET", "/auth/roles", RoleRest.get_roles)

app.router.add_route("GET", "/auth/all", AuthenticationRest.get_all)

return app

async def create_engine(self):
Expand All @@ -66,3 +80,16 @@ async def create_engine(self):

async def create_database(self):
Base.metadata.create_all(self.engine)

async def populate_database(self):
session = sessionmaker(bind=self.engine)
s = session()
now = datetime.now()

for role in self.config.roles.roles:
instance = s.query(Role).filter(Role.code == role.code, Role.role_name == role.name).one_or_none()
if instance is None: # pragma: no cover
r = Role(code=role.code, role_name=role.name, created_at=now, updated_at=now,)
s.add(r)
s.commit()
s.close()
Loading

0 comments on commit 47d475a

Please sign in to comment.