diff options
Diffstat (limited to 'authentication/authlib.py')
| -rw-r--r-- | authentication/authlib.py | 77 |
1 files changed, 52 insertions, 25 deletions
diff --git a/authentication/authlib.py b/authentication/authlib.py index 8472062..a37d68f 100644 --- a/authentication/authlib.py +++ b/authentication/authlib.py | |||
| @@ -1,31 +1,37 @@ | |||
| 1 | import json | 1 | import json |
| 2 | from collections import defaultdict | 2 | from collections import defaultdict |
| 3 | from typing import ClassVar | ||
| 3 | from unittest.mock import patch | 4 | from unittest.mock import patch |
| 4 | 5 | ||
| 5 | from authlib.common.security import generate_token as generate_random_token | 6 | from authlib.common.security import generate_token as generate_random_token |
| 6 | from authlib.oauth2 import AuthorizationServer as BaseAuthorizationServer, OAuth2Request | 7 | from authlib.oauth2 import AuthorizationServer as BaseAuthorizationServer |
| 7 | from authlib.oauth2.rfc6749 import AuthorizationCodeGrant as BaseAuthorizationCodeGrant, \ | 8 | from authlib.oauth2 import OAuth2Request |
| 8 | RefreshTokenGrant as BaseRefreshTokenGrant, OAuth2Payload | 9 | from authlib.oauth2.rfc6749 import AuthorizationCodeGrant as BaseAuthorizationCodeGrant |
| 10 | from authlib.oauth2.rfc6749 import OAuth2Payload | ||
| 11 | from authlib.oauth2.rfc6749 import RefreshTokenGrant as BaseRefreshTokenGrant | ||
| 9 | from authlib.oauth2.rfc6750 import BearerTokenGenerator | 12 | from authlib.oauth2.rfc6750 import BearerTokenGenerator |
| 10 | from authlib.oauth2.rfc7636 import CodeChallenge | 13 | from authlib.oauth2.rfc7636 import CodeChallenge |
| 14 | from fastapi import Request, Response | ||
| 11 | from sqlalchemy.exc import NoResultFound | 15 | from sqlalchemy.exc import NoResultFound |
| 12 | |||
| 13 | from sqlmodel import select | 16 | from sqlmodel import select |
| 14 | from fastapi import Request, Response | ||
| 15 | 17 | ||
| 16 | from db.models import ClientApplication, AuthToken, AuthCode, User | 18 | from db.models import AuthCode, AuthToken, ClientApplication, User |
| 17 | from db.session import get_session_context | 19 | from db.session import get_session_context |
| 18 | 20 | ||
| 19 | 21 | ||
| 20 | class AuthorizationCodeGrant(BaseAuthorizationCodeGrant): | 22 | class AuthorizationCodeGrant(BaseAuthorizationCodeGrant): |
| 21 | TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post', 'none'] | 23 | TOKEN_ENDPOINT_AUTH_METHODS: ClassVar[list[str]] = [ |
| 24 | "client_secret_basic", | ||
| 25 | "client_secret_post", | ||
| 26 | "none", | ||
| 27 | ] | ||
| 22 | 28 | ||
| 23 | def save_authorization_code(self, code: str, request: OAuth2Request): | 29 | def save_authorization_code(self, code: str, request: OAuth2Request): |
| 24 | with get_session_context() as db_session: | 30 | with get_session_context() as db_session: |
| 25 | db_session.add(request.user) | 31 | db_session.add(request.user) |
| 26 | 32 | ||
| 27 | code_challenge = request.payload.data.get('code_challenge') | 33 | code_challenge = request.payload.data.get("code_challenge") |
| 28 | code_challenge_method = request.payload.data.get('code_challenge_method') | 34 | code_challenge_method = request.payload.data.get("code_challenge_method") |
| 29 | 35 | ||
| 30 | auth_code = AuthCode( | 36 | auth_code = AuthCode( |
| 31 | code=code, | 37 | code=code, |
| @@ -41,10 +47,16 @@ class AuthorizationCodeGrant(BaseAuthorizationCodeGrant): | |||
| 41 | 47 | ||
| 42 | return auth_code | 48 | return auth_code |
| 43 | 49 | ||
| 44 | def query_authorization_code(self, code: str, client: ClientApplication) -> AuthCode | None: | 50 | def query_authorization_code( |
| 51 | self, code: str, client: ClientApplication | ||
| 52 | ) -> AuthCode | None: | ||
| 45 | try: | 53 | try: |
| 46 | with get_session_context() as db_session: | 54 | with get_session_context() as db_session: |
| 47 | auth_code = db_session.exec(select(AuthCode).where(AuthCode.code == code, AuthCode.client_id == client.client_id)).one() | 55 | auth_code = db_session.exec( |
| 56 | select(AuthCode).where( | ||
| 57 | AuthCode.code == code, AuthCode.client_id == client.client_id | ||
| 58 | ) | ||
| 59 | ).one() | ||
| 48 | except NoResultFound: | 60 | except NoResultFound: |
| 49 | return None | 61 | return None |
| 50 | 62 | ||
| @@ -65,7 +77,9 @@ class RefreshTokenGrant(BaseRefreshTokenGrant): | |||
| 65 | def authenticate_refresh_token(self, refresh_token) -> AuthToken | None: | 77 | def authenticate_refresh_token(self, refresh_token) -> AuthToken | None: |
| 66 | try: | 78 | try: |
| 67 | with get_session_context() as db_session: | 79 | with get_session_context() as db_session: |
| 68 | token = db_session.exec(select(AuthToken).where(AuthToken.refresh_token == refresh_token)).one() | 80 | token = db_session.exec( |
| 81 | select(AuthToken).where(AuthToken.refresh_token == refresh_token) | ||
| 82 | ).one() | ||
| 69 | except NoResultFound: | 83 | except NoResultFound: |
| 70 | return None | 84 | return None |
| 71 | 85 | ||
| @@ -82,6 +96,7 @@ class RefreshTokenGrant(BaseRefreshTokenGrant): | |||
| 82 | credential.revoked = True | 96 | credential.revoked = True |
| 83 | db_session.add(credential) | 97 | db_session.add(credential) |
| 84 | 98 | ||
| 99 | |||
| 85 | async def prepare_oauth_request(request: Request) -> Request: | 100 | async def prepare_oauth_request(request: Request) -> Request: |
| 86 | async with request.form() as form: | 101 | async with request.form() as form: |
| 87 | request.state.form_data = dict(form) | 102 | request.state.form_data = dict(form) |
| @@ -96,7 +111,7 @@ class FastApiOAuth2Payload(OAuth2Payload): | |||
| 96 | def data(self): | 111 | def data(self): |
| 97 | return { | 112 | return { |
| 98 | **self._request.query_params, | 113 | **self._request.query_params, |
| 99 | **getattr(self._request.state, 'form_data', {}), | 114 | **getattr(self._request.state, "form_data", {}), |
| 100 | } | 115 | } |
| 101 | 116 | ||
| 102 | @property | 117 | @property |
| @@ -106,9 +121,10 @@ class FastApiOAuth2Payload(OAuth2Payload): | |||
| 106 | values[k].extend([self.data[k]]) | 121 | values[k].extend([self.data[k]]) |
| 107 | return values | 122 | return values |
| 108 | 123 | ||
| 124 | |||
| 109 | class FastApiOAuth2Request(OAuth2Request): | 125 | class FastApiOAuth2Request(OAuth2Request): |
| 110 | def __init__(self, request: Request): | 126 | def __init__(self, request: Request): |
| 111 | with patch('authlib.oauth2.rfc6749.errors.InsecureTransportError'): | 127 | with patch("authlib.oauth2.rfc6749.errors.InsecureTransportError"): |
| 112 | super().__init__( | 128 | super().__init__( |
| 113 | method=request.method, | 129 | method=request.method, |
| 114 | uri=str(request.url), | 130 | uri=str(request.url), |
| @@ -128,7 +144,7 @@ class FastApiOAuth2Request(OAuth2Request): | |||
| 128 | 144 | ||
| 129 | @property | 145 | @property |
| 130 | def form(self): | 146 | def form(self): |
| 131 | return getattr(self._request.state, 'form_data', {}) | 147 | return getattr(self._request.state, "form_data", {}) |
| 132 | 148 | ||
| 133 | 149 | ||
| 134 | class AuthorizationServer(BaseAuthorizationServer): | 150 | class AuthorizationServer(BaseAuthorizationServer): |
| @@ -136,22 +152,33 @@ class AuthorizationServer(BaseAuthorizationServer): | |||
| 136 | super().__init__(*args, **kwargs) | 152 | super().__init__(*args, **kwargs) |
| 137 | self.register_grant(AuthorizationCodeGrant, extensions=[CodeChallenge()]) | 153 | self.register_grant(AuthorizationCodeGrant, extensions=[CodeChallenge()]) |
| 138 | self.register_grant(RefreshTokenGrant) | 154 | self.register_grant(RefreshTokenGrant) |
| 139 | self.register_token_generator("default", BearerTokenGenerator( | 155 | self.register_token_generator( |
| 140 | access_token_generator=lambda *args, **kwargs: generate_random_token(42), | 156 | "default", |
| 141 | refresh_token_generator=lambda *args, **kwargs: generate_random_token(48), | 157 | BearerTokenGenerator( |
| 142 | )) | 158 | access_token_generator=lambda *args, **kwargs: generate_random_token( |
| 159 | 42 | ||
| 160 | ), | ||
| 161 | refresh_token_generator=lambda *args, **kwargs: generate_random_token( | ||
| 162 | 48 | ||
| 163 | ), | ||
| 164 | ), | ||
| 165 | ) | ||
| 143 | 166 | ||
| 144 | def query_client(self, client_id: str) -> ClientApplication: | 167 | def query_client(self, client_id: str) -> ClientApplication: |
| 145 | with get_session_context() as db_session: | 168 | with get_session_context() as db_session: |
| 146 | return db_session.exec(select(ClientApplication).where(ClientApplication.client_id == client_id)).one() | 169 | return db_session.exec( |
| 170 | select(ClientApplication).where( | ||
| 171 | ClientApplication.client_id == client_id | ||
| 172 | ) | ||
| 173 | ).one() | ||
| 147 | 174 | ||
| 148 | def save_token(self, token: dict, request: OAuth2Request): | 175 | def save_token(self, token: dict, request: OAuth2Request): |
| 149 | with get_session_context() as db_session: | 176 | with get_session_context() as db_session: |
| 150 | db_session.add(AuthToken( | 177 | db_session.add( |
| 151 | user_id=request.user.id, | 178 | AuthToken( |
| 152 | client_id=request.client.client_id, | 179 | user_id=request.user.id, client_id=request.client.client_id, **token |
| 153 | **token | 180 | ) |
| 154 | )) | 181 | ) |
| 155 | 182 | ||
| 156 | def create_oauth2_request(self, request: Request) -> OAuth2Request: | 183 | def create_oauth2_request(self, request: Request) -> OAuth2Request: |
| 157 | return FastApiOAuth2Request(request) | 184 | return FastApiOAuth2Request(request) |
