Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
nick8325 committed Feb 12, 2024
1 parent 1cf43b2 commit f0fb5db
Show file tree
Hide file tree
Showing 12 changed files with 62 additions and 114 deletions.
31 changes: 7 additions & 24 deletions karp-backend/src/karp/auth_infrastructure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,20 @@
import injector

from karp.auth_infrastructure.queries import (
LexGetResourcePermissions,
LexIsResourceProtected,
ResourcePermissionQueries,
)
from karp.auth_infrastructure.services import (
JWTAuthService,
JWTAuthServiceConfig,
)
from karp.lex_infrastructure import ResourceQueries
from karp.lex.application.repositories import ResourceRepository


class AuthInfrastructure(injector.Module): # noqa: D101
@injector.provider
def resource_permissions( # noqa: D102
self, resources: ResourceQueries
) -> LexGetResourcePermissions:
return LexGetResourcePermissions(resources)

@injector.provider
def is_resource_protected( # noqa: D102
self, resources: ResourceQueries
) -> LexIsResourceProtected:
return LexIsResourceProtected(resources)
self, resources: ResourceRepository
) -> ResourcePermissionQueries:
return ResourcePermissionQueries(resources)


class JwtAuthInfrastructure(injector.Module): # noqa: D101
Expand All @@ -32,14 +24,5 @@ def __init__(self, pubkey_path: Path) -> None: # noqa: D107
self.pubkey_path = pubkey_path

@injector.provider
@injector.singleton
def jwt_auth_service_config(self) -> JWTAuthServiceConfig: # noqa: D102
return JWTAuthServiceConfig(self.pubkey_path)

@injector.provider
def jwt_auth_service( # noqa: D102
self, is_resource_protected: LexIsResourceProtected
) -> JWTAuthService:
return JWTAuthService(
pubkey_path=self.pubkey_path, is_resource_protected=is_resource_protected
)
def jwt_auth_service(self) -> JWTAuthService:
return JWTAuthService(pubkey_path=self.pubkey_path)
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .lex_resources import (
LexGetResourcePermissions,
LexIsResourceProtected,
ResourcePermissionQueries,
)
31 changes: 19 additions & 12 deletions karp-backend/src/karp/auth_infrastructure/queries/lex_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

from karp.auth.application.queries.resources import ResourcePermissionDto
from karp.auth.domain import errors
from karp.auth.domain.entities.user import User
from karp.foundation.value_objects.permission_level import PermissionLevel
from karp.lex_infrastructure import ResourceQueries
from karp.lex.application.repositories import ResourceRepository


class LexGetResourcePermissions:
def __init__(self, resources: ResourceQueries):
class ResourcePermissionQueries:
def __init__(self, resources: ResourceRepository):
self.resources = resources

def query(self) -> typing.List[ResourcePermissionDto]: # noqa: D102
def get_resource_permissions(self) -> typing.List[ResourcePermissionDto]: # noqa: D102
resource_permissions = []
for resource in self.resources.get_published_resources():
resource_obj = {"resource_id": resource.resource_id}
Expand All @@ -31,16 +32,22 @@ def query(self) -> typing.List[ResourcePermissionDto]: # noqa: D102

return resource_permissions


class LexIsResourceProtected:
def __init__(self, resource_queries: ResourceQueries) -> None: # noqa: D107
super().__init__()
self.resource_queries = resource_queries

def query(self, resource_id: str, level: PermissionLevel) -> bool: # noqa: D102
def is_resource_protected(self, resource_id: str, level: PermissionLevel) -> bool: # noqa: D102
if level in [PermissionLevel.write, PermissionLevel.admin]:
return True
resource = self.resource_queries.by_resource_id_optional(resource_id=resource_id)
resource = self.resources.by_resource_id_optional(resource_id=resource_id)
if not resource:
raise errors.ResourceNotFound(f"Can't find resource '{resource_id}'")
return resource.config.get("protected", {}).get("read", False)

