mirror of
https://github.com/gosticks/plane.git
synced 2025-10-16 12:45:33 +00:00
[WEB-4943]: add url has allowed host or scheme for validating valid redirections (#7809)
* feat: enhance path validation and URL safety in path_validator.py * Added get_allowed_hosts function to retrieve allowed hosts from settings. * Updated get_safe_redirect_url to validate URLs against allowed hosts. * Improved URL construction logic for safer redirection handling. * feat: enhance URL validation in authentication views * Added url_has_allowed_host_and_scheme checks in SignUpAuthSpaceEndpoint and MagicSignInSpaceEndpoint for safer redirection. * Updated redirect logic to fallback to base host if the constructed URL is not allowed. * Improved overall URL safety and handling in authentication flows. * fix: improve host extraction in get_allowed_hosts function * Updated get_allowed_hosts to extract only the host from ADMIN_BASE_URL and SPACE_BASE_URL settings for better URL validation. * Enhanced overall safety and clarity in allowed hosts retrieval.
This commit is contained in:
parent
d521eab22f
commit
6d3d9e6df7
@ -3,6 +3,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.core.validators import validate_email
|
from django.core.validators import validate_email
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
from django.utils.http import url_has_allowed_host_and_scheme
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.authentication.provider.credentials.email import EmailProvider
|
from plane.authentication.provider.credentials.email import EmailProvider
|
||||||
@ -200,7 +201,10 @@ class SignUpAuthSpaceEndpoint(View):
|
|||||||
# redirect to referer path
|
# redirect to referer path
|
||||||
next_path = validate_next_path(next_path=next_path)
|
next_path = validate_next_path(next_path=next_path)
|
||||||
url = f"{base_host(request=request, is_space=True).rstrip("/")}{next_path}"
|
url = f"{base_host(request=request, is_space=True).rstrip("/")}{next_path}"
|
||||||
|
if url_has_allowed_host_and_scheme(url, allowed_hosts=get_allowed_hosts()):
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
else:
|
||||||
|
return HttpResponseRedirect(base_host(request=request, is_space=True))
|
||||||
except AuthenticationException as e:
|
except AuthenticationException as e:
|
||||||
params = e.get_error_dict()
|
params = e.get_error_dict()
|
||||||
url = get_safe_redirect_url(
|
url = get_safe_redirect_url(
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
from django.core.validators import validate_email
|
from django.core.validators import validate_email
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
from django.utils.http import url_has_allowed_host_and_scheme
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -20,7 +21,7 @@ from plane.authentication.adapter.error import (
|
|||||||
AuthenticationException,
|
AuthenticationException,
|
||||||
AUTHENTICATION_ERROR_CODES,
|
AUTHENTICATION_ERROR_CODES,
|
||||||
)
|
)
|
||||||
from plane.utils.path_validator import get_safe_redirect_url, validate_next_path
|
from plane.utils.path_validator import get_safe_redirect_url, validate_next_path, get_allowed_hosts
|
||||||
|
|
||||||
|
|
||||||
class MagicGenerateSpaceEndpoint(APIView):
|
class MagicGenerateSpaceEndpoint(APIView):
|
||||||
@ -96,7 +97,10 @@ class MagicSignInSpaceEndpoint(View):
|
|||||||
# redirect to referer path
|
# redirect to referer path
|
||||||
next_path = validate_next_path(next_path=next_path)
|
next_path = validate_next_path(next_path=next_path)
|
||||||
url = f"{base_host(request=request, is_space=True).rstrip("/")}{next_path}"
|
url = f"{base_host(request=request, is_space=True).rstrip("/")}{next_path}"
|
||||||
|
if url_has_allowed_host_and_scheme(url, allowed_hosts=get_allowed_hosts()):
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
else:
|
||||||
|
return HttpResponseRedirect(base_host(request=request, is_space=True))
|
||||||
|
|
||||||
except AuthenticationException as e:
|
except AuthenticationException as e:
|
||||||
params = e.get_error_dict()
|
params = e.get_error_dict()
|
||||||
@ -155,7 +159,10 @@ class MagicSignUpSpaceEndpoint(View):
|
|||||||
# redirect to referer path
|
# redirect to referer path
|
||||||
next_path = validate_next_path(next_path=next_path)
|
next_path = validate_next_path(next_path=next_path)
|
||||||
url = f"{base_host(request=request, is_space=True).rstrip("/")}{next_path}"
|
url = f"{base_host(request=request, is_space=True).rstrip("/")}{next_path}"
|
||||||
|
if url_has_allowed_host_and_scheme(url, allowed_hosts=get_allowed_hosts()):
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
else:
|
||||||
|
return HttpResponseRedirect(base_host(request=request, is_space=True))
|
||||||
|
|
||||||
except AuthenticationException as e:
|
except AuthenticationException as e:
|
||||||
params = e.get_error_dict()
|
params = e.get_error_dict()
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
|
# Django imports
|
||||||
|
from django.utils.http import url_has_allowed_host_and_scheme
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
# Python imports
|
# Python imports
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
def _contains_suspicious_patterns(path: str) -> bool:
|
def _contains_suspicious_patterns(path: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Check for suspicious patterns that might indicate malicious intent.
|
Check for suspicious patterns that might indicate malicious intent.
|
||||||
@ -38,6 +43,21 @@ def _contains_suspicious_patterns(path: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_allowed_hosts() -> list[str]:
|
||||||
|
"""Get the allowed hosts from the settings."""
|
||||||
|
base_origin = settings.WEB_URL or settings.APP_BASE_URL
|
||||||
|
allowed_hosts = [base_origin]
|
||||||
|
if settings.ADMIN_BASE_URL:
|
||||||
|
# Get only the host
|
||||||
|
host = urlparse(settings.ADMIN_BASE_URL).netloc
|
||||||
|
allowed_hosts.append(host)
|
||||||
|
if settings.SPACE_BASE_URL:
|
||||||
|
# Get only the host
|
||||||
|
host = urlparse(settings.SPACE_BASE_URL).netloc
|
||||||
|
allowed_hosts.append(host)
|
||||||
|
return allowed_hosts
|
||||||
|
|
||||||
|
|
||||||
def validate_next_path(next_path: str) -> str:
|
def validate_next_path(next_path: str) -> str:
|
||||||
"""Validates that next_path is a safe relative path for redirection."""
|
"""Validates that next_path is a safe relative path for redirection."""
|
||||||
# Browsers interpret backslashes as forward slashes. Remove all backslashes.
|
# Browsers interpret backslashes as forward slashes. Remove all backslashes.
|
||||||
@ -92,7 +112,14 @@ def get_safe_redirect_url(base_url: str, next_path: str = "", params: dict = {})
|
|||||||
base_url = base_url.rstrip('/')
|
base_url = base_url.rstrip('/')
|
||||||
if params:
|
if params:
|
||||||
encoded_params = urlencode(params)
|
encoded_params = urlencode(params)
|
||||||
return f"{base_url}/?next_path={validated_path}&{encoded_params}"
|
url = f"{base_url}/?next_path={validated_path}&{encoded_params}"
|
||||||
|
else:
|
||||||
|
url = f"{base_url}/?next_path={validated_path}"
|
||||||
|
|
||||||
return f"{base_url}/?next_path={validated_path}"
|
# Check if the URL is allowed
|
||||||
|
if url_has_allowed_host_and_scheme(url, allowed_hosts=get_allowed_hosts()):
|
||||||
|
return url
|
||||||
|
|
||||||
|
# Return the base URL if the URL is not allowed
|
||||||
|
return base_url
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user