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

add capability to crud assignments via scope

parent ad0a7122
No related branches found
No related tags found
No related merge requests found
SERVER__HOST="localhost"
SERVER__PORT=8081
SERVER__CORS_ORIGINS='["http://localhost:8080", "http://localhost:8081","http://localhost", "http://0.0.0.0:8081","http://0.0.0.0", "http://127.0.0.1:8081","http://127.0.0.1"]'
SERVER__CORS_ORIGINS='["http://localhost:8080", "http://localhost:8081","http://localhost", "http://0.0.0.0:8081", "http://0.0.0.0", "http://127.0.0.1:8081", "http://127.0.0.1"]'
SERVER__HEADER_CORS=true
DB__HOST="localhost"
......
......@@ -4,4 +4,5 @@ toml==0.10.2
email-validator==1.2.1
python-dotenv==0.20.0
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
\ No newline at end of file
passlib[bcrypt]==1.7.4
PyYAML==6.0
\ No newline at end of file
......@@ -13,16 +13,16 @@ router = APIRouter()
router.include_router(ping.router, prefix='/ping')
# route to fetch, manage, submit item annotations
router.include_router(annotations.router, prefix='/annotations')
router.include_router(annotations.router, prefix='/annotations', tags=['annotations'])
# route for all user-related endpoints (everything not related to authentication)
router.include_router(users.router, prefix='/users')
router.include_router(users.router, prefix='/users', tags=['users'])
# route for authentication
router.include_router(auth.router, prefix='/login')
router.include_router(auth.router, prefix='/login', tags=['oauth'])
# route for general project things (aka non-project-specific)
router.include_router(projects.router, prefix='/projects')
router.include_router(projects.router, prefix='/projects', tags=['projects'])
# route for project related things
router.include_router(project.router, prefix='/project')
router.include_router(project.router, prefix='/project', tags=['project'])
from fastapi import APIRouter, Depends, HTTPException, status as http_status, Header
from nacsos_data.models.annotations import AnnotationTaskModel, AnnotationTaskLabel, AnnotationTaskLabelChoice, \
AssignmentScopeModel, AssignmentModel, AssignmentStatus
from nacsos_data.models.annotations import AnnotationTaskModel, \
AnnotationTaskLabel, \
AnnotationTaskLabelChoice, \
AssignmentScopeModel, \
AssignmentModel, \
AssignmentStatus, \
AssignmentScopeBaseConfig, \
AssignmentScopeConfig
from nacsos_data.models.items import ItemModel
from nacsos_data.models.items.twitter import TwitterItemModel
from nacsos_data.db.crud.items.twitter import read_tweet_by_item_id
from nacsos_data.db.crud.annotations import \
read_assignment, \
read_assignments_for_scope, \
read_assignments_for_scope_for_user, \
read_assignment_scopes_for_project, \
read_assignment_scopes_for_project_for_user, \
read_annotations_for_assignment, \
read_assignment_scope, \
read_next_assignment_for_scope_for_user, \
read_next_open_assignment_for_scope_for_user, \
read_annotation_task, \
read_annotation_tasks_for_project, \
read_assignment, \
upsert_annotations, \
UserProjectAssignmentScope
read_assignment_scope, \
upsert_assignment_scope, \
delete_assignment_scope, \
read_item_ids_with_assignment_count_for_project, \
read_assignment_counts_for_scope, \
ItemWithCount, \
AssignmentCounts, \
UserProjectAssignmentScope, \
store_assignments
from nacsos_data.util.annotations.validation import merge_task_and_annotations, annotated_task_to_annotations
from nacsos_data.util.annotations.assignments.random import AssignmentScopeRandomConfig, random_assignments
from pydantic import BaseModel
from server.util.security import UserPermissionChecker
......@@ -135,6 +150,42 @@ async def get_assignment_scopes_for_project(permissions=Depends(UserPermissionCh
return scopes
@router.get('/annotate/scope/{assignment_scope_id}', response_model=AssignmentScopeModel)
async def get_assignment_scope(assignment_scope_id: str,
permissions=Depends(UserPermissionChecker(['annotations_read', 'annotations_edit'],
fulfill_all=False))) \
-> AssignmentScopeModel:
scope = await read_assignment_scope(assignment_scope_id=assignment_scope_id, engine=db_engine)
return scope
@router.put('/annotate/scope/', response_model=str)
async def put_assignment_scope(assignment_scope: AssignmentScopeModel,
permissions=Depends(UserPermissionChecker('annotations_edit'))) -> str:
key = await upsert_assignment_scope(assignment_scope=assignment_scope, engine=db_engine)
return str(key)
@router.delete('/annotate/scope/{assignment_scope_id}')
async def remove_assignment_scope(assignment_scope_id: str,
permissions=Depends(UserPermissionChecker('annotations_edit'))) -> None:
try:
await delete_assignment_scope(assignment_scope_id=assignment_scope_id, engine=db_engine)
except ValueError as e:
raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST,
detail=str(e))
@router.get('/annotate/scope/counts/{assignment_scope_id}', response_model=AssignmentCounts)
async def get_num_assignments_for_scope(assignment_scope_id: str,
permissions=Depends(
UserPermissionChecker(['annotations_read', 'annotations_edit'],
fulfill_all=False))) \
-> AssignmentCounts:
scope = await read_assignment_counts_for_scope(assignment_scope_id=assignment_scope_id, engine=db_engine)
return scope
@router.get('/annotate/assignments/{assignment_scope_id}', response_model=list[AssignmentModel])
async def get_assignments(assignment_scope_id: str, permissions=Depends(UserPermissionChecker('annotations_read'))) \
-> list[AssignmentModel]:
......@@ -144,6 +195,15 @@ async def get_assignments(assignment_scope_id: str, permissions=Depends(UserPerm
return assignments
@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'))) \
-> list[AssignmentModel]:
assignments = await read_assignments_for_scope(assignment_scope_id=assignment_scope_id,
engine=db_engine)
return assignments
@router.get('/annotate/annotations/{assignment_scope_id}', response_model=list[AssignmentModel])
async def get_annotations(assignment_scope_id: str, permissions=Depends(UserPermissionChecker('annotations_read'))) \
-> list[AssignmentModel]:
......@@ -163,7 +223,6 @@ async def save_annotation(annotated_item: AnnotatedItem,
and str(assignment_db.assignment_scope_id) == annotated_item.assignment.assignment_scope_id \
and str(assignment_db.item_id) == annotated_item.assignment.item_id \
and str(assignment_db.task_id) == annotated_item.assignment.task_id:
print('permission yay')
annotations = annotated_task_to_annotations(annotated_item.task)
status = await upsert_annotations(annotations=annotations,
assignment_id=annotated_item.assignment.assignment_id,
......@@ -174,3 +233,42 @@ async def save_annotation(annotated_item: AnnotatedItem,
status_code=http_status.HTTP_403_FORBIDDEN,
detail=f'The combination of project, assignment, user, task, and item is invalid.',
)
@router.get('/config/items/', response_model=list[ItemWithCount])
async def get_annotations(permissions=Depends(UserPermissionChecker('dataset_read'))) \
-> list[ItemWithCount]:
items = await read_item_ids_with_assignment_count_for_project(project_id=permissions.permissions.project_id,
engine=db_engine)
return items
class MakeAssignmentsRequestModel(BaseModel):
task_id: str
scope_id: str
config: AssignmentScopeConfig
save: bool = False
@router.post('/config/assignments/', response_model=list[AssignmentModel])
async def make_assignments(payload: MakeAssignmentsRequestModel,
permissions=Depends(UserPermissionChecker('annotations_edit'))):
if payload.config.config_type == 'random':
print(payload.config)
try:
assignments = await random_assignments(assignment_scope_id=payload.scope_id,
annotation_task_id=payload.task_id,
project_id=permissions.permissions.project_id,
config=payload.config,
engine=db_engine)
except ValueError as e:
raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST,
detail=str(e))
else:
raise HTTPException(status_code=http_status.HTTP_501_NOT_IMPLEMENTED,
detail=f'Method "{payload.config.config_type}" is unknown.')
if payload.save:
await store_assignments(assignments=assignments, engine=db_engine)
return assignments
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Query
from server.util.logging import get_logger
from nacsos_data.models.users import UserModel, UserInDBModel
from nacsos_data.db.crud.users import read_all_users
from nacsos_data.db.crud.users import \
read_all_users, \
read_user_by_id, \
read_users_by_ids, \
read_project_users
from server.util.security import UserPermissionChecker, UserPermissions
from server.data import db_engine
......@@ -9,9 +13,38 @@ logger = get_logger('nacsos.api.route.admin.users')
router = APIRouter()
@router.get('/list', response_model=list[UserModel])
# FIXME refine required permission
@router.get('/list/all', response_model=list[UserModel])
async def get_all_users(permissions: UserPermissions = Depends(UserPermissionChecker('annotations_edit'))) \
-> list[UserInDBModel]:
if permissions.permissions.annotations_edit:
result = await read_all_users(engine=db_engine)
return result
result = await read_all_users(engine=db_engine)
return result
# FIXME refine required permission
@router.get('/list/project/{project_id}', response_model=list[UserModel])
async def get_all_users(project_id: str,
permissions: UserPermissions = Depends(UserPermissionChecker('annotations_edit'))) \
-> list[UserInDBModel]:
result = await read_project_users(project_id=project_id, engine=db_engine)
return result
# FIXME refine required permission
@router.get('/details/{user_id}', response_model=UserModel)
async def get_user_by_id(user_id: str,
permissions: UserPermissions = Depends(UserPermissionChecker('annotations_edit'))) \
-> UserInDBModel:
result = await read_user_by_id(user_id=user_id, engine=db_engine)
return result
# FIXME refine required permission
@router.get('/details', response_model=list[UserModel])
async def get_users_by_ids(user_id: list[str] = Query(),
permissions: UserPermissions = Depends(UserPermissionChecker('annotations_edit'))) \
-> list[UserInDBModel]:
print(user_id)
result = await read_users_by_ids(user_ids=user_id, engine=db_engine)
return result
from typing import Any, Dict
from typing import Any
import secrets
import json
import yaml
......@@ -50,7 +50,7 @@ class DatabaseConfig(BaseModel):
CONNECTION_STR: PostgresDsn | None = None
@validator('CONNECTION_STR', pre=True)
def build_connection_string(cls, v: str | None, values: Dict[str, Any]) -> Any:
def build_connection_string(cls, v: str | None, values: dict[str, Any]) -> Any:
if isinstance(v, str):
return v
return PostgresDsn.build(
......
......@@ -120,8 +120,9 @@ async def get_project_permissions_for_user(project_id: str, current_user: UserMo
class UserPermissionChecker:
def __init__(self, permissions: list[ProjectPermission] | ProjectPermission = None):
def __init__(self, permissions: list[ProjectPermission] | ProjectPermission = None, fulfill_all: bool = True):
self.permissions = permissions
self.fulfill_all = fulfill_all
# convert singular permission to list for unified processing later
if type(self.permissions) is str:
......@@ -150,13 +151,24 @@ class UserPermissionChecker:
if self.permissions is None:
return user_permissions
any_permission_fulfilled = False
# check that each required permission is fulfilled
for permission in self.permissions:
if not project_permissions[permission]:
if self.fulfill_all and not project_permissions[permission]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f'User does not have permission "{permission}" for project "{x_project_id}".',
)
any_permission_fulfilled = any_permission_fulfilled or project_permissions[permission]
if not any_permission_fulfilled and not self.fulfill_all:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f'User does not have any of the required permissions ({self.permissions}) '
f'for project "{x_project_id}".',
)
return user_permissions
raise HTTPException(
......
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