def has_permission( # noqa: ANN201, D102
self,
level: PermissionLevel,
user: User,
resource_ids: typing.List[str],
) -> bool:
return not any(
self.is_resource_protected(resource_id, level)
and (not user or not user.has_enough_permissions(resource_id, level))
for resource_id in resource_ids
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pydantic
import logging

from karp.auth_infrastructure import LexIsResourceProtected
from karp.auth_infrastructure import ResourcePermissionQueries
from karp.foundation import value_objects
from karp.auth.domain.errors import ExpiredToken, TokenError, InvalidTokenPayload
from karp.auth.domain.entities.user import User
Expand Down Expand Up @@ -49,10 +49,9 @@ def pubkey_path(self) -> Path: # noqa: D102

class JWTAuthService:
def __init__( # noqa: D107
self, pubkey_path: Path, is_resource_protected: LexIsResourceProtected
self, pubkey_path: Path
) -> None:
self._jwt_key = load_jwt_key(pubkey_path)
self.is_resource_protected = is_resource_protected
logger.debug("JWTAuthenticator created")

def authenticate(self, _scheme: str, credentials: str) -> User: # noqa: D102
Expand All @@ -76,15 +75,3 @@ def authenticate(self, _scheme: str, credentials: str) -> User: # noqa: D102
if payload.scope and "lexica" in payload.scope:
lexicon_permissions = payload.scope["lexica"]
return User(payload.sub, lexicon_permissions, payload.levels)

def authorize( # noqa: ANN201, D102
self,
level: value_objects.PermissionLevel,
user: User,
resource_ids: List[str],
):
return not any(
self.is_resource_protected.query(resource_id, level)
and (not user or not user.has_enough_permissions(resource_id, level))
for resource_id in resource_ids
)
28 changes: 6 additions & 22 deletions karp-backend/src/karp/karp_v6_api/dependencies/auth_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
from karp.auth.domain.errors import TokenError
from karp.auth_infrastructure import (
JWTAuthService,
JWTAuthServiceConfig,
LexGetResourcePermissions,
LexIsResourceProtected,
ResourcePermissionQueries,
)
from karp.main.errors import KarpError # noqa: F401

from ...lex_infrastructure import ResourceQueries
from karp.lex.application.repositories import ResourceRepository
from . import lex_deps
from .fastapi_injector import inject_from_req

Expand All @@ -36,26 +34,12 @@ def bearer_scheme(authorization=Header(None)): # noqa: ANN201, D103


def get_resource_permissions( # noqa: D103
resources: ResourceQueries = Depends(lex_deps.get_resource_queries),
) -> LexGetResourcePermissions:
return LexGetResourcePermissions(resources)
resources: ResourceRepository = Depends(lex_deps.get_resource_repository),
) -> ResourcePermissionQueries:
return ResourcePermissionQueries(resources)


def get_is_resource_protected( # noqa: D103
repo: ResourceQueries = Depends(lex_deps.get_resource_queries),
) -> LexIsResourceProtected:
return LexIsResourceProtected(repo)


def get_auth_service( # noqa: D103
config: JWTAuthServiceConfig = Depends(inject_from_req(JWTAuthServiceConfig)),
query: LexIsResourceProtected = Depends(get_is_resource_protected),
) -> JWTAuthService:
return JWTAuthService(
pubkey_path=config.pubkey_path,
is_resource_protected=query,
)

get_auth_service = inject_from_req(JWTAuthService)

# TODO this one uses "bearer_scheme"
# get_user uses "auth_scheme"
Expand Down
18 changes: 9 additions & 9 deletions karp-backend/src/karp/karp_v6_api/routes/entries_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
)
from starlette import responses

