From 154e1079dad01294267c1b6836ec7f45664579ee Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Mon, 17 Jul 2023 21:35:45 +0200 Subject: [PATCH] Make HSLan always authenticated for GET --- spejstore/settings.py | 13 +++---- storage/apiviews.py | 32 ++++++++--------- storage/authentication.py | 72 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 storage/authentication.py diff --git a/spejstore/settings.py b/spejstore/settings.py index 2386909..97094b1 100644 --- a/spejstore/settings.py +++ b/spejstore/settings.py @@ -34,7 +34,7 @@ DEBUG = not PROD ALLOWED_HOSTS = env( "ALLOWED_HOSTS", "devinventory,inventory.waw.hackerspace.pl,inventory.hackerspace.pl,i,inventory" - + (",127.0.0.1" if not PROD else ""), + + (",127.0.0.1,locahost,*" if not PROD else ""), ).split(",") LOGIN_REDIRECT_URL = "/admin/" @@ -102,7 +102,7 @@ DATABASES = { "NAME": env("DB_NAME", "postgres"), "USER": env("DB_USER", "postgres"), "PASSWORD": env("DB_PASSWORD", None), - "HOST": env("DB_HOST", "db"), + "HOST": env("DB_HOST", "127.0.0.1"), "PORT": env("DB_PORT", 5432), } } @@ -171,12 +171,10 @@ REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. "DEFAULT_PERMISSION_CLASSES": [ - "rest_framework.permissions.IsAuthenticated", + "rest_framework.permissions.IsAuthenticatedOrReadOnly", ], "DEFAULT_AUTHENTICATION_CLASSES": [ - "rest_framework.authentication.BasicAuthentication", - "rest_framework.authentication.SessionAuthentication", - "rest_framework.authentication.TokenAuthentication", + "storage.authentication.LanAuthentication", ], } @@ -188,3 +186,6 @@ SOCIAL_AUTH_JSONFIELD_ENABLED = True LABEL_API = env("LABEL_API", "http://label.waw.hackerspace.pl:4567") LOGIN_URL = "/admin/login/" +LAN_ALLOWED_ADDRES_SPACE = "10.8.0.0/16" +LAN_ALLOWED_HEADER = "X-LAN-ALLOWED" +PROXY_TRUSTED_IPS = ["172.21.37.1"] diff --git a/storage/apiviews.py b/storage/apiviews.py index 6e632e8..b7fb54d 100644 --- a/storage/apiviews.py +++ b/storage/apiviews.py @@ -1,13 +1,11 @@ -from rest_framework import viewsets, generics, filters +from rest_framework import viewsets, filters from rest_framework.response import Response from rest_framework.decorators import action - -from rest_framework.permissions import AllowAny +from storage.authentication import LanAuthentication from storage.models import Item, Label from storage.serializers import ItemSerializer, LabelSerializer from django.http import Http404 -from django.shortcuts import get_object_or_404 from storage.views import apply_smart_search @@ -40,7 +38,10 @@ class LabelViewSet(viewsets.ModelViewSet): queryset = Label.objects.all() serializer_class = LabelSerializer - @action(detail=True, methods=["post"], permission_classes=[AllowAny]) + @action( + detail=True, + methods=["post"], + ) def print(self, request, pk): return api_print(request.query_params.get("quantity", 1), self.get_object()) @@ -77,40 +78,35 @@ class ItemViewSet(viewsets.ModelViewSet): except Label.DoesNotExist: raise Http404() - @action(detail=True, methods=["post"], permission_classes=[AllowAny]) + @action( + detail=True, + methods=["post"], + ) def print(self, request, pk): return api_print(request.query_params.get("quantity", 1), self.get_object()) - @action( - detail=True, - ) + @action(detail=True, authentication_classes=[LanAuthentication]) def children(self, request, pk): item = self.get_object() return Response( self.serializer_class(item.get_children().all(), many=True).data ) - @action( - detail=True, - ) + @action(detail=True, authentication_classes=[LanAuthentication]) def ancestors(self, request, pk): item = self.get_object() return Response( self.serializer_class(item.get_ancestors().all(), many=True).data ) - @action( - detail=True, - ) + @action(detail=True, authentication_classes=[LanAuthentication]) def descendants(self, request, pk): item = self.get_object() return Response( self.serializer_class(item.get_descendants().all(), many=True).data ) - @action( - detail=True, - ) + @action(detail=True, authentication_classes=[LanAuthentication]) def siblings(self, request, pk): item = self.get_object() return Response( diff --git a/storage/authentication.py b/storage/authentication.py new file mode 100644 index 0000000..81f5ebe --- /dev/null +++ b/storage/authentication.py @@ -0,0 +1,72 @@ +import ipaddress +from rest_framework import exceptions + +from rest_framework.authentication import BaseAuthentication +from spejstore.settings import ( + LAN_ALLOWED_ADDRES_SPACE, + LAN_ALLOWED_HEADER, + PROD, + PROXY_TRUSTED_IPS, +) + + +headers_to_check_for_ip = [ + "HTTP_X_FORWARDED_FOR", + "X_FORWARDED_FOR", + "HTTP_CLIENT_IP", + "HTTP_X_REAL_IP", + "HTTP_X_FORWARDED", + "HTTP_X_CLUSTER_CLIENT_IP", + "HTTP_FORWARDED_FOR", + "HTTP_FORWARDED", + "HTTP_VIA", +] + + +def get_request_meta(request, key): + value = request.META.get(key, request).strip() + if value == "": + return None + return value + + +def get_ip_from_request(request): + for header in headers_to_check_for_ip: + ip = get_request_meta(request, header) + if not ip: + ip = get_request_meta(request, header.replace("_", "-")) + if ip: + return ip + return None + + +class LanAuthentication(BaseAuthentication): + def authenticate(self, request): + is_authorized = self.has_permission(request) + if is_authorized: + user = getattr(request._request, "user", None) + return (user, "authorized") + else: + raise exceptions.AuthenticationFailed( + "Unauthorized: not in subnet of " + LAN_ALLOWED_ADDRES_SPACE + ) + + def authenticate_header(self, request): + return LAN_ALLOWED_HEADER + + def has_permission(self, request): + if PROD: + client_ip = get_ip_from_request(request) + if client_ip is None: + raise exceptions.AuthenticationFailed("Unauthorized: no ip detected?") + # Make sure that we need to check PROXY_TRUSTED_IPS here + if len(PROXY_TRUSTED_IPS) > 0: + if request.META["REMOTE_ADDR"] not in PROXY_TRUSTED_IPS: + raise exceptions.AuthenticationFailed( + "Unauthorized: request is not coming from the PROXY_TRUSTED_IPS machine" + ) + return ipaddress.IPv4Address(client_ip) in ipaddress.IPv4Network( + LAN_ALLOWED_ADDRES_SPACE + ) + else: + return True