Skip to content

Commit

Permalink
convert django permissions to scopes
Browse files Browse the repository at this point in the history
  • Loading branch information
mstingl committed Nov 4, 2023
1 parent 1490214 commit 074a402
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 12 deletions.
9 changes: 8 additions & 1 deletion djfapi/redis/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from ..security.jwt import JWTTokenDjangoPermissions
from .utils import get_connection


Expand All @@ -10,5 +11,11 @@ def _save_transaction_token(self, token_id: str):
self.redis = get_connection(self.redis_url)

key = f'access:token:{token_id}:scopes'
self.redis.sadd(key, *self.get_all_permissions())
self.redis.sadd(
key,
*[
JWTTokenDjangoPermissions.convert_permission_to_scope(permission)
for permission in self.get_all_permissions()
],
)
self.redis.expire(key, 310) # Transaction Token expires after 5 minutes (300s) + buffer (10s)
52 changes: 42 additions & 10 deletions djfapi/security/jwt.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
from typing import Optional, Union, List
from jose import jwt
from fastapi import HTTPException
from fastapi.security import SecurityScopes
from fastapi.security.api_key import APIKeyHeader
from starlette.requests import Request
import re
from functools import lru_cache
from typing import List, Optional, Union

from asgiref.sync import sync_to_async
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
from djdantic import context
from asgiref.sync import sync_to_async
from djdantic.schemas import Error, Access, AccessToken, AccessScope
from sentry_tools import set_user, set_extra
from djdantic.schemas import Access, AccessScope, AccessToken, Error
from fastapi import HTTPException
from fastapi.security import SecurityScopes
from fastapi.security.api_key import APIKeyHeader
from jose import jwt
from sentry_tools import set_extra, set_user
from starlette.requests import Request

from ..exceptions import AuthError


Expand Down Expand Up @@ -109,6 +113,31 @@ async def __call__(self, request: Request, scopes: SecurityScopes) -> Optional[A


class JWTTokenDjangoPermissions(JWTToken):
DJANGO_PERMISSION_REGEX = r"^(?P<service>[_\w]+)\.(?P<action>\w+)_(?P<resource>\w+)(_(?P<selector>.*))?$"
DJANGO_PERMISSION_ACTION_TO_CRUD = {
'add': 'create',
'view': 'read',
'change': 'update',
'delete': 'delete',
}

@classmethod
@lru_cache
def convert_permission_to_scope(cls, permission: str):
match = re.match(cls.DJANGO_PERMISSION_REGEX, permission)
if not match:
raise ValueError('permission is malformed')

scope = [
match.group("service"),
match.group("resource"),
cls.DJANGO_PERMISSION_ACTION_TO_CRUD[match.group("action")],
]
if match.group('action') != 'add':
scope.append(match.group("selector") or "any")

return '.'.join(scope)

async def get_user(self, access: Access):
try:
return await get_user_model().objects.aget(id=access.user_id)
Expand All @@ -119,5 +148,8 @@ async def get_user(self, access: Access):
async def _create_access(self, token):
access = await super()._create_access(token)
user = await self.get_user(access)
access.token.aud = list(await sync_to_async(user.get_all_permissions)())
access.token.aud = [
self.convert_permission_to_scope(permission)
for permission in await sync_to_async(user.get_all_permissions)()
]
return access
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = djfapi
version = 0.0.71.b1
version = 0.0.72b1
author = Manuel Stingl
author_email = opensource@voltane.eu
description = Utilities for use with FastAPI and django
Expand Down

0 comments on commit 074a402

Please sign in to comment.