diff --git a/fastapi_users/authentication/__init__.py b/fastapi_users/authentication/__init__.py index d2aa3529..25a5a23f 100644 --- a/fastapi_users/authentication/__init__.py +++ b/fastapi_users/authentication/__init__.py @@ -9,7 +9,7 @@ from fastapi_users import models from fastapi_users.authentication.base import BaseAuthentication # noqa: F401 from fastapi_users.authentication.cookie import CookieAuthentication # noqa: F401 from fastapi_users.authentication.jwt import JWTAuthentication # noqa: F401 -from fastapi_users.manager import UserManager, UserManagerDependency +from fastapi_users.manager import BaseUserManager, UserManagerDependency INVALID_CHARS_PATTERN = re.compile(r"[^0-9a-zA-Z_]") INVALID_LEADING_CHARS_PATTERN = re.compile(r"^[^a-zA-Z_]+") @@ -108,7 +108,7 @@ class Authenticator: async def _authenticate( self, *args, - user_manager: UserManager[models.UC, models.UD], + user_manager: BaseUserManager[models.UC, models.UD], optional: bool = False, active: bool = False, verified: bool = False, diff --git a/fastapi_users/authentication/base.py b/fastapi_users/authentication/base.py index a8b9fe4a..91d3c5db 100644 --- a/fastapi_users/authentication/base.py +++ b/fastapi_users/authentication/base.py @@ -4,7 +4,7 @@ from fastapi import Response from fastapi.security.base import SecurityBase from fastapi_users import models -from fastapi_users.manager import UserManager +from fastapi_users.manager import BaseUserManager T = TypeVar("T") @@ -28,7 +28,9 @@ class BaseAuthentication(Generic[T, models.UC, models.UD]): self.logout = logout async def __call__( - self, credentials: Optional[T], user_manager: UserManager[models.UC, models.UD] + self, + credentials: Optional[T], + user_manager: BaseUserManager[models.UC, models.UD], ) -> Optional[models.UD]: raise NotImplementedError() diff --git a/fastapi_users/authentication/cookie.py b/fastapi_users/authentication/cookie.py index baf8fb81..d8188f1e 100644 --- a/fastapi_users/authentication/cookie.py +++ b/fastapi_users/authentication/cookie.py @@ -8,7 +8,7 @@ from pydantic import UUID4 from fastapi_users import models from fastapi_users.authentication import BaseAuthentication from fastapi_users.jwt import SecretType, decode_jwt, generate_jwt -from fastapi_users.manager import UserManager, UserNotExists +from fastapi_users.manager import BaseUserManager, UserNotExists class CookieAuthentication( @@ -69,7 +69,7 @@ class CookieAuthentication( async def __call__( self, credentials: Optional[str], - user_manager: UserManager[models.UC, models.UD], + user_manager: BaseUserManager[models.UC, models.UD], ) -> Optional[models.UD]: if credentials is None: return None diff --git a/fastapi_users/authentication/jwt.py b/fastapi_users/authentication/jwt.py index c0020a84..8b13704a 100644 --- a/fastapi_users/authentication/jwt.py +++ b/fastapi_users/authentication/jwt.py @@ -8,7 +8,7 @@ from pydantic import UUID4 from fastapi_users import models from fastapi_users.authentication.base import BaseAuthentication from fastapi_users.jwt import SecretType, decode_jwt, generate_jwt -from fastapi_users.manager import UserManager, UserNotExists +from fastapi_users.manager import BaseUserManager, UserNotExists class JWTAuthentication( @@ -46,7 +46,7 @@ class JWTAuthentication( async def __call__( self, credentials: Optional[str], - user_manager: UserManager[models.UC, models.UD], + user_manager: BaseUserManager[models.UC, models.UD], ) -> Optional[models.UD]: if credentials is None: return None diff --git a/fastapi_users/fastapi_users.py b/fastapi_users/fastapi_users.py index 44384a4d..322b7944 100644 --- a/fastapi_users/fastapi_users.py +++ b/fastapi_users/fastapi_users.py @@ -1,13 +1,11 @@ from typing import Any, Callable, Dict, Generic, Optional, Sequence, Type -from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, Request from fastapi_users import models from fastapi_users.authentication import Authenticator, BaseAuthentication -from fastapi_users.db import BaseUserDatabase -from fastapi_users.db.base import UserDatabaseDependency from fastapi_users.jwt import SecretType -from fastapi_users.manager import UserManager, ValidatePasswordProtocol +from fastapi_users.manager import UserManagerDependency from fastapi_users.router import ( get_auth_router, get_register_router, @@ -28,21 +26,19 @@ class FastAPIUsers(Generic[models.U, models.UC, models.UU, models.UD]): """ Main object that ties together the component for users authentication. - :param get_db: Dependency callable returning a database adapter instance. + :param get_user_manager: Dependency callable getter to inject the + user manager class instance. :param auth_backends: List of authentication backends. :param user_model: Pydantic model of a user. :param user_create_model: Pydantic model for creating a user. :param user_update_model: Pydantic model for updating a user. :param user_db_model: Pydantic model of a DB representation of a user. - :attribute get_user_manager: Dependency callable getter to inject the - user manager class instance. :attribute current_user: Dependency callable getter to inject authenticated user with a specific set of parameters. """ authenticator: Authenticator - validate_password: Optional[ValidatePasswordProtocol] _user_model: Type[models.U] _user_create_model: Type[models.UC] _user_update_model: Type[models.UU] @@ -50,18 +46,13 @@ class FastAPIUsers(Generic[models.U, models.UC, models.UU, models.UD]): def __init__( self, - get_db: UserDatabaseDependency[models.UD], + get_user_manager: UserManagerDependency[models.UC, models.UD], auth_backends: Sequence[BaseAuthentication], user_model: Type[models.U], user_create_model: Type[models.UC], user_update_model: Type[models.UU], user_db_model: Type[models.UD], ): - def get_user_manager( - user_db: BaseUserDatabase[models.UD] = Depends(get_db), - ): - return UserManager(user_db_model, user_db) - self.authenticator = Authenticator(auth_backends, get_user_manager) self._user_model = user_model diff --git a/fastapi_users/manager.py b/fastapi_users/manager.py index 32088c83..0cde1105 100644 --- a/fastapi_users/manager.py +++ b/fastapi_users/manager.py @@ -1,12 +1,7 @@ -from typing import Any, Awaitable, Callable, Dict, Generic, Optional, Type, Union +from typing import Any, Callable, Dict, Generic, Optional, Type, Union from pydantic.types import UUID4 -try: - from typing import Protocol -except ImportError: # pragma: no cover - from typing_extensions import Protocol # type: ignore - from fastapi.security import OAuth2PasswordRequestForm from fastapi_users import models, password @@ -35,14 +30,7 @@ class InvalidPasswordException(FastAPIUsersException): self.reason = reason -class ValidatePasswordProtocol(Protocol): # pragma: no cover - def __call__( - self, password: str, user: Union[models.UC, models.UD] - ) -> Awaitable[None]: - pass - - -class UserManager(Generic[models.UC, models.UD]): +class BaseUserManager(Generic[models.UC, models.UD]): user_db_model: Type[models.UD] user_db: BaseUserDatabase[models.UD] @@ -115,7 +103,7 @@ class UserManager(Generic[models.UC, models.UD]): async def validate_password( self, password: str, user: Union[models.UC, models.UD] ) -> None: - return + return # pragma: no cover async def authenticate( self, credentials: OAuth2PasswordRequestForm @@ -164,4 +152,4 @@ class UserManager(Generic[models.UC, models.UD]): return updated_user -UserManagerDependency = Callable[..., UserManager[models.UC, models.UD]] +UserManagerDependency = Callable[..., BaseUserManager[models.UC, models.UD]] diff --git a/fastapi_users/router/auth.py b/fastapi_users/router/auth.py index be3e28c2..7879ef0e 100644 --- a/fastapi_users/router/auth.py +++ b/fastapi_users/router/auth.py @@ -3,7 +3,7 @@ from fastapi.security import OAuth2PasswordRequestForm from fastapi_users import models from fastapi_users.authentication import Authenticator, BaseAuthentication -from fastapi_users.manager import UserManager, UserManagerDependency +from fastapi_users.manager import BaseUserManager, UserManagerDependency from fastapi_users.router.common import ErrorCode @@ -23,7 +23,7 @@ def get_auth_router( async def login( response: Response, credentials: OAuth2PasswordRequestForm = Depends(), - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): user = await user_manager.authenticate(credentials) diff --git a/fastapi_users/router/oauth.py b/fastapi_users/router/oauth.py index a9746ef3..ac60b460 100644 --- a/fastapi_users/router/oauth.py +++ b/fastapi_users/router/oauth.py @@ -8,7 +8,7 @@ from httpx_oauth.oauth2 import BaseOAuth2 from fastapi_users import models from fastapi_users.authentication import Authenticator from fastapi_users.jwt import SecretType, decode_jwt, generate_jwt -from fastapi_users.manager import UserManager, UserManagerDependency, UserNotExists +from fastapi_users.manager import BaseUserManager, UserManagerDependency, UserNotExists from fastapi_users.password import generate_password, get_password_hash from fastapi_users.router.common import ErrorCode, run_handler @@ -83,7 +83,7 @@ def get_oauth_router( request: Request, response: Response, access_token_state=Depends(oauth2_authorize_callback), - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): token, state = access_token_state account_id, account_email = await oauth_client.get_id_email( diff --git a/fastapi_users/router/register.py b/fastapi_users/router/register.py index fd1db326..92b94e1c 100644 --- a/fastapi_users/router/register.py +++ b/fastapi_users/router/register.py @@ -6,7 +6,7 @@ from fastapi_users import models from fastapi_users.manager import ( InvalidPasswordException, UserAlreadyExists, - UserManager, + BaseUserManager, UserManagerDependency, ) from fastapi_users.router.common import ErrorCode, run_handler @@ -27,7 +27,7 @@ def get_register_router( async def register( request: Request, user: user_create_model, # type: ignore - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): try: created_user = await user_manager.create(user, safe=True) diff --git a/fastapi_users/router/reset.py b/fastapi_users/router/reset.py index 609d312f..a9a26358 100644 --- a/fastapi_users/router/reset.py +++ b/fastapi_users/router/reset.py @@ -8,7 +8,7 @@ from fastapi_users import models from fastapi_users.jwt import SecretType, decode_jwt, generate_jwt from fastapi_users.manager import ( InvalidPasswordException, - UserManager, + BaseUserManager, UserManagerDependency, UserNotExists, ) @@ -32,7 +32,7 @@ def get_reset_password_router( async def forgot_password( request: Request, email: EmailStr = Body(..., embed=True), - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): try: user = await user_manager.get_by_email(email) @@ -56,7 +56,7 @@ def get_reset_password_router( request: Request, token: str = Body(...), password: str = Body(...), - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): try: data = decode_jwt( diff --git a/fastapi_users/router/users.py b/fastapi_users/router/users.py index 2d62b75a..e368ff81 100644 --- a/fastapi_users/router/users.py +++ b/fastapi_users/router/users.py @@ -8,7 +8,7 @@ from fastapi_users.authentication import Authenticator from fastapi_users.manager import ( InvalidPasswordException, UserAlreadyExists, - UserManager, + BaseUserManager, UserManagerDependency, UserNotExists, ) @@ -36,7 +36,7 @@ def get_users_router( async def get_user_or_404( id: UUID4, - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ) -> models.UD: try: return await user_manager.get(id) @@ -58,7 +58,7 @@ def get_users_router( request: Request, user_update: user_update_model, # type: ignore user: user_db_model = Depends(get_current_active_user), # type: ignore - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): try: updated_user = await user_manager.update(user_update, user, safe=True) @@ -101,7 +101,7 @@ def get_users_router( user_update: user_update_model, # type: ignore request: Request, user=Depends(get_user_or_404), - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): try: updated_user = await user_manager.update(user_update, user, safe=False) @@ -135,7 +135,7 @@ def get_users_router( ) async def delete_user( user=Depends(get_user_or_404), - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): await user_manager.delete(user) return None diff --git a/fastapi_users/router/verify.py b/fastapi_users/router/verify.py index 3f7839e5..01b09653 100644 --- a/fastapi_users/router/verify.py +++ b/fastapi_users/router/verify.py @@ -8,7 +8,7 @@ from fastapi_users import models from fastapi_users.jwt import SecretType, decode_jwt, generate_jwt from fastapi_users.manager import ( UserAlreadyVerified, - UserManager, + BaseUserManager, UserManagerDependency, UserNotExists, ) @@ -33,7 +33,7 @@ def get_verify_router( async def request_verify_token( request: Request, email: EmailStr = Body(..., embed=True), - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): try: user = await user_manager.get_by_email(email) @@ -60,7 +60,7 @@ def get_verify_router( async def verify( request: Request, token: str = Body(..., embed=True), - user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager), + user_manager: BaseUserManager[models.UC, models.UD] = Depends(get_user_manager), ): try: data = decode_jwt( diff --git a/tests/conftest.py b/tests/conftest.py index b9f87ed2..68280bbc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,7 +15,7 @@ from fastapi_users.authentication import Authenticator, BaseAuthentication from fastapi_users.db import BaseUserDatabase from fastapi_users.jwt import SecretType from fastapi_users.manager import InvalidPasswordException -from fastapi_users.manager import UserManager as BaseUserManager +from fastapi_users.manager import BaseUserManager from fastapi_users.manager import UserNotExists from fastapi_users.models import BaseOAuthAccount, BaseOAuthAccountMixin from fastapi_users.password import get_password_hash diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 6de2323b..93291cbf 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -6,7 +6,7 @@ from fastapi.security.base import SecurityBase from fastapi_users import models from fastapi_users.authentication import BaseAuthentication, DuplicateBackendNamesError -from fastapi_users.manager import UserManager +from fastapi_users.manager import BaseUserManager class MockSecurityScheme(SecurityBase): @@ -24,7 +24,7 @@ class BackendNone( async def __call__( self, credentials: Optional[str], - user_manager: UserManager[models.UC, models.UD], + user_manager: BaseUserManager[models.UC, models.UD], ) -> Optional[models.UD]: return None @@ -40,7 +40,7 @@ class BackendUser( async def __call__( self, credentials: Optional[str], - user_manager: UserManager[models.UC, models.UD], + user_manager: BaseUserManager[models.UC, models.UD], ) -> Optional[models.UD]: return self.user diff --git a/tests/test_fastapi_users.py b/tests/test_fastapi_users.py index 0edebf50..7a48c8a1 100644 --- a/tests/test_fastapi_users.py +++ b/tests/test_fastapi_users.py @@ -12,13 +12,13 @@ from tests.conftest import User, UserCreate, UserDB, UserUpdate @pytest.mark.asyncio async def test_app_client( secret, - get_mock_user_db, + get_user_manager, mock_authentication, oauth_client, get_test_client, ) -> AsyncGenerator[httpx.AsyncClient, None]: fastapi_users = FastAPIUsers[User, UserCreate, UserUpdate, UserDB]( - get_mock_user_db, + get_user_manager, [mock_authentication], User, UserCreate, diff --git a/tests/test_manager.py b/tests/test_manager.py index dca5bc11..f8759a4e 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -4,13 +4,8 @@ import pytest from fastapi.security import OAuth2PasswordRequestForm from pytest_mock import MockerFixture -from fastapi_users.manager import UserAlreadyExists, UserAlreadyVerified, UserManager -from tests.conftest import UserCreate, UserDB - - -@pytest.fixture -def user_manager(mock_user_db) -> UserManager[UserCreate, UserDB]: - return UserManager(UserDB, mock_user_db) +from fastapi_users.manager import UserAlreadyExists, UserAlreadyVerified +from tests.conftest import UserCreate, UserDB, UserManager @pytest.fixture @@ -28,25 +23,19 @@ class TestCreateUser: @pytest.mark.parametrize( "email", ["king.arthur@camelot.bt", "King.Arthur@camelot.bt"] ) - async def test_existing_user( - self, email: str, user_manager: UserManager[UserCreate, UserDB] - ): + async def test_existing_user(self, email: str, user_manager: UserManager): user = UserCreate(email=email, password="guinevere") with pytest.raises(UserAlreadyExists): await user_manager.create(user) @pytest.mark.parametrize("email", ["lancelot@camelot.bt", "Lancelot@camelot.bt"]) - async def test_regular_user( - self, email: str, user_manager: UserManager[UserCreate, UserDB] - ): + async def test_regular_user(self, email: str, user_manager: UserManager): user = UserCreate(email=email, password="guinevere") created_user = await user_manager.create(user) assert type(created_user) == UserDB @pytest.mark.parametrize("safe,result", [(True, False), (False, True)]) - async def test_superuser( - self, user_manager: UserManager[UserCreate, UserDB], safe: bool, result: bool - ): + async def test_superuser(self, user_manager: UserManager, safe: bool, result: bool): user = UserCreate( email="lancelot@camelot.b", password="guinevere", is_superuser=True ) @@ -55,9 +44,7 @@ class TestCreateUser: assert created_user.is_superuser is result @pytest.mark.parametrize("safe,result", [(True, True), (False, False)]) - async def test_is_active( - self, user_manager: UserManager[UserCreate, UserDB], safe: bool, result: bool - ): + async def test_is_active(self, user_manager: UserManager, safe: bool, result: bool): user = UserCreate( email="lancelot@camelot.b", password="guinevere", is_active=False ) @@ -69,14 +56,12 @@ class TestCreateUser: @pytest.mark.asyncio class TestVerifyUser: async def test_already_verified_user( - self, user_manager: UserManager[UserCreate, UserDB], verified_user: UserDB + self, user_manager: UserManager, verified_user: UserDB ): with pytest.raises(UserAlreadyVerified): await user_manager.verify(verified_user) - async def test_non_verified_user( - self, user_manager: UserManager[UserCreate, UserDB], user: UserDB - ): + async def test_non_verified_user(self, user_manager: UserManager, user: UserDB): user = await user_manager.verify(user) assert user.is_verified @@ -89,7 +74,7 @@ class TestAuthenticate: create_oauth2_password_request_form: Callable[ [str, str], OAuth2PasswordRequestForm ], - user_manager: UserManager[UserCreate, UserDB], + user_manager: UserManager, ): form = create_oauth2_password_request_form("lancelot@camelot.bt", "guinevere") user = await user_manager.authenticate(form) @@ -101,7 +86,7 @@ class TestAuthenticate: create_oauth2_password_request_form: Callable[ [str, str], OAuth2PasswordRequestForm ], - user_manager: UserManager[UserCreate, UserDB], + user_manager: UserManager, ): form = create_oauth2_password_request_form("king.arthur@camelot.bt", "percival") user = await user_manager.authenticate(form) @@ -113,7 +98,7 @@ class TestAuthenticate: create_oauth2_password_request_form: Callable[ [str, str], OAuth2PasswordRequestForm ], - user_manager: UserManager[UserCreate, UserDB], + user_manager: UserManager, ): form = create_oauth2_password_request_form( "king.arthur@camelot.bt", "guinevere" @@ -129,7 +114,7 @@ class TestAuthenticate: create_oauth2_password_request_form: Callable[ [str, str], OAuth2PasswordRequestForm ], - user_manager: UserManager[UserCreate, UserDB], + user_manager: UserManager, ): verify_and_update_password_patch = mocker.patch( "fastapi_users.password.verify_and_update_password"