Skip to content
Snippets Groups Projects
Commit 431a3afe authored by Tim Repke's avatar Tim Repke
Browse files

Merge branch 'master' into 'production'

Master

See merge request !40
parents ab4c5221 7920bd68
No related branches found
No related tags found
1 merge request!40Master
Pipeline #1418 passed
from fastapi import status as http_status
class DataNotFoundWarning(Warning):
pass
status = http_status.HTTP_204_NO_CONTENT
class NoDataForKeyError(Exception):
......@@ -23,7 +26,7 @@ class AnnotationSchemeNotFoundError(Exception):
class NoNextAssignmentWarning(Warning):
pass
status = http_status.HTTP_204_NO_CONTENT
class AssignmentScopeNotFoundError(Exception):
......
import uuid
from typing import TYPE_CHECKING
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy import select, asc, join, and_
from sqlalchemy.orm import load_only
from fastapi import APIRouter, Depends, HTTPException, status as http_status, Query
......@@ -9,7 +10,7 @@ from nacsos_data.db.schemas import \
BotAnnotationMetaData, \
AssignmentScope, \
User, \
Annotation
Annotation, Assignment
from nacsos_data.models.annotations import \
AnnotationSchemeModel, \
AssignmentScopeModel, \
......@@ -286,6 +287,49 @@ async def get_assignments(assignment_scope_id: str, permissions=Depends(UserPerm
return assignments
class ProgressIndicator(BaseModel):
assignment_id: str | uuid.UUID
item_id: str | uuid.UUID
order: int
status: AssignmentStatus
value_int: int | None = None
value_bool: bool | None = None
@router.get('/annotate/assignment/progress/{assignment_scope_id}', response_model=list[ProgressIndicator])
async def get_assignment_indicators_for_scope_for_user(assignment_scope_id: str,
key: str | None = Query(default=None),
repeat: int | None = Query(default=None),
permissions=Depends(UserPermissionChecker('annotations_read'))) \
-> list[ProgressIndicator]:
async with db_engine.session() as session: # type: AsyncSession
if key is None:
stmt = select(Assignment.assignment_id,
Assignment.item_id,
Assignment.order,
Assignment.status)
else:
stmt = select(Assignment.assignment_id,
Assignment.item_id,
Assignment.order,
Assignment.status,
Annotation.value_int,
Annotation.value_bool) \
.select_from(join(left=Assignment,
right=Annotation,
onclause=and_(Assignment.assignment_id == Annotation.assignment_id,
Annotation.repeat == (1 if repeat is None else repeat),
Annotation.key == key),
isouter=True))
stmt = stmt \
.where(Assignment.user_id == permissions.user.user_id,
Assignment.assignment_scope_id == assignment_scope_id) \
.order_by(asc(Assignment.order))
results = (await session.execute(stmt)).mappings().all()
return [ProgressIndicator.parse_obj(r) for r in results]
@router.get('/annotate/assignments/scope/{assignment_scope_id}', response_model=list[AssignmentModel])
async def get_assignments_for_scope(assignment_scope_id: str,
permissions=Depends(UserPermissionChecker('annotations_read'))) \
......@@ -364,11 +408,12 @@ async def make_assignments(payload: MakeAssignmentsRequestModel,
detail=str(e))
elif payload.config.config_type == 'random_exclusion':
try:
assignments = await random_assignments_with_exclusion(assignment_scope_id=payload.scope_id,
annotation_scheme_id=payload.annotation_scheme_id,
project_id=permissions.permissions.project_id,
config=payload.config, # type: ignore[arg-type] # FIXME
engine=db_engine)
assignments = await random_assignments_with_exclusion(
assignment_scope_id=payload.scope_id,
annotation_scheme_id=payload.annotation_scheme_id,
project_id=permissions.permissions.project_id,
config=payload.config, # type: ignore[arg-type] # FIXME
engine=db_engine)
except ValueError as e:
raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST,
detail=str(e))
......@@ -516,7 +561,7 @@ async def list_saved_resolved_annotations(permissions=Depends(UserPermissionChec
return [BotAnnotationMetaDataBaseModel.parse_obj(e.__dict__) for e in exports]
@router.get('/config/resolved/:bot_annotation_meta_id', response_model=SavedResolutionResponse)
@router.get('/config/resolved/{bot_annotation_meta_id}', response_model=SavedResolutionResponse)
async def get_saved_resolved_annotations(bot_annotation_metadata_id: str,
permissions=Depends(UserPermissionChecker('annotations_edit'))):
bot_annotations = await read_bot_annotations(bot_annotation_metadata_id=bot_annotation_metadata_id,
......@@ -533,7 +578,7 @@ async def get_saved_resolved_annotations(bot_annotation_metadata_id: str,
)
@router.delete('/config/resolved/:bot_annotation_meta_id')
@router.delete('/config/resolved/{bot_annotation_meta_id}')
async def delete_saved_resolved_annotations(bot_annotation_metadata_id: str,
permissions=Depends(UserPermissionChecker('annotations_edit'))):
async with db_engine.session() as session: # type: AsyncSession
......
......@@ -65,16 +65,25 @@ class ErrorHandlingMiddleware(BaseHTTPMiddleware):
except Error: # type: ignore[misc]
logger.error('Some unspecified error occurred...')
headers: dict[str, Any] | None = None
if hasattr(ew, 'headers'):
headers = getattr(ew, 'headers')
level: Literal['WARNING', 'ERROR'] = 'ERROR'
if isinstance(ew, Warning):
level = 'WARNING'
return await http_exception_handler(
request,
exc=HTTPException(
status_code=self._resolve_status(ew),
detail=ErrorDetail(
level='WARNING' if isinstance(ew, Warning) else 'ERROR',
level=level,
type=ew.__class__.__name__,
message=error_str,
args=self._resolve_args(ew)
).dict()
).dict(),
headers=headers
))
......
from fastapi import Depends, HTTPException, status as http_status, Header
from fastapi import Depends, status as http_status, Header
from fastapi.security import OAuth2PasswordBearer
from nacsos_data.models.users import UserModel, UserInDBModel
......@@ -15,6 +15,12 @@ logger = get_logger('nacsos.util.security')
class InsufficientPermissions(Exception):
status = http_status.HTTP_403_FORBIDDEN
headers = {'WWW-Authenticate': 'Bearer'}
class NotAuthenticated(Exception):
status = http_status.HTTP_401_UNAUTHORIZED
headers = {'WWW-Authenticate': 'Bearer'}
auth_helper = Authentication(engine=db_engine,
......@@ -26,25 +32,21 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl='api/login/token', auto_error=Fals
async def get_current_user(token: str = Depends(oauth2_scheme)) -> UserInDBModel:
try:
return await auth_helper.get_current_user(token_id=token)
except (InvalidCredentialsError, InsufficientPermissionError) as e:
raise HTTPException(
status_code=http_status.HTTP_401_UNAUTHORIZED,
detail=repr(e),
headers={'WWW-Authenticate': 'Bearer'},
)
except InvalidCredentialsError as e:
raise NotAuthenticated(str(e))
except InsufficientPermissionError as e:
raise InsufficientPermissions(str(e))
async def get_current_active_user(current_user: UserModel = Depends(get_current_user)) -> UserModel:
if not current_user.is_active:
raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST, detail='Inactive user')
raise InsufficientPermissions('Inactive user')
return current_user
def get_current_active_superuser(current_user: UserModel = Depends(get_current_active_user)) -> UserModel:
if not current_user.is_superuser:
raise HTTPException(
status_code=http_status.HTTP_400_BAD_REQUEST, detail="The user doesn't have enough privileges"
)
raise InsufficientPermissions('The user doesn\'t have enough privileges')
return current_user
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment