Skip to content

Commit b7b4651

Browse files
committed
Extended scope for admin and added/extended endpoints
1 parent d845077 commit b7b4651

File tree

10 files changed

+584
-148
lines changed

10 files changed

+584
-148
lines changed

grader_service/auth/auth.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from grader_service.utils import maybe_future, url_path_join
1111

1212
from .login import LoginHandler
13+
from ..handlers.base_handler import BaseHandler
1314

1415

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

514-
async def refresh_user(self, user, handler=None):
515+
async def refresh_user(self, user, handler:BaseHandler=None):
515516
"""Refresh auth data for a given user
516517
517518
Allows refreshing or invalidating auth data.
@@ -537,6 +538,7 @@ async def refresh_user(self, user, handler=None):
537538
Any fields not present will be left unchanged.
538539
This can include updating `.admin` or `.auth_state` fields.
539540
"""
541+
user.is_admin = handler.authenticator.is_admin(handler=self, authentication={'name': user.name})
540542
return True
541543

542544
def is_admin(self, handler, authentication):

grader_service/handlers/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
lectures,
1616
permission,
1717
submissions,
18+
roles,
19+
users
1820
)
1921
from grader_service.handlers.handler_utils import GitRepoType
2022

@@ -29,4 +31,6 @@
2931
"config",
3032
"base_handler",
3133
"GitRepoType",
34+
"roles",
35+
"users",
3236
]

grader_service/handlers/assignment.py

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class AssignmentBaseHandler(GraderBaseHandler):
6363
6464
route: /lectures/{lecture_id}/assignments."""
6565

66-
@authorize([Scope.student, Scope.tutor, Scope.instructor])
66+
@authorize([Scope.student, Scope.tutor, Scope.instructor, Scope.admin])
6767
async def get(self, lecture_id: int):
6868
"""Returns all assignments of a lecture.
6969
@@ -73,35 +73,44 @@ async def get(self, lecture_id: int):
7373
"""
7474
lecture_id = parse_ids(lecture_id)
7575
self.validate_parameters("include-submissions")
76-
role = self.get_role(lecture_id)
77-
if role.lecture.deleted == DeleteState.deleted:
78-
raise HTTPError(HTTPStatus.NOT_FOUND, reason="Lecture not found")
7976

80-
if role.role == Scope.student: # students do not get assignments that are created
81-
assignments = (
82-
self.session.query(Assignment)
83-
.filter(
84-
Assignment.lectid == role.lecture.id,
85-
Assignment.deleted == DeleteState.active,
86-
Assignment.status != "created",
87-
Assignment.status != "pushed",
77+
if not self.user.is_admin:
78+
role = self.get_role(lecture_id)
79+
if role.lecture.deleted == DeleteState.deleted:
80+
raise HTTPError(HTTPStatus.NOT_FOUND, reason="Lecture not found")
81+
82+
if role.role == Scope.student: # students do not get assignments that are created
83+
assignments = (
84+
self.session.query(Assignment)
85+
.filter(
86+
Assignment.lectid == role.lecture.id,
87+
Assignment.deleted == DeleteState.active,
88+
Assignment.status != "created",
89+
Assignment.status != "pushed",
90+
)
91+
.all()
8892
)
89-
.all()
90-
)
91-
93+
else:
94+
assignments = [a for a in role.lecture.assignments if (a.deleted == DeleteState.active)]
9295
else:
93-
assignments = [a for a in role.lecture.assignments if (a.deleted == DeleteState.active)]
96+
lecture = self.get_lecture(lecture_id=lecture_id)
97+
if lecture is None:
98+
raise HTTPError(HTTPStatus.NOT_FOUND, reason="Lecture not found")
99+
assignments = lecture.assignments
94100

95101
# Handle the case that the user wants to include submissions
96102
include_submissions = self.get_argument("include-submissions", "true") == "true"
97103
if include_submissions:
98104
assignids = [a.id for a in assignments]
99-
user_id = self.user.id
100-
results = (
101-
self.session.query(Submission)
102-
.filter(Submission.assignid.in_(assignids), Submission.user_id == user_id)
103-
.all()
104-
)
105+
if not self.user.is_admin:
106+
user_id = self.user.id
107+
results = (
108+
self.session.query(Submission)
109+
.filter(Submission.assignid.in_(assignids), Submission.user_id == user_id)
110+
.all()
111+
)
112+
else:
113+
results = self.session.query(Submission).filter(Submission.assignid.in_(assignids)).all()
105114
# Create a combined list of assignments and submissions
106115
assignments = [a.serialize() for a in assignments]
107116
submissions = [s.serialize() for s in results]
@@ -242,7 +251,7 @@ async def put(self, lecture_id: int, assignment_id: int):
242251
self.session.commit()
243252
self.write_json(assignment)
244253

245-
@authorize([Scope.student, Scope.tutor, Scope.instructor])
254+
@authorize([Scope.student, Scope.tutor, Scope.instructor, Scope.admin])
246255
async def get(self, lecture_id: int, assignment_id: int):
247256
"""Returns a specific assignment of a lecture.
248257
@@ -256,9 +265,9 @@ async def get(self, lecture_id: int, assignment_id: int):
256265
assignment = self.get_assignment(lecture_id=lecture_id, assignment_id=assignment_id)
257266
self.write_json(assignment)
258267

