From 7cc854f0ed9c21361d06fd5314dc34ce81532d59 Mon Sep 17 00:00:00 2001
From: janmax <j.michal@stud.uni-goettingen.de>
Date: Mon, 27 Nov 2017 21:34:34 +0100
Subject: [PATCH] Fixed viewset permissions and added logging to permission
 base

* IT waS A FUCKIng tyPO
* ran isort and flake8 and fixed reported issues
---
 backend/core/models.py                        |  3 +-
 backend/core/permissions.py                   | 31 +++++++++----
 backend/core/tests/data_factories.py          |  1 +
 backend/core/tests/test_access_rights.py      | 45 +++++++++++++++++--
 backend/core/tests/test_auth.py               |  4 +-
 backend/core/tests/test_examlist.py           |  3 +-
 .../core/tests/test_tutor_api_endpoints.py    |  5 +--
 backend/core/urls.py                          |  2 +-
 backend/core/views.py                         |  8 +---
 backend/grady/settings/default.py             | 15 +++----
 10 files changed, 80 insertions(+), 37 deletions(-)

diff --git a/backend/core/models.py b/backend/core/models.py
index b7c7f3eb..04d8d10d 100644
--- a/backend/core/models.py
+++ b/backend/core/models.py
@@ -7,8 +7,7 @@ database.
 '''
 
 from collections import OrderedDict
-from random import randrange, sample
-from string import ascii_lowercase
+from random import randrange
 from typing import Dict, Union
 
 from django.contrib.auth import get_user_model
diff --git a/backend/core/permissions.py b/backend/core/permissions.py
index 564a93c6..e9b87940 100644
--- a/backend/core/permissions.py
+++ b/backend/core/permissions.py
@@ -1,37 +1,50 @@
+import logging
+
 from django.http import HttpRequest
 from django.views import View
 from rest_framework import permissions
 
 from core.models import Reviewer, Student, Tutor
 
+log = logging.getLogger(__name__)
+
 
 class IsUserGenericPermission(permissions.BasePermission):
     """ Generic class that encapsulates how to identify someone
     as a member of a user Group """
 
     def has_permission(self, request: HttpRequest, view: View) -> bool:
-        """ required by BasePermission. Check if user is instance of model"""
-        assert self.model is not None, (
-            "'%s' has to include a `model` attribute"
+        """ required by BasePermission. Check if user is instance of any
+        of the models provided in class' models attribute """
+        log.warn("Checking permission of request %s on view %s for user %s",
+                 request, view, request.user)
+
+        assert self.models is not None, (
+            "'%s' has to include a `models` attribute"
             % self.__class__.__name__
         )
 
         user = request.user
-        return user.is_authenticated() and isinstance(
-            user.get_associated_user(), self.model
-        )
+        is_authorized = user.is_authenticated() and any(isinstance(
+            user.get_associated_user(), models) for models in self.models)
+
+        if not is_authorized:
+            log.warn('User %s has no permission to view %s',
+                     user.username, view.__class__.__name__)
+
+        return is_authorized
 
 
 class IsStudent(IsUserGenericPermission):
     """ Has student permissions """
-    model = Student
+    models = (Student,)
 
 
 class IsReviewer(IsUserGenericPermission):
     """ Has reviewer permissions """
-    model = Reviewer
+    models = (Reviewer,)
 
 
 class IsTutor(IsUserGenericPermission):
     """ Has tutor permissions """
-    model = Tutor
+    models = (Tutor,)
diff --git a/backend/core/tests/data_factories.py b/backend/core/tests/data_factories.py
index 25e5ca71..f2f8b4f5 100644
--- a/backend/core/tests/data_factories.py
+++ b/backend/core/tests/data_factories.py
@@ -7,6 +7,7 @@ from core.models import (ExamType, Feedback, Reviewer, Student, Submission,
 # These methods are meant to be used to provide data to insert into the test
 # database
 
+
 def make_user(username='user01',
               password='p',
               fullname='us er01',
diff --git a/backend/core/tests/test_access_rights.py b/backend/core/tests/test_access_rights.py
index 51987f53..6e172626 100644
--- a/backend/core/tests/test_access_rights.py
+++ b/backend/core/tests/test_access_rights.py
@@ -3,7 +3,8 @@ from rest_framework import status
 from rest_framework.test import (APIRequestFactory, APITestCase,
                                  force_authenticate)
 
-from core.views import StudentSelfApiViewSet, TutorApiViewSet
+from core.views import (ExamApiViewSet, StudentReviewerApiViewSet,
+                        StudentSelfApiViewSet, TutorApiViewSet)
 from util.factories import GradyUserFactory
 
 
@@ -44,7 +45,8 @@ class AccessRightsOfStudentAPIViewTests(APITestCase):
 
 
 class AccessRightsOfTutorAPIViewTests(APITestCase):
-    """ Tests to ensure that only Reviewers have access to the TutorList information"""
+    """ Tests to ensure that only Reviewers have access to the TutorList
+        information """
     @classmethod
     def setUpTestData(cls):
         cls.factory = APIRequestFactory()
@@ -78,7 +80,8 @@ class AccessRightsOfTutorAPIViewTests(APITestCase):
 
 
 class AccessRightsOfStudentReviewerAPIViewTest(APITestCase):
-    """ Tests to ensure that only Reviewers have access to the StudentReviewerApi endpoint information"""
+    """ Tests to ensure that only Reviewers have access to the
+    StudentReviewerApi endpoint information"""
 
     @classmethod
     def setUpTestData(cls):
@@ -90,7 +93,7 @@ class AccessRightsOfStudentReviewerAPIViewTest(APITestCase):
         self.tutor = self.user_factory.make_tutor()
         self.reviewer = self.user_factory.make_reviewer()
         self.request = self.factory.get(reverse('student-list'))
-        self.view = TutorApiViewSet.as_view({'get': 'list'})
+        self.view = StudentReviewerApiViewSet.as_view({'get': 'list'})
 
     def test_unauthenticated_access_denied(self):
         response = self.view(self.request)
@@ -110,3 +113,37 @@ class AccessRightsOfStudentReviewerAPIViewTest(APITestCase):
         force_authenticate(self.request, user=self.reviewer.user)
         response = self.view(self.request)
         self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+
+class AccessRightsOfExamTypeAPIViewTest(APITestCase):
+    """ Tests who can access the exam list. The rational here is, that this
+    list contains information about what number of points was necessary to pass
+    the exam. There is no reason why anyone should see this information except
+    for their own module. """
+
+    @classmethod
+    def setUpTestData(cls):
+        cls.factory = APIRequestFactory()
+        cls.user_factory = GradyUserFactory()
+
+    def setUp(self):
+        self.student = self.user_factory.make_student()
+        self.tutor = self.user_factory.make_tutor()
+        self.reviewer = self.user_factory.make_reviewer()
+        self.request = self.factory.get(reverse('examtype-list'))
+        self.view = ExamApiViewSet.as_view({'get': 'list'})
+
+    def test_student_has_no_access(self):
+        force_authenticate(self.request, user=self.student.user)
+        response = self.view(self.request)
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+    def test_tutor_has_no_access(self):
+        force_authenticate(self.request, user=self.tutor.user)
+        response = self.view(self.request)
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+    def test_reviewer_has_access(self):
+        force_authenticate(self.request, user=self.reviewer.user)
+        response = self.view(self.request)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
diff --git a/backend/core/tests/test_auth.py b/backend/core/tests/test_auth.py
index 85505585..d9af01e8 100644
--- a/backend/core/tests/test_auth.py
+++ b/backend/core/tests/test_auth.py
@@ -4,10 +4,12 @@ from core.models import UserAccount
 
 
 class AuthTests(APITestCase):
+
     @classmethod
     def setUpTestData(cls):
         cls.credentials = {'username': 'user', 'password': 'p'}
-        cls.user = UserAccount.objects.create(username=cls.credentials['username'])
+        cls.user = UserAccount.objects.create(
+            username=cls.credentials['username'])
         cls.user.set_password(cls.credentials['password'])
         cls.user.save()
         cls.client = APIClient()
diff --git a/backend/core/tests/test_examlist.py b/backend/core/tests/test_examlist.py
index 67b3c84d..9443c9a5 100644
--- a/backend/core/tests/test_examlist.py
+++ b/backend/core/tests/test_examlist.py
@@ -19,7 +19,8 @@ class ExamListTest(APITestCase):
 
     def setUp(self):
         self.request = self.factory.get(reverse('examtype-list'))
-        force_authenticate(self.request, self.user_factory.make_student().user)
+        force_authenticate(self.request,
+                           self.user_factory.make_reviewer().user)
         self.view = ExamApiViewSet.as_view({'get': 'list'})
         self.response = self.view(self.request)
 
diff --git a/backend/core/tests/test_tutor_api_endpoints.py b/backend/core/tests/test_tutor_api_endpoints.py
index 6f65aade..deef555a 100644
--- a/backend/core/tests/test_tutor_api_endpoints.py
+++ b/backend/core/tests/test_tutor_api_endpoints.py
@@ -4,15 +4,12 @@
     * POST /tutor/:username/:email create a new tutor and email password
     * GET /tutorlist list of all tutors with their scores
 """
-import logging as log
-from unittest import skip
-
 from rest_framework import status
 from rest_framework.reverse import reverse
 from rest_framework.test import (APIClient, APIRequestFactory, APITestCase,
                                  force_authenticate)
 
-from core.models import Feedback, Reviewer, Tutor
+from core.models import Feedback, Tutor
 from core.views import TutorApiViewSet
 from util.factories import GradyUserFactory
 
diff --git a/backend/core/urls.py b/backend/core/urls.py
index a5d9818d..490d9f23 100644
--- a/backend/core/urls.py
+++ b/backend/core/urls.py
@@ -7,10 +7,10 @@ from core import views
 
 # Create a router and register our viewsets with it.
 router = DefaultRouter()
+router.register(r'student', views.StudentReviewerApiViewSet)
 router.register(r'examtype', views.ExamApiViewSet)
 router.register(r'submissiontype', views.SubmissionTypeApiView)
 router.register(r'tutor', views.TutorApiViewSet)
-router.register(r'student', views.StudentReviewerApiViewSet)
 router.register(r'student-page', views.StudentSelfApiViewSet, base_name='student_page')
 
 urlpatterns = [
diff --git a/backend/core/views.py b/backend/core/views.py
index 084a4a3e..a0ebac92 100644
--- a/backend/core/views.py
+++ b/backend/core/views.py
@@ -1,8 +1,6 @@
 """ All API views that are used to retrieve data from the database. They
 can be categorized by the permissions they require. All views require a
 user to be authenticated and most are only accessible by one user group """
-import logging
-
 from rest_framework import mixins, viewsets
 
 from core.models import ExamType, Student, SubmissionType, Tutor
@@ -11,8 +9,6 @@ from core.serializers import (ExamSerializer, StudentSerializer,
                               StudentSerializerForListView,
                               SubmissionTypeSerializer, TutorSerializer)
 
-log = logging.getLogger(__name__)
-
 
 class StudentSelfApiViewSet(viewsets.ReadOnlyModelViewSet):
     """ Gets all data that belongs to one student """
@@ -28,7 +24,7 @@ class StudentSelfApiViewSet(viewsets.ReadOnlyModelViewSet):
 
 class ExamApiViewSet(viewsets.ReadOnlyModelViewSet):
     """ Gets a list of an individual exam by Id if provided """
-    permissions_classes = (IsReviewer,)
+    permission_classes = (IsReviewer,)
     queryset = ExamType.objects.all()
     serializer_class = ExamSerializer
 
@@ -39,7 +35,7 @@ class TutorApiViewSet(mixins.RetrieveModelMixin,
                       mixins.ListModelMixin,
                       viewsets.GenericViewSet):
     """ Api endpoint for creating, listing, viewing or deleteing tutors """
-    permissions_classes = (IsReviewer,)
+    permission_classes = (IsReviewer,)
     queryset = Tutor.objects.all()
     serializer_class = TutorSerializer
     lookup_field = 'user__username'
diff --git a/backend/grady/settings/default.py b/backend/grady/settings/default.py
index a4a5fd8e..f839d3ef 100644
--- a/backend/grady/settings/default.py
+++ b/backend/grady/settings/default.py
@@ -40,9 +40,9 @@ INSTALLED_APPS = [
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'django_extensions',
-    'core',
     'rest_framework',
     'corsheaders',
+    'core',
 ]
 
 MIDDLEWARE = [
@@ -119,8 +119,8 @@ GRAPH_MODELS = {
     'group_models': True,
 }
 
-LOGIN_REDIRECT_URL  = '/'
-LOGIN_URL           = '/'
+LOGIN_REDIRECT_URL = '/'
+LOGIN_URL = '/'
 
 
 MESSAGE_TAGS = {
@@ -131,9 +131,6 @@ MESSAGE_TAGS = {
     messages.ERROR: 'alert-danger',
 }
 
-COMPRESS_ENABLED = False
-COMPRESS_OFFLINE = True
-
 STATICFILES_FINDERS = (
     'django.contrib.staticfiles.finders.FileSystemFinder',
     'django.contrib.staticfiles.finders.AppDirectoriesFinder',
@@ -190,16 +187,16 @@ LOGGING = {
         'django': {
             'level': 'INFO',
             'class': 'logging.StreamHandler',
-            'formatter': 'django.server'
+            'formatter': 'core'
         },
-        'mail_admins': { # TODO: configuration
+        'mail_admins': {  # TODO: configuration
             'level': 'ERROR',
             'class': 'django.utils.log.AdminEmailHandler',
         }
     },
     'loggers': {
         'django': {
-            'handlers': ['django'],
+            'handlers': [],
         },
         'django.request': {
             'handlers': ['django'],
-- 
GitLab