Skip to content

Commit

Permalink
Add app session test
Browse files Browse the repository at this point in the history
  • Loading branch information
mawandm committed May 22, 2024
1 parent 0304597 commit e0f73e1
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 39 deletions.
62 changes: 48 additions & 14 deletions nesis/api/core/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
User,
Datasource,
Task,
AppRole,
App,
)
from nesis.api.core.models.objects import ResourceType
from sqlalchemy.orm import Session
Expand All @@ -21,7 +23,7 @@
UserRoleService,
)
from nesis.api.core.services.task_service import TaskService
from nesis.api.core.services.util import PermissionException
from nesis.api.core.services.util import PermissionException, UnauthorizedAccess
from nesis.api.core.services.app_service import AppService


Expand Down Expand Up @@ -80,26 +82,42 @@ def authorized(
resource: str = None,
) -> dict:
user_session = session_service.get(token=token)
session_user = user_session["user"]
session_user = user_session.get("user")
session_app = user_session.get("app")

if session_user.get("root") or False:
return session_user
if not any([session_app, session_user]):
raise UnauthorizedAccess()

# noinspection PyTypeChecker
query = (
session.query(RoleAction)
.filter(RoleAction.role == UserRole.role)
.filter(RoleAction.action == action)
.filter(RoleAction.resource_type == resource_type)
.filter(UserRole.user == User.id)
.filter(User.uuid == session_user["id"])
)

if session_user:

if session_user.get("root") or False:
return session_user

query = (
query.filter(RoleAction.role == UserRole.role)
.filter(UserRole.user == User.id)
.filter(User.uuid == session_user["id"])
)

if session_app:
query = (
query.filter(RoleAction.role == AppRole.role)
.filter(AppRole.app == App.id)
.filter(App.uuid == session_app["id"])
)

if resource:
query = query.filter(RoleAction.resource.in_([resource, "*"]))

