mirror of
https://github.com/fastapi-users/fastapi-users.git
synced 2025-08-26 12:31:25 +08:00
Inject a user manager dep callable directly to FastAPIUsers
This commit is contained in:
@ -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,
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]]
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
Reference in New Issue
Block a user