Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion grader_service/auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from grader_service.utils import maybe_future, url_path_join

from .login import LoginHandler
from ..handlers.base_handler import BaseHandler


class Authenticator(LoggingConfigurable):
Expand Down Expand Up @@ -511,7 +512,7 @@ async def get_authenticated_user(self, handler, data):
self.log.warning("User %r not allowed.", username)
return

async def refresh_user(self, user, handler=None):
async def refresh_user(self, user, handler:BaseHandler=None):
"""Refresh auth data for a given user

Allows refreshing or invalidating auth data.
Expand All @@ -537,6 +538,7 @@ async def refresh_user(self, user, handler=None):
Any fields not present will be left unchanged.
This can include updating `.admin` or `.auth_state` fields.
"""
user.is_admin = handler.authenticator.is_admin(handler=self, authentication={'name': user.name})
return True

def is_admin(self, handler, authentication):
Expand Down
4 changes: 4 additions & 0 deletions grader_service/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
lectures,
permission,
submissions,
roles,
users
)
from grader_service.handlers.handler_utils import GitRepoType

Expand All @@ -29,4 +31,6 @@
"config",
"base_handler",
"GitRepoType",
"roles",
"users",
]
109 changes: 69 additions & 40 deletions grader_service/handlers/assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class AssignmentBaseHandler(GraderBaseHandler):

route: /lectures/{lecture_id}/assignments."""

@authorize([Scope.student, Scope.tutor, Scope.instructor])
@authorize([Scope.student, Scope.tutor, Scope.instructor, Scope.admin])
async def get(self, lecture_id: int):
"""Returns all assignments of a lecture.

Expand All @@ -73,35 +73,44 @@ async def get(self, lecture_id: int):
"""
lecture_id = parse_ids(lecture_id)
self.validate_parameters("include-submissions")
role = self.get_role(lecture_id)
if role.lecture.deleted == DeleteState.deleted:
raise HTTPError(HTTPStatus.NOT_FOUND, reason="Lecture not found")

if role.role == Scope.student: # students do not get assignments that are created
assignments = (
self.session.query(Assignment)
.filter(
Assignment.lectid == role.lecture.id,
Assignment.deleted == DeleteState.active,
Assignment.status != "created",
Assignment.status != "pushed",
if not self.user.is_admin:
role = self.get_role(lecture_id)
if role.lecture.deleted == DeleteState.deleted:
raise HTTPError(HTTPStatus.NOT_FOUND, reason="Lecture not found")

if role.role == Scope.student: # students do not get assignments that are created
assignments = (
self.session.query(Assignment)
.filter(
Assignment.lectid == role.lecture.id,
Assignment.deleted == DeleteState.active,
Assignment.status != "created",
Assignment.status != "pushed",
)
.all()
)
.all()
)

else:
assignments = [a for a in role.lecture.assignments if (a.deleted == DeleteState.active)]
else:
assignments = [a for a in role.lecture.assignments if (a.deleted == DeleteState.active)]
lecture = self.get_lecture(lecture_id=lecture_id)
if lecture is None:
raise HTTPError(HTTPStatus.NOT_FOUND, reason="Lecture not found")
assignments = lecture.assignments

# Handle the case that the user wants to include submissions
include_submissions = self.get_argument("include-submissions", "true") == "true"
if include_submissions:
assignids = [a.id for a in assignments]
user_id = self.user.id
results = (
self.session.query(Submission)
.filter(Submission.assignid.in_(assignids), Submission.user_id == user_id)
.all()
)
if not self.user.is_admin:
user_id = self.user.id
results = (
self.session.query(Submission)
.filter(Submission.assignid.in_(assignids), Submission.user_id == user_id)
.all()
)
else:
results = self.session.query(Submission).filter(Submission.assignid.in_(assignids)).all()
# Create a combined list of assignments and submissions
assignments = [a.serialize() for a in assignments]
submissions = [s.serialize() for s in results]
Expand Down Expand Up @@ -242,7 +251,7 @@ async def put(self, lecture_id: int, assignment_id: int):
self.session.commit()
self.write_json(assignment)

@authorize([Scope.student, Scope.tutor, Scope.instructor])
@authorize([Scope.student, Scope.tutor, Scope.instructor, Scope.admin])
async def get(self, lecture_id: int, assignment_id: int):
"""Returns a specific assignment of a lecture.

Expand All @@ -256,9 +265,9 @@ async def get(self, lecture_id: int, assignment_id: int):
assignment = self.get_assignment(lecture_id=lecture_id, assignment_id=assignment_id)
self.write_json(assignment)

@authorize([Scope.instructor])
@authorize([Scope.instructor, Scope.admin])
async def delete(self, lecture_id: int, assignment_id: int):
"""Soft-Deletes a specific assignment.
"""Soft or Hard-Deletes a specific assignment.

:param lecture_id: id of the lecture
:type lecture_id: int
Expand All @@ -270,26 +279,46 @@ async def delete(self, lecture_id: int, assignment_id: int):
self.validate_parameters()
assignment = self.get_assignment(lecture_id, assignment_id)

if assignment.status in ["released", "complete"]:
msg = "Cannot delete released or completed assignments!"
raise HTTPError(HTTPStatus.CONFLICT, reason=msg)
if not self.user.is_admin:
if assignment.status in ["released", "complete"]:
msg = "Cannot delete released or completed assignments!"
raise HTTPError(HTTPStatus.CONFLICT, reason=msg)

previously_deleted = (
self.session.query(Assignment)
.filter(
Assignment.lectid == lecture_id,
Assignment.name == assignment.name,
Assignment.deleted == DeleteState.deleted,
previously_deleted = (
self.session.query(Assignment)
.filter(
Assignment.lectid == lecture_id,
Assignment.name == assignment.name,
Assignment.deleted == DeleteState.deleted,
)
.one_or_none()
)
.one_or_none()
)
if previously_deleted is not None:
self.session.delete(previously_deleted)
if previously_deleted is not None:
if len(previously_deleted.submissions) > 0:
msg = "Assignment cannot be deleted: submissions still exist. Delete submissions first!"
raise HTTPError(HTTPStatus.CONFLICT, reason=msg)

self.delete_assignment_files(previously_deleted)
self.session.delete(previously_deleted)
self.session.commit()

assignment.deleted = DeleteState.deleted
self.session.commit()
else:
if len(assignment.submissions) > 0:
msg = "Assignment cannot be deleted: submissions still exist. Delete submissions first!"
raise HTTPError(HTTPStatus.CONFLICT, reason=msg)

assignment.deleted = DeleteState.deleted
self.session.commit()
self.delete_assignment_files(assignment)
self.session.delete(assignment)
self.session.commit()

def delete_assignment_files(self, assignment):
# delete all associated directories of the assignment
assignment_path = os.path.abspath(os.path.join(self.gitbase, assignment.lecture.code, str(assignment.id)))
tmp_assignment_path = os.path.abspath(os.path.join(self.tmpbase, assignment.lecture.code, str(assignment.id)))
shutil.rmtree(assignment_path, ignore_errors=True)
shutil.rmtree(tmp_assignment_path, ignore_errors=True)

@register_handler(
path=r"\/api\/lectures\/(?P<lecture_id>\d*)\/assignments\/(?P<assignment_id>\d*)\/reset\/?",
Expand Down
Loading
Loading