2022-03-20 03:45:40 +00:00
|
|
|
"""
|
|
|
|
/user endpoints.
|
|
|
|
"""
|
|
|
|
|
2022-03-15 16:19:37 +00:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
2022-03-19 04:07:19 +00:00
|
|
|
from fastapi.security import OAuth2PasswordRequestForm
|
2022-03-15 16:19:37 +00:00
|
|
|
from pydantic import BaseModel
|
|
|
|
|
2022-03-19 02:22:49 +00:00
|
|
|
from ..config import Config
|
2022-03-29 19:57:33 +00:00
|
|
|
from ..db import TagValue, User, UserCreate, UserRead
|
2022-03-30 20:57:09 +00:00
|
|
|
from ._common import (Responses, get_current_config, get_current_user,
|
|
|
|
get_user_by_name)
|
2022-03-15 16:25:07 +00:00
|
|
|
|
2022-03-24 23:45:01 +00:00
|
|
|
router = APIRouter(prefix="/user", tags=["user"])
|
2022-03-15 16:19:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Token(BaseModel):
|
2022-03-20 03:45:40 +00:00
|
|
|
"""
|
|
|
|
Response model for issuing tokens.
|
|
|
|
"""
|
|
|
|
|
2022-03-15 16:19:37 +00:00
|
|
|
access_token: str
|
|
|
|
token_type: str
|
|
|
|
|
|
|
|
|
2022-04-07 06:23:09 +00:00
|
|
|
@router.post(
|
|
|
|
"/authenticate",
|
|
|
|
responses={
|
|
|
|
status.HTTP_200_OK: Responses.OK,
|
|
|
|
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
|
|
|
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
|
|
|
},
|
|
|
|
response_model=Token,
|
|
|
|
)
|
2022-03-18 23:45:09 +00:00
|
|
|
async def login(
|
2022-03-18 23:04:28 +00:00
|
|
|
form_data: OAuth2PasswordRequestForm = Depends(),
|
2022-03-30 20:57:09 +00:00
|
|
|
current_config: Config = Depends(get_current_config),
|
2022-03-19 00:38:57 +00:00
|
|
|
):
|
2022-03-20 03:45:40 +00:00
|
|
|
"""
|
|
|
|
POST ./authenticate: Authenticate a user. Issues a bearer token.
|
2022-04-07 06:23:09 +00:00
|
|
|
|
|
|
|
Status:
|
|
|
|
|
|
|
|
- 401: username/password is incorrect
|
2022-03-20 03:45:40 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
# try logging in
|
2022-03-29 23:36:23 +00:00
|
|
|
if (user := User.authenticate(
|
2022-03-28 20:18:19 +00:00
|
|
|
name=form_data.username,
|
2022-03-19 00:38:57 +00:00
|
|
|
password=form_data.password,
|
2022-03-29 23:36:23 +00:00
|
|
|
)) is None:
|
2022-03-20 04:00:14 +00:00
|
|
|
# authentication failed
|
2022-03-19 00:38:57 +00:00
|
|
|
raise HTTPException(
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
detail="Could not validate credentials",
|
|
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
|
|
)
|
|
|
|
|
2022-03-20 03:45:40 +00:00
|
|
|
# authentication succeeded
|
2022-03-19 04:07:19 +00:00
|
|
|
access_token = await current_config.jwt.create_token(user.name)
|
2022-03-19 00:38:57 +00:00
|
|
|
return {"access_token": access_token, "token_type": "bearer"}
|
|
|
|
|
|
|
|
|
2022-04-07 06:23:09 +00:00
|
|
|
@router.get(
|
|
|
|
"/current",
|
|
|
|
responses={
|
|
|
|
status.HTTP_200_OK: Responses.OK,
|
|
|
|
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
|
|
|
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
|
|
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_USER,
|
|
|
|
},
|
|
|
|
response_model=UserRead,
|
|
|
|
)
|
2022-03-30 02:02:45 +00:00
|
|
|
async def get_current_user_route(
|
2022-03-29 23:36:23 +00:00
|
|
|
current_user: User = Depends(get_current_user),
|
2022-03-19 00:38:57 +00:00
|
|
|
):
|
2022-03-20 03:45:40 +00:00
|
|
|
"""
|
|
|
|
GET ./current: Respond with the currently logged-in user.
|
|
|
|
"""
|
|
|
|
|
2022-03-19 00:38:57 +00:00
|
|
|
return current_user
|
2022-03-19 18:06:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
@router.post(
|
2022-03-23 13:40:14 +00:00
|
|
|
"",
|
2022-03-19 18:06:28 +00:00
|
|
|
responses={
|
2022-04-01 06:35:28 +00:00
|
|
|
status.HTTP_201_CREATED: Responses.ENTRY_ADDED,
|
2022-03-20 03:45:40 +00:00
|
|
|
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
|
|
|
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
2022-03-30 02:07:22 +00:00
|
|
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
2022-03-20 03:45:40 +00:00
|
|
|
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
|
2022-03-19 18:06:28 +00:00
|
|
|
},
|
2022-03-28 23:11:58 +00:00
|
|
|
response_model=UserRead,
|
2022-04-01 06:35:28 +00:00
|
|
|
status_code=status.HTTP_201_CREATED,
|
2022-03-19 18:06:28 +00:00
|
|
|
)
|
|
|
|
async def add_user(
|
2022-03-20 02:32:40 +00:00
|
|
|
user: UserCreate,
|
2022-03-30 02:02:45 +00:00
|
|
|
current_user: User = Depends(get_current_user),
|
2022-03-28 23:11:58 +00:00
|
|
|
) -> User:
|
2022-03-20 03:45:40 +00:00
|
|
|
"""
|
2022-03-23 13:25:00 +00:00
|
|
|
POST ./: Create a new user in the database.
|
2022-04-07 06:23:09 +00:00
|
|
|
|
|
|
|
Status:
|
|
|
|
|
|
|
|
- 403: no user permission to create user
|
|
|
|
- 409: user could not be created
|
2022-03-20 03:45:40 +00:00
|
|
|
"""
|
|
|
|
|
2022-03-30 02:02:45 +00:00
|
|
|
# check permissions
|
2022-04-07 08:53:54 +00:00
|
|
|
try:
|
|
|
|
current_user.check_create(User)
|
|
|
|
|
|
|
|
except PermissionError as e:
|
|
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) from e
|
2022-03-30 02:02:45 +00:00
|
|
|
|
|
|
|
# create the new user
|
2022-03-29 00:01:28 +00:00
|
|
|
new_user = User.create(user=user)
|
2022-03-19 18:06:28 +00:00
|
|
|
|
2022-03-20 03:45:40 +00:00
|
|
|
# fail if creation was unsuccessful
|
2022-03-19 18:06:28 +00:00
|
|
|
if new_user is None:
|
|
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
|
|
|
|
2022-03-30 01:51:43 +00:00
|
|
|
new_user.add_tags([TagValue.login])
|
2022-03-28 23:11:58 +00:00
|
|
|
new_user.update()
|
|
|
|
|
2022-03-20 03:45:40 +00:00
|
|
|
# return the created user on success
|
2022-03-19 18:06:28 +00:00
|
|
|
return new_user
|
2022-03-23 00:39:19 +00:00
|
|
|
|
|
|
|
|
2022-03-23 13:25:00 +00:00
|
|
|
@router.delete(
|
|
|
|
"/{user_name}",
|
|
|
|
responses={
|
|
|
|
status.HTTP_200_OK: Responses.OK,
|
|
|
|
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
|
|
|
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
2022-03-30 02:02:45 +00:00
|
|
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
2022-03-23 13:25:00 +00:00
|
|
|
},
|
|
|
|
response_model=User,
|
|
|
|
)
|
|
|
|
async def remove_user(
|
2022-03-30 02:02:45 +00:00
|
|
|
current_user: User = Depends(get_current_user),
|
2022-03-29 00:10:24 +00:00
|
|
|
user: User = Depends(get_user_by_name),
|
2022-03-23 13:25:00 +00:00
|
|
|
):
|
|
|
|
"""
|
|
|
|
DELETE ./{user_name}: Remove a user from the database.
|
2022-04-07 06:23:09 +00:00
|
|
|
|
|
|
|
Status:
|
|
|
|
|
|
|
|
- 403: no user permission to admin user
|
2022-03-23 13:25:00 +00:00
|
|
|
"""
|
|
|
|
|
2022-03-30 02:02:45 +00:00
|
|
|
# check permissions
|
2022-04-07 08:53:54 +00:00
|
|
|
try:
|
|
|
|
current_user.check_admin(user)
|
|
|
|
|
|
|
|
except PermissionError as e:
|
|
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) from e
|
2022-03-29 15:38:36 +00:00
|
|
|
|
2022-03-28 20:18:19 +00:00
|
|
|
# delete user
|
|
|
|
user.delete()
|
|
|
|
|
2022-03-23 13:25:00 +00:00
|
|
|
|
2022-03-23 00:39:19 +00:00
|
|
|
@router.post(
|
2022-03-29 19:57:33 +00:00
|
|
|
"/{user_name}/tags",
|
2022-03-23 00:39:19 +00:00
|
|
|
responses={
|
2022-04-01 06:35:28 +00:00
|
|
|
status.HTTP_201_CREATED: Responses.ENTRY_ADDED,
|
2022-03-23 00:39:19 +00:00
|
|
|
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
|
|
|
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
2022-03-30 02:07:22 +00:00
|
|
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
2022-03-23 00:39:19 +00:00
|
|
|
},
|
2022-04-01 06:35:28 +00:00
|
|
|
status_code=status.HTTP_201_CREATED,
|
2022-03-23 00:39:19 +00:00
|
|
|
)
|
2022-03-29 19:57:33 +00:00
|
|
|
async def extend_tags(
|
|
|
|
tags: list[TagValue],
|
2022-03-30 02:02:45 +00:00
|
|
|
current_user: User = Depends(get_current_user),
|
2022-03-29 00:10:24 +00:00
|
|
|
user: User = Depends(get_user_by_name),
|
2022-03-23 00:39:19 +00:00
|
|
|
):
|
|
|
|
"""
|
2022-03-29 19:57:33 +00:00
|
|
|
POST ./{user_name}/tags: Add tags to a user.
|
2022-04-07 06:23:09 +00:00
|
|
|
|
|
|
|
Status:
|
|
|
|
|
|
|
|
- 403: no user permission to admin user
|
2022-03-23 00:39:19 +00:00
|
|
|
"""
|
|
|
|
|
2022-03-30 02:02:45 +00:00
|
|
|
# check permissions
|
2022-04-07 08:53:54 +00:00
|
|
|
try:
|
|
|
|
current_user.check_admin(user)
|
|
|
|
|
|
|
|
except PermissionError as e:
|
|
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) from e
|
2022-03-23 00:39:19 +00:00
|
|
|
|
2022-03-30 02:02:45 +00:00
|
|
|
# change user
|
|
|
|
user.add_tags(tags)
|
2022-03-28 20:18:19 +00:00
|
|
|
user.update()
|
2022-03-23 00:39:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
@router.delete(
|
2022-03-29 19:57:33 +00:00
|
|
|
"/{user_name}/tags",
|
2022-03-23 00:39:19 +00:00
|
|
|
responses={
|
|
|
|
status.HTTP_200_OK: Responses.OK,
|
|
|
|
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
|
|
|
|
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
|
2022-03-30 02:07:22 +00:00
|
|
|
status.HTTP_403_FORBIDDEN: Responses.NEEDS_PERMISSION,
|
2022-03-23 00:39:19 +00:00
|
|
|
},
|
|
|
|
)
|
2022-03-29 19:57:33 +00:00
|
|
|
async def remove_tags(
|
|
|
|
tags: list[TagValue],
|
2022-03-30 02:02:45 +00:00
|
|
|
current_user: User = Depends(get_current_user),
|
2022-03-29 00:10:24 +00:00
|
|
|
user: User = Depends(get_user_by_name),
|
2022-03-23 00:39:19 +00:00
|
|
|
):
|
|
|
|
"""
|
2022-03-29 19:57:33 +00:00
|
|
|
DELETE ./{user_name}/tags: Remove tags from a user.
|
2022-04-07 06:23:09 +00:00
|
|
|
|
|
|
|
Status:
|
|
|
|
|
|
|
|
- 403: no user permission to admin user
|
2022-03-23 00:39:19 +00:00
|
|
|
"""
|
|
|
|
|
2022-03-30 02:02:45 +00:00
|
|
|
# check permissions
|
2022-04-07 08:53:54 +00:00
|
|
|
try:
|
|
|
|
current_user.check_admin(user)
|
|
|
|
|
|
|
|
except PermissionError as e:
|
|
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) from e
|
2022-03-23 00:39:19 +00:00
|
|
|
|
2022-03-30 02:02:45 +00:00
|
|
|
# change user
|
|
|
|
user.remove_tags(tags)
|
2022-03-28 20:18:19 +00:00
|
|
|
user.update()
|