Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.2.0 #7

Merged
merged 19 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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