from karp.auth_infrastructure import JWTAuthService
from karp.auth_infrastructure import ResourcePermissionQueries
from karp.entry_commands import EntryCommands
from karp.lex_core.value_objects import UniqueId, unique_id
from karp.lex_core.value_objects.unique_id import UniqueIdStr
Expand All @@ -41,10 +41,10 @@ def get_history_for_entry( # noqa: ANN201, D103
entry_id: UniqueIdStr,
version: Optional[int] = Query(None),
user: auth.User = Security(deps.get_user, scopes=["admin"]),
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
entry_queries: EntryQueries = Depends(deps.get_entry_queries),
):
if not auth_service.authorize(auth.PermissionLevel.admin, user, [resource_id]):
if not resource_permissions.has_permission(auth.PermissionLevel.admin, user, [resource_id]):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
Expand All @@ -70,10 +70,10 @@ def add_entry( # noqa: ANN201, D103
resource_id: str,
data: schemas.EntryAdd,
user: User = Security(deps.get_user, scopes=["write"]),
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
entry_commands: EntryCommands = Depends(inject_from_req(EntryCommands)),
):
if not auth_service.authorize(PermissionLevel.write, user, [resource_id]):
if not resource_permissions.has_permission(PermissionLevel.write, user, [resource_id]):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
Expand Down Expand Up @@ -116,10 +116,10 @@ def update_entry( # noqa: ANN201, D103
entry_id: UniqueId,
data: schemas.EntryUpdate,
user: User = Security(deps.get_user, scopes=["write"]),
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
entry_commands: EntryCommands = Depends(inject_from_req(EntryCommands)),
):
if not auth_service.authorize(PermissionLevel.write, user, [resource_id]):
if not resource_permissions.has_permission(PermissionLevel.write, user, [resource_id]):
raise HTTPException(
status_code=status.HTTP_403,
detail="Not enough permissions",
Expand Down Expand Up @@ -179,11 +179,11 @@ def delete_entry( # noqa: ANN201
entry_id: UniqueId,
version: int,
user: User = Security(deps.get_user, scopes=["write"]),
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
entry_commands: EntryCommands = Depends(inject_from_req(EntryCommands)),
):
"""Delete a entry from a resource."""
if not auth_service.authorize(PermissionLevel.write, user, [resource_id]):
if not resource_permissions.has_permission(PermissionLevel.write, user, [resource_id]):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
Expand Down
10 changes: 5 additions & 5 deletions karp-backend/src/karp/karp_v6_api/routes/history_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Security, status

from karp import auth, lex
from karp.auth_infrastructure import JWTAuthService
from karp.auth_infrastructure import ResourcePermissionQueries
from karp.lex_core.value_objects import unique_id
from karp.lex_core.value_objects.unique_id import UniqueIdStr

Expand Down Expand Up @@ -34,10 +34,10 @@ def get_diff( # noqa: ANN201, D103
from_date: Optional[float] = None,
to_date: Optional[float] = None,
entry: Optional[Dict] = None,
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
entry_queries: EntryQueries = Depends(deps.get_entry_queries),
):
if not auth_service.authorize(auth.PermissionLevel.admin, user, [resource_id]):
if not resource_permissions.has_permission(auth.PermissionLevel.admin, user, [resource_id]):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
Expand Down Expand Up @@ -70,10 +70,10 @@ def get_history( # noqa: ANN201, D103
from_version: Optional[int] = Query(None),
current_page: int = Query(0),
page_size: int = Query(100),
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
entry_queries: EntryQueries = Depends(deps.get_entry_queries),
):
if not auth_service.authorize(auth.PermissionLevel.admin, user, [resource_id]):
if not resource_permissions.has_permission(auth.PermissionLevel.admin, user, [resource_id]):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
Expand Down
14 changes: 7 additions & 7 deletions karp-backend/src/karp/karp_v6_api/routes/query_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Security, status

from karp import auth, search
from karp.auth_infrastructure import JWTAuthService
from karp.auth_infrastructure import ResourcePermissionQueries
from karp.main import errors as karp_errors
from karp.search.application.queries import QueryRequest

Expand Down Expand Up @@ -37,11 +37,11 @@ def get_entries_by_id( # noqa: ANN201, D103
regex=r"^\w(,\w)*",
),
user: auth.User = Security(deps.get_user_optional, scopes=["read"]),
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
search_service: Es6SearchService = Depends(inject_from_req(Es6SearchService)),
):
logger.debug("karp_v6_api.views.get_entries_by_id")
if not auth_service.authorize(auth.PermissionLevel.read, user, [resource_id]):
if not resource_permissions.has_permission(auth.PermissionLevel.read, user, [resource_id]):
raise HTTPException(
status_code=status.HTTP_403,
detail="Not enough permissions",
Expand All @@ -67,12 +67,12 @@ def query_split( # noqa: ANN201, D103
size: int = Query(25, description="Number of entries in page."),
lexicon_stats: bool = Query(True, description="Show the hit count per lexicon"),
user: auth.User = Security(deps.get_user_optional, scopes=["read"]),
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
search_service: Es6SearchService = Depends(inject_from_req(Es6SearchService)),
):
logger.debug("/query/split called", extra={"resources": resources})
resource_list = resources.split(",")
if not auth_service.authorize(auth.PermissionLevel.read, user, resource_list):
if not resource_permissions.has_permission(auth.PermissionLevel.read, user, resource_list):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
Expand Down Expand Up @@ -139,7 +139,7 @@ def query( # noqa: ANN201
None, description="Comma-separated list of which fields to remove from result"
),
user: auth.User = Security(deps.get_user_optional, scopes=["read"]),
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
search_service: Es6SearchService = Depends(inject_from_req(Es6SearchService)),
):
"""
Expand All @@ -151,7 +151,7 @@ def query( # noqa: ANN201
extra={"resources": resources, "from": from_, "size": size},
)
resource_list = resources.split(",")
if not auth_service.authorize(auth.PermissionLevel.read, user, resource_list):
if not resource_permissions.has_permission(auth.PermissionLevel.read, user, resource_list):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
Expand Down
6 changes: 3 additions & 3 deletions karp-backend/src/karp/karp_v6_api/routes/resources_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from fastapi import APIRouter, Depends, HTTPException, status

from karp.auth.application.queries.resources import ResourcePermissionDto
from karp.auth_infrastructure import LexGetResourcePermissions
from karp.auth_infrastructure import ResourcePermissionQueries
from karp.karp_v6_api.schemas import ResourcePublic, ResourceProtected
from karp.karp_v6_api import dependencies as deps
from karp.karp_v6_api.dependencies.fastapi_injector import inject_from_req
Expand All @@ -17,9 +17,9 @@

@router.get("/permissions", response_model=list[ResourcePermissionDto])
def list_resource_permissions( # noqa: ANN201, D103
query: LexGetResourcePermissions = Depends(deps.get_resource_permissions),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
):
return query.query()
return resource_permissions.get_resource_permissions()


@router.get(
Expand Down
6 changes: 3 additions & 3 deletions karp-backend/src/karp/karp_v6_api/routes/stats_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)

from karp import auth
from karp.auth_infrastructure import JWTAuthService
from karp.auth_infrastructure import ResourcePermissionQueries
from karp.foundation.value_objects import PermissionLevel
from karp.search_infrastructure.queries import Es6SearchService
from karp.karp_v6_api import schemas # noqa: F401
Expand All @@ -36,10 +36,10 @@ def get_field_values( # noqa: ANN201, D103
resource_id: str,
field: str,
user: auth.User = Security(deps.get_user_optional, scopes=["read"]),
auth_service: JWTAuthService = Depends(deps.get_auth_service),
resource_permissions: ResourcePermissionQueries = Depends(deps.get_resource_permissions),
search_service: Es6SearchService = Depends(inject_from_req(Es6SearchService)),
):
if not auth_service.authorize(PermissionLevel.read, user, [resource_id]):
if not resource_permissions.has_permission(PermissionLevel.read, user, [resource_id]):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
Expand Down
Loading

0 comments on commit f0fb5db

Please sign in to comment.