diff --git a/backend/routes/dependencies/role_dependencies.py b/backend/routes/dependencies/role_dependencies.py index 9fcaec32..cee8e8ba 100644 --- a/backend/routes/dependencies/role_dependencies.py +++ b/backend/routes/dependencies/role_dependencies.py @@ -1,5 +1,4 @@ -from fastapi import Depends -from fastapi.security import APIKeyHeader +from starlette.requests import Request from controllers.auth.token_controller import verify_token from db.sessions import get_session @@ -18,39 +17,43 @@ InvalidStudentCredentialsError, InvalidTeacherCredentialsError, NoAccessToDataError, + NoCasHeaderError, ) -auth_scheme = APIKeyHeader(name="cas") +def get_authenticated_user(request: Request) -> int: + token: str | None = request.headers.get("cas") + + if token is None: + raise NoCasHeaderError -def get_authenticated_user(token: str = Depends(auth_scheme)) -> int: uid = verify_token(token) if uid is None: raise InvalidAuthenticationError return uid -def get_authenticated_admin() -> AdminDataclass: +def get_authenticated_admin(request: Request) -> AdminDataclass: session = next(get_session()) - uid = get_authenticated_user() + uid = get_authenticated_user(request) if not is_user_admin(session, uid): raise InvalidAdminCredentialsError return get_admin(session, uid) -def get_authenticated_teacher() -> TeacherDataclass: +def get_authenticated_teacher(request: Request) -> TeacherDataclass: session = next(get_session()) - uid = get_authenticated_user() + uid = get_authenticated_user(request) if not is_user_teacher(session, uid): raise InvalidTeacherCredentialsError return get_teacher(session, uid) -def get_authenticated_student() -> StudentDataclass: +def get_authenticated_student(request: Request) -> StudentDataclass: session = next(get_session()) - uid = get_authenticated_user() + uid = get_authenticated_user(request) if not is_user_student(session, uid): raise InvalidStudentCredentialsError @@ -58,26 +61,26 @@ def get_authenticated_student() -> StudentDataclass: return get_student(session, uid) -def ensure_user_authorized_for_subject(subject_id: int) -> None: +def ensure_user_authorized_for_subject(request: Request, subject_id: int) -> None: session = next(get_session()) - uid = get_authenticated_user() + uid = get_authenticated_user(request) if not is_user_authorized_for_subject(subject_id, session, uid): raise NoAccessToDataError -def ensure_user_authorized_for_project(project_id: int) -> None: +def ensure_user_authorized_for_project(request: Request, project_id: int) -> None: session = next(get_session()) - uid = get_authenticated_user() + uid = get_authenticated_user(request) project = get_project(session, project_id) if not is_user_authorized_for_subject(project.subject_id, session, uid): raise NoAccessToDataError -def ensure_student_authorized_for_subject(subject_id: int) -> StudentDataclass: +def ensure_student_authorized_for_subject(request: Request, subject_id: int) -> StudentDataclass: session = next(get_session()) - student = get_authenticated_student() + student = get_authenticated_student(request) subjects_of_student = get_subjects_of_student(session, student.id) if subject_id not in [subject.id for subject in subjects_of_student]: @@ -85,9 +88,9 @@ def ensure_student_authorized_for_subject(subject_id: int) -> StudentDataclass: return student -def ensure_teacher_authorized_for_subject(subject_id: int) -> TeacherDataclass: +def ensure_teacher_authorized_for_subject(request: Request, subject_id: int) -> TeacherDataclass: session = next(get_session()) - teacher = get_authenticated_teacher() + teacher = get_authenticated_teacher(request) subjects_of_teacher = get_subjects_of_teacher(session, teacher.id) if subject_id not in [subject.id for subject in subjects_of_teacher]: @@ -95,9 +98,9 @@ def ensure_teacher_authorized_for_subject(subject_id: int) -> TeacherDataclass: return teacher -def ensure_student_authorized_for_project(project_id: int) -> StudentDataclass: +def ensure_student_authorized_for_project(request: Request, project_id: int) -> StudentDataclass: session = next(get_session()) - student = get_authenticated_student() + student = get_authenticated_student(request) projects_of_student = get_projects_of_student(session, student.id) if project_id not in [project.id for project in projects_of_student]: @@ -105,9 +108,9 @@ def ensure_student_authorized_for_project(project_id: int) -> StudentDataclass: return student -def ensure_teacher_authorized_for_project(project_id: int) -> TeacherDataclass: +def ensure_teacher_authorized_for_project(request: Request, project_id: int) -> TeacherDataclass: session = next(get_session()) - teacher = get_authenticated_teacher() + teacher = get_authenticated_teacher(request) projects_of_teacher = get_projects_of_teacher(session, teacher.id) if project_id not in [project.id for project in projects_of_teacher]: @@ -115,9 +118,9 @@ def ensure_teacher_authorized_for_project(project_id: int) -> TeacherDataclass: return teacher -def ensure_student_authorized_for_group(group_id: int) -> StudentDataclass: +def ensure_student_authorized_for_group(request: Request, group_id: int) -> StudentDataclass: session = next(get_session()) - student = get_authenticated_student() + student = get_authenticated_student(request) group = get_group(session, group_id) projects_of_student = get_projects_of_student(session, student.id) @@ -126,9 +129,9 @@ def ensure_student_authorized_for_group(group_id: int) -> StudentDataclass: return student -def ensure_user_authorized_for_group(group_id: int) -> None: +def ensure_user_authorized_for_group(request: Request, group_id: int) -> None: session = next(get_session()) - uid = get_authenticated_user() + uid = get_authenticated_user(request) group = get_group(session, group_id) project = get_project(session, group.project_id) @@ -136,18 +139,18 @@ def ensure_user_authorized_for_group(group_id: int) -> None: raise NoAccessToDataError -def ensure_student_in_group(group_id: int) -> StudentDataclass: +def ensure_student_in_group(request: Request, group_id: int) -> StudentDataclass: session = next(get_session()) - student = get_authenticated_student() + student = get_authenticated_student(request) if student not in get_students_of_group(session, group_id): raise NoAccessToDataError return student -def ensure_user_authorized_for_submission(group_id: int) -> None: +def ensure_user_authorized_for_submission(request: Request, group_id: int) -> None: session = next(get_session()) - uid = get_authenticated_user() + uid = get_authenticated_user(request) group = get_group(session, group_id) if is_user_student(session, uid): diff --git a/backend/routes/errors/authentication.py b/backend/routes/errors/authentication.py index 8929ce30..ed2ce464 100644 --- a/backend/routes/errors/authentication.py +++ b/backend/routes/errors/authentication.py @@ -20,3 +20,7 @@ class NoAccessToDataError(Exception): class InvalidAuthenticationError(Exception): ERROR_MESSAGE = "User is not authenticated" + + +class NoCasHeaderError(Exception): + ERROR_MESSAGE = "No CAS header is present in the request." diff --git a/backend/routes/group.py b/backend/routes/group.py index ebedd112..c20d2b1c 100644 --- a/backend/routes/group.py +++ b/backend/routes/group.py @@ -22,20 +22,20 @@ @group_router.post("/groups/{group_id}/join", tags=[Tags.GROUP], summary="Join a certain group.") def group_join(request: Request, group_id: int) -> None: session = request.state.session - student = ensure_student_authorized_for_group(group_id) + student = ensure_student_authorized_for_group(request, group_id) add_student_to_group(session, student.id, group_id) @group_router.post("/groups/{group_id}/leave", tags=[Tags.GROUP], summary="Leave a certain group.") def group_leave(request: Request, group_id: int) -> None: session = request.state.session - student = ensure_student_authorized_for_group(group_id) + student = ensure_student_authorized_for_group(request, group_id) remove_student_from_group(session, student.id, group_id) @group_router.get("/groups/{group_id}/members", tags=[Tags.GROUP], summary="List group members.") def list_group_members(request: Request, group_id: int) -> list[StudentDataclass]: - ensure_user_authorized_for_group(group_id) + ensure_user_authorized_for_group(request, group_id) session = request.state.session return get_students_of_group(session, group_id) @@ -43,5 +43,5 @@ def list_group_members(request: Request, group_id: int) -> list[StudentDataclass @group_router.get("/projects/{project_id}/group", tags=[Tags.GROUP], summary="Get your group for a project.") def project_get_group(request: Request, project_id: int) -> GroupDataclass | None: session = request.state.session - student = ensure_student_authorized_for_project(project_id) + student = ensure_student_authorized_for_project(request, project_id) return get_group_for_student_and_project(session, student.id, project_id) diff --git a/backend/routes/project.py b/backend/routes/project.py index e6b20a54..d7bf1b9e 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -17,7 +17,7 @@ @project_router.get("/projects/{project_id}", tags=[Tags.PROJECT], summary="Get a certain project.") def project_get(request: Request, project_id: int) -> ProjectDataclass: session = request.state.session - ensure_user_authorized_for_project(project_id) + ensure_user_authorized_for_project(request, project_id) project: ProjectDataclass = get_project(session, project_id) return project @@ -25,19 +25,19 @@ def project_get(request: Request, project_id: int) -> ProjectDataclass: @project_router.get("/projects/{project_id}/groups", tags=[Tags.PROJECT], summary="Get all groups of a project.") def project_get_groups(request: Request, project_id: int) -> list[GroupDataclass]: session = request.state.session - ensure_user_authorized_for_project(project_id) + ensure_user_authorized_for_project(request, project_id) return get_groups_of_project(session, project_id) @project_router.post("/projects/{project_id}/groups", tags=[Tags.PROJECT], summary="Create a group for a project.") def project_create_group(request: Request, project_id: int) -> GroupDataclass: session = request.state.session - ensure_teacher_authorized_for_project(project_id) + ensure_teacher_authorized_for_project(request, project_id) return create_group(session, project_id) @project_router.patch("/projects/{project_id}", tags=[Tags.PROJECT], summary="Update a project.") def patch_update_project(request: Request, project_id: int, project: ProjectInput) -> None: session = request.state.session - ensure_teacher_authorized_for_project(project_id) + ensure_teacher_authorized_for_project(request, project_id) update_project(session, project_id, project) diff --git a/backend/routes/student.py b/backend/routes/student.py index 3b8287b1..d08eaf37 100644 --- a/backend/routes/student.py +++ b/backend/routes/student.py @@ -13,17 +13,17 @@ @student_router.get("/student/subjects", tags=[Tags.STUDENT], summary="Get all subjects of the student.") def subjects_of_student_get(request: Request) -> list[SubjectDataclass]: - student = get_authenticated_student() + student = get_authenticated_student(request) return get_subjects_of_student(request.state.session, student.id) @student_router.get("/student/projects", tags=[Tags.STUDENT], summary="Get all projects of the student.") def projects_of_student_get(request: Request) -> list[ProjectDataclass]: - student = get_authenticated_student() + student = get_authenticated_student(request) return get_projects_of_student(request.state.session, student.id) @student_router.post("/student/subjects/{subject_id}/join", tags=[Tags.STUDENT], summary="Join a subject.") def student_subject_join(request: Request, subject_id: int) -> None: - student = get_authenticated_student() + student = get_authenticated_student(request) add_student_to_subject(request.state.session, student.id, subject_id) diff --git a/backend/routes/subject.py b/backend/routes/subject.py index 125566e7..6f1ab1a3 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -20,35 +20,35 @@ @subject_router.get("/subjects/{subject_id}", tags=[Tags.SUBJECT], summary="Get a certain subject.") def subject_get(request: Request, subject_id: int) -> SubjectDataclass: session = request.state.session - get_authenticated_user() + get_authenticated_user(request) return get_subject(session, subject_id) @subject_router.get("/subjects/{subject_id}/projects", tags=[Tags.SUBJECT], summary="Get all projects of subject.") def get_subject_projects(request: Request, subject_id: int) -> list[ProjectDataclass]: session = request.state.session - ensure_user_authorized_for_subject(subject_id) + ensure_user_authorized_for_subject(request, subject_id) return get_projects_of_subject(session, subject_id) @subject_router.get("/subjects/{subject_id}/teachers", tags=[Tags.SUBJECT], summary="Get all teachers of subject.") def get_subject_teachers(request: Request, subject_id: int) -> list[TeacherDataclass]: session = request.state.session - ensure_user_authorized_for_subject(subject_id) + ensure_user_authorized_for_subject(request, subject_id) return get_teachers_of_subject(session, subject_id) @subject_router.get("/subjects/{subject_id}/students", tags=[Tags.SUBJECT], summary="Get all students of subject.") def get_subject_students(request: Request, subject_id: int) -> list[StudentDataclass]: session = request.state.session - ensure_user_authorized_for_subject(subject_id) + ensure_user_authorized_for_subject(request, subject_id) return get_students_of_subject(session, subject_id) @subject_router.post("/subjects/{subject_id}/projects", tags=[Tags.PROJECT], summary="Create project in a course.") def new_project(request: Request, subject_id: int, project: ProjectInput) -> ProjectDataclass: session = request.state.session - ensure_teacher_authorized_for_subject(subject_id) + ensure_teacher_authorized_for_subject(request, subject_id) return create_project( session, subject_id=subject_id, diff --git a/backend/routes/submission.py b/backend/routes/submission.py index 420138c5..c71d3cd6 100644 --- a/backend/routes/submission.py +++ b/backend/routes/submission.py @@ -16,7 +16,7 @@ @submission_router.post("/groups/{group_id}/submission", tags=[Tags.SUBMISSION], summary="Make a submission.") def make_submission(request: Request, group_id: int, file: Annotated[bytes, File()]) -> SubmissionDataclass: session = request.state.session - student = ensure_student_in_group(group_id) + student = ensure_student_in_group(request, group_id) filename = hashlib.sha256(file).hexdigest() with open(f"submissions/{filename}", "wb") as f: @@ -36,14 +36,14 @@ def make_submission(request: Request, group_id: int, file: Annotated[bytes, File @submission_router.get("/groups/{group_id}/submission", tags=[Tags.SUBMISSION], summary="Get latest submission.") def retrieve_submission(request: Request, group_id: int) -> SubmissionDataclass: session = request.state.session - ensure_user_authorized_for_submission(group_id) + ensure_user_authorized_for_submission(request, group_id) return get_last_submission(session, group_id) @submission_router.get("/groups/{group_id}/submission/file", tags=[Tags.SUBMISSION], summary="Get last submission") def retrieve_submission_file(request: Request, group_id: int) -> Response: session = request.state.session - ensure_user_authorized_for_submission(group_id) + ensure_user_authorized_for_submission(request, group_id) submission = get_last_submission(session, group_id) with open(f"submissions/{submission.filename}", "rb") as file: diff --git a/backend/routes/teacher.py b/backend/routes/teacher.py index ce93ddf9..76f919bd 100644 --- a/backend/routes/teacher.py +++ b/backend/routes/teacher.py @@ -14,21 +14,21 @@ @teacher_router.get("/teacher/subjects", tags=[Tags.TEACHER], summary="Get all subjects the teacher manages.") def subjects_of_teacher_get(request: Request) -> list[SubjectDataclass]: session = request.state.session - teacher = get_authenticated_teacher() + teacher = get_authenticated_teacher(request) return get_subjects_of_teacher(session, teacher.id) @teacher_router.get("/teacher/projects", tags=[Tags.TEACHER], summary="Get all projects of the teacher.") def projects_of_teacher_get(request: Request) -> list[ProjectDataclass]: session = request.state.session - teacher = get_authenticated_teacher() + teacher = get_authenticated_teacher(request) return get_projects_of_teacher(session, teacher.id) @teacher_router.post("/teacher/subjects", tags=[Tags.SUBJECT], summary="Create a new subject.") def create_subject_post(request: Request, subject: SubjectInput) -> SubjectDataclass: session = request.state.session - teacher = get_authenticated_teacher() + teacher = get_authenticated_teacher(request) new_subject = create_subject(session, name=subject.name) add_teacher_to_subject(session, teacher_id=teacher.id, subject_id=new_subject.id) diff --git a/backend/routes/user.py b/backend/routes/user.py index 09321bac..c5046364 100644 --- a/backend/routes/user.py +++ b/backend/routes/user.py @@ -16,14 +16,14 @@ @users_router.get("/user", tags=[Tags.USER], summary="Get the current user (and its roles).") def get_current_user(request: Request) -> APIUser: session = request.state.session - uid = get_authenticated_user() + uid = get_authenticated_user(request) return convert_user(session, get_user(session, uid)) @users_router.patch("/user", tags=[Tags.USER], summary="Modify the language of the user.") def modify_current_user(request: Request, language: str) -> Response: session = request.state.session - uid = get_authenticated_user() + uid = get_authenticated_user(request) modify_language(session, uid, language) return Response(status_code=status.HTTP_204_NO_CONTENT) @@ -32,7 +32,7 @@ def modify_current_user(request: Request, language: str) -> Response: @users_router.get("/users", tags=[Tags.USER], summary="Get all users.") def get_users(request: Request) -> list[APIUser]: session = request.state.session - get_authenticated_admin() + get_authenticated_admin(request) users: list[UserDataclass] = [user.to_domain_model() for user in get_all(session, User)] return [convert_user(session, user) for user in users] @@ -41,7 +41,7 @@ def get_users(request: Request) -> list[APIUser]: @users_router.get("/users/{uid}", tags=[Tags.USER], summary="Get a certain user.") def admin_get_user(request: Request, uid: int) -> APIUser: session = request.state.session - get_authenticated_admin() + get_authenticated_admin(request) user: UserDataclass = get(session, User, uid).to_domain_model() return convert_user(session, user) @@ -50,7 +50,7 @@ def admin_get_user(request: Request, uid: int) -> APIUser: @users_router.patch("/users/{uid}", tags=[Tags.USER], summary="Modify the roles of a certain user.") def modify_user(request: Request, uid: int, roles: list[Role]) -> Response: session = request.state.session - get_authenticated_admin() + get_authenticated_admin(request) modify_user_roles(session, uid, roles) return Response(status_code=status.HTTP_204_NO_CONTENT)