diff --git a/backend/app.py b/backend/app.py index 42693c23..af04255e 100644 --- a/backend/app.py +++ b/backend/app.py @@ -6,7 +6,7 @@ from starlette.responses import JSONResponse from db.errors.database_errors import ActionAlreadyPerformedError, ItemNotFoundError, NoSuchRelationError -from routes.errors.authentication import InvalidRoleCredentialsError, InvalidTokenError, NoAccessToSubjectError +from routes.errors.authentication import InvalidAuthenticationError, InvalidRoleCredentialsError, NoAccessToSubjectError from routes.group import group_router from routes.login import login_router from routes.project import project_router @@ -87,8 +87,8 @@ def no_such_relation_error_handler(request: Request, exc: NoSuchRelationError) - ) -@app.exception_handler(InvalidTokenError) -def invalid_token_error_handler(request: Request, exc: NoSuchRelationError) -> JSONResponse: +@app.exception_handler(InvalidAuthenticationError) +def invalid_authentication_error_handler(request: Request, exc: NoSuchRelationError) -> JSONResponse: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, content={"detail": str(exc)}, diff --git a/backend/controllers/auth/authentication_controller.py b/backend/controllers/auth/authentication_controller.py index 0f605ee0..a8bb2748 100644 --- a/backend/controllers/auth/authentication_controller.py +++ b/backend/controllers/auth/authentication_controller.py @@ -55,24 +55,26 @@ def parse_cas_xml(xml: str) -> dict | None: namespace = "{http://www.yale.edu/tp/cas}" root: Element | None = fromstring(xml).find(f"{namespace}authenticationSuccess") - if root is not None: - user_information: Element | None = root.find(f"{namespace}attributes") - if user_information: - givenname: Element | None = user_information.find(f"{namespace}givenname") - surname: Element | None = user_information.find(f"{namespace}surname") - email: Element | None = user_information.find(f"{namespace}mail") - role: list | None = user_information.find(f"{namespace}objectClass") - if role is not None and givenname is not None and surname is not None and email is not None: - role_str: str = "" - for r in role: - if r.text == "ugentStudent" and role_str == "": - role_str = "student" - elif r.text == "ugentEmployee": - role_str = "teacher" + if root is None: + return None + user_information: Element | None = root.find(f"{namespace}attributes") + if user_information is None: + return None + givenname: Element | None = user_information.find(f"{namespace}givenname") + surname: Element | None = user_information.find(f"{namespace}surname") + email: Element | None = user_information.find(f"{namespace}mail") + role: list | None = user_information.findall(f"{namespace}objectClass") + if role is not None and givenname is not None and surname is not None and email is not None: + role_str = "" + for r in role: + if r.text == "ugentStudent" and role_str == "": + role_str = "student" + elif r.text == "ugentEmployee": + role_str = "teacher" - return { - "email": email.text.lower(), - "name": f"{givenname.text} {surname.text}", - "role": role_str, - } + return { + "email": email.text.lower(), + "name": f"{givenname.text} {surname.text}", + "role": role_str, + } return None diff --git a/backend/domain/models/APIUser.py b/backend/domain/models/APIUser.py index c37fb477..c4ec1a67 100644 --- a/backend/domain/models/APIUser.py +++ b/backend/domain/models/APIUser.py @@ -8,3 +8,7 @@ class APIUser(BaseModel): name: str email: EmailStr roles: list[Role] + + +class LoginResponse(BaseModel): + token: str diff --git a/backend/routes/dependencies/role_dependencies.py b/backend/routes/dependencies/role_dependencies.py index e16bfd1a..559c08ba 100644 --- a/backend/routes/dependencies/role_dependencies.py +++ b/backend/routes/dependencies/role_dependencies.py @@ -15,9 +15,9 @@ from domain.models.TeacherDataclass import TeacherDataclass from routes.errors.authentication import ( InvalidAdminCredentialsError, + InvalidAuthenticationError, InvalidStudentCredentialsError, InvalidTeacherCredentialsError, - InvalidTokenError, NoAccessToSubjectError, ) @@ -27,7 +27,7 @@ def get_authenticated_user(token: str = Depends(auth_scheme)) -> int: uid = verify_token(token) if uid is None: - raise InvalidTokenError + raise InvalidAuthenticationError return uid diff --git a/backend/routes/errors/authentication.py b/backend/routes/errors/authentication.py index cf8acdec..8c9b507d 100644 --- a/backend/routes/errors/authentication.py +++ b/backend/routes/errors/authentication.py @@ -18,5 +18,5 @@ class NoAccessToSubjectError(Exception): ERROR_MESSAGE = "User doesn't have access to subject" -class InvalidTokenError(Exception): +class InvalidAuthenticationError(Exception): ERROR_MESSAGE = "User is not authenticated" diff --git a/backend/routes/login.py b/backend/routes/login.py index 5c2aab6c..d7d43773 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -1,10 +1,12 @@ -from fastapi import APIRouter, Depends, Response +from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from controllers.auth.authentication_controller import authenticate_user from controllers.auth.token_controller import create_token from db.sessions import get_session +from domain.models.APIUser import LoginResponse from domain.models.UserDataclass import UserDataclass +from routes.errors.authentication import InvalidAuthenticationError # test url: https://login.ugent.be/login?service=https://localhost:8080/api/login login_router = APIRouter() @@ -14,7 +16,7 @@ def login( ticket: str, session: Session = Depends(get_session), -) -> Response: +) -> LoginResponse: """ This function starts a session for the user. For authentication, it uses the given ticket and the UGent CAS server (https://login.ugent.be). @@ -26,6 +28,6 @@ def login( - Invalid Ticket: Response: with status_code 401 (unauthenticated) and an error message """ user: UserDataclass | None = authenticate_user(session, ticket) - if user: - return Response(content=create_token(user)) - return Response(status_code=401, content="Invalid Ticket!") + if not user: + raise InvalidAuthenticationError + return LoginResponse(token=create_token(user))