Skip to content

Commit

Permalink
Merge backend authentication into development (#73)
Browse files Browse the repository at this point in the history
* start authentication

* authentication start decorators

* login_required should work with access token

* backend authentication for most endpoints (very rough draft of functions in authentication.py)

* clean_up_function

* authentication cleanup

* give error when access_token fails

* documentation auth functions

* fixed imports

* actual import fix

* added requests

* authorize submissions

* removed double checks

* start testing setup backend authentication

* poging testen

* github tests check

* user tests with authentication

* auth url accessible hopefully

* change authorization to be easier to deal with since it doesn't matter for tests

* fixed jobCategory -> jobTitle

* fix authentication

* user tests zouden moeten slagen

* fix authentication arguments

* project tests with authentication

* changed auth server id of teacher

* maybe correct primary keys

* second try on primary key of course relations

* further test authentication

* authentication on project assignment files

* auth on course_join_codes and extra tests

* teacher_id in function when necessary

* user tests with authentication

* extra testing

* fixed comments

* lots of testing changes

* should be 1 error test now

* fix tests
  • Loading branch information
Vucis authored Mar 14, 2024
1 parent 1af5c7e commit 7c0cc08
Show file tree
Hide file tree
Showing 25 changed files with 650 additions and 240 deletions.
9 changes: 9 additions & 0 deletions backend/Dockerfile_auth_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.9
RUN mkdir /auth-app
WORKDIR /auth-app
ADD ./test_auth_server /auth-app/
COPY auth_requirements.txt /auth-app/requirements.txt
RUN pip3 install -r requirements.txt
COPY . /auth-app
ENTRYPOINT ["python"]
CMD ["__main__.py"]
4 changes: 4 additions & 0 deletions backend/auth_requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flask~=3.0.2
flask-restful
python-dotenv~=1.0.1
psycopg2-binary
6 changes: 5 additions & 1 deletion backend/project/endpoints/courses/course_admin_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from urllib.parse import urljoin
from dotenv import load_dotenv

from flask import request
from flask import abort, request
from flask_restful import Resource

from project.models.course_relation import CourseAdmin
Expand All @@ -21,6 +21,7 @@
json_message
)
from project.utils.query_agent import query_selected_from_model, insert_into_model
from project.utils.authentication import login_required, authorize_teacher_of_course, authorize_teacher_or_course_admin

load_dotenv()
API_URL = getenv("API_HOST")
Expand All @@ -32,6 +33,7 @@ class CourseForAdmins(Resource):
the /courses/course_id/admins url, only the teacher of a course can do this
"""

@authorize_teacher_or_course_admin
def get(self, course_id):
"""
This function will return all the admins of a course
Expand All @@ -47,6 +49,7 @@ def get(self, course_id):
filters={"course_id": course_id},
)

@authorize_teacher_of_course
def post(self, course_id):
"""
Api endpoint for adding new admins to a course, can only be done by the teacher
Expand All @@ -72,6 +75,7 @@ def post(self, course_id):
"uid"
)

@authorize_teacher_of_course
def delete(self, course_id):
"""
Api endpoint for removing admins of a course, can only be done by the teacher
Expand Down
4 changes: 4 additions & 0 deletions backend/project/endpoints/courses/course_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from project.db_in import db
from project.utils.query_agent import delete_by_id_from_model, patch_by_id_from_model
from project.utils.authentication import login_required, authorize_teacher_of_course

load_dotenv()
API_URL = getenv("API_HOST")
Expand All @@ -27,6 +28,7 @@
class CourseByCourseId(Resource):
"""Api endpoint for the /courses/course_id link"""

@login_required
def get(self, course_id):
"""
This get function will return all the related projects of the course
Expand Down Expand Up @@ -86,6 +88,7 @@ def get(self, course_id):
"error": "Something went wrong while querying the database.",
"url": RESPONSE_URL}, 500

@authorize_teacher_of_course
def delete(self, course_id):
"""
This function will delete the course with course_id
Expand All @@ -97,6 +100,7 @@ def delete(self, course_id):
RESPONSE_URL
)

@authorize_teacher_of_course
def patch(self, course_id):
"""
This function will update the course with course_id
Expand Down
8 changes: 6 additions & 2 deletions backend/project/endpoints/courses/course_student_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
)

from project.utils.query_agent import query_selected_from_model
from project.utils.authentication import login_required, authorize_teacher_or_course_admin

load_dotenv()
API_URL = getenv("API_HOST")
Expand All @@ -38,13 +39,14 @@ class CourseToAddStudents(Resource):
and everyone should be able to list all students assigned to a course
"""

