Skip to content

Commit

Permalink
Move access_mode handling to permission class
Browse files Browse the repository at this point in the history
  • Loading branch information
raphendyr committed Aug 16, 2016
1 parent f37ec08 commit b69cc5c
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 49 deletions.
54 changes: 53 additions & 1 deletion authorization/permissions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from django.utils.translation import ugettext_lazy as _

from lib.helpers import Enum
from lib.messages import error as error_msg

"""
Base permission classes.
Expand Down Expand Up @@ -45,4 +50,51 @@ def has_permission(self, request, view):
return False

def has_object_permission(self, request, view, obj):
return False
return False


# Access mode
# ===========

# All access levels
ACCESS = Enum(
('ANONYMOUS', 0, _("Any user authenticated or not")),
('ENROLL', 1, None),
('STUDENT', 3, _("Any authenticated student")),
('ENROLLED', 4, _("Enrolled student of the course")),
('ASSISTANT', 5, _("Assistant of the course")),
('GRADING', 6, _("Grading. Assistant if course has that option or teacher")),
('TEACHER', 10, _("Teacher of the course")),
)


class AccessModePermission(Permission):
"""
If view has access_mode that is not anonymous, then require authentication
"""
message = _("Permission denied by access mode")

def has_permission(self, request, view):
access_mode = view.get_access_mode()

if access_mode == ACCESS.ANONYMOUS:
return True
if not request.user.is_authenticated():
return False

if access_mode >= ACCESS.TEACHER:
if not view.is_teacher:
error_msg(request, _("Only course teachers shall pass."))
return False

elif access_mode >= ACCESS.ASSISTANT:
if not view.is_course_staff:
error_msg(request, _("Only course staff shall pass."))
return False

elif access_mode == ACCESS.ENROLLED:
if not view.instance.is_student(request.user):
error_msg(request, _("Only enrolled students shall pass."))
return False

return True
36 changes: 19 additions & 17 deletions course/viewbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from django.utils.translation import ugettext_lazy as _

from lib.viewbase import BaseTemplateView
from userprofile.viewbase import ACCESS, UserProfileMixin
from authorization.permissions import ACCESS
from userprofile.viewbase import UserProfileMixin
from .models import Course, CourseInstance, CourseModule


Expand All @@ -22,14 +23,6 @@ def get_resource_objects(self):
self.is_teacher = self.course.is_teacher(self.request.user)
self.note("course", "is_teacher")

def access_control(self):
super().access_control()
if self.access_mode >= ACCESS.TEACHER:
if not self.is_teacher:
messages.error(self.request,
_("Only course teachers shall pass."))
raise PermissionDenied()


class CourseBaseView(CourseMixin, BaseTemplateView):
pass
Expand All @@ -54,14 +47,9 @@ def get_resource_objects(self):
translation.activate(self.instance.language)

def access_control(self):

# Loosen the access mode if instance is public.
if self.instance.view_content_to == 4 and \
self.access_mode in (ACCESS.STUDENT, ACCESS.ENROLL):
self.access_mode = ACCESS.ANONYMOUS

super().access_control()
if self.access_mode >= ACCESS.ASSISTANT:
access_mode = self.get_access_mode()
if access_mode >= ACCESS.ASSISTANT:
if not self.is_course_staff:
messages.error(self.request,
_("Only course staff shall pass."))
Expand All @@ -74,7 +62,7 @@ def access_control(self):

# View content access.
if not self.is_course_staff:
if self.access_mode == ACCESS.ENROLLED or (self.instance.view_content_to == 1 and self.access_mode > ACCESS.ENROLL):
if access_mode == ACCESS.ENROLLED or (self.instance.view_content_to == 1 and access_mode > ACCESS.ENROLL):
if not self.instance.is_student(self.request.user):
messages.error(self.request, _("Only enrolled students shall pass."))
raise PermissionDenied()
Expand All @@ -87,6 +75,20 @@ def access_control(self):
raise PermissionDenied()


def get_access_mode(self):
access_mode = super().get_access_mode()

if hasattr(self, 'instance'):
# Loosen the access mode if instance is public
show_for = self.instance.view_content_to
is_public = show_for == CourseInstance.VIEW_ACCESS.PUBLIC
access_mode_student = access_mode in (ACCESS.STUDENT, ACCESS.ENROLL)
if is_public and acecss_mode_student:
access_mode = ACCESS.ANONYMOUS

return access_mode


class CourseInstanceBaseView(CourseInstanceMixin, BaseTemplateView):
pass

Expand Down
3 changes: 2 additions & 1 deletion course/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

from lib.helpers import settings_text
from lib.viewbase import BaseTemplateView, BaseRedirectView, BaseFormView, BaseView
from userprofile.viewbase import ACCESS, UserProfileView
from authorization.permissions import ACCESS
from userprofile.viewbase import UserProfileView
from .forms import GroupsForm, GroupSelectForm
from .models import CourseInstance, Enrollment
from .viewbase import CourseBaseView, CourseInstanceBaseView, \
Expand Down
2 changes: 1 addition & 1 deletion deviations/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from course.viewbase import CourseInstanceBaseView, CourseInstanceMixin
from lib.viewbase import BaseFormView, BaseRedirectView
from userprofile.viewbase import ACCESS
from authorization.permissions import ACCESS
from .forms import DeadlineRuleDeviationForm
from .models import DeadlineRuleDeviation

Expand Down
2 changes: 1 addition & 1 deletion edit_course/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from course.viewbase import CourseInstanceBaseView, CourseInstanceMixin
from lib.viewbase import BaseTemplateView, BaseRedirectMixin, BaseFormView, \
BaseRedirectView
from userprofile.viewbase import ACCESS
from authorization.permissions import ACCESS
from .course_forms import CourseInstanceForm, CourseIndexForm, \
CourseContentForm, CloneInstanceForm
from .managers import CategoryManager, ModuleManager, ExerciseManager
Expand Down
2 changes: 1 addition & 1 deletion exercise/staff_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from course.viewbase import CourseInstanceBaseView, CourseInstanceMixin
from lib.viewbase import BaseRedirectView, BaseFormView, BaseView
from notification.models import Notification
from userprofile.viewbase import ACCESS
from authorization.permissions import ACCESS
from .models import LearningObject
from .presentation.results import ResultTable
from .forms import SubmissionReviewForm, SubmissionCreateAndReviewForm
Expand Down
7 changes: 4 additions & 3 deletions exercise/viewbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from course.viewbase import CourseModuleMixin
from lib.viewbase import BaseTemplateView
from userprofile.viewbase import ACCESS
from authorization.permissions import ACCESS
from .models import LearningObject, Submission, BaseExercise


Expand All @@ -29,12 +29,13 @@ def access_control(self):
and self.exercise.status == LearningObject.STATUS_HIDDEN:
raise Http404()
if isinstance(self.exercise, BaseExercise):
if self.access_mode >= ACCESS.ASSISTANT:
access_mode = self.get_access_mode()
if access_mode >= ACCESS.ASSISTANT:
if not (self.is_teacher or self.exercise.allow_assistant_viewing):
messages.error(self.request,
_("Assistant viewing is not allowed for this exercise."))
raise PermissionDenied()
if self.access_mode == ACCESS.GRADING:
if access_mode == ACCESS.GRADING:
if not (self.is_teacher or self.exercise.allow_assistant_grading):
messages.error(self.request,
_("Assistant grading is not allowed for this exercise."))
Expand Down
14 changes: 9 additions & 5 deletions exercise/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from course.viewbase import CourseInstanceBaseView
from lib.viewbase import BaseRedirectMixin, BaseView
from userprofile.viewbase import ACCESS
from authorization.permissions import ACCESS
from .models import LearningObjectDisplay
from .presentation.summary import UserExerciseSummary
from .protocol.exercise_page import ExercisePage
Expand Down Expand Up @@ -42,10 +42,14 @@ class ExerciseView(BaseRedirectMixin, ExerciseBaseView):
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

def access_control(self):
if self.exercise.status == 'enrollment' and self.access_mode == ACCESS.STUDENT:
self.access_mode = ACCESS.ENROLL
super().access_control()
def get_access_mode(self):
access_mode = super().get_access_mode()

# Loosen the access mode if exercise is enrollment
if self.exercise.status == 'enrollment' and access_mode == ACCESS.STUDENT:
access_mode = ACCESS.ENROLL

return access_mode

def get_after_new_submission(self):
self.submissions = self.exercise.get_submissions_for_student(
Expand Down
8 changes: 8 additions & 0 deletions lib/viewbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from authorization.views import AuthorizedResourceMixin
from authorization.permissions import (
Permission,
AccessModePermission,
)


Expand All @@ -30,7 +31,11 @@ class BaseMixin(object):
get/post methods. Calling the super method is required when overriding
the base methods.
"""
# NOTE: access_mode is not defined here, so if any derived class forgets to
# define it AccessModePermission will raise assertion error
#access_mode = ACCESS.ANONYMOUS
base_permission_classes = [
AccessModePermission,
AccessControlPermission,
]

