mirror of
https://github.com/fastapi-users/fastapi-users.git
synced 2026-03-13 07:49:55 +08:00
Move validate_password into UserManager
This commit is contained in:
@@ -43,7 +43,7 @@ class Authenticator:
|
||||
def __init__(
|
||||
self,
|
||||
backends: Sequence[BaseAuthentication],
|
||||
get_user_manager: UserManagerDependency[models.UD],
|
||||
get_user_manager: UserManagerDependency[models.UC, models.UD],
|
||||
):
|
||||
self.backends = backends
|
||||
self.get_user_manager = get_user_manager
|
||||
@@ -108,7 +108,7 @@ class Authenticator:
|
||||
async def _authenticate(
|
||||
self,
|
||||
*args,
|
||||
user_manager: UserManager[models.UD],
|
||||
user_manager: UserManager[models.UC, models.UD],
|
||||
optional: bool = False,
|
||||
active: bool = False,
|
||||
verified: bool = False,
|
||||
|
||||
@@ -9,7 +9,7 @@ from fastapi_users.manager import UserManager
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class BaseAuthentication(Generic[T]):
|
||||
class BaseAuthentication(Generic[T, models.UC, models.UD]):
|
||||
"""
|
||||
Base authentication backend.
|
||||
|
||||
@@ -28,7 +28,7 @@ class BaseAuthentication(Generic[T]):
|
||||
self.logout = logout
|
||||
|
||||
async def __call__(
|
||||
self, credentials: Optional[T], user_manager: UserManager[models.UD]
|
||||
self, credentials: Optional[T], user_manager: UserManager[models.UC, models.UD]
|
||||
) -> Optional[models.UD]:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any, Generic, List, Optional
|
||||
|
||||
import jwt
|
||||
from fastapi import Response
|
||||
@@ -11,7 +11,9 @@ from fastapi_users.jwt import SecretType, decode_jwt, generate_jwt
|
||||
from fastapi_users.manager import UserManager, UserNotExists
|
||||
|
||||
|
||||
class CookieAuthentication(BaseAuthentication[str]):
|
||||
class CookieAuthentication(
|
||||
Generic[models.UC, models.UD], BaseAuthentication[str, models.UC, models.UD]
|
||||
):
|
||||
"""
|
||||
Authentication backend using a cookie.
|
||||
|
||||
@@ -67,7 +69,7 @@ class CookieAuthentication(BaseAuthentication[str]):
|
||||
async def __call__(
|
||||
self,
|
||||
credentials: Optional[str],
|
||||
user_manager: UserManager[models.UD],
|
||||
user_manager: UserManager[models.UC, models.UD],
|
||||
) -> Optional[models.UD]:
|
||||
if credentials is None:
|
||||
return None
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any, Generic, List, Optional
|
||||
|
||||
import jwt
|
||||
from fastapi import Response
|
||||
@@ -11,7 +11,9 @@ from fastapi_users.jwt import SecretType, decode_jwt, generate_jwt
|
||||
from fastapi_users.manager import UserManager, UserNotExists
|
||||
|
||||
|
||||
class JWTAuthentication(BaseAuthentication[str]):
|
||||
class JWTAuthentication(
|
||||
Generic[models.UC, models.UD], BaseAuthentication[str, models.UC, models.UD]
|
||||
):
|
||||
"""
|
||||
Authentication backend using a JWT in a Bearer header.
|
||||
|
||||
@@ -44,7 +46,7 @@ class JWTAuthentication(BaseAuthentication[str]):
|
||||
async def __call__(
|
||||
self,
|
||||
credentials: Optional[str],
|
||||
user_manager: UserManager[models.UD],
|
||||
user_manager: UserManager[models.UC, models.UD],
|
||||
) -> Optional[models.UD]:
|
||||
if credentials is None:
|
||||
return None
|
||||
|
||||
@@ -34,8 +34,6 @@ class FastAPIUsers(Generic[models.U, models.UC, models.UU, models.UD]):
|
||||
: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.
|
||||
:param validate_password: Optional function to validate the password
|
||||
at user registration, user update or password reset.
|
||||
|
||||
:attribute get_user_manager: Dependency callable getter to inject the
|
||||
user manager class instance.
|
||||
@@ -58,12 +56,11 @@ class FastAPIUsers(Generic[models.U, models.UC, models.UU, models.UD]):
|
||||
user_create_model: Type[models.UC],
|
||||
user_update_model: Type[models.UU],
|
||||
user_db_model: Type[models.UD],
|
||||
validate_password: Optional[ValidatePasswordProtocol] = None,
|
||||
):
|
||||
def get_user_manager(
|
||||
user_db: BaseUserDatabase[models.UD] = Depends(get_db),
|
||||
):
|
||||
return UserManager(user_db_model, user_db, validate_password)
|
||||
return UserManager(user_db_model, user_db)
|
||||
|
||||
self.authenticator = Authenticator(auth_backends, get_user_manager)
|
||||
|
||||
@@ -73,8 +70,6 @@ class FastAPIUsers(Generic[models.U, models.UC, models.UU, models.UD]):
|
||||
self._user_update_model = user_update_model
|
||||
self._user_db_model = user_db_model
|
||||
|
||||
self.validate_password = validate_password
|
||||
|
||||
self.get_user_manager = get_user_manager
|
||||
self.current_user = self.authenticator.current_user
|
||||
|
||||
|
||||
@@ -42,21 +42,18 @@ class ValidatePasswordProtocol(Protocol): # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
class UserManager(Generic[models.UD]):
|
||||
class UserManager(Generic[models.UC, models.UD]):
|
||||
|
||||
user_db_model: Type[models.UD]
|
||||
user_db: BaseUserDatabase[models.UD]
|
||||
validate_password: Optional[ValidatePasswordProtocol]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_db_model: Type[models.UD],
|
||||
user_db: BaseUserDatabase[models.UD],
|
||||
validate_password: Optional[ValidatePasswordProtocol] = None,
|
||||
):
|
||||
self.user_db_model = user_db_model
|
||||
self.user_db = user_db
|
||||
self.validate_password = validate_password
|
||||
|
||||
async def get(self, id: UUID4) -> models.UD:
|
||||
user = await self.user_db.get(id)
|
||||
@@ -83,8 +80,7 @@ class UserManager(Generic[models.UD]):
|
||||
return user
|
||||
|
||||
async def create(self, user: models.UC, safe: bool = False) -> models.UD:
|
||||
if self.validate_password:
|
||||
await self.validate_password(user.password, user)
|
||||
await self.validate_password(user.password, user)
|
||||
|
||||
existing_user = await self.user_db.get_by_email(user.email)
|
||||
if existing_user is not None:
|
||||
@@ -116,6 +112,11 @@ class UserManager(Generic[models.UD]):
|
||||
async def delete(self, user: models.UD) -> None:
|
||||
await self.user_db.delete(user)
|
||||
|
||||
async def validate_password(
|
||||
self, password: str, user: Union[models.UC, models.UD]
|
||||
) -> None:
|
||||
return
|
||||
|
||||
async def authenticate(
|
||||
self, credentials: OAuth2PasswordRequestForm
|
||||
) -> Optional[models.UD]:
|
||||
@@ -154,8 +155,7 @@ class UserManager(Generic[models.UD]):
|
||||
user.email = update_dict[field]
|
||||
elif field == "password":
|
||||
password = update_dict[field]
|
||||
if self.validate_password:
|
||||
await self.validate_password(password, user)
|
||||
await self.validate_password(password, user)
|
||||
hashed_password = get_password_hash(password)
|
||||
user.hashed_password = hashed_password
|
||||
else:
|
||||
@@ -164,4 +164,4 @@ class UserManager(Generic[models.UD]):
|
||||
return updated_user
|
||||
|
||||
|
||||
UserManagerDependency = Callable[..., UserManager[models.UD]]
|
||||
UserManagerDependency = Callable[..., UserManager[models.UC, models.UD]]
|
||||
|
||||
@@ -9,7 +9,7 @@ from fastapi_users.router.common import ErrorCode
|
||||
|
||||
def get_auth_router(
|
||||
backend: BaseAuthentication,
|
||||
get_user_manager: UserManagerDependency[models.UD],
|
||||
get_user_manager: UserManagerDependency[models.UC, models.UD],
|
||||
authenticator: Authenticator,
|
||||
requires_verification: bool = False,
|
||||
) -> APIRouter:
|
||||
@@ -23,7 +23,7 @@ def get_auth_router(
|
||||
async def login(
|
||||
response: Response,
|
||||
credentials: OAuth2PasswordRequestForm = Depends(),
|
||||
user_manager: UserManager[models.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
):
|
||||
user = await user_manager.authenticate(credentials)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ def generate_state_token(
|
||||
|
||||
def get_oauth_router(
|
||||
oauth_client: BaseOAuth2,
|
||||
get_user_manager: UserManagerDependency[models.UD],
|
||||
get_user_manager: UserManagerDependency[models.UC, models.UD],
|
||||
user_db_model: Type[models.UD],
|
||||
authenticator: Authenticator,
|
||||
state_secret: SecretType,
|
||||
@@ -83,7 +83,7 @@ def get_oauth_router(
|
||||
request: Request,
|
||||
response: Response,
|
||||
access_token_state=Depends(oauth2_authorize_callback),
|
||||
user_manager: UserManager[models.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
):
|
||||
token, state = access_token_state
|
||||
account_id, account_email = await oauth_client.get_id_email(
|
||||
|
||||
@@ -13,7 +13,7 @@ from fastapi_users.router.common import ErrorCode, run_handler
|
||||
|
||||
|
||||
def get_register_router(
|
||||
get_user_manager: UserManagerDependency[models.UD],
|
||||
get_user_manager: UserManagerDependency[models.UC, models.UD],
|
||||
user_model: Type[models.U],
|
||||
user_create_model: Type[models.UC],
|
||||
after_register: Optional[Callable[[models.UD, Request], None]] = None,
|
||||
@@ -27,7 +27,7 @@ def get_register_router(
|
||||
async def register(
|
||||
request: Request,
|
||||
user: user_create_model, # type: ignore
|
||||
user_manager: UserManager[models.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
):
|
||||
try:
|
||||
created_user = await user_manager.create(user, safe=True)
|
||||
|
||||
@@ -11,7 +11,6 @@ from fastapi_users.manager import (
|
||||
UserManager,
|
||||
UserManagerDependency,
|
||||
UserNotExists,
|
||||
ValidatePasswordProtocol,
|
||||
)
|
||||
from fastapi_users.password import get_password_hash
|
||||
from fastapi_users.router.common import ErrorCode, run_handler
|
||||
@@ -20,12 +19,11 @@ RESET_PASSWORD_TOKEN_AUDIENCE = "fastapi-users:reset"
|
||||
|
||||
|
||||
def get_reset_password_router(
|
||||
get_user_manager: UserManagerDependency[models.UD],
|
||||
get_user_manager: UserManagerDependency[models.UC, models.UD],
|
||||
reset_password_token_secret: SecretType,
|
||||
reset_password_token_lifetime_seconds: int = 3600,
|
||||
after_forgot_password: Optional[Callable[[models.UD, str, Request], None]] = None,
|
||||
after_reset_password: Optional[Callable[[models.UD, Request], None]] = None,
|
||||
validate_password: Optional[ValidatePasswordProtocol] = None,
|
||||
) -> APIRouter:
|
||||
"""Generate a router with the reset password routes."""
|
||||
router = APIRouter()
|
||||
@@ -34,7 +32,7 @@ def get_reset_password_router(
|
||||
async def forgot_password(
|
||||
request: Request,
|
||||
email: EmailStr = Body(..., embed=True),
|
||||
user_manager: UserManager[models.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
):
|
||||
try:
|
||||
user = await user_manager.get_by_email(email)
|
||||
@@ -58,7 +56,7 @@ def get_reset_password_router(
|
||||
request: Request,
|
||||
token: str = Body(...),
|
||||
password: str = Body(...),
|
||||
user_manager: UserManager[models.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
):
|
||||
try:
|
||||
data = decode_jwt(
|
||||
@@ -93,17 +91,16 @@ def get_reset_password_router(
|
||||
detail=ErrorCode.RESET_PASSWORD_BAD_TOKEN,
|
||||
)
|
||||
|
||||
if validate_password:
|
||||
try:
|
||||
await validate_password(password, user)
|
||||
except InvalidPasswordException as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": ErrorCode.RESET_PASSWORD_INVALID_PASSWORD,
|
||||
"reason": e.reason,
|
||||
},
|
||||
)
|
||||
try:
|
||||
await user_manager.validate_password(password, user)
|
||||
except InvalidPasswordException as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": ErrorCode.RESET_PASSWORD_INVALID_PASSWORD,
|
||||
"reason": e.reason,
|
||||
},
|
||||
)
|
||||
|
||||
user.hashed_password = get_password_hash(password)
|
||||
await user_manager.user_db.update(user)
|
||||
|
||||
@@ -16,7 +16,7 @@ from fastapi_users.router.common import ErrorCode, run_handler
|
||||
|
||||
|
||||
def get_users_router(
|
||||
get_user_manager: UserManagerDependency[models.UD],
|
||||
get_user_manager: UserManagerDependency[models.UC, models.UD],
|
||||
user_model: Type[models.U],
|
||||
user_update_model: Type[models.UU],
|
||||
user_db_model: Type[models.UD],
|
||||
@@ -35,7 +35,8 @@ def get_users_router(
|
||||
)
|
||||
|
||||
async def get_user_or_404(
|
||||
id: UUID4, user_manager: UserManager[models.UD] = Depends(get_user_manager)
|
||||
id: UUID4,
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
) -> models.UD:
|
||||
try:
|
||||
return await user_manager.get(id)
|
||||
@@ -57,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.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
):
|
||||
try:
|
||||
updated_user = await user_manager.update(user_update, user, safe=True)
|
||||
@@ -100,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.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
):
|
||||
try:
|
||||
updated_user = await user_manager.update(user_update, user, safe=False)
|
||||
@@ -134,7 +135,7 @@ def get_users_router(
|
||||
)
|
||||
async def delete_user(
|
||||
user=Depends(get_user_or_404),
|
||||
user_manager: UserManager[models.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
):
|
||||
await user_manager.delete(user)
|
||||
return None
|
||||
|
||||
@@ -18,7 +18,7 @@ VERIFY_USER_TOKEN_AUDIENCE = "fastapi-users:verify"
|
||||
|
||||
|
||||
def get_verify_router(
|
||||
get_user_manager: UserManagerDependency[models.UD],
|
||||
get_user_manager: UserManagerDependency[models.UC, models.UD],
|
||||
user_model: Type[models.U],
|
||||
verification_token_secret: SecretType,
|
||||
verification_token_lifetime_seconds: int = 3600,
|
||||
@@ -33,7 +33,7 @@ def get_verify_router(
|
||||
async def request_verify_token(
|
||||
request: Request,
|
||||
email: EmailStr = Body(..., embed=True),
|
||||
user_manager: UserManager[models.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[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.UD] = Depends(get_user_manager),
|
||||
user_manager: UserManager[models.UC, models.UD] = Depends(get_user_manager),
|
||||
):
|
||||
try:
|
||||
data = decode_jwt(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import asyncio
|
||||
from typing import AsyncGenerator, List, Optional
|
||||
from typing import AsyncGenerator, List, Optional, Union
|
||||
|
||||
import asynctest
|
||||
import httpx
|
||||
import pytest
|
||||
from asgi_lifespan import LifespanManager
|
||||
@@ -15,13 +14,10 @@ from fastapi_users import models
|
||||
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,
|
||||
UserManager,
|
||||
UserNotExists,
|
||||
ValidatePasswordProtocol,
|
||||
)
|
||||
from fastapi_users.models import BaseOAuthAccount, BaseOAuthAccountMixin, BaseUserDB
|
||||
from fastapi_users.manager import InvalidPasswordException
|
||||
from fastapi_users.manager import UserManager as BaseUserManager
|
||||
from fastapi_users.manager import UserNotExists
|
||||
from fastapi_users.models import BaseOAuthAccount, BaseOAuthAccountMixin
|
||||
from fastapi_users.password import get_password_hash
|
||||
|
||||
guinevere_password_hash = get_password_hash("guinevere")
|
||||
@@ -55,6 +51,16 @@ class UserDBOAuth(UserOAuth, UserDB):
|
||||
pass
|
||||
|
||||
|
||||
class UserManager(BaseUserManager[UserCreate, UserDB]):
|
||||
async def validate_password(
|
||||
self, password: str, user: Union[UserCreate, UserDB]
|
||||
) -> None:
|
||||
if len(password) < 3:
|
||||
raise InvalidPasswordException(
|
||||
reason="Password should be at least 3 characters"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
"""Force the pytest-asyncio loop to be the main one."""
|
||||
@@ -218,17 +224,6 @@ def oauth_account5() -> BaseOAuthAccount:
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def validate_password() -> ValidatePasswordProtocol:
|
||||
async def _validate_password(password: str, user: models.UD) -> None:
|
||||
if len(password) < 3:
|
||||
raise InvalidPasswordException(
|
||||
reason="Password should be at least 3 characters"
|
||||
)
|
||||
|
||||
return asynctest.CoroutineMock(wraps=_validate_password)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_user_db(
|
||||
user, verified_user, inactive_user, superuser, verified_superuser
|
||||
@@ -356,32 +351,32 @@ def get_mock_user_db_oauth(mock_user_db_oauth):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user_manager(mock_user_db, validate_password):
|
||||
return UserManager(UserDB, mock_user_db, validate_password)
|
||||
def user_manager(mock_user_db):
|
||||
return UserManager(UserDB, mock_user_db)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_user_manager(get_mock_user_db, validate_password):
|
||||
def get_user_manager(get_mock_user_db):
|
||||
def _get_user_manager(user_db=Depends(get_mock_user_db)):
|
||||
return UserManager(UserDB, user_db, validate_password)
|
||||
return UserManager(UserDB, user_db)
|
||||
|
||||
return _get_user_manager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_user_manager_oauth(get_mock_user_db_oauth, validate_password):
|
||||
def get_user_manager_oauth(get_mock_user_db_oauth):
|
||||
def _get_user_manager_oauth(user_db=Depends(get_mock_user_db_oauth)):
|
||||
return UserManager(UserDBOAuth, user_db, validate_password)
|
||||
return UserManager(UserDBOAuth, user_db)
|
||||
|
||||
return _get_user_manager_oauth
|
||||
|
||||
|
||||
class MockAuthentication(BaseAuthentication[str]):
|
||||
class MockAuthentication(BaseAuthentication[str, UserCreate, UserDB]):
|
||||
def __init__(self, name: str = "mock"):
|
||||
super().__init__(name, logout=True)
|
||||
self.scheme = OAuth2PasswordBearer("/login", auto_error=False)
|
||||
|
||||
async def __call__(self, credentials: Optional[str], user_manager: UserManager):
|
||||
async def __call__(self, credentials: Optional[str], user_manager: BaseUserManager):
|
||||
if credentials is not None:
|
||||
try:
|
||||
token_uuid = UUID4(credentials)
|
||||
@@ -392,10 +387,10 @@ class MockAuthentication(BaseAuthentication[str]):
|
||||
return None
|
||||
return None
|
||||
|
||||
async def get_login_response(self, user: BaseUserDB, response: Response):
|
||||
async def get_login_response(self, user: UserDB, response: Response):
|
||||
return {"token": user.id}
|
||||
|
||||
async def get_logout_response(self, user: BaseUserDB, response: Response):
|
||||
async def get_logout_response(self, user: UserDB, response: Response):
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Generic, Optional
|
||||
|
||||
import pytest
|
||||
from fastapi import Request, status
|
||||
@@ -14,25 +14,33 @@ class MockSecurityScheme(SecurityBase):
|
||||
return "mock"
|
||||
|
||||
|
||||
class BackendNone(BaseAuthentication[str]):
|
||||
class BackendNone(
|
||||
Generic[models.UC, models.UD], BaseAuthentication[str, models.UC, models.UD]
|
||||
):
|
||||
def __init__(self, name="none"):
|
||||
super().__init__(name, logout=False)
|
||||
self.scheme = MockSecurityScheme()
|
||||
|
||||
async def __call__(
|
||||
self, credentials: Optional[str], user_manager: UserManager[models.UD]
|
||||
self,
|
||||
credentials: Optional[str],
|
||||
user_manager: UserManager[models.UC, models.UD],
|
||||
) -> Optional[models.UD]:
|
||||
return None
|
||||
|
||||
|
||||
class BackendUser(BaseAuthentication[str]):
|
||||
class BackendUser(
|
||||
Generic[models.UC, models.UD], BaseAuthentication[str, models.UC, models.UD]
|
||||
):
|
||||
def __init__(self, user: models.UD, name="user"):
|
||||
super().__init__(name, logout=False)
|
||||
self.scheme = MockSecurityScheme()
|
||||
self.user = user
|
||||
|
||||
async def __call__(
|
||||
self, credentials: Optional[str], user_manager: UserManager[models.UD]
|
||||
self,
|
||||
credentials: Optional[str],
|
||||
user_manager: UserManager[models.UC, models.UD],
|
||||
) -> Optional[models.UD]:
|
||||
return self.user
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ async def test_app_client(
|
||||
mock_authentication,
|
||||
oauth_client,
|
||||
get_test_client,
|
||||
validate_password,
|
||||
) -> AsyncGenerator[httpx.AsyncClient, None]:
|
||||
fastapi_users = FastAPIUsers[User, UserCreate, UserUpdate, UserDB](
|
||||
get_mock_user_db,
|
||||
@@ -25,7 +24,6 @@ async def test_app_client(
|
||||
UserCreate,
|
||||
UserUpdate,
|
||||
UserDB,
|
||||
validate_password,
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@@ -9,7 +9,7 @@ from tests.conftest import UserCreate, UserDB
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user_manager(mock_user_db) -> UserManager[UserDB]:
|
||||
def user_manager(mock_user_db) -> UserManager[UserCreate, UserDB]:
|
||||
return UserManager(UserDB, mock_user_db)
|
||||
|
||||
|
||||
@@ -28,20 +28,24 @@ 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[UserDB]):
|
||||
async def test_existing_user(
|
||||
self, email: str, user_manager: UserManager[UserCreate, UserDB]
|
||||
):
|
||||
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[UserDB]):
|
||||
async def test_regular_user(
|
||||
self, email: str, user_manager: UserManager[UserCreate, UserDB]
|
||||
):
|
||||
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[UserDB], safe: bool, result: bool
|
||||
self, user_manager: UserManager[UserCreate, UserDB], safe: bool, result: bool
|
||||
):
|
||||
user = UserCreate(
|
||||
email="lancelot@camelot.b", password="guinevere", is_superuser=True
|
||||
@@ -52,7 +56,7 @@ class TestCreateUser:
|
||||
|
||||
@pytest.mark.parametrize("safe,result", [(True, True), (False, False)])
|
||||
async def test_is_active(
|
||||
self, user_manager: UserManager[UserDB], safe: bool, result: bool
|
||||
self, user_manager: UserManager[UserCreate, UserDB], safe: bool, result: bool
|
||||
):
|
||||
user = UserCreate(
|
||||
email="lancelot@camelot.b", password="guinevere", is_active=False
|
||||
@@ -65,13 +69,13 @@ class TestCreateUser:
|
||||
@pytest.mark.asyncio
|
||||
class TestVerifyUser:
|
||||
async def test_already_verified_user(
|
||||
self, user_manager: UserManager[UserDB], verified_user: UserDB
|
||||
self, user_manager: UserManager[UserCreate, UserDB], verified_user: UserDB
|
||||
):
|
||||
with pytest.raises(UserAlreadyVerified):
|
||||
await user_manager.verify(verified_user)
|
||||
|
||||
async def test_non_verified_user(
|
||||
self, user_manager: UserManager[UserDB], user: UserDB
|
||||
self, user_manager: UserManager[UserCreate, UserDB], user: UserDB
|
||||
):
|
||||
user = await user_manager.verify(user)
|
||||
assert user.is_verified
|
||||
@@ -85,7 +89,7 @@ class TestAuthenticate:
|
||||
create_oauth2_password_request_form: Callable[
|
||||
[str, str], OAuth2PasswordRequestForm
|
||||
],
|
||||
user_manager: UserManager[UserDB],
|
||||
user_manager: UserManager[UserCreate, UserDB],
|
||||
):
|
||||
form = create_oauth2_password_request_form("lancelot@camelot.bt", "guinevere")
|
||||
user = await user_manager.authenticate(form)
|
||||
@@ -97,7 +101,7 @@ class TestAuthenticate:
|
||||
create_oauth2_password_request_form: Callable[
|
||||
[str, str], OAuth2PasswordRequestForm
|
||||
],
|
||||
user_manager: UserManager[UserDB],
|
||||
user_manager: UserManager[UserCreate, UserDB],
|
||||
):
|
||||
form = create_oauth2_password_request_form("king.arthur@camelot.bt", "percival")
|
||||
user = await user_manager.authenticate(form)
|
||||
@@ -109,7 +113,7 @@ class TestAuthenticate:
|
||||
create_oauth2_password_request_form: Callable[
|
||||
[str, str], OAuth2PasswordRequestForm
|
||||
],
|
||||
user_manager: UserManager[UserDB],
|
||||
user_manager: UserManager[UserCreate, UserDB],
|
||||
):
|
||||
form = create_oauth2_password_request_form(
|
||||
"king.arthur@camelot.bt", "guinevere"
|
||||
@@ -125,7 +129,7 @@ class TestAuthenticate:
|
||||
create_oauth2_password_request_form: Callable[
|
||||
[str, str], OAuth2PasswordRequestForm
|
||||
],
|
||||
user_manager: UserManager[UserDB],
|
||||
user_manager: UserManager[UserCreate, UserDB],
|
||||
):
|
||||
verify_and_update_password_patch = mocker.patch(
|
||||
"fastapi_users.password.verify_and_update_password"
|
||||
|
||||
@@ -75,7 +75,7 @@ class TestRegister:
|
||||
assert after_register.called is False
|
||||
|
||||
async def test_invalid_password(
|
||||
self, test_app_client: httpx.AsyncClient, after_register, validate_password
|
||||
self, test_app_client: httpx.AsyncClient, after_register
|
||||
):
|
||||
json = {"email": "king.arthur@camelot.bt", "password": "g"}
|
||||
response = await test_app_client.post("/register", json=json)
|
||||
@@ -85,9 +85,6 @@ class TestRegister:
|
||||
"code": ErrorCode.REGISTER_INVALID_PASSWORD,
|
||||
"reason": "Password should be at least 3 characters",
|
||||
}
|
||||
validate_password.assert_called_with(
|
||||
"g", UserCreate(email="king.arthur@camelot.bt", password="g")
|
||||
)
|
||||
assert after_register.called is False
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -58,7 +58,6 @@ async def test_app_client(
|
||||
after_forgot_password,
|
||||
after_reset_password,
|
||||
get_test_client,
|
||||
validate_password,
|
||||
) -> AsyncGenerator[httpx.AsyncClient, None]:
|
||||
reset_router = get_reset_password_router(
|
||||
get_user_manager,
|
||||
@@ -66,7 +65,6 @@ async def test_app_client(
|
||||
LIFETIME,
|
||||
after_forgot_password,
|
||||
after_reset_password,
|
||||
validate_password,
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
@@ -255,7 +253,6 @@ class TestResetPassword:
|
||||
forgot_password_token,
|
||||
user: UserDB,
|
||||
after_reset_password,
|
||||
validate_password,
|
||||
):
|
||||
mocker.spy(mock_user_db, "update")
|
||||
|
||||
@@ -270,7 +267,6 @@ class TestResetPassword:
|
||||
"code": ErrorCode.RESET_PASSWORD_INVALID_PASSWORD,
|
||||
"reason": "Password should be at least 3 characters",
|
||||
}
|
||||
validate_password.assert_called_with("h", user)
|
||||
assert mock_user_db.update.called is False
|
||||
assert after_reset_password.called is False
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ def after_update(request):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app_factory(get_user_manager, mock_authentication, after_update, validate_password):
|
||||
def app_factory(get_user_manager, mock_authentication, after_update):
|
||||
def _app_factory(requires_verification: bool) -> FastAPI:
|
||||
mock_authentication_bis = MockAuthentication(name="mock-bis")
|
||||
authenticator = Authenticator(
|
||||
@@ -168,7 +168,6 @@ class TestUpdateMe:
|
||||
test_app_client: Tuple[httpx.AsyncClient, bool],
|
||||
user: UserDB,
|
||||
after_update,
|
||||
validate_password,
|
||||
):
|
||||
client, requires_verification = test_app_client
|
||||
response = await client.patch(
|
||||
@@ -186,7 +185,6 @@ class TestUpdateMe:
|
||||
"code": ErrorCode.UPDATE_USER_INVALID_PASSWORD,
|
||||
"reason": "Password should be at least 3 characters",
|
||||
}
|
||||
validate_password.assert_called_with("m", user)
|
||||
assert after_update.called is False
|
||||
|
||||
async def test_empty_body(
|
||||
@@ -751,7 +749,6 @@ class TestUpdateUser:
|
||||
user: UserDB,
|
||||
verified_superuser: UserDB,
|
||||
after_update,
|
||||
validate_password,
|
||||
):
|
||||
client, _ = test_app_client
|
||||
response = await client.patch(
|
||||
@@ -765,7 +762,6 @@ class TestUpdateUser:
|
||||
"code": ErrorCode.UPDATE_USER_INVALID_PASSWORD,
|
||||
"reason": "Password should be at least 3 characters",
|
||||
}
|
||||
validate_password.assert_called_with("m", user)
|
||||
assert after_update.called is False
|
||||
|
||||
async def test_valid_body_verified_superuser(
|
||||
|
||||
Reference in New Issue
Block a user