@login_required
def get(self, course_id):
"""
Get function at /courses/course_id/students
to get all the users assigned to a course
everyone can get this data so no need to have uid query in the link
"""
abort_url = f"{API_URL}/courses/{str(course_id)}/students"
abort_url = f"{API_URL}/courses/{course_id}/students"
get_course_abort_if_not_found(course_id)

return query_selected_from_model(
Expand All @@ -55,12 +57,13 @@ def get(self, course_id):
filters={"course_id": course_id}
)

@authorize_teacher_or_course_admin
def post(self, course_id):
"""
Allows admins of a course to assign new students by posting to:
/courses/course_id/students with a list of uid in the request body under key "students"
"""
abort_url = f"{API_URL}/courses/{str(course_id)}/students"
abort_url = f"{API_URL}/courses/{course_id}/students"
uid = request.args.get("uid")
data = request.get_json()
student_uids = data.get("students")
Expand All @@ -85,6 +88,7 @@ def post(self, course_id):
response["data"] = data
return response, 201

@authorize_teacher_or_course_admin
def delete(self, course_id):
"""
This function allows admins of a course to remove students by sending a delete request to
Expand Down
10 changes: 7 additions & 3 deletions backend/project/endpoints/courses/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from project.models.course import Course
from project.utils.query_agent import query_selected_from_model, insert_into_model
from project.utils.authentication import login_required, authorize_teacher

load_dotenv()
API_URL = getenv("API_HOST")
Expand All @@ -22,6 +23,7 @@
class CourseForUser(Resource):
"""Api endpoint for the /courses link"""

@login_required
def get(self):
""" "
Get function for /courses this will be the main endpoint
Expand All @@ -36,15 +38,17 @@ def get(self):
filters=request.args
)

def post(self):
@authorize_teacher
def post(self, teacher_id=None):
"""
This function will create a new course
if the body of the post contains a name and uid is an admin or teacher
"""