Expand All @@ -39,6 +44,9 @@ def get_permissions(self):
perms.extend((Perm() for Perm in self.base_permission_classes))
return perms

def get_access_mode(self):
return self.access_mode

@deprecated("access_control is deprecated and should be replaced with correct permission_classes")
def access_control(self):
"""
Expand Down
11 changes: 10 additions & 1 deletion notification/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from course.viewbase import CourseInstanceBaseView
from lib.viewbase import PagerMixin
from userprofile.viewbase import ACCESS
from authorization.permissions import ACCESS

from .models import NotificationSet

Expand All @@ -10,6 +10,15 @@ class NotificationsView(PagerMixin, CourseInstanceBaseView):
template_name = "notification/notifications.html"
ajax_template_name = "notification/_notifications_list.html"

def get_access_mode(self):
access_mode = super().get_access_mode()

# Always require at least logged in student
if access_mode < ACCESS.STUDENT:
access_mode = ACCESS.STUDENT

return access_mode

def get_common_objects(self):
super().get_common_objects()
notifications_set = NotificationSet.get_course(
Expand Down
18 changes: 1 addition & 17 deletions userprofile/viewbase.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
from django.core.exceptions import PermissionDenied
from django.template.response import SimpleTemplateResponse
from django.views.generic.base import View

from lib.viewbase import BaseMixin, BaseTemplateView
from authorization.permissions import ACCESS
from .models import UserProfile


class ACCESS(object):
ANONYMOUS = 0
ENROLL = 1
STUDENT = 3
ENROLLED = 4
ASSISTANT = 5
GRADING = 6
TEACHER = 10


class UserProfileMixin(BaseMixin):
access_mode = ACCESS.STUDENT
login_redirect = True
Expand All @@ -33,12 +23,6 @@ def get_resource_objects(self):
# Add available for template
self.note("profile", "is_external_student")

def access_control(self):
super().access_control()
if self.access_mode > ACCESS.ANONYMOUS \
and not self.request.user.is_authenticated():
raise PermissionDenied


class UserProfileView(UserProfileMixin, BaseTemplateView):
pass
3 changes: 2 additions & 1 deletion userprofile/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from django.utils.http import is_safe_url

from lib.helpers import settings_text
from .viewbase import UserProfileView, ACCESS
from authorization.permissions import ACCESS
from .viewbase import UserProfileView


def login(request):
Expand Down

0 comments on commit b69cc5c

Please sign in to comment.