Commit c2c2f7d4 authored by Florent Chehab's avatar Florent Chehab

enhance(request security):

* Updated all DRF custom permissions to make sure they have has_permission and has_object_permission
* Changed default permission
* Updated middleware to filter incomming request based on their type
* Added test for this

Closes #118
parent 7dc6e615
Pipeline #40469 passed with stages
in 3 minutes and 30 seconds
...@@ -9,19 +9,24 @@ from backend_app.utils import is_member ...@@ -9,19 +9,24 @@ from backend_app.utils import is_member
class IsAuthenticated(rf_IsAuthenticated): class IsAuthenticated(rf_IsAuthenticated):
pass def has_object_permission(self, request, view, obj):
return self.has_permission(request, view)
class IsStaff(IsAdminUser): class IsStaff(IsAdminUser):
pass def has_object_permission(self, request, view, obj):
return self.has_permission(request, view)
class IsDri(permissions.BasePermission): class IsDri(BasePermission):
""" """
Permission to make a viewset readonly unless the request user Permission to make a viewset readonly unless the request user
is a member of the DRI group. is a member of the DRI group.
""" """
def has_object_permission(self, request, view, obj):
return self.has_permission(request, view)
def has_permission(self, request, view): def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
return True return True
...@@ -44,29 +49,35 @@ class IsOwner(BasePermission): ...@@ -44,29 +49,35 @@ class IsOwner(BasePermission):
# For the user model # For the user model
return request.user == obj return request.user == obj
def has_permission(self, request, view):
return True
class NoDelete(BasePermission): class NoDelete(BasePermission):
""" """
Permission to prevent the use of the DELETE method. Permission to prevent the use of the DELETE method.
""" """
def has_permission(self, request, view): def has_object_permission(self, request, view, obj):
if request.method == "DELETE": return self.has_permission(request, view)
return False
return True def has_permission(self, request, view):
return request.method != "DELETE"
class NoPost(permissions.BasePermission): class NoPost(BasePermission):
""" """
Permission to disallow POST request Permission to disallow POST request
""" """
def has_object_permission(self, request, view, obj):
return self.has_permission(request, view)
def has_permission(self, request, view): def has_permission(self, request, view):
return request.method != "POST" return request.method != "POST"
class ReadOnly(permissions.BasePermission): class ReadOnly(BasePermission):
""" """
Permission to make a viewset read-only. Permission to make a viewset read-only.
""" """
......
from backend_app.permissions.app_permissions import NoDelete, IsAuthenticated, IsStaff from backend_app.permissions.app_permissions import IsAuthenticated
DEFAULT_VIEWSET_PERMISSIONS = IsAuthenticated & (IsStaff | NoDelete) DEFAULT_VIEWSET_PERMISSIONS = IsAuthenticated
from django.utils.deprecation import MiddlewareMixin from re import compile
from django.http import HttpResponseRedirect
from django.conf import settings from django.conf import settings
from re import compile from django.http import HttpResponseRedirect, HttpResponse
from django.utils.deprecation import MiddlewareMixin
from rest_framework import permissions
EXEMPT_URLS = [] EXEMPT_URLS = []
if hasattr(settings, "LOGIN_EXEMPT_URLS"): if hasattr(settings, "LOGIN_EXEMPT_URLS"):
EXEMPT_URLS += [compile(str.lstrip("/")) for str in settings.LOGIN_EXEMPT_URLS] EXEMPT_URLS += [compile(str.lstrip("/")) for str in settings.LOGIN_EXEMPT_URLS]
AUTHORIZED_REQUEST_METHODS = list(permissions.SAFE_METHODS) + ["POST", "PUT", "DELETE"]
class LoginRequiredMiddleware(MiddlewareMixin): class RexDriRequestMiddleware(MiddlewareMixin):
""" """
Middleware that requires a user to be authenticated to view any page other This middleware performs different actions.
- It checks that the HTTP request method is authorized on the plateform.
- It requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings via a list of regular expressions in LOGIN_EXEMPT_URLS (which in settings via a list of regular expressions in LOGIN_EXEMPT_URLS (which
you can copy from your urls.py). you can copy from your urls.py).
...@@ -21,14 +29,20 @@ class LoginRequiredMiddleware(MiddlewareMixin): ...@@ -21,14 +29,20 @@ class LoginRequiredMiddleware(MiddlewareMixin):
""" """
def process_request(self, request): def process_request(self, request):
# Check that the request.method is authorized on the site
if request.method not in AUTHORIZED_REQUEST_METHODS:
return HttpResponse("Unauthorized", status=401)
assert hasattr( assert hasattr(
request, "user" request, "user"
), "The Login Required middleware\ ), "The RexDriRequestMiddleware\
requires authentication middleware to be installed. Edit your\ requires authentication middleware to be installed. Edit your\
MIDDLEWARE_CLASSES setting to insert\ MIDDLEWARE_CLASSES setting to insert\
'django.contrib.auth.middlware.AuthenticationMiddleware'. If that doesn't\ 'django.contrib.auth.middlware.AuthenticationMiddleware'. If that doesn't\
work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\ work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\
'django.core.context_processors.auth'." 'django.core.context_processors.auth'."
# If the user is not authenticated redirect him/her to the login page
if not request.user.is_authenticated: if not request.user.is_authenticated:
path = request.path_info.lstrip("/") path = request.path_info.lstrip("/")
if not any(m.match(path) for m in EXEMPT_URLS): if not any(m.match(path) for m in EXEMPT_URLS):
......
...@@ -121,7 +121,7 @@ MIDDLEWARE = [ ...@@ -121,7 +121,7 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"base_app.middleware.LoginRequiredMiddleware", "base_app.middleware.RexDriRequestMiddleware",
] ]
# #
......
from django.test import TestCase
from rest_framework.test import APIClient
class MiddlewareAuthorizedMethodsTestCase(TestCase):
"""
Test of REX-DRI middleware.
Part regarding authorized HTTP methods
"""
url = "/admin/login/"
def test_get_allowed(self):
client = APIClient()
response = client.get(self.url)
self.assertNotEqual(response.status_code, 201)
def test_patch_not_allowed(self):
client = APIClient()
response = client.patch(self.url)
self.assertEqual(response.status_code, 401)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment