Loading portal/database/models.py +4 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,10 @@ class User(db.Model, EntityBase): result.append(role.course) return result def get_roles_in_course(self, course): return Role.query.filter_by(course=course) \ .join(Role.users).filter(User.id == self.id).all() def __init__(self, uco, email, username, is_admin=False) -> None: self.uco = uco self.email = email Loading portal/service/errors.py +3 −2 Original line number Diff line number Diff line Loading @@ -57,10 +57,11 @@ class UnauthorizedError(PortalAPIError): class ForbiddenError(PortalAPIError): # could use a resource identification (like 404) def __init__(self, uid, note=None): def __init__(self, uid=None, note=None): user_message = f"Forbidden for user: {uid}!" if uid else "Forbidden action." message = dict( uid=uid, message=f"Forbidden for user: {uid}!", message=user_message, ) if note: message['note'] = note Loading portal/service/policies.py 0 → 100644 +85 −0 Original line number Diff line number Diff line from portal.database.models import User, Course, Component, Role from portal.service.errors import ForbiddenError def require_user(user: User, course: Course, permissions: list): if not check_user(user, course=course, permissions=permissions): note = f"Course: {course.id}[{course.codename}]; Permissions: {permissions}" raise ForbiddenError(uid=user.id, note=note) def require_sysadmin(user: User): if not check_sysadmin(user): note = f"User is not sysadmin!" raise ForbiddenError(uid=user.id, note=note) def check_component(component: Component, course: Course, permissions: list) -> bool: """Checks component permissions for a course Args: component(Component): Component instance course(Course): Course instance permissions(list): List of permissions required Returns(bool): True if checks has passed """ # TODO return True def check_user(user: User, course: Course, permissions: list) -> bool: """Checks a user's permissions based on his roles in the course Args: user(User): User instance course(Course): Course instance permissions(list): Permissions list Returns(bool): True if checks pass """ if check_sysadmin(user): return True roles = user.get_roles_in_course(course=course) return all(__evaluate_permission_for_roles(roles, perm) for perm in permissions) def check_sysadmin(user: User) -> bool: """Checks whether the user is sysadmin Args: user(User): User instance Returns(bool): True if checks pass """ return user.is_admin def __evaluate_permission_for_role(role: Role, permission: str, default=False) -> bool: """Evaluates permission for a role If the permission doesn't exist, False is returned. Args: role(Role): the role to check permission(str): Permission name default(bool): Default behaviour when the permission does not exist Returns(bool): True if permission is True in the role """ return getattr(role, permission, default=default) def __evaluate_permission_for_roles(roles: list, permission: str) -> bool: """Evaluates permissions for all a list of roles Args: roles(list): Role list permission(str): Permission name Returns(bool): True if any of the roles allows the permission """ return any(__evaluate_permission_for_role(role, permission) for role in roles) sample_data/data_init.py +4 −3 Original line number Diff line number Diff line Loading @@ -49,11 +49,12 @@ def create_groups(db): def create_submissions(db): user = User.query.filter_by(username="xfoo").first() sub = Submission(user=user, project=Project.query.filter_by(name="p1").first(), parameters="") xfoo = User.query.filter_by(username="xfoo").first() xbar = User.query.filter_by(username="xbar").first() sub = Submission(user=xfoo, project=Project.query.filter_by(name="p1").first(), parameters="") sub.state = SubmissionState.IN_PROGRESS db.session.add(sub) db.session.add(Submission(user=user, project=Project.query.filter_by(name="p1").first(), parameters="")) db.session.add(Submission(user=xbar, project=Project.query.filter_by(name="p1").first(), parameters="")) db.session.commit() Loading tests/database/test_db.py +30 −2 Original line number Diff line number Diff line Loading @@ -845,10 +845,38 @@ def test_user_submission_review(session): assert submission in user.submissions review = Review(submission=submission) ri1 = ReviewItem(user, review, "foo", 10) ri2 = ReviewItem(user, review, "bar", 1) ri1 = ReviewItem(user, review, "foo", 10, content="something") ri2 = ReviewItem(user, review, "bar", 1, content="something else") session.flush() assert ri1 in user.review_items assert ri2 in user.review_items def test_user_roles_for_course(session): user = User(uco=123, email='foo', username='xfoo') course = Course(name="C++", codename="PB161") c_java = Course(name="Java", codename="PB162") teacher_cpp = Role(course=course, name='teacher') lect_cpp = Role(course=course, name='lecturer') student_java = Role(course=c_java, name='student') user.roles.append(teacher_cpp) user.roles.append(lect_cpp) user.roles.append(student_java) session.add(user) session.flush() assert len(user.roles) == 3 cpp_roles = user.get_roles_in_course(course=course) assert len(cpp_roles) == 2 assert teacher_cpp in cpp_roles assert lect_cpp in cpp_roles java_roles = user.get_roles_in_course(course=c_java) assert len(java_roles) == 1 assert student_java in java_roles Loading
portal/database/models.py +4 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,10 @@ class User(db.Model, EntityBase): result.append(role.course) return result def get_roles_in_course(self, course): return Role.query.filter_by(course=course) \ .join(Role.users).filter(User.id == self.id).all() def __init__(self, uco, email, username, is_admin=False) -> None: self.uco = uco self.email = email Loading
portal/service/errors.py +3 −2 Original line number Diff line number Diff line Loading @@ -57,10 +57,11 @@ class UnauthorizedError(PortalAPIError): class ForbiddenError(PortalAPIError): # could use a resource identification (like 404) def __init__(self, uid, note=None): def __init__(self, uid=None, note=None): user_message = f"Forbidden for user: {uid}!" if uid else "Forbidden action." message = dict( uid=uid, message=f"Forbidden for user: {uid}!", message=user_message, ) if note: message['note'] = note Loading
portal/service/policies.py 0 → 100644 +85 −0 Original line number Diff line number Diff line from portal.database.models import User, Course, Component, Role from portal.service.errors import ForbiddenError def require_user(user: User, course: Course, permissions: list): if not check_user(user, course=course, permissions=permissions): note = f"Course: {course.id}[{course.codename}]; Permissions: {permissions}" raise ForbiddenError(uid=user.id, note=note) def require_sysadmin(user: User): if not check_sysadmin(user): note = f"User is not sysadmin!" raise ForbiddenError(uid=user.id, note=note) def check_component(component: Component, course: Course, permissions: list) -> bool: """Checks component permissions for a course Args: component(Component): Component instance course(Course): Course instance permissions(list): List of permissions required Returns(bool): True if checks has passed """ # TODO return True def check_user(user: User, course: Course, permissions: list) -> bool: """Checks a user's permissions based on his roles in the course Args: user(User): User instance course(Course): Course instance permissions(list): Permissions list Returns(bool): True if checks pass """ if check_sysadmin(user): return True roles = user.get_roles_in_course(course=course) return all(__evaluate_permission_for_roles(roles, perm) for perm in permissions) def check_sysadmin(user: User) -> bool: """Checks whether the user is sysadmin Args: user(User): User instance Returns(bool): True if checks pass """ return user.is_admin def __evaluate_permission_for_role(role: Role, permission: str, default=False) -> bool: """Evaluates permission for a role If the permission doesn't exist, False is returned. Args: role(Role): the role to check permission(str): Permission name default(bool): Default behaviour when the permission does not exist Returns(bool): True if permission is True in the role """ return getattr(role, permission, default=default) def __evaluate_permission_for_roles(roles: list, permission: str) -> bool: """Evaluates permissions for all a list of roles Args: roles(list): Role list permission(str): Permission name Returns(bool): True if any of the roles allows the permission """ return any(__evaluate_permission_for_role(role, permission) for role in roles)
sample_data/data_init.py +4 −3 Original line number Diff line number Diff line Loading @@ -49,11 +49,12 @@ def create_groups(db): def create_submissions(db): user = User.query.filter_by(username="xfoo").first() sub = Submission(user=user, project=Project.query.filter_by(name="p1").first(), parameters="") xfoo = User.query.filter_by(username="xfoo").first() xbar = User.query.filter_by(username="xbar").first() sub = Submission(user=xfoo, project=Project.query.filter_by(name="p1").first(), parameters="") sub.state = SubmissionState.IN_PROGRESS db.session.add(sub) db.session.add(Submission(user=user, project=Project.query.filter_by(name="p1").first(), parameters="")) db.session.add(Submission(user=xbar, project=Project.query.filter_by(name="p1").first(), parameters="")) db.session.commit() Loading
tests/database/test_db.py +30 −2 Original line number Diff line number Diff line Loading @@ -845,10 +845,38 @@ def test_user_submission_review(session): assert submission in user.submissions review = Review(submission=submission) ri1 = ReviewItem(user, review, "foo", 10) ri2 = ReviewItem(user, review, "bar", 1) ri1 = ReviewItem(user, review, "foo", 10, content="something") ri2 = ReviewItem(user, review, "bar", 1, content="something else") session.flush() assert ri1 in user.review_items assert ri2 in user.review_items def test_user_roles_for_course(session): user = User(uco=123, email='foo', username='xfoo') course = Course(name="C++", codename="PB161") c_java = Course(name="Java", codename="PB162") teacher_cpp = Role(course=course, name='teacher') lect_cpp = Role(course=course, name='lecturer') student_java = Role(course=c_java, name='student') user.roles.append(teacher_cpp) user.roles.append(lect_cpp) user.roles.append(student_java) session.add(user) session.flush() assert len(user.roles) == 3 cpp_roles = user.get_roles_in_course(course=course) assert len(cpp_roles) == 2 assert teacher_cpp in cpp_roles assert lect_cpp in cpp_roles java_roles = user.get_roles_in_course(course=c_java) assert len(java_roles) == 1 assert student_java in java_roles