2021-10-15 03:25:38 -05:00
|
|
|
"""
|
|
|
|
Custom django middleware.
|
|
|
|
"""
|
|
|
|
|
2022-03-17 18:38:50 -04:00
|
|
|
import base64
|
|
|
|
|
2022-03-08 09:27:45 -04:00
|
|
|
from django.conf import settings
|
2022-03-17 18:38:50 -04:00
|
|
|
from django.contrib.auth import authenticate
|
|
|
|
from django.http import HttpResponse, JsonResponse
|
2022-03-08 09:27:45 -04:00
|
|
|
from django.middleware.common import CommonMiddleware
|
2022-03-17 18:38:50 -04:00
|
|
|
from django.utils.deprecation import MiddlewareMixin
|
2022-03-08 09:27:45 -04:00
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
from peeringdb_server.context import current_request
|
2022-03-17 18:38:50 -04:00
|
|
|
from peeringdb_server.models import OrganizationAPIKey, UserAPIKey
|
|
|
|
from peeringdb_server.permissions import get_key_from_request
|
2021-07-07 17:57:04 -05:00
|
|
|
|
2022-04-19 12:45:02 -04:00
|
|
|
ERR_MULTI_AUTH = "Cannot authenticate through Authorization header while logged in. Please log out and try again."
|
|
|
|
|
2021-07-07 17:57:04 -05:00
|
|
|
|
|
|
|
class CurrentRequestContext:
|
|
|
|
|
|
|
|
"""
|
2021-10-15 03:25:38 -05:00
|
|
|
Middleware that sets the current request context.
|
2021-07-07 17:57:04 -05:00
|
|
|
|
2021-10-15 03:25:38 -05:00
|
|
|
This allows access to the current request from anywhere.
|
2021-07-07 17:57:04 -05:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, get_response):
|
|
|
|
self.get_response = get_response
|
|
|
|
|
|
|
|
def __call__(self, request):
|
|
|
|
with current_request(request):
|
|
|
|
return self.get_response(request)
|
2022-03-08 09:27:45 -04:00
|
|
|
|
|
|
|
|
2022-03-17 18:38:50 -04:00
|
|
|
class HttpResponseUnauthorized(HttpResponse):
|
|
|
|
status_code = 401
|
|
|
|
|
|
|
|
|
2022-03-08 09:27:45 -04:00
|
|
|
class PDBCommonMiddleware(CommonMiddleware):
|
|
|
|
def has_subdomain(self, request):
|
|
|
|
# Check if the request has a subdomain and does not start with www
|
|
|
|
host = request.get_host()
|
|
|
|
if host.startswith("www.") or (len(host.split(".")) > 2):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def process_request(self, request):
|
|
|
|
must_prepend = settings.PDB_PREPEND_WWW and not self.has_subdomain(request)
|
|
|
|
redirect_url = (
|
|
|
|
("%s://www.%s" % (request.scheme, request.get_host()))
|
|
|
|
if must_prepend
|
|
|
|
else ""
|
|
|
|
)
|
|
|
|
# Check if a slash should be appended
|
|
|
|
if self.should_redirect_with_slash(request):
|
|
|
|
path = self.get_full_path_with_slash(request)
|
|
|
|
else:
|
|
|
|
path = request.get_full_path()
|
|
|
|
|
|
|
|
# Return a redirect if necessary
|
|
|
|
|
|
|
|
if redirect_url or path != request.get_full_path():
|
|
|
|
redirect_url += path
|
|
|
|
return self.response_redirect_class(redirect_url)
|
2022-03-17 18:38:50 -04:00
|
|
|
|
|
|
|
|
|
|
|
class PDBPermissionMiddleware(MiddlewareMixin):
|
|
|
|
|
|
|
|
"""
|
|
|
|
Middleware that checks if the current user has the correct permissions
|
|
|
|
to access the requested resource.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get_username_and_password(self, http_auth):
|
|
|
|
"""
|
|
|
|
Get the username and password from the HTTP auth header.
|
|
|
|
"""
|
|
|
|
# Check if the HTTP auth header is valid.
|
|
|
|
if http_auth.startswith("Basic "):
|
|
|
|
# Get the HTTP auth header without the "Basic " prefix.
|
|
|
|
http_auth = http_auth[6:]
|
|
|
|
else:
|
|
|
|
# Return an empty tuple.
|
|
|
|
return tuple()
|
|
|
|
# Decode the HTTP auth header.
|
|
|
|
http_auth = base64.b64decode(http_auth).decode("utf-8")
|
|
|
|
# If username or password is empty return an empty tuple.
|
|
|
|
# Split the username and password from the HTTP auth header.
|
|
|
|
userpw = http_auth.split(":", 1)
|
|
|
|
|
|
|
|
return userpw
|
|
|
|
|
|
|
|
def response_unauthorized(self, request, status=None, message=None):
|
|
|
|
"""
|
|
|
|
Return a Unauthorized response.
|
|
|
|
"""
|
|
|
|
return JsonResponse({"meta": {"error": message}}, status=status)
|
|
|
|
|
|
|
|
def process_request(self, request):
|
|
|
|
|
|
|
|
http_auth = request.META.get("HTTP_AUTHORIZATION", None)
|
|
|
|
req_key = get_key_from_request(request)
|
|
|
|
api_key = None
|
|
|
|
|
2022-04-19 12:45:02 -04:00
|
|
|
# session auth already exists, set x-auth-id value and return
|
|
|
|
|
|
|
|
if request.user.is_authenticated:
|
|
|
|
request.auth_id = request.user.username
|
|
|
|
|
|
|
|
# request attempting to provide separate authentication while
|
|
|
|
# already authenticated through session cookie, fail with
|
|
|
|
# bad request
|
|
|
|
|
|
|
|
if req_key or http_auth:
|
|
|
|
return self.response_unauthorized(
|
|
|
|
request,
|
|
|
|
message=ERR_MULTI_AUTH,
|
|
|
|
status=400,
|
|
|
|
)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
2022-03-17 18:38:50 -04:00
|
|
|
# Check if HTTP auth is valid and if the request is made with basic auth.
|
2022-04-19 12:45:02 -04:00
|
|
|
|
2022-03-17 18:38:50 -04:00
|
|
|
if http_auth and http_auth.startswith("Basic "):
|
2022-04-19 12:45:02 -04:00
|
|
|
|
2022-03-17 18:38:50 -04:00
|
|
|
# Get the username and password from the HTTP auth header.
|
|
|
|
username, password = self.get_username_and_password(http_auth)
|
|
|
|
# Check if the username and password are valid.
|
|
|
|
user = authenticate(username=username, password=password)
|
2022-04-19 12:45:02 -04:00
|
|
|
|
|
|
|
# return username input in x-auth-id header
|
|
|
|
request.auth_id = username
|
|
|
|
|
2022-03-17 18:38:50 -04:00
|
|
|
# if user is not authenticated return 401 Unauthorized
|
|
|
|
if not user:
|
2022-04-19 12:45:02 -04:00
|
|
|
|
|
|
|
# truncate the username if needed.
|
|
|
|
if len(username) > 255:
|
|
|
|
request.auth_id = username[:255]
|
|
|
|
|
2022-03-17 18:38:50 -04:00
|
|
|
return self.response_unauthorized(
|
|
|
|
request, message="Invalid username or password", status=401
|
|
|
|
)
|
|
|
|
|
|
|
|
# Check API keys
|
|
|
|
if req_key:
|
|
|
|
try:
|
|
|
|
api_key = OrganizationAPIKey.objects.get_from_key(req_key)
|
|
|
|
|
|
|
|
except OrganizationAPIKey.DoesNotExist:
|
|
|
|
pass
|
|
|
|
|
|
|
|
try:
|
|
|
|
api_key = UserAPIKey.objects.get_from_key(req_key)
|
|
|
|
|
|
|
|
except UserAPIKey.DoesNotExist:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# If api key is not valid return 401 Unauthorized
|
|
|
|
if not api_key:
|
2022-04-12 16:39:19 -04:00
|
|
|
if len(req_key) > 16:
|
2022-04-19 12:45:02 -04:00
|
|
|
req_key = req_key[:16]
|
|
|
|
request.auth_id = f"apikey_{req_key}"
|
2022-03-17 18:38:50 -04:00
|
|
|
return self.response_unauthorized(
|
|
|
|
request, message="Invalid API key", status=401
|
|
|
|
)
|
|
|
|
|
|
|
|
# If API key is provided, check if the user has an active session
|
|
|
|
if api_key:
|
2022-04-19 12:45:02 -04:00
|
|
|
request.auth_id = f"apikey_{api_key.prefix}"
|
2022-03-17 18:38:50 -04:00
|
|
|
if request.session.get("_auth_user_id") and request.user.id:
|
|
|
|
if int(request.user.id) == int(
|
|
|
|
request.session.get("_auth_user_id")
|
|
|
|
):
|
|
|
|
|
|
|
|
return self.response_unauthorized(
|
|
|
|
request,
|
2022-04-19 12:45:02 -04:00
|
|
|
message=ERR_MULTI_AUTH,
|
2022-03-17 18:38:50 -04:00
|
|
|
status=400,
|
|
|
|
)
|
2022-04-12 16:39:19 -04:00
|
|
|
|
|
|
|
def process_response(self, request, response):
|
|
|
|
|
2022-04-19 12:45:02 -04:00
|
|
|
if hasattr(request, "auth_id"):
|
2022-04-12 16:39:19 -04:00
|
|
|
# Sanitizes the auth_id
|
2022-04-19 12:45:02 -04:00
|
|
|
request.auth_id = request.auth_id.replace(" ", "_")
|
2022-04-12 16:39:19 -04:00
|
|
|
# If auth_id ends with a 401 make sure is it limited to 16 bytes
|
2022-04-19 12:45:02 -04:00
|
|
|
if response.status_code == 401 and len(request.auth_id) > 16:
|
|
|
|
if not request.auth_id.startswith("apikey_"):
|
|
|
|
request.auth_id = request.auth_id[:16]
|
2022-04-12 16:39:19 -04:00
|
|
|
|
2022-04-19 12:45:02 -04:00
|
|
|
response["X-Auth-ID"] = request.auth_id
|
2022-04-12 16:39:19 -04:00
|
|
|
return response
|