req = request.json
req["teacher"] = teacher_id
return insert_into_model(
Course,
request.json,
req,
RESPONSE_URL,
"course_id",
required_fields=["name", "teacher"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from project.utils.query_agent import query_by_id_from_model, delete_by_id_from_model
from project.models.course_share_code import CourseShareCode
from project.endpoints.courses.join_codes.join_codes_utils import check_course_exists
from project.utils.authentication import authorize_teacher_of_course

load_dotenv()
API_URL = getenv("API_HOST")
Expand All @@ -18,7 +19,7 @@
class CourseJoinCode(Resource):
"""
This class will handle post and delete queries to
the /courses/course_id/join_codes url, only an admin of a course can do this
the /courses/course_id/join_codes/<join_code> url, only an admin of a course can do this
"""

@check_course_exists
Expand All @@ -35,9 +36,10 @@ def get(self, course_id, join_code):
)

@check_course_exists
@authorize_teacher_of_course
def delete(self, course_id, join_code):
"""
Api endpoint for adding new join codes to a course, can only be done by the teacher
Api endpoint for deleting join codes from a course, can only be done by the teacher
"""

return delete_by_id_from_model(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@
from project.utils.query_agent import query_selected_from_model, insert_into_model
from project.models.course_share_code import CourseShareCode
from project.endpoints.courses.courses_utils import get_course_abort_if_not_found
from project.utils.authentication import login_required, authorize_teacher_of_course

load_dotenv()
API_URL = getenv("API_HOST")
RESPONSE_URL = urljoin(f"{API_URL}/", "courses")

class CourseJoinCodes(Resource):
"""
This class will handle post and delete queries to
This class will handle get and post queries to
the /courses/course_id/join_codes url, only an admin of a course can do this
"""

@login_required
def get(self, course_id):
"""
This function will return all the join codes of a course
Expand All @@ -36,6 +38,7 @@ def get(self, course_id):
filters={"course_id": course_id}
)

@authorize_teacher_of_course
def post(self, course_id):
"""
Api endpoint for adding new join codes to a course, can only be done by the teacher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def check_course_exists(func):
"""
Middleware to check if the course exists before handling the request
"""
def wrapper(self, course_id, join_code, *args, **kwargs):
get_course_abort_if_not_found(course_id)
return func(self, course_id, join_code, *args, **kwargs)
def wrapper(*args, **kwargs):
get_course_abort_if_not_found(kwargs["course_id"])
return func(*args, **kwargs)
return wrapper
3 changes: 3 additions & 0 deletions backend/project/endpoints/projects/project_assignment_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from project.models.project import Project
from project.utils.query_agent import query_by_id_from_model
from project.utils.authentication import authorize_project_visible

API_URL = os.getenv('API_HOST')
RESPONSE_URL = urljoin(API_URL, "projects")
Expand All @@ -21,6 +22,8 @@ class ProjectAssignmentFiles(Resource):
"""
Class for getting the assignment files of a project
"""

@authorize_project_visible
def get(self, project_id):
"""
Get the assignment files of a project
Expand Down
5 changes: 4 additions & 1 deletion backend/project/endpoints/projects/project_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from project.models.project import Project
from project.utils.query_agent import query_by_id_from_model, delete_by_id_from_model, \
patch_by_id_from_model

from project.utils.authentication import authorize_teacher_or_project_admin, authorize_teacher_of_project, authorize_project_visible

API_URL = getenv('API_HOST')
RESPONSE_URL = urljoin(API_URL, "projects")
Expand All @@ -24,6 +24,7 @@ class ProjectDetail(Resource):
for implementing get, delete and put methods
"""

@authorize_project_visible
def get(self, project_id):
"""
Get method for listing a specific project
Expand All @@ -37,6 +38,7 @@ def get(self, project_id):
project_id,
RESPONSE_URL)

@authorize_teacher_or_project_admin
def patch(self, project_id):
"""
Update method for updating a specific project
Expand All @@ -51,6 +53,7 @@ def patch(self, project_id):
request.json
)

@authorize_teacher_of_project
def delete(self, project_id):
"""
Delete a project and all of its submissions in cascade
Expand Down
8 changes: 5 additions & 3 deletions backend/project/endpoints/projects/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
from flask import request, jsonify
from flask_restful import Resource


from project.models.project import Project
from project.utils.query_agent import query_selected_from_model, create_model_instance
from project.utils.authentication import authorize_teacher

from project.endpoints.projects.endpoint_parser import parse_project_params

Expand All @@ -25,7 +25,8 @@ class ProjectsEndpoint(Resource):
for implementing get method
"""

def get(self):
@authorize_teacher
def get(self, teacher_id=None):
"""
Get method for listing all available projects
that are currently in the API
Expand All @@ -39,7 +40,8 @@ def get(self):
filters=request.args
)

def post(self):
@authorize_teacher
def post(self, teacher_id=None):
"""
Post functionality for project
using flask_restfull parse lib
Expand Down
8 changes: 7 additions & 1 deletion backend/project/endpoints/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from project.utils.files import filter_files, all_files_uploaded, zip_files
from project.utils.user import is_valid_user
from project.utils.project import is_valid_project
from project.utils.authentication import authorize_submission_request, authorize_submissions_request, authorize_grader, authorize_student_submission, authorize_submission_author

load_dotenv()
API_HOST = getenv("API_HOST")
Expand All @@ -24,6 +25,7 @@
class SubmissionsEndpoint(Resource):
"""API endpoint for the submissions"""

@authorize_submissions_request
def get(self) -> dict[str, any]:
"""Get all the submissions from a user for a project
Expand Down Expand Up @@ -66,6 +68,7 @@ def get(self) -> dict[str, any]:
data["message"] = "An error occurred while fetching the submissions"
return data, 500

@authorize_student_submission
def post(self) -> dict[str, any]:
"""Post a new submission to a project
Expand Down Expand Up @@ -142,6 +145,7 @@ def post(self) -> dict[str, any]:
class SubmissionEndpoint(Resource):
"""API endpoint for the submission"""

@authorize_submission_request
def get(self, submission_id: int) -> dict[str, any]:
"""Get the submission given an submission ID
Expand Down Expand Up @@ -180,6 +184,7 @@ def get(self, submission_id: int) -> dict[str, any]:
f"An error occurred while fetching the submission (submission_id={submission_id})"
return data, 500

@authorize_grader
def patch(self, submission_id:int) -> dict[str, any]:
"""Update some fields of a submission given a submission ID
Expand Down Expand Up @@ -232,6 +237,7 @@ def patch(self, submission_id:int) -> dict[str, any]:
f"An error occurred while patching submission (submission_id={submission_id})"
return data, 500

@authorize_submission_author
def delete(self, submission_id: int) -> dict[str, any]:
"""Delete a submission given a submission ID
Expand Down Expand Up @@ -270,4 +276,4 @@ def delete(self, submission_id: int) -> dict[str, any]:
submissions_bp.add_url_rule(
"/submissions/<int:submission_id>",
view_func=SubmissionEndpoint.as_view("submission")
)
)
Loading

0 comments on commit 7c0cc08

Please sign in to comment.