diff --git a/apps/api/plane/authentication/views/app/email.py b/apps/api/plane/authentication/views/app/email.py index 0ac51265e0d..417e7b40eaa 100644 --- a/apps/api/plane/authentication/views/app/email.py +++ b/apps/api/plane/authentication/views/app/email.py @@ -1,6 +1,3 @@ -# Python imports -from urllib.parse import urlencode, urljoin - # Django imports from django.core.exceptions import ValidationError from django.core.validators import validate_email @@ -19,7 +16,7 @@ AuthenticationException, AUTHENTICATION_ERROR_CODES, ) -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class SignInAuthEndpoint(View): @@ -34,11 +31,11 @@ def post(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) # Base URL join - url = urljoin( - base_host(request=request, is_app=True), "sign-in?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -58,10 +55,10 @@ def post(self, request): ) params = exc.get_error_dict() # Next path - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "sign-in?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -76,10 +73,10 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "sign-in?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -92,10 +89,10 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "sign-in?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -112,19 +109,23 @@ def post(self, request): user_login(request=request, user=user, is_app=True) # Get the redirection path if next_path: - path = str(validate_next_path(next_path)) + path = next_path else: path = get_redirection_path(user=user) - # redirect to referer path - url = urljoin(base_host(request=request, is_app=True), path) + # Get the safe redirect URL + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=path, + params={}, + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "sign-in?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -141,10 +142,10 @@ def post(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -161,10 +162,10 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) # Validate the email @@ -179,10 +180,10 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -197,10 +198,10 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -217,17 +218,21 @@ def post(self, request): user_login(request=request, user=user, is_app=True) # Get the redirection path if next_path: - path = str(validate_next_path(next_path)) + path = next_path else: path = get_redirection_path(user=user) - # redirect to referer path - url = urljoin(base_host(request=request, is_app=True), path) + + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=path, + params={}, + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/app/github.py b/apps/api/plane/authentication/views/app/github.py index 18cbe7b6c7c..425f125499e 100644 --- a/apps/api/plane/authentication/views/app/github.py +++ b/apps/api/plane/authentication/views/app/github.py @@ -1,5 +1,5 @@ +# Python imports import uuid -from urllib.parse import urlencode, urljoin # Django import from django.http import HttpResponseRedirect @@ -16,8 +16,7 @@ AuthenticationException, AUTHENTICATION_ERROR_CODES, ) -from plane.utils.path_validator import validate_next_path - +from plane.utils.path_validator import get_safe_redirect_url class GitHubOauthInitiateEndpoint(View): def get(self, request): @@ -35,10 +34,10 @@ def get(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params ) return HttpResponseRedirect(url) try: @@ -49,10 +48,10 @@ def get(self, request): return HttpResponseRedirect(auth_url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params ) return HttpResponseRedirect(url) @@ -70,9 +69,11 @@ def get(self, request): error_message="GITHUB_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin(base_host, "?" + urlencode(params)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) if not code: @@ -81,9 +82,11 @@ def get(self, request): error_message="GITHUB_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin(base_host, "?" + urlencode(params)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -93,17 +96,23 @@ def get(self, request): user = provider.authenticate() # Login the user and record his device info user_login(request=request, user=user, is_app=True) - # Get the redirection path if next_path: - path = str(validate_next_path(next_path)) + path = next_path else: path = get_redirection_path(user=user) - # redirect to referer path - url = urljoin(base_host, path) + + # Get the safe redirect URL + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=path, + params={} + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin(base_host, "?" + urlencode(params)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/app/gitlab.py b/apps/api/plane/authentication/views/app/gitlab.py index d6479e95490..e22911d32e2 100644 --- a/apps/api/plane/authentication/views/app/gitlab.py +++ b/apps/api/plane/authentication/views/app/gitlab.py @@ -1,5 +1,5 @@ +# Python imports import uuid -from urllib.parse import urlencode, urljoin # Django import from django.http import HttpResponseRedirect @@ -16,7 +16,7 @@ AuthenticationException, AUTHENTICATION_ERROR_CODES, ) -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class GitLabOauthInitiateEndpoint(View): @@ -25,7 +25,7 @@ def get(self, request): request.session["host"] = base_host(request=request, is_app=True) next_path = request.GET.get("next_path") if next_path: - request.session["next_path"] = str(validate_next_path(next_path)) + request.session["next_path"] = str(next_path) # Check instance configuration instance = Instance.objects.first() @@ -35,10 +35,10 @@ def get(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params ) return HttpResponseRedirect(url) try: @@ -49,10 +49,10 @@ def get(self, request): return HttpResponseRedirect(auth_url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params ) return HttpResponseRedirect(url) @@ -70,9 +70,11 @@ def get(self, request): error_message="GITLAB_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(next_path) - url = urljoin(base_host, "?" + urlencode(params)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) if not code: @@ -81,9 +83,11 @@ def get(self, request): error_message="GITLAB_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin(base_host, "?" + urlencode(params)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -94,16 +98,23 @@ def get(self, request): # Login the user and record his device info user_login(request=request, user=user, is_app=True) # Get the redirection path + if next_path: - path = str(validate_next_path(next_path)) + path = next_path else: path = get_redirection_path(user=user) # redirect to referer path - url = urljoin(base_host, path) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=path, + params={} + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin(base_host, "?" + urlencode(params)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/app/google.py b/apps/api/plane/authentication/views/app/google.py index 66b6f7662d8..aa65fa7fb62 100644 --- a/apps/api/plane/authentication/views/app/google.py +++ b/apps/api/plane/authentication/views/app/google.py @@ -1,6 +1,5 @@ # Python imports import uuid -from urllib.parse import urlencode, urljoin # Django import from django.http import HttpResponseRedirect @@ -18,7 +17,7 @@ AuthenticationException, AUTHENTICATION_ERROR_CODES, ) -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class GoogleOauthInitiateEndpoint(View): @@ -36,10 +35,10 @@ def get(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params ) return HttpResponseRedirect(url) @@ -51,10 +50,10 @@ def get(self, request): return HttpResponseRedirect(auth_url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params ) return HttpResponseRedirect(url) @@ -72,9 +71,11 @@ def get(self, request): error_message="GOOGLE_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin(base_host, "?" + urlencode(params)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) if not code: exc = AuthenticationException( @@ -82,9 +83,11 @@ def get(self, request): error_message="GOOGLE_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin(base_host, "?" + urlencode(params)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: provider = GoogleOAuthProvider( @@ -94,15 +97,21 @@ def get(self, request): # Login the user and record his device info user_login(request=request, user=user, is_app=True) # Get the redirection path - path = get_redirection_path(user=user) - # redirect to referer path - url = urljoin( - base_host, str(validate_next_path(next_path)) if next_path else path + if next_path: + path = next_path + else: + path = get_redirection_path(user=user) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=path, + params={} ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin(base_host, "?" + urlencode(params)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/app/magic.py b/apps/api/plane/authentication/views/app/magic.py index 61591439db5..9be3693e55a 100644 --- a/apps/api/plane/authentication/views/app/magic.py +++ b/apps/api/plane/authentication/views/app/magic.py @@ -1,6 +1,3 @@ -# Python imports -from urllib.parse import urlencode, urljoin - # Django imports from django.core.validators import validate_email from django.http import HttpResponseRedirect @@ -26,7 +23,7 @@ AUTHENTICATION_ERROR_CODES, ) from plane.authentication.rate_limit import AuthenticationThrottle -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class MagicGenerateEndpoint(APIView): @@ -72,10 +69,10 @@ def post(self, request): error_message="MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "sign-in?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -88,10 +85,10 @@ def post(self, request): error_message="USER_DOES_NOT_EXIST", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "sign-in?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -117,15 +114,19 @@ def post(self, request): else str(get_redirection_path(user=user)) ) # redirect to referer path - url = urljoin(base_host(request=request, is_app=True), path) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=path, + params={}, + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "sign-in?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -145,10 +146,10 @@ def post(self, request): error_message="MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) # Existing user @@ -160,9 +161,11 @@ def post(self, request): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + params["next_path"] = str(next_path) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) @@ -178,18 +181,22 @@ def post(self, request): user_login(request=request, user=user, is_app=True) # Get the redirection path if next_path: - path = str(validate_next_path(next_path)) + path = next_path else: path = get_redirection_path(user=user) # redirect to referer path - url = urljoin(base_host(request=request, is_app=True), path) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=path, + params={}, + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = urljoin( - base_host(request=request, is_app=True), "?" + urlencode(params) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=params, ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/email.py b/apps/api/plane/authentication/views/space/email.py index 6fa2d451745..cd0954db836 100644 --- a/apps/api/plane/authentication/views/space/email.py +++ b/apps/api/plane/authentication/views/space/email.py @@ -1,6 +1,3 @@ -# Python imports -from urllib.parse import urlencode - # Django imports from django.core.exceptions import ValidationError from django.core.validators import validate_email @@ -17,7 +14,7 @@ AUTHENTICATION_ERROR_CODES, AuthenticationException, ) -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class SignInAuthSpaceEndpoint(View): @@ -32,9 +29,11 @@ def post(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) # set the referer as session to redirect after login @@ -51,9 +50,11 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) # Validate email @@ -67,9 +68,11 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) # Existing User @@ -82,9 +85,11 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -95,13 +100,19 @@ def post(self, request): # Login the user and record his device info user_login(request=request, user=user, is_space=True) # redirect to next path - url = f"{base_host(request=request, is_space=True)}{str(next_path) if next_path else ''}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params={} + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) @@ -117,9 +128,11 @@ def post(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) email = request.POST.get("email", False) @@ -135,9 +148,11 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) # Validate the email email = email.strip().lower() @@ -151,9 +166,11 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) # Existing User @@ -166,9 +183,11 @@ def post(self, request): payload={"email": str(email)}, ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -179,11 +198,17 @@ def post(self, request): # Login the user and record his device info user_login(request=request, user=user, is_space=True) # redirect to referer path - url = f"{base_host(request=request, is_space=True)}{str(next_path) if next_path else ''}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params={} + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/github.py b/apps/api/plane/authentication/views/space/github.py index fec71cb48b7..e3b64e8a0d1 100644 --- a/apps/api/plane/authentication/views/space/github.py +++ b/apps/api/plane/authentication/views/space/github.py @@ -1,6 +1,5 @@ # Python imports import uuid -from urllib.parse import urlencode # Django import from django.http import HttpResponseRedirect @@ -15,7 +14,7 @@ AUTHENTICATION_ERROR_CODES, AuthenticationException, ) -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class GitHubOauthInitiateSpaceEndpoint(View): @@ -23,9 +22,6 @@ def get(self, request): # Get host and next path request.session["host"] = base_host(request=request, is_space=True) next_path = request.GET.get("next_path") - if next_path: - request.session["next_path"] = str(next_path) - # Check instance configuration instance = Instance.objects.first() if instance is None or not instance.is_setup_done: @@ -34,9 +30,11 @@ def get(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -47,9 +45,11 @@ def get(self, request): return HttpResponseRedirect(auth_url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(next_path) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) @@ -66,9 +66,11 @@ def get(self, request): error_message="GITHUB_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) if not code: @@ -77,9 +79,11 @@ def get(self, request): error_message="GITHUB_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -89,11 +93,17 @@ def get(self, request): user_login(request=request, user=user, is_space=True) # Process workspace and project invitations # redirect to referer path - url = f"{base_host(request=request, is_space=True)}{str(next_path) if next_path else ''}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/gitlab.py b/apps/api/plane/authentication/views/space/gitlab.py index 4bdcf9514e2..a63466005f1 100644 --- a/apps/api/plane/authentication/views/space/gitlab.py +++ b/apps/api/plane/authentication/views/space/gitlab.py @@ -1,6 +1,5 @@ # Python imports import uuid -from urllib.parse import urlencode # Django import from django.http import HttpResponseRedirect @@ -15,7 +14,7 @@ AUTHENTICATION_ERROR_CODES, AuthenticationException, ) -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class GitLabOauthInitiateSpaceEndpoint(View): @@ -23,8 +22,6 @@ def get(self, request): # Get host and next path request.session["host"] = base_host(request=request, is_space=True) next_path = request.GET.get("next_path") - if next_path: - request.session["next_path"] = str(next_path) # Check instance configuration instance = Instance.objects.first() @@ -34,9 +31,11 @@ def get(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -47,9 +46,11 @@ def get(self, request): return HttpResponseRedirect(auth_url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(next_path) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) @@ -66,9 +67,11 @@ def get(self, request): error_message="GITLAB_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) if not code: @@ -77,9 +80,11 @@ def get(self, request): error_message="GITLAB_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -89,11 +94,17 @@ def get(self, request): user_login(request=request, user=user, is_space=True) # Process workspace and project invitations # redirect to referer path - url = f"{base_host(request=request, is_space=True)}{str(next_path) if next_path else ''}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/google.py b/apps/api/plane/authentication/views/space/google.py index 03ad9779357..7b9728762fa 100644 --- a/apps/api/plane/authentication/views/space/google.py +++ b/apps/api/plane/authentication/views/space/google.py @@ -1,6 +1,5 @@ # Python imports import uuid -from urllib.parse import urlencode # Django import from django.http import HttpResponseRedirect @@ -15,15 +14,13 @@ AuthenticationException, AUTHENTICATION_ERROR_CODES, ) -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class GoogleOauthInitiateSpaceEndpoint(View): def get(self, request): request.session["host"] = base_host(request=request, is_space=True) next_path = request.GET.get("next_path") - if next_path: - request.session["next_path"] = str(next_path) # Check instance configuration instance = Instance.objects.first() @@ -33,9 +30,11 @@ def get(self, request): error_message="INSTANCE_NOT_CONFIGURED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -46,9 +45,11 @@ def get(self, request): return HttpResponseRedirect(auth_url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) @@ -65,9 +66,11 @@ def get(self, request): error_message="GOOGLE_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) if not code: exc = AuthenticationException( @@ -75,9 +78,11 @@ def get(self, request): error_message="GOOGLE_OAUTH_PROVIDER_ERROR", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: provider = GoogleOAuthProvider(request=request, code=code) @@ -85,11 +90,17 @@ def get(self, request): # Login the user and record his device info user_login(request=request, user=user, is_space=True) # redirect to referer path - url = f"{base_host(request=request, is_space=True)}{str(next_path) if next_path else ''}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/magic.py b/apps/api/plane/authentication/views/space/magic.py index d230af7edf7..052a2118a16 100644 --- a/apps/api/plane/authentication/views/space/magic.py +++ b/apps/api/plane/authentication/views/space/magic.py @@ -1,6 +1,3 @@ -# Python imports -from urllib.parse import urlencode - # Django imports from django.core.validators import validate_email from django.http import HttpResponseRedirect @@ -23,7 +20,7 @@ AuthenticationException, AUTHENTICATION_ERROR_CODES, ) -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class MagicGenerateSpaceEndpoint(APIView): @@ -66,9 +63,11 @@ def post(self, request): error_message="MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) existing_user = User.objects.filter(email=email).first() @@ -79,9 +78,11 @@ def post(self, request): error_message="USER_DOES_NOT_EXIST", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) # Active User @@ -93,15 +94,19 @@ def post(self, request): # Login the user and record his device info user_login(request=request, user=user, is_space=True) # redirect to referer path - path = str(next_path) if next_path else "" - url = f"{base_host(request=request, is_space=True)}{path}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(next_path) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + base_url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path + ) + url = urljoin(base_url, "?" + urlencode(params)) return HttpResponseRedirect(url) @@ -120,9 +125,11 @@ def post(self, request): error_message="MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) # Existing User existing_user = User.objects.filter(email=email).first() @@ -133,9 +140,11 @@ def post(self, request): error_message="USER_ALREADY_EXIST", ) params = exc.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) try: @@ -146,12 +155,17 @@ def post(self, request): # Login the user and record his device info user_login(request=request, user=user, is_space=True) # redirect to referer path - url = f"{base_host(request=request, is_space=True)}{str(next_path) if next_path else ''}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path + ) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() - if next_path: - params["next_path"] = str(validate_next_path(next_path)) - url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=params + ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/authentication/views/space/signout.py b/apps/api/plane/authentication/views/space/signout.py index 11e617436e3..613f705ade0 100644 --- a/apps/api/plane/authentication/views/space/signout.py +++ b/apps/api/plane/authentication/views/space/signout.py @@ -7,7 +7,7 @@ # Module imports from plane.authentication.utils.host import base_host, user_ip from plane.db.models import User -from plane.utils.path_validator import validate_next_path +from plane.utils.path_validator import get_safe_redirect_url class SignOutAuthSpaceEndpoint(View): @@ -22,8 +22,14 @@ def post(self, request): user.save() # Log the user out logout(request) - url = f"{base_host(request=request, is_space=True)}{str(validate_next_path(next_path)) if next_path else ''}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path + ) return HttpResponseRedirect(url) except Exception: - url = f"{base_host(request=request, is_space=True)}{str(validate_next_path(next_path)) if next_path else ''}" + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path + ) return HttpResponseRedirect(url) diff --git a/apps/api/plane/license/api/views/admin.py b/apps/api/plane/license/api/views/admin.py index e1e38608276..3a9563e3bbc 100644 --- a/apps/api/plane/license/api/views/admin.py +++ b/apps/api/plane/license/api/views/admin.py @@ -34,6 +34,7 @@ AuthenticationException, ) from plane.utils.ip_address import get_client_ip +from plane.utils.path_validator import get_safe_redirect_url class InstanceAdminEndpoint(BaseAPIView): @@ -392,7 +393,14 @@ def post(self, request): user.save() # Log the user out logout(request) - url = urljoin(base_host(request=request, is_admin=True)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_admin=True), + next_path="" + ) return HttpResponseRedirect(url) except Exception: - return HttpResponseRedirect(base_host(request=request, is_admin=True)) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_admin=True), + next_path="" + ) + return HttpResponseRedirect(url) diff --git a/apps/api/plane/utils/path_validator.py b/apps/api/plane/utils/path_validator.py index aad28239feb..ebac7ca0bea 100644 --- a/apps/api/plane/utils/path_validator.py +++ b/apps/api/plane/utils/path_validator.py @@ -2,9 +2,55 @@ from urllib.parse import urlparse +def _contains_suspicious_patterns(path: str) -> bool: + """ + Check for suspicious patterns that might indicate malicious intent. + + Args: + path (str): The path to check + + Returns: + bool: True if suspicious patterns found, False otherwise + """ + suspicious_patterns = [ + r'javascript:', # JavaScript injection + r'data:', # Data URLs + r'vbscript:', # VBScript injection + r'file:', # File protocol + r'ftp:', # FTP protocol + r'%2e%2e', # URL encoded path traversal + r'%2f%2f', # URL encoded double slash + r'%5c%5c', # URL encoded backslashes + r'