259-
@authorize([Scope.instructor])
268+
@authorize([Scope.instructor, Scope.admin])
260269
async def delete(self, lecture_id: int, assignment_id: int):
261-
"""Soft-Deletes a specific assignment.
270+
"""Soft or Hard-Deletes a specific assignment.
262271
263272
:param lecture_id: id of the lecture
264273
:type lecture_id: int
@@ -270,26 +279,46 @@ async def delete(self, lecture_id: int, assignment_id: int):
270279
self.validate_parameters()
271280
assignment = self.get_assignment(lecture_id, assignment_id)
272281

273-
if assignment.status in ["released", "complete"]:
274-
msg = "Cannot delete released or completed assignments!"
275-
raise HTTPError(HTTPStatus.CONFLICT, reason=msg)
282+
if not self.user.is_admin:
283+
if assignment.status in ["released", "complete"]:
284+
msg = "Cannot delete released or completed assignments!"
285+
raise HTTPError(HTTPStatus.CONFLICT, reason=msg)
276286

277-
previously_deleted = (
278-
self.session.query(Assignment)
279-
.filter(
280-
Assignment.lectid == lecture_id,
281-
Assignment.name == assignment.name,
282-
Assignment.deleted == DeleteState.deleted,
287+
previously_deleted = (
288+
self.session.query(Assignment)
289+
.filter(
290+
Assignment.lectid == lecture_id,
291+
Assignment.name == assignment.name,
292+
Assignment.deleted == DeleteState.deleted,
293+
)
294+
.one_or_none()
283295
)
284-
.one_or_none()
285-
)
286-
if previously_deleted is not None:
287-
self.session.delete(previously_deleted)
296+
if previously_deleted is not None:
297+
if len(previously_deleted.submissions) > 0:
298+
msg = "Assignment cannot be deleted: submissions still exist. Delete submissions first!"
299+
raise HTTPError(HTTPStatus.CONFLICT, reason=msg)
300+
301+
self.delete_assignment_files(previously_deleted)
302+
self.session.delete(previously_deleted)
303+
self.session.commit()
304+
305+
assignment.deleted = DeleteState.deleted
288306
self.session.commit()
307+
else:
308+
if len(assignment.submissions) > 0:
309+
msg = "Assignment cannot be deleted: submissions still exist. Delete submissions first!"
310+
raise HTTPError(HTTPStatus.CONFLICT, reason=msg)
289311

290-
assignment.deleted = DeleteState.deleted
291-
self.session.commit()
312+
self.delete_assignment_files(assignment)
313+
self.session.delete(assignment)
314+
self.session.commit()
292315

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

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

0 commit comments

Comments
 (0)