diff --git a/apps/api/plane/authentication/views/space/email.py b/apps/api/plane/authentication/views/space/email.py index 2fb1c2c5e..b25e2d015 100644 --- a/apps/api/plane/authentication/views/space/email.py +++ b/apps/api/plane/authentication/views/space/email.py @@ -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: diff --git a/apps/api/plane/authentication/views/space/github.py b/apps/api/plane/authentication/views/space/github.py index dd148b8c1..acc956cf3 100644 --- a/apps/api/plane/authentication/views/space/github.py +++ b/apps/api/plane/authentication/views/space/github.py @@ -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( diff --git a/apps/api/plane/authentication/views/space/gitlab.py b/apps/api/plane/authentication/views/space/gitlab.py index 77a10a914..493cd823e 100644 --- a/apps/api/plane/authentication/views/space/gitlab.py +++ b/apps/api/plane/authentication/views/space/gitlab.py @@ -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( diff --git a/apps/api/plane/authentication/views/space/google.py b/apps/api/plane/authentication/views/space/google.py index d8fef9da4..c7c833771 100644 --- a/apps/api/plane/authentication/views/space/google.py +++ b/apps/api/plane/authentication/views/space/google.py @@ -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( diff --git a/apps/api/plane/authentication/views/space/magic.py b/apps/api/plane/authentication/views/space/magic.py index 85e3a185c..5052d40c0 100644 --- a/apps/api/plane/authentication/views/space/magic.py +++ b/apps/api/plane/authentication/views/space/magic.py @@ -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: diff --git a/apps/api/plane/utils/path_validator.py b/apps/api/plane/utils/path_validator.py index a89c8b969..ccb67d868 100644 --- a/apps/api/plane/utils/path_validator.py +++ b/apps/api/plane/utils/path_validator.py @@ -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 - \ No newline at end of file + return base_url + (f"?{encoded_params}" if encoded_params else "") \ No newline at end of file