11from __future__ import annotations
22
33import logging
4- import random
54import re
6- import string
7- from abc import ABCMeta , abstractmethod
5+ from abc import ABCMeta
86from asyncio import gather
9- from datetime import datetime
10- from typing import Annotated , Any , Iterable , final
7+ from typing import Any , Iterable
118
12- from annotated_types import MinLen
13- from event_schema .auth import UserLogin , UserLoginKey
14- from fastapi import APIRouter , Depends
15- from fastapi_sqlalchemy import db
9+ from fastapi import APIRouter
1610from sqlalchemy .orm import Session as DbSession
1711
18- from auth_backend .base import Base
19- from auth_backend .exceptions import LastAuthMethodDelete
20- from auth_backend .models .db import AuthMethod , User , UserSession
12+ from auth_backend .auth_method .session import Session
13+ from auth_backend .models .db import User , UserSession
2114from auth_backend .schemas .types .scopes import Scope as TypeScope
2215from auth_backend .settings import get_settings
23- from auth_backend .utils .security import UnionAuth
2416from auth_backend .utils .user_session_control import create_session
2517
2618
2719logger = logging .getLogger (__name__ )
2820settings = get_settings ()
2921
3022
31- def random_string (length : int = 32 ) -> str :
32- return "" .join ([random .choice (string .ascii_letters ) for _ in range (length )])
23+ AUTH_METHODS : dict [str , type [AuthPluginMeta ]] = {}
3324
3425
35- class Session (Base ):
36- token : Annotated [str , MinLen (1 )]
37- expires : datetime
38- id : int
39- user_id : int
40- session_scopes : list [TypeScope ]
41-
42-
43- AUTH_METHODS : dict [str , type [AuthMethodMeta ]] = {}
44-
45-
46- class AuthMethodMeta (metaclass = ABCMeta ):
26+ class AuthPluginMeta (metaclass = ABCMeta ):
4727 router : APIRouter
4828 prefix : str
4929 tags : list [str ] = []
@@ -58,35 +38,11 @@ def __init__(self):
5838 self .router .add_api_route ("/login" , self ._login , methods = ["POST" ], response_model = Session )
5939
6040 def __init_subclass__ (cls , ** kwargs ):
61- if cls .__name__ .endswith ('Meta' ):
41+ if cls .__name__ .endswith ('Meta' ) or cls . __name__ . endswith ( 'Mixin' ) :
6242 return
6343 logger .info (f'Init authmethod { cls .__name__ } ' )
6444 AUTH_METHODS [cls .__name__ ] = cls
6545
66- @staticmethod
67- @abstractmethod
68- async def _register (* args , ** kwargs ) -> object :
69- raise NotImplementedError ()
70-
71- @staticmethod
72- @abstractmethod
73- async def _login (* args , ** kwargs ) -> Session :
74- raise NotImplementedError ()
75-
76- @staticmethod
77- @final
78- def generate_kafka_key (user_id : int ) -> UserLoginKey :
79- """
80- Мы генерируем ключи так как для сообщений с одинаковыми ключами
81- Kafka гарантирует последовательность чтений
82- Args:
83- user_id: Айди пользователя
84-
85- Returns:
86- Ничего
87- """
88- return UserLoginKey .model_validate ({"user_id" : user_id })
89-
9046 @staticmethod
9147 async def _create_session (
9248 user : User , scopes_list_names : list [TypeScope ] | None , session_name : str | None = None , * , db_session : DbSession
@@ -124,11 +80,6 @@ async def _get_user(
12480 return user_session .user
12581 return
12682
127- @classmethod
128- @abstractmethod
129- async def _convert_data_to_userdata_format (cls , data : Any ) -> UserLogin :
130- raise NotImplementedError ()
131-
13283 @staticmethod
13384 async def user_updated (
13485 new_user : dict [str , Any ] | None ,
@@ -173,7 +124,7 @@ async def user_updated(
173124 ```
174125 """
175126 exceptions = await gather (
176- * [m .on_user_update (new_user , old_user ) for m in AuthMethodMeta .active_auth_methods ()],
127+ * [m .on_user_update (new_user , old_user ) for m in AuthPluginMeta .active_auth_methods ()],
177128 return_exceptions = True ,
178129 )
179130 if len (exceptions ) > 0 :
@@ -193,96 +144,7 @@ def is_active(cls):
193144 return settings .ENABLED_AUTH_METHODS is None or cls .get_name () in settings .ENABLED_AUTH_METHODS
194145
195146 @staticmethod
196- def active_auth_methods () -> Iterable [type ['AuthMethodMeta ' ]]:
147+ def active_auth_methods () -> Iterable [type ['AuthPluginMeta ' ]]:
197148 for method in AUTH_METHODS .values ():
198149 if method .is_active ():
199150 yield method
200-
201-
202- class OauthMeta (AuthMethodMeta ):
203- """Абстрактная авторизация и аутентификация через OAuth"""
204-
205- class UrlSchema (Base ):
206- url : str
207-
208- def __init__ (self ):
209- super ().__init__ ()
210- self .router .add_api_route ("/redirect_url" , self ._redirect_url , methods = ["GET" ], response_model = self .UrlSchema )
211- self .router .add_api_route ("/auth_url" , self ._auth_url , methods = ["GET" ], response_model = self .UrlSchema )
212- self .router .add_api_route ("" , self ._unregister , methods = ["DELETE" ])
213-
214- @staticmethod
215- @abstractmethod
216- async def _redirect_url (* args , ** kwargs ) -> UrlSchema :
217- """URL на который происходит редирект после завершения входа на стороне провайдера"""
218- raise NotImplementedError ()
219-
220- @staticmethod
221- @abstractmethod
222- async def _auth_url (* args , ** kwargs ) -> UrlSchema :
223- """URL на который происходит редирект из приложения для авторизации на стороне провайдера"""
224- raise NotImplementedError ()
225-
226- @classmethod
227- async def _unregister (cls , user_session : UserSession = Depends (UnionAuth (scopes = [], auto_error = True ))):
228- """Отключает для пользователя метод входа"""
229- old_user = {"user_id" : user_session .user .id }
230- new_user = {"user_id" : user_session .user .id }
231- old_user_params = await cls ._delete_auth_methods (user_session .user , db_session = db .session )
232- old_user [cls .get_name ()] = old_user_params
233- await AuthMethodMeta .user_updated (new_user , old_user )
234- return None
235-
236- @classmethod
237- async def _get_user (cls , key : str , value : str | int , * , db_session : DbSession ) -> User | None :
238- auth_method : AuthMethod = (
239- AuthMethod .query (session = db_session )
240- .filter (
241- AuthMethod .param == key ,
242- AuthMethod .value == str (value ),
243- AuthMethod .auth_method == cls .get_name (),
244- )
245- .limit (1 )
246- .one_or_none ()
247- )
248- if auth_method :
249- return auth_method .user
250-
251- @classmethod
252- async def _register_auth_method (cls , key : str , value : str | int , user : User , * , db_session ) -> AuthMethod :
253- """Добавление пользователю новый AuthMethod"""
254- return AuthMethod .create (
255- user_id = user .id ,
256- auth_method = cls .get_name (),
257- param = key ,
258- value = str (value ),
259- session = db_session ,
260- )
261-
262- @classmethod
263- async def _delete_auth_methods (cls , user : User , * , db_session ) -> list [AuthMethod ]:
264- """Удаляет пользователю все AuthMethod конкретной авторизации"""
265- auth_methods : list [AuthMethod ] = (
266- AuthMethod .query (session = db_session )
267- .filter (
268- AuthMethod .user_id == user .id ,
269- AuthMethod .auth_method == cls .get_name (),
270- )
271- .all ()
272- )
273- all_auth_methods = AuthMethod .query (session = db_session ).filter (AuthMethod .user_id == user .id ).all ()
274- if len (all_auth_methods ) - len (auth_methods ) == 0 :
275- raise LastAuthMethodDelete ()
276- logger .debug (auth_methods )
277- for method in auth_methods :
278- method .is_deleted = True
279- db_session .flush ()
280- return {m .param : m .value for m in auth_methods }
281-
282- @classmethod
283- def userdata_process_empty_strings (cls , userdata : UserLogin ) -> UserLogin :
284- '''Изменяет значения с пустыми строками в параметре категории юзердаты на None'''
285- for item in userdata .items :
286- if item .value == '' :
287- item .value = None
288- return userdata
0 commit comments