Make HSLan always authenticated for GET

pull/1/head
palid 2023-07-17 21:35:45 +02:00
parent 30c3c3eb7a
commit 154e1079da
Signed by: palid
SSH Key Fingerprint: SHA256:Mus3wCd2x6nxtARI0DpWGT7lIWbNy3R90BVDg0j35PI
3 changed files with 93 additions and 24 deletions

View File

@ -34,7 +34,7 @@ DEBUG = not PROD
ALLOWED_HOSTS = env( ALLOWED_HOSTS = env(
"ALLOWED_HOSTS", "ALLOWED_HOSTS",
"devinventory,inventory.waw.hackerspace.pl,inventory.hackerspace.pl,i,inventory" "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(",") ).split(",")
LOGIN_REDIRECT_URL = "/admin/" LOGIN_REDIRECT_URL = "/admin/"
@ -102,7 +102,7 @@ DATABASES = {
"NAME": env("DB_NAME", "postgres"), "NAME": env("DB_NAME", "postgres"),
"USER": env("DB_USER", "postgres"), "USER": env("DB_USER", "postgres"),
"PASSWORD": env("DB_PASSWORD", None), "PASSWORD": env("DB_PASSWORD", None),
"HOST": env("DB_HOST", "db"), "HOST": env("DB_HOST", "127.0.0.1"),
"PORT": env("DB_PORT", 5432), "PORT": env("DB_PORT", 5432),
} }
} }
@ -171,12 +171,10 @@ REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions, # Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users. # or allow read-only access for unauthenticated users.
"DEFAULT_PERMISSION_CLASSES": [ "DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated", "rest_framework.permissions.IsAuthenticatedOrReadOnly",
], ],
"DEFAULT_AUTHENTICATION_CLASSES": [ "DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.BasicAuthentication", "storage.authentication.LanAuthentication",
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
], ],
} }
@ -188,3 +186,6 @@ SOCIAL_AUTH_JSONFIELD_ENABLED = True
LABEL_API = env("LABEL_API", "http://label.waw.hackerspace.pl:4567") LABEL_API = env("LABEL_API", "http://label.waw.hackerspace.pl:4567")
LOGIN_URL = "/admin/login/" 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"]

View File

@ -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.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from storage.authentication import LanAuthentication
from rest_framework.permissions import AllowAny
from storage.models import Item, Label from storage.models import Item, Label
from storage.serializers import ItemSerializer, LabelSerializer from storage.serializers import ItemSerializer, LabelSerializer
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404
from storage.views import apply_smart_search from storage.views import apply_smart_search
@ -40,7 +38,10 @@ class LabelViewSet(viewsets.ModelViewSet):
queryset = Label.objects.all() queryset = Label.objects.all()
serializer_class = LabelSerializer serializer_class = LabelSerializer
@action(detail=True, methods=["post"], permission_classes=[AllowAny]) @action(
detail=True,
methods=["post"],
)
def print(self, request, pk): def print(self, request, pk):
return api_print(request.query_params.get("quantity", 1), self.get_object()) return api_print(request.query_params.get("quantity", 1), self.get_object())
@ -77,40 +78,35 @@ class ItemViewSet(viewsets.ModelViewSet):
except Label.DoesNotExist: except Label.DoesNotExist:
raise Http404() raise Http404()
@action(detail=True, methods=["post"], permission_classes=[AllowAny]) @action(
detail=True,
methods=["post"],
)
def print(self, request, pk): def print(self, request, pk):
return api_print(request.query_params.get("quantity", 1), self.get_object()) return api_print(request.query_params.get("quantity", 1), self.get_object())
@action( @action(detail=True, authentication_classes=[LanAuthentication])
detail=True,
)
def children(self, request, pk): def children(self, request, pk):
item = self.get_object() item = self.get_object()
return Response( return Response(
self.serializer_class(item.get_children().all(), many=True).data self.serializer_class(item.get_children().all(), many=True).data
) )
@action( @action(detail=True, authentication_classes=[LanAuthentication])
detail=True,
)
def ancestors(self, request, pk): def ancestors(self, request, pk):
item = self.get_object() item = self.get_object()
return Response( return Response(
self.serializer_class(item.get_ancestors().all(), many=True).data self.serializer_class(item.get_ancestors().all(), many=True).data
) )
@action( @action(detail=True, authentication_classes=[LanAuthentication])
detail=True,
)
def descendants(self, request, pk): def descendants(self, request, pk):
item = self.get_object() item = self.get_object()
return Response( return Response(
self.serializer_class(item.get_descendants().all(), many=True).data self.serializer_class(item.get_descendants().all(), many=True).data
) )
@action( @action(detail=True, authentication_classes=[LanAuthentication])
detail=True,
)
def siblings(self, request, pk): def siblings(self, request, pk):
item = self.get_object() item = self.get_object()
return Response( return Response(

72
storage/authentication.py Normal file
View File

@ -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