aboutsummaryrefslogtreecommitdiffstats
path: root/authentication/authlib.py
diff options
context:
space:
mode:
Diffstat (limited to 'authentication/authlib.py')
-rw-r--r--authentication/authlib.py77
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 @@
1import json 1import json
2from collections import defaultdict 2from collections import defaultdict
3from typing import ClassVar
3from unittest.mock import patch 4from unittest.mock import patch
4 5
5from authlib.common.security import generate_token as generate_random_token 6from authlib.common.security import generate_token as generate_random_token
6from authlib.oauth2 import AuthorizationServer as BaseAuthorizationServer, OAuth2Request 7from authlib.oauth2 import AuthorizationServer as BaseAuthorizationServer
7from authlib.oauth2.rfc6749 import AuthorizationCodeGrant as BaseAuthorizationCodeGrant, \ 8from authlib.oauth2 import OAuth2Request
8 RefreshTokenGrant as BaseRefreshTokenGrant, OAuth2Payload 9from authlib.oauth2.rfc6749 import AuthorizationCodeGrant as BaseAuthorizationCodeGrant
10from authlib.oauth2.rfc6749 import OAuth2Payload
11from authlib.oauth2.rfc6749 import RefreshTokenGrant as BaseRefreshTokenGrant
9from authlib.oauth2.rfc6750 import BearerTokenGenerator 12from authlib.oauth2.rfc6750 import BearerTokenGenerator
10from authlib.oauth2.rfc7636 import CodeChallenge 13from authlib.oauth2.rfc7636 import CodeChallenge
14from fastapi import Request, Response
11from sqlalchemy.exc import NoResultFound 15from sqlalchemy.exc import NoResultFound
12
13from sqlmodel import select 16from sqlmodel import select
14from fastapi import Request, Response
15 17
16from db.models import ClientApplication, AuthToken, AuthCode, User 18from db.models import AuthCode, AuthToken, ClientApplication, User
17from db.session import get_session_context 19from db.session import get_session_context
18 20
19 21
20class AuthorizationCodeGrant(BaseAuthorizationCodeGrant): 22class 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
85async def prepare_oauth_request(request: Request) -> Request: 100async 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
109class FastApiOAuth2Request(OAuth2Request): 125class 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
134class AuthorizationServer(BaseAuthorizationServer): 150class 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)