user_role_actions = query.all()
if len(user_role_actions) == 0:
role_actions = query.all()
if len(role_actions) == 0:
message = (
f"Not authorized to perform {action.name} on {resource}"
if resource
Expand All @@ -118,25 +136,41 @@ def authorized_resources(
resource_type: ResourceType,
) -> list[RoleAction]:
user_session = session_service.get(token=token)
session_user = user_session["user"]
session_user = user_session.get("user")
session_app = user_session.get("app")

if not any([session_app, session_user]):
raise UnauthorizedAccess()

# noinspection PyTypeChecker
query = (
session.query(RoleAction)
.filter(RoleAction.role == UserRole.role)
.filter(RoleAction.action == action)
.filter(RoleAction.resource_type == resource_type)
.filter(UserRole.user == User.id)
)

if session_user:
query = (
query.filter(RoleAction.role == UserRole.role)
.filter(UserRole.user == User.id)
.filter(User.uuid == session_user["id"])
)

if session_app:
query = (
query.filter(RoleAction.role == AppRole.role)
.filter(AppRole.app == App.id)
.filter(App.uuid == session_app["id"])
)

# If root, return all actions
def get_enabled_datasources():
return session.query(Datasource).filter(Datasource.enabled.is_(True)).all()

def get_enabled_tasks():
return session.query(Task).filter(Task.enabled.is_(True)).all()

if session_user.get("root") or False:
if session_user is not None and session_user.get("root") or False:
match resource_type:
case ResourceType.DATASOURCES:
dss = get_enabled_datasources()
Expand Down Expand Up @@ -166,7 +200,7 @@ def get_enabled_tasks():
action_list = []
dss = None
tasks = None
for role_action in query.filter(User.uuid == session_user["id"]).all():
for role_action in query.all():
if role_action.resource and role_action.resource.strip() == "*":
match resource_type:
case ResourceType.DATASOURCES:
Expand Down
46 changes: 28 additions & 18 deletions nesis/api/core/services/app_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,17 +245,12 @@ def get(self, **kwargs):
if session:
session.close()

def get_by_id(self, app_id) -> App:

@staticmethod
def get_app(**kwargs) -> App:
app_id = kwargs["app_id"]
session = DBSession()
try:

session.expire_on_commit = False
query = session.query(App).filter(App.uuid == app_id)
return query.all()
except Exception as e:
self._LOG.exception(f"Error when fetching apps")
raise
return session.query(App).filter(App.uuid == app_id).first()
finally:
if session:
session.close()
Expand Down Expand Up @@ -372,28 +367,43 @@ def get(self, **kwargs):

key = self.__cache_app_key(token)
value = self.__cache.get(key)
session_object = {"token": token, "app": value}

if session_object is None:

if value is None:
encoded_secret = base64.b64decode(token).decode("utf-8")
encoded_secret_parts = encoded_secret.split(":")
if len(encoded_secret_parts) != 2:
raise UnauthorizedAccess("Invalid app token supplied")

app: App = AppService.get_app(app_id=encoded_secret_parts[0])
if app is None:
raise UnauthorizedAccess("Invalid token")
auth_secret = encoded_secret_parts[1].encode("utf-8")
if not bcrypt.checkpw(auth_secret, app.secret):
raise UnauthorizedAccess("Invalid app token supplied")
value = app.to_dict()

default_expiry = 1800
expiry = (
(self.__config.get("apps") or {})
.get("session", {"expiry": default_expiry})
.get("expiry", default_expiry)
)

app: App = AppService.get_by_id(app_id=encoded_secret_parts[0])
self.__cache.set(key=key, val=value, time=expiry)

raise UnauthorizedAccess("Invalid token")
session_object = {"token": token, "app": value}

return session_object

def delete(self, **kwargs):
raise NotImplementedError("Invalid operation on datasource")
raise NotImplementedError("Invalid operation on service")

@staticmethod
def __cache_app_key(key):
return f"application/{key}"
return f"applications/{key}"

def create(self, **kwargs) -> None:
raise NotImplementedError("Invalid operation on datasource")
raise NotImplementedError("Invalid operation on service")

def update(self, **kwargs):
raise NotImplementedError("Invalid operation on datasource")
raise NotImplementedError("Invalid operation on service")
12 changes: 5 additions & 7 deletions nesis/api/core/services/management.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
import os
from typing import Optional, List
from typing import Optional, List, Dict, Any

import bcrypt
import memcache
Expand All @@ -12,6 +12,7 @@
from nesis.api.core.models import DBSession
from nesis.api.core.models.objects import ResourceType, UserSession
from nesis.api.core.models.entities import User, Role, RoleAction, UserRole, Action
from nesis.api.core.services.app_service import AppSessionService
from nesis.api.core.services.util import (
ServiceOperation,
ServiceException,
Expand All @@ -29,12 +30,11 @@ class UserSessionService(ServiceOperation):

def __init__(self, config):
self.__config = config
self.__enable_user_verification = config.get("verify_users", False)
self.__verify_users_override = config.get("verify_users_override", False)
self._app_session_service = AppSessionService(config=config)
self.__cache = memcache.Client(config["memcache"]["hosts"], debug=1)
self.__LOG = logging.getLogger(self.__module__ + "." + self.__class__.__name__)

def get(self, **kwargs):
def get(self, **kwargs) -> Dict[str, Any]:
token = kwargs.get("token")
if token is None:
raise UnauthorizedAccess("Token not supplied")
Expand All @@ -45,9 +45,7 @@ def get(self, **kwargs):
if value is not None:
session_object = {"token": token, "user": value}
else:
key = self.__cache_app_key(token)
value = self.__cache.get(key)
session_object = {"token": token, "app": value}
session_object = self._app_session_service.get(**kwargs)

if session_object is None:
raise UnauthorizedAccess("Invalid token")
Expand Down
29 changes: 29 additions & 0 deletions nesis/api/tests/core/services/test_app_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,32 @@ def test_create_app(http_client, tc):
encoded_secret_parts = encoded_secret.split(":")

assert encoded_secret_parts[0] == app.uuid

return app


def test_app_session(http_client, tc):
app: App = test_create_app(http_client=http_client, tc=tc)

app_api_key = app.secret.decode("utf-8")
services.user_session_service.get(token=app_api_key)

role = {
"name": "user-admin-2",
"policy": {
"items": [
{"action": "create", "resource": "roles/*"},
{"action": "create", "resource": "users/*"},
]
},
}

# api key creates the role
create_role(service=services.role_service, role=role, token=app_api_key)

app2: App = test_create_app(http_client=http_client, tc=tc)
create_role(
service=services.role_service, role=role, token=app2.secret.decode("utf-8")
)

# create_datasource()

0 comments on commit e0f73e1

Please sign in to comment.