[WEB-4943] refactor: enhance URL validation and redirection logic in authentication views (#7815)

* refactor: enhance URL validation and redirection logic in authentication views

* Updated authentication views (SignInAuthSpaceEndpoint, GitHubCallbackSpaceEndpoint, GitLabCallbackSpaceEndpoint, GoogleCallbackSpaceEndpoint, and MagicSignInSpaceEndpoint) to include url_has_allowed_host_and_scheme checks for safer redirection.
* Improved URL construction by ensuring proper formatting and fallback to base host when necessary.
* Added get_allowed_hosts function to path_validator.py for better host validation.

* refactor: improve comments and clean up code in path_validator.py

* Updated comments for clarity in the get_safe_redirect_url function.
* Removed unnecessary blank line to enhance
This commit is contained in:
Nikhil 2025-09-17 16:13:32 +05:30 committed by GitHub
parent 6d3d9e6df7
commit 3d06189723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 49 additions and 20 deletions

View File

@ -15,8 +15,7 @@ from plane.authentication.adapter.error import (
AUTHENTICATION_ERROR_CODES,
AuthenticationException,
)
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 SignInAuthSpaceEndpoint(View):
def post(self, request):
@ -200,7 +199,7 @@ class SignUpAuthSpaceEndpoint(View):
user_login(request=request, user=user, is_space=True)
# redirect to referer 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)
else:

View File

@ -4,6 +4,7 @@ import uuid
# Django import
from django.http import HttpResponseRedirect
from django.views import View
from django.utils.http import url_has_allowed_host_and_scheme
# Module imports
from plane.authentication.provider.oauth.github import GitHubOAuthProvider
@ -14,7 +15,7 @@ from plane.authentication.adapter.error import (
AUTHENTICATION_ERROR_CODES,
AuthenticationException,
)
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 GitHubOauthInitiateSpaceEndpoint(View):
@ -94,8 +95,11 @@ class GitHubCallbackSpaceEndpoint(View):
# Process workspace and project invitations
# redirect to referer path
next_path = validate_next_path(next_path=next_path)
url = f"{base_host(request=request, is_space=True).rstrip("/")}{next_path}"
return HttpResponseRedirect(url)
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)
else:
return HttpResponseRedirect(base_host(request=request, is_space=True))
except AuthenticationException as e:
params = e.get_error_dict()
url = get_safe_redirect_url(

View File

@ -4,6 +4,7 @@ import uuid
# Django import
from django.http import HttpResponseRedirect
from django.views import View
from django.utils.http import url_has_allowed_host_and_scheme
# Module imports
from plane.authentication.provider.oauth.gitlab import GitLabOAuthProvider
@ -14,7 +15,7 @@ from plane.authentication.adapter.error import (
AUTHENTICATION_ERROR_CODES,
AuthenticationException,
)
from plane.utils.path_validator import get_safe_redirect_url, validate_next_path
from plane.utils.path_validator import get_safe_redirect_url, get_allowed_hosts, validate_next_path
class GitLabOauthInitiateSpaceEndpoint(View):
@ -95,8 +96,11 @@ class GitLabCallbackSpaceEndpoint(View):
# Process workspace and project invitations
# redirect to referer path
next_path = validate_next_path(next_path=next_path)
url = f"{base_host(request=request, is_space=True).rstrip("/")}{next_path}"
return HttpResponseRedirect(url)
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)
else:
return HttpResponseRedirect(base_host(request=request, is_space=True))
except AuthenticationException as e:
params = e.get_error_dict()
url = get_safe_redirect_url(

View File

@ -4,6 +4,7 @@ import uuid
# Django import
from django.http import HttpResponseRedirect
from django.views import View
from django.utils.http import url_has_allowed_host_and_scheme
# Module imports
from plane.authentication.provider.oauth.google import GoogleOAuthProvider
@ -14,7 +15,7 @@ from plane.authentication.adapter.error import (
AuthenticationException,
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 GoogleOauthInitiateSpaceEndpoint(View):
@ -91,8 +92,11 @@ class GoogleCallbackSpaceEndpoint(View):
user_login(request=request, user=user, is_space=True)
# redirect to referer path
next_path = validate_next_path(next_path=next_path)
url = f"{base_host(request=request, is_space=True).rstrip("/")}{next_path}"
return HttpResponseRedirect(url)
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)
else:
return HttpResponseRedirect(base_host(request=request, is_space=True))
except AuthenticationException as e:
params = e.get_error_dict()
url = get_safe_redirect_url(

View File

@ -96,7 +96,7 @@ class MagicSignInSpaceEndpoint(View):
user_login(request=request, user=user, is_space=True)
# redirect to referer 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)
else:
@ -158,7 +158,7 @@ class MagicSignUpSpaceEndpoint(View):
user_login(request=request, user=user, is_space=True)
# redirect to referer 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)
else:

View File

@ -46,7 +46,11 @@ def _contains_suspicious_patterns(path: str) -> bool:
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]
allowed_hosts = []
if base_origin:
host = urlparse(base_origin).netloc
allowed_hosts.append(host)
if settings.ADMIN_BASE_URL:
# Get only the host
host = urlparse(settings.ADMIN_BASE_URL).netloc
@ -107,19 +111,33 @@ def get_safe_redirect_url(base_url: str, next_path: str = "", params: dict = {})
# Validate the next path
validated_path = validate_next_path(next_path)
# Add the next path to the parameters
base_url = base_url.rstrip('/')
# Prepare the query parameters
query_parts = []
encoded_params = ""
# Add the next path to the parameters
if validated_path:
query_parts.append(f"next_path={validated_path}")
# Add additional parameters
if params:
encoded_params = urlencode(params)
url = f"{base_url}/?next_path={validated_path}&{encoded_params}"
query_parts.append(encoded_params)
# Construct the url query string
if query_parts:
query_string = "&".join(query_parts)
url = f"{base_url}/?{query_string}"
else:
url = f"{base_url}/?next_path={validated_path}"
url = base_url
# 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
return base_url + (f"?{encoded_params}" if encoded_params else "")