Files
fastapi-users/tests/test_authentication_authenticator.py
François Voron 72aa68c462 Native model and generic ID (#971)
* Use a generic Protocol model for User instead of Pydantic

* Remove UserDB Pydantic schema

* Harmonize schema variable naming to avoid confusions

* Revamp OAuth account model management

* Revamp AccessToken DB strategy to adopt generic model approach

* Make ID a generic instead of forcing UUIDs

* Improve generic typing

* Improve Strategy typing

* Tweak base DB typing

* Don't set Pydantic schemas on FastAPIUsers class: pass it directly on router creation

* Add IntegerIdMixin and export related classes

* Start to revamp doc for V10

* Revamp OAuth documentation

* Fix code highlights

* Write the 9.x.x ➡️ 10.x.x migration doc

* Fix pyproject.toml
2022-05-05 14:51:19 +02:00

159 lines
4.9 KiB
Python

from typing import AsyncGenerator, Generic, List, Optional, Sequence
import httpx
import pytest
from fastapi import Depends, FastAPI, Request, status
from fastapi.security.base import SecurityBase
from fastapi_users import models
from fastapi_users.authentication import AuthenticationBackend, Authenticator
from fastapi_users.authentication.authenticator import DuplicateBackendNamesError
from fastapi_users.authentication.strategy import Strategy
from fastapi_users.authentication.transport import Transport
from fastapi_users.manager import BaseUserManager
from fastapi_users.types import DependencyCallable
from tests.conftest import User, UserModel
class MockSecurityScheme(SecurityBase):
def __call__(self, request: Request) -> Optional[str]:
return "mock"
class MockTransport(Transport):
scheme: MockSecurityScheme
def __init__(self):
self.scheme = MockSecurityScheme()
class NoneStrategy(Strategy):
async def read_token(
self, token: Optional[str], user_manager: BaseUserManager[models.UP, models.ID]
) -> Optional[models.UP]:
return None
class UserStrategy(Strategy, Generic[models.UP]):
def __init__(self, user: models.UP):
self.user = user
async def read_token(
self, token: Optional[str], user_manager: BaseUserManager[models.UP, models.ID]
) -> Optional[models.UP]:
return self.user
@pytest.fixture
def get_backend_none():
def _get_backend_none(name: str = "none"):
return AuthenticationBackend(
name=name, transport=MockTransport(), get_strategy=lambda: NoneStrategy()
)
return _get_backend_none
@pytest.fixture
def get_backend_user(user: UserModel):
def _get_backend_user(name: str = "user"):
return AuthenticationBackend(
name=name,
transport=MockTransport(),
get_strategy=lambda: UserStrategy(user),
)
return _get_backend_user
@pytest.fixture
@pytest.mark.asyncio
def get_test_auth_client(get_user_manager, get_test_client):
async def _get_test_auth_client(
backends: List[AuthenticationBackend],
get_enabled_backends: Optional[
DependencyCallable[Sequence[AuthenticationBackend]]
] = None,
) -> AsyncGenerator[httpx.AsyncClient, None]:
app = FastAPI()
authenticator = Authenticator(backends, get_user_manager)
@app.get("/test-current-user", response_model=User)
def test_current_user(
user: UserModel = Depends(
authenticator.current_user(get_enabled_backends=get_enabled_backends)
),
):
return user
@app.get("/test-current-active-user", response_model=User)
def test_current_active_user(
user: UserModel = Depends(
authenticator.current_user(
active=True, get_enabled_backends=get_enabled_backends
)
),
):
return user
@app.get("/test-current-superuser", response_model=User)
def test_current_superuser(
user: UserModel = Depends(
authenticator.current_user(
active=True,
superuser=True,
get_enabled_backends=get_enabled_backends,
)
),
):
return user
async for client in get_test_client(app):
yield client
return _get_test_auth_client
@pytest.mark.authentication
@pytest.mark.asyncio
async def test_authenticator(get_test_auth_client, get_backend_none, get_backend_user):
async for client in get_test_auth_client([get_backend_none(), get_backend_user()]):
response = await client.get("/test-current-user")
assert response.status_code == status.HTTP_200_OK
@pytest.mark.authentication
@pytest.mark.asyncio
async def test_authenticator_none(get_test_auth_client, get_backend_none):
async for client in get_test_auth_client(
[get_backend_none(), get_backend_none(name="none-bis")]
):
response = await client.get("/test-current-user")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
@pytest.mark.authentication
@pytest.mark.asyncio
async def test_authenticator_none_enabled(
get_test_auth_client, get_backend_none, get_backend_user
):
backend_none = get_backend_none()
backend_user = get_backend_user()
async def get_enabled_backends():
return [backend_none]
async for client in get_test_auth_client(
[backend_none, backend_user], get_enabled_backends
):
response = await client.get("/test-current-user")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
@pytest.mark.authentication
@pytest.mark.asyncio
async def test_authenticators_with_same_name(get_test_auth_client, get_backend_none):
with pytest.raises(DuplicateBackendNamesError):
async for _ in get_test_auth_client([get_backend_none(), get_backend_none()]):
pass