From d9e3405f5ad3f2831ae97c9c55c805db914bbd48 Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 2 Apr 2025 23:09:27 +0530 Subject: [PATCH] [WEB-3700] chore: improve authentication redirections (#6836) * chore: update redirections to be from allowed hosts * chore: update redirection logic * chore: add web url in settings * chore: add next path validation * chore: update typings * chore: update typings * chore: update types --------- Co-authored-by: sriram veeraghanta --- apiserver/plane/api/views/cycle.py | 10 +++---- apiserver/plane/api/views/intake.py | 4 +-- apiserver/plane/api/views/issue.py | 5 ++-- apiserver/plane/api/views/module.py | 7 +++-- apiserver/plane/api/views/project.py | 8 +++--- apiserver/plane/app/views/cycle/base.py | 11 ++++---- apiserver/plane/app/views/cycle/issue.py | 6 ++--- apiserver/plane/app/views/intake/base.py | 7 ++--- apiserver/plane/app/views/issue/archive.py | 8 +++--- apiserver/plane/app/views/issue/attachment.py | 10 +++---- apiserver/plane/app/views/issue/base.py | 12 ++++----- apiserver/plane/app/views/issue/comment.py | 12 ++++----- apiserver/plane/app/views/issue/link.py | 8 +++--- apiserver/plane/app/views/issue/reaction.py | 6 ++--- apiserver/plane/app/views/issue/relation.py | 6 ++--- apiserver/plane/app/views/issue/sub_issue.py | 4 +-- apiserver/plane/app/views/module/base.py | 8 +++--- apiserver/plane/app/views/module/issue.py | 10 +++---- apiserver/plane/app/views/project/base.py | 8 +++--- apiserver/plane/app/views/project/invite.py | 4 +-- apiserver/plane/app/views/workspace/draft.py | 8 +++--- apiserver/plane/app/views/workspace/invite.py | 8 +++--- .../plane/authentication/adapter/base.py | 6 ++--- apiserver/plane/authentication/utils/host.py | 20 +++++++------- apiserver/plane/authentication/utils/login.py | 6 ++--- .../plane/authentication/views/app/email.py | 26 +++++++++---------- .../plane/authentication/views/app/github.py | 14 +++++----- .../plane/authentication/views/app/gitlab.py | 14 +++++----- .../plane/authentication/views/app/google.py | 14 +++++----- .../plane/authentication/views/app/magic.py | 18 ++++++------- .../views/app/password_management.py | 2 +- .../plane/authentication/views/space/email.py | 21 ++++++++------- .../authentication/views/space/github.py | 9 ++++--- .../authentication/views/space/gitlab.py | 9 ++++--- .../authentication/views/space/google.py | 11 ++++---- .../plane/authentication/views/space/magic.py | 16 ++++++------ .../views/space/password_management.py | 2 +- .../authentication/views/space/signout.py | 5 ++-- .../plane/bgtasks/magic_link_code_task.py | 2 +- apiserver/plane/license/api/views/admin.py | 5 ++-- .../plane/middleware/api_log_middleware.py | 5 ++-- apiserver/plane/settings/common.py | 4 +-- apiserver/plane/utils/host.py | 25 ++++++++++-------- apiserver/plane/utils/path_validator.py | 21 +++++++++++++++ deploy/selfhost/docker-compose.yml | 1 + 45 files changed, 230 insertions(+), 196 deletions(-) create mode 100644 apiserver/plane/utils/path_validator.py diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index e0f44f984..3288c1750 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -39,7 +39,7 @@ from plane.db.models import ( UserFavorite, ) from plane.utils.analytics_plot import burndown_plot - +from plane.utils.host import base_host from .base import BaseAPIView from plane.bgtasks.webhook_task import model_activity @@ -259,7 +259,7 @@ class CycleAPIEndpoint(BaseAPIView): current_instance=None, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -331,7 +331,7 @@ class CycleAPIEndpoint(BaseAPIView): current_instance=current_instance, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -702,7 +702,7 @@ class CycleIssueAPIEndpoint(BaseAPIView): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) # Return all Cycle Issues return Response( @@ -1176,7 +1176,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response({"message": "Success"}, status=status.HTTP_200_OK) diff --git a/apiserver/plane/api/views/intake.py b/apiserver/plane/api/views/intake.py index faefc3761..1aee7195e 100644 --- a/apiserver/plane/api/views/intake.py +++ b/apiserver/plane/api/views/intake.py @@ -18,7 +18,7 @@ from plane.api.serializers import IntakeIssueSerializer, IssueSerializer from plane.app.permissions import ProjectLitePermission from plane.bgtasks.issue_activities_task import issue_activity from plane.db.models import Intake, IntakeIssue, Issue, Project, ProjectMember, State - +from plane.utils.host import base_host from .base import BaseAPIView @@ -297,7 +297,7 @@ class IntakeIssueAPIEndpoint(BaseAPIView): current_instance=current_instance, epoch=int(timezone.now().timestamp()), notification=False, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), intake=str(intake_issue.id), ) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 9f9b189ae..e0aee3a20 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -56,6 +56,7 @@ from plane.db.models import ( from plane.settings.storage import S3Storage from plane.bgtasks.storage_metadata_task import get_asset_object_metadata from .base import BaseAPIView +from plane.utils.host import base_host class WorkspaceIssueAPIEndpoint(BaseAPIView): @@ -1048,7 +1049,7 @@ class IssueAttachmentEndpoint(BaseAPIView): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) # Get the storage metadata @@ -1108,7 +1109,7 @@ class IssueAttachmentEndpoint(BaseAPIView): current_instance=json.dumps(serializer.data, cls=DjangoJSONEncoder), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) # Update the attachment diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 9e4f4143c..9995bb806 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -33,6 +33,7 @@ from plane.db.models import ( from .base import BaseAPIView from plane.bgtasks.webhook_task import model_activity +from plane.utils.host import base_host class ModuleAPIEndpoint(BaseAPIView): @@ -174,7 +175,7 @@ class ModuleAPIEndpoint(BaseAPIView): current_instance=None, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) module = Module.objects.get(pk=serializer.data["id"]) serializer = ModuleSerializer(module) @@ -226,7 +227,7 @@ class ModuleAPIEndpoint(BaseAPIView): current_instance=current_instance, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_200_OK) @@ -280,6 +281,7 @@ class ModuleAPIEndpoint(BaseAPIView): project_id=str(project_id), current_instance=json.dumps({"module_name": str(module.name)}), epoch=int(timezone.now().timestamp()), + origin=base_host(request=request, is_app=True), ) module.delete() # Delete the module issues @@ -449,6 +451,7 @@ class ModuleIssueAPIEndpoint(BaseAPIView): } ), epoch=int(timezone.now().timestamp()), + origin=base_host(request=request, is_app=True), ) return Response( diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index e98f35d57..6c9f8073d 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -30,7 +30,7 @@ from plane.db.models import ( ) from plane.bgtasks.webhook_task import model_activity, webhook_activity from .base import BaseAPIView - +from plane.utils.host import base_host class ProjectAPIEndpoint(BaseAPIView): """Project Endpoints to create, update, list, retrieve and delete endpoint""" @@ -228,7 +228,7 @@ class ProjectAPIEndpoint(BaseAPIView): current_instance=None, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) serializer = ProjectSerializer(project) @@ -297,7 +297,7 @@ class ProjectAPIEndpoint(BaseAPIView): current_instance=current_instance, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) serializer = ProjectSerializer(project) @@ -334,7 +334,7 @@ class ProjectAPIEndpoint(BaseAPIView): new_value=None, actor_id=request.user.id, slug=slug, - current_site=request.META.get("HTTP_ORIGIN"), + current_site=base_host(request=request, is_app=True), event_id=project.id, old_identifier=None, new_identifier=None, diff --git a/apiserver/plane/app/views/cycle/base.py b/apiserver/plane/app/views/cycle/base.py index 45b6f94d8..e88acaf82 100644 --- a/apiserver/plane/app/views/cycle/base.py +++ b/apiserver/plane/app/views/cycle/base.py @@ -51,8 +51,7 @@ from plane.db.models import ( ) from plane.utils.analytics_plot import burndown_plot from plane.bgtasks.recent_visited_task import recent_visited_task - -# Module imports +from plane.utils.host import base_host from .. import BaseAPIView, BaseViewSet from plane.bgtasks.webhook_task import model_activity from plane.utils.timezone_converter import convert_to_utc, user_timezone_converter @@ -335,7 +334,7 @@ class CycleViewSet(BaseViewSet): current_instance=None, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(cycle, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -428,7 +427,7 @@ class CycleViewSet(BaseViewSet): current_instance=current_instance, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(cycle, status=status.HTTP_200_OK) @@ -541,7 +540,7 @@ class CycleViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) # TODO: Soft delete the cycle break the onetoone relationship with cycle issue cycle.delete() @@ -1080,7 +1079,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response({"message": "Success"}, status=status.HTTP_200_OK) diff --git a/apiserver/plane/app/views/cycle/issue.py b/apiserver/plane/app/views/cycle/issue.py index 6e131d428..9b9e1ad30 100644 --- a/apiserver/plane/app/views/cycle/issue.py +++ b/apiserver/plane/app/views/cycle/issue.py @@ -27,7 +27,7 @@ from plane.utils.issue_filters import issue_filters from plane.utils.order_queryset import order_issue_queryset from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator from plane.app.permissions import allow_permission, ROLE - +from plane.utils.host import base_host class CycleIssueViewSet(BaseViewSet): serializer_class = CycleIssueSerializer @@ -291,7 +291,7 @@ class CycleIssueViewSet(BaseViewSet): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response({"message": "success"}, status=status.HTTP_201_CREATED) @@ -317,7 +317,7 @@ class CycleIssueViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) cycle_issue.delete() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/intake/base.py b/apiserver/plane/app/views/intake/base.py index fb10bc002..9c97415b5 100644 --- a/apiserver/plane/app/views/intake/base.py +++ b/apiserver/plane/app/views/intake/base.py @@ -37,6 +37,7 @@ from plane.app.serializers import ( ) from plane.utils.issue_filters import issue_filters from plane.bgtasks.issue_activities_task import issue_activity +from plane.utils.host import base_host class IntakeViewSet(BaseViewSet): @@ -283,7 +284,7 @@ class IntakeIssueViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), intake=str(intake_issue.id), ) intake_issue = ( @@ -407,7 +408,7 @@ class IntakeIssueViewSet(BaseViewSet): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), intake=str(intake_issue.id), ) issue_serializer.save() @@ -467,7 +468,7 @@ class IntakeIssueViewSet(BaseViewSet): current_instance=current_instance, epoch=int(timezone.now().timestamp()), notification=False, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), intake=(intake_issue.id), ) diff --git a/apiserver/plane/app/views/issue/archive.py b/apiserver/plane/app/views/issue/archive.py index 4f1e357da..48b317c84 100644 --- a/apiserver/plane/app/views/issue/archive.py +++ b/apiserver/plane/app/views/issue/archive.py @@ -37,7 +37,7 @@ from plane.utils.order_queryset import order_issue_queryset from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator from plane.app.permissions import allow_permission, ROLE from plane.utils.error_codes import ERROR_CODES - +from plane.utils.host import base_host # Module imports from .. import BaseViewSet, BaseAPIView @@ -259,7 +259,7 @@ class IssueArchiveViewSet(BaseViewSet): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) issue.archived_at = timezone.now().date() issue.save() @@ -287,7 +287,7 @@ class IssueArchiveViewSet(BaseViewSet): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) issue.archived_at = None issue.save() @@ -333,7 +333,7 @@ class BulkArchiveIssuesEndpoint(BaseAPIView): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) issue.archived_at = timezone.now().date() bulk_archive_issues.append(issue) diff --git a/apiserver/plane/app/views/issue/attachment.py b/apiserver/plane/app/views/issue/attachment.py index d519a5269..0ff85572f 100644 --- a/apiserver/plane/app/views/issue/attachment.py +++ b/apiserver/plane/app/views/issue/attachment.py @@ -21,7 +21,7 @@ from plane.bgtasks.issue_activities_task import issue_activity from plane.app.permissions import allow_permission, ROLE from plane.settings.storage import S3Storage from plane.bgtasks.storage_metadata_task import get_asset_object_metadata - +from plane.utils.host import base_host class IssueAttachmentEndpoint(BaseAPIView): serializer_class = IssueAttachmentSerializer @@ -48,7 +48,7 @@ class IssueAttachmentEndpoint(BaseAPIView): current_instance=json.dumps(serializer.data, cls=DjangoJSONEncoder), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -67,7 +67,7 @@ class IssueAttachmentEndpoint(BaseAPIView): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(status=status.HTTP_204_NO_CONTENT) @@ -155,7 +155,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(status=status.HTTP_204_NO_CONTENT) @@ -213,7 +213,7 @@ class IssueAttachmentV2Endpoint(BaseAPIView): current_instance=json.dumps(serializer.data, cls=DjangoJSONEncoder), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) # Update the attachment diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index 498130538..d73288897 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -61,7 +61,7 @@ from plane.bgtasks.recent_visited_task import recent_visited_task from plane.utils.global_paginator import paginate from plane.bgtasks.webhook_task import model_activity from plane.bgtasks.issue_description_version_task import issue_description_version_task - +from plane.utils.host import base_host class IssueListEndpoint(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @@ -379,7 +379,7 @@ class IssueViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) issue = ( issue_queryset_grouper( @@ -429,7 +429,7 @@ class IssueViewSet(BaseViewSet): current_instance=None, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) # updated issue description version issue_description_version_task.delay( @@ -650,7 +650,7 @@ class IssueViewSet(BaseViewSet): current_instance=current_instance, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) model_activity.delay( model_name="issue", @@ -659,7 +659,7 @@ class IssueViewSet(BaseViewSet): current_instance=current_instance, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) # updated issue description version issue_description_version_task.delay( @@ -691,8 +691,8 @@ class IssueViewSet(BaseViewSet): current_instance={}, epoch=int(timezone.now().timestamp()), notification=True, + origin=base_host(request=request, is_app=True), subscriber=False, - origin=request.META.get("HTTP_ORIGIN"), ) return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/issue/comment.py b/apiserver/plane/app/views/issue/comment.py index f257683fe..2d81201c9 100644 --- a/apiserver/plane/app/views/issue/comment.py +++ b/apiserver/plane/app/views/issue/comment.py @@ -17,7 +17,7 @@ from plane.app.serializers import IssueCommentSerializer, CommentReactionSeriali from plane.app.permissions import allow_permission, ROLE from plane.db.models import IssueComment, ProjectMember, CommentReaction, Project, Issue from plane.bgtasks.issue_activities_task import issue_activity - +from plane.utils.host import base_host class IssueCommentViewSet(BaseViewSet): serializer_class = IssueCommentSerializer @@ -87,7 +87,7 @@ class IssueCommentViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -121,7 +121,7 @@ class IssueCommentViewSet(BaseViewSet): current_instance=current_instance, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -144,7 +144,7 @@ class IssueCommentViewSet(BaseViewSet): current_instance=current_instance, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(status=status.HTTP_204_NO_CONTENT) @@ -188,7 +188,7 @@ class CommentReactionViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -222,7 +222,7 @@ class CommentReactionViewSet(BaseViewSet): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) comment_reaction.delete() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/issue/link.py b/apiserver/plane/app/views/issue/link.py index 85faa8368..45cab8479 100644 --- a/apiserver/plane/app/views/issue/link.py +++ b/apiserver/plane/app/views/issue/link.py @@ -15,7 +15,7 @@ from plane.app.serializers import IssueLinkSerializer from plane.app.permissions import ProjectEntityPermission from plane.db.models import IssueLink from plane.bgtasks.issue_activities_task import issue_activity - +from plane.utils.host import base_host class IssueLinkViewSet(BaseViewSet): permission_classes = [ProjectEntityPermission] @@ -52,7 +52,7 @@ class IssueLinkViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -77,7 +77,7 @@ class IssueLinkViewSet(BaseViewSet): current_instance=current_instance, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -98,7 +98,7 @@ class IssueLinkViewSet(BaseViewSet): current_instance=current_instance, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) issue_link.delete() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/issue/reaction.py b/apiserver/plane/app/views/issue/reaction.py index 7fe53b456..b92970382 100644 --- a/apiserver/plane/app/views/issue/reaction.py +++ b/apiserver/plane/app/views/issue/reaction.py @@ -15,7 +15,7 @@ from plane.app.serializers import IssueReactionSerializer from plane.app.permissions import allow_permission, ROLE from plane.db.models import IssueReaction from plane.bgtasks.issue_activities_task import issue_activity - +from plane.utils.host import base_host class IssueReactionViewSet(BaseViewSet): serializer_class = IssueReactionSerializer @@ -53,7 +53,7 @@ class IssueReactionViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -78,7 +78,7 @@ class IssueReactionViewSet(BaseViewSet): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) issue_reaction.delete() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/issue/relation.py b/apiserver/plane/app/views/issue/relation.py index 4b50e4b03..0a8ffa2f9 100644 --- a/apiserver/plane/app/views/issue/relation.py +++ b/apiserver/plane/app/views/issue/relation.py @@ -27,7 +27,7 @@ from plane.db.models import ( ) from plane.bgtasks.issue_activities_task import issue_activity from plane.utils.issue_relation_mapper import get_actual_relation - +from plane.utils.host import base_host class IssueRelationViewSet(BaseViewSet): serializer_class = IssueRelationSerializer @@ -253,7 +253,7 @@ class IssueRelationViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) if relation_type in ["blocking", "start_after", "finish_after"]: @@ -290,6 +290,6 @@ class IssueRelationViewSet(BaseViewSet): current_instance=current_instance, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/issue/sub_issue.py b/apiserver/plane/app/views/issue/sub_issue.py index 19e2522d2..e9199ed04 100644 --- a/apiserver/plane/app/views/issue/sub_issue.py +++ b/apiserver/plane/app/views/issue/sub_issue.py @@ -22,7 +22,7 @@ from plane.db.models import Issue, IssueLink, FileAsset, CycleIssue from plane.bgtasks.issue_activities_task import issue_activity from plane.utils.timezone_converter import user_timezone_converter from collections import defaultdict - +from plane.utils.host import base_host class SubIssuesEndpoint(BaseAPIView): permission_classes = [ProjectEntityPermission] @@ -176,7 +176,7 @@ class SubIssuesEndpoint(BaseAPIView): current_instance=json.dumps({"parent": str(sub_issue_id)}), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) for sub_issue_id in sub_issue_ids ] diff --git a/apiserver/plane/app/views/module/base.py b/apiserver/plane/app/views/module/base.py index d9118de0a..62840f555 100644 --- a/apiserver/plane/app/views/module/base.py +++ b/apiserver/plane/app/views/module/base.py @@ -61,7 +61,7 @@ from plane.utils.timezone_converter import user_timezone_converter from plane.bgtasks.webhook_task import model_activity from .. import BaseAPIView, BaseViewSet from plane.bgtasks.recent_visited_task import recent_visited_task - +from plane.utils.host import base_host class ModuleViewSet(BaseViewSet): model = Module @@ -376,7 +376,7 @@ class ModuleViewSet(BaseViewSet): current_instance=None, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) datetime_fields = ["created_at", "updated_at"] module = user_timezone_converter( @@ -768,7 +768,7 @@ class ModuleViewSet(BaseViewSet): current_instance=current_instance, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) datetime_fields = ["created_at", "updated_at"] @@ -795,7 +795,7 @@ class ModuleViewSet(BaseViewSet): current_instance=json.dumps({"module_name": str(module.name)}), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) for issue in module_issues ] diff --git a/apiserver/plane/app/views/module/issue.py b/apiserver/plane/app/views/module/issue.py index 832e9a5cd..089d73ef9 100644 --- a/apiserver/plane/app/views/module/issue.py +++ b/apiserver/plane/app/views/module/issue.py @@ -34,7 +34,7 @@ from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPagina # Module imports from .. import BaseViewSet - +from plane.utils.host import base_host class ModuleIssueViewSet(BaseViewSet): serializer_class = ModuleIssueSerializer @@ -221,7 +221,7 @@ class ModuleIssueViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) for issue in issues ] @@ -261,7 +261,7 @@ class ModuleIssueViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) for module in modules ] @@ -284,7 +284,7 @@ class ModuleIssueViewSet(BaseViewSet): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) module_issue.delete() @@ -309,7 +309,7 @@ class ModuleIssueViewSet(BaseViewSet): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) module_issue.delete() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/project/base.py b/apiserver/plane/app/views/project/base.py index b4f0b3aad..f5089b44f 100644 --- a/apiserver/plane/app/views/project/base.py +++ b/apiserver/plane/app/views/project/base.py @@ -39,7 +39,7 @@ from plane.utils.cache import cache_response from plane.bgtasks.webhook_task import model_activity, webhook_activity from plane.bgtasks.recent_visited_task import recent_visited_task from plane.utils.exception_logger import log_exception - +from plane.utils.host import base_host class ProjectViewSet(BaseViewSet): serializer_class = ProjectListSerializer @@ -331,7 +331,7 @@ class ProjectViewSet(BaseViewSet): current_instance=None, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) serializer = ProjectListSerializer(project) @@ -409,7 +409,7 @@ class ProjectViewSet(BaseViewSet): current_instance=current_instance, actor_id=request.user.id, slug=slug, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) serializer = ProjectListSerializer(project) return Response(serializer.data, status=status.HTTP_200_OK) @@ -454,7 +454,7 @@ class ProjectViewSet(BaseViewSet): new_value=None, actor_id=request.user.id, slug=slug, - current_site=request.META.get("HTTP_ORIGIN"), + current_site=base_host(request=request, is_app=True), event_id=project.id, old_identifier=None, new_identifier=None, diff --git a/apiserver/plane/app/views/project/invite.py b/apiserver/plane/app/views/project/invite.py index 51eb997f6..72c0bae06 100644 --- a/apiserver/plane/app/views/project/invite.py +++ b/apiserver/plane/app/views/project/invite.py @@ -27,7 +27,7 @@ from plane.db.models import ( IssueUserProperty, ) from plane.db.models.project import ProjectNetwork - +from plane.utils.host import base_host class ProjectInvitationsViewset(BaseViewSet): serializer_class = ProjectMemberInviteSerializer @@ -99,7 +99,7 @@ class ProjectInvitationsViewset(BaseViewSet): project_invitations = ProjectMemberInvite.objects.bulk_create( project_invitations, batch_size=10, ignore_conflicts=True ) - current_site = request.META.get("HTTP_ORIGIN") + current_site = base_host(request=request, is_app=True) # Send invitations for invitation in project_invitations: diff --git a/apiserver/plane/app/views/workspace/draft.py b/apiserver/plane/app/views/workspace/draft.py index fa161cbab..9503781f1 100644 --- a/apiserver/plane/app/views/workspace/draft.py +++ b/apiserver/plane/app/views/workspace/draft.py @@ -36,7 +36,7 @@ from plane.db.models import ( from .. import BaseViewSet from plane.bgtasks.issue_activities_task import issue_activity from plane.utils.issue_filters import issue_filters - +from plane.utils.host import base_host class WorkspaceDraftIssueViewSet(BaseViewSet): model = DraftIssue @@ -241,7 +241,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) if request.data.get("cycle_id", None): @@ -270,7 +270,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): ), epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) if request.data.get("module_ids", []): @@ -300,7 +300,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): current_instance=None, epoch=int(timezone.now().timestamp()), notification=True, - origin=request.META.get("HTTP_ORIGIN"), + origin=base_host(request=request, is_app=True), ) for module in request.data.get("module_ids", []) ] diff --git a/apiserver/plane/app/views/workspace/invite.py b/apiserver/plane/app/views/workspace/invite.py index fd3f97c19..a60dd3fc9 100644 --- a/apiserver/plane/app/views/workspace/invite.py +++ b/apiserver/plane/app/views/workspace/invite.py @@ -7,7 +7,6 @@ import jwt from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import validate_email -from django.db.models import Count from django.utils import timezone # Third party modules @@ -26,7 +25,8 @@ from plane.bgtasks.event_tracking_task import workspace_invite_event from plane.bgtasks.workspace_invitation_task import workspace_invitation from plane.db.models import User, Workspace, WorkspaceMember, WorkspaceMemberInvite from plane.utils.cache import invalidate_cache, invalidate_cache_directly - +from plane.utils.host import base_host +from plane.utils.ip_address import get_client_ip from .. import BaseViewSet @@ -122,7 +122,7 @@ class WorkspaceInvitationsViewset(BaseViewSet): workspace_invitations, batch_size=10, ignore_conflicts=True ) - current_site = request.META.get("HTTP_ORIGIN") + current_site = base_host(request=request, is_app=True) # Send invitations for invitation in workspace_invitations: @@ -213,7 +213,7 @@ class WorkspaceJoinEndpoint(BaseAPIView): user=user.id if user is not None else None, email=email, user_agent=request.META.get("HTTP_USER_AGENT"), - ip=request.META.get("REMOTE_ADDR"), + ip=get_client_ip(request=request), event_name="MEMBER_ACCEPTED", accepted_from="EMAIL", ) diff --git a/apiserver/plane/authentication/adapter/base.py b/apiserver/plane/authentication/adapter/base.py index c7a8c43d3..f788dcb41 100644 --- a/apiserver/plane/authentication/adapter/base.py +++ b/apiserver/plane/authentication/adapter/base.py @@ -15,8 +15,8 @@ from plane.db.models import Profile, User, WorkspaceMemberInvite from plane.license.utils.instance_value import get_configuration_value from .error import AuthenticationException, AUTHENTICATION_ERROR_CODES from plane.bgtasks.user_activation_email_task import user_activation_email -from plane.authentication.utils.host import base_host - +from plane.utils.host import base_host +from plane.utils.ip_address import get_client_ip class Adapter: """Common interface for all auth providers""" @@ -108,7 +108,7 @@ class Adapter: user.last_login_medium = self.provider user.last_active = timezone.now() user.last_login_time = timezone.now() - user.last_login_ip = self.request.META.get("REMOTE_ADDR") + user.last_login_ip = get_client_ip(request=self.request) user.last_login_uagent = self.request.META.get("HTTP_USER_AGENT") user.token_updated_at = timezone.now() # If user is not active, send the activation email and set the user as active diff --git a/apiserver/plane/authentication/utils/host.py b/apiserver/plane/authentication/utils/host.py index 4046c1e20..c4625279c 100644 --- a/apiserver/plane/authentication/utils/host.py +++ b/apiserver/plane/authentication/utils/host.py @@ -1,18 +1,16 @@ -# Python imports -from urllib.parse import urlsplit - # Django imports from django.conf import settings +from django.http import HttpRequest +# Third party imports +from rest_framework.request import Request +# Module imports +from plane.utils.ip_address import get_client_ip -def base_host(request, is_admin=False, is_space=False, is_app=False): +def base_host(request: Request | HttpRequest, is_admin: bool = False, is_space: bool = False, is_app: bool = False) -> str: """Utility function to return host / origin from the request""" # Calculate the base origin from request - base_origin = str( - request.META.get("HTTP_ORIGIN") - or f"{urlsplit(request.META.get('HTTP_REFERER')).scheme}://{urlsplit(request.META.get('HTTP_REFERER')).netloc}" - or f"""{"https" if request.is_secure() else "http"}://{request.get_host()}""" - ) + base_origin = settings.WEB_URL or settings.APP_BASE_URL # Admin redirections if is_admin: @@ -38,5 +36,5 @@ def base_host(request, is_admin=False, is_space=False, is_app=False): return base_origin -def user_ip(request): - return str(request.META.get("REMOTE_ADDR")) +def user_ip(request: Request | HttpRequest) -> str: + return get_client_ip(request=request) diff --git a/apiserver/plane/authentication/utils/login.py b/apiserver/plane/authentication/utils/login.py index ba7f9d1e1..f8c0ed842 100644 --- a/apiserver/plane/authentication/utils/login.py +++ b/apiserver/plane/authentication/utils/login.py @@ -3,8 +3,8 @@ from django.contrib.auth import login from django.conf import settings # Module imports -from plane.authentication.utils.host import base_host - +from plane.utils.host import base_host +from plane.utils.ip_address import get_client_ip def user_login(request, user, is_app=False, is_admin=False, is_space=False): login(request=request, user=user) @@ -15,7 +15,7 @@ def user_login(request, user, is_app=False, is_admin=False, is_space=False): device_info = { "user_agent": request.META.get("HTTP_USER_AGENT", ""), - "ip_address": request.META.get("REMOTE_ADDR", ""), + "ip_address": get_client_ip(request=request), "domain": base_host( request=request, is_app=is_app, is_admin=is_admin, is_space=is_space ), diff --git a/apiserver/plane/authentication/views/app/email.py b/apiserver/plane/authentication/views/app/email.py index 805b273a1..7e91b21c1 100644 --- a/apiserver/plane/authentication/views/app/email.py +++ b/apiserver/plane/authentication/views/app/email.py @@ -19,7 +19,7 @@ from plane.authentication.adapter.error import ( AuthenticationException, AUTHENTICATION_ERROR_CODES, ) - +from plane.utils.path_validator import validate_next_path class SignInAuthEndpoint(View): def post(self, request): @@ -34,7 +34,7 @@ class SignInAuthEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(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) @@ -58,7 +58,7 @@ class SignInAuthEndpoint(View): params = exc.get_error_dict() # Next path if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "sign-in?" + urlencode(params) ) @@ -76,7 +76,7 @@ class SignInAuthEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "sign-in?" + urlencode(params) ) @@ -92,7 +92,7 @@ class SignInAuthEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "sign-in?" + urlencode(params) ) @@ -111,7 +111,7 @@ class SignInAuthEndpoint(View): user_login(request=request, user=user, is_app=True) # Get the redirection path if next_path: - path = str(next_path) + path = str(validate_next_path(next_path)) else: path = get_redirection_path(user=user) @@ -121,7 +121,7 @@ class SignInAuthEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "sign-in?" + urlencode(params) ) @@ -141,7 +141,7 @@ class SignUpAuthEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -161,7 +161,7 @@ class SignUpAuthEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -179,7 +179,7 @@ class SignUpAuthEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -197,7 +197,7 @@ class SignUpAuthEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -216,7 +216,7 @@ class SignUpAuthEndpoint(View): user_login(request=request, user=user, is_app=True) # Get the redirection path if next_path: - path = next_path + path = str(validate_next_path(next_path)) else: path = get_redirection_path(user=user) # redirect to referer path @@ -225,7 +225,7 @@ class SignUpAuthEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) diff --git a/apiserver/plane/authentication/views/app/github.py b/apiserver/plane/authentication/views/app/github.py index f1a15474c..f558bcd4b 100644 --- a/apiserver/plane/authentication/views/app/github.py +++ b/apiserver/plane/authentication/views/app/github.py @@ -16,7 +16,7 @@ from plane.authentication.adapter.error import ( AuthenticationException, AUTHENTICATION_ERROR_CODES, ) - +from plane.utils.path_validator import validate_next_path class GitHubOauthInitiateEndpoint(View): def get(self, request): @@ -35,7 +35,7 @@ class GitHubOauthInitiateEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -49,7 +49,7 @@ class GitHubOauthInitiateEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -70,7 +70,7 @@ class GitHubCallbackEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin(base_host, "?" + urlencode(params)) return HttpResponseRedirect(url) @@ -81,7 +81,7 @@ class GitHubCallbackEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin(base_host, "?" + urlencode(params)) return HttpResponseRedirect(url) @@ -94,7 +94,7 @@ class GitHubCallbackEndpoint(View): user_login(request=request, user=user, is_app=True) # Get the redirection path if next_path: - path = next_path + path = str(validate_next_path(next_path)) else: path = get_redirection_path(user=user) # redirect to referer path @@ -103,6 +103,6 @@ class GitHubCallbackEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin(base_host, "?" + urlencode(params)) return HttpResponseRedirect(url) diff --git a/apiserver/plane/authentication/views/app/gitlab.py b/apiserver/plane/authentication/views/app/gitlab.py index bc0c9c8d7..c3a0f5876 100644 --- a/apiserver/plane/authentication/views/app/gitlab.py +++ b/apiserver/plane/authentication/views/app/gitlab.py @@ -16,7 +16,7 @@ from plane.authentication.adapter.error import ( AuthenticationException, AUTHENTICATION_ERROR_CODES, ) - +from plane.utils.path_validator import validate_next_path class GitLabOauthInitiateEndpoint(View): def get(self, request): @@ -24,7 +24,7 @@ class GitLabOauthInitiateEndpoint(View): 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(next_path) + request.session["next_path"] = str(validate_next_path(next_path)) # Check instance configuration instance = Instance.objects.first() @@ -35,7 +35,7 @@ class GitLabOauthInitiateEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -49,7 +49,7 @@ class GitLabOauthInitiateEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -81,7 +81,7 @@ class GitLabCallbackEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin(base_host, "?" + urlencode(params)) return HttpResponseRedirect(url) @@ -94,7 +94,7 @@ class GitLabCallbackEndpoint(View): user_login(request=request, user=user, is_app=True) # Get the redirection path if next_path: - path = next_path + path = str(validate_next_path(next_path)) else: path = get_redirection_path(user=user) # redirect to referer path @@ -103,6 +103,6 @@ class GitLabCallbackEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin(base_host, "?" + urlencode(params)) return HttpResponseRedirect(url) diff --git a/apiserver/plane/authentication/views/app/google.py b/apiserver/plane/authentication/views/app/google.py index 46c0d1980..2caf9f51b 100644 --- a/apiserver/plane/authentication/views/app/google.py +++ b/apiserver/plane/authentication/views/app/google.py @@ -18,7 +18,7 @@ from plane.authentication.adapter.error import ( AuthenticationException, AUTHENTICATION_ERROR_CODES, ) - +from plane.utils.path_validator import validate_next_path class GoogleOauthInitiateEndpoint(View): def get(self, request): @@ -36,7 +36,7 @@ class GoogleOauthInitiateEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -51,7 +51,7 @@ class GoogleOauthInitiateEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -72,7 +72,7 @@ class GoogleCallbackEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin(base_host, "?" + urlencode(params)) return HttpResponseRedirect(url) if not code: @@ -82,7 +82,7 @@ class GoogleCallbackEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = next_path + params["next_path"] = str(validate_next_path(next_path)) url = urljoin(base_host, "?" + urlencode(params)) return HttpResponseRedirect(url) try: @@ -95,11 +95,11 @@ class GoogleCallbackEndpoint(View): # Get the redirection path path = get_redirection_path(user=user) # redirect to referer path - url = urljoin(base_host, str(next_path) if next_path else path) + url = urljoin(base_host, str(validate_next_path(next_path)) if next_path else path) return HttpResponseRedirect(url) except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin(base_host, "?" + urlencode(params)) return HttpResponseRedirect(url) diff --git a/apiserver/plane/authentication/views/app/magic.py b/apiserver/plane/authentication/views/app/magic.py index b3bf8c777..4b1bdb02e 100644 --- a/apiserver/plane/authentication/views/app/magic.py +++ b/apiserver/plane/authentication/views/app/magic.py @@ -26,6 +26,7 @@ from plane.authentication.adapter.error import ( AUTHENTICATION_ERROR_CODES, ) from plane.authentication.rate_limit import AuthenticationThrottle +from plane.utils.path_validator import validate_next_path class MagicGenerateEndpoint(APIView): @@ -43,14 +44,13 @@ class MagicGenerateEndpoint(APIView): ) return Response(exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST) - origin = request.META.get("HTTP_ORIGIN", "/") email = request.data.get("email", "").strip().lower() try: validate_email(email) adapter = MagicCodeProvider(request=request, key=email) key, token = adapter.initiate() # If the smtp is configured send through here - magic_link.delay(email, key, token, origin) + magic_link.delay(email, key, token) return Response({"key": str(key)}, status=status.HTTP_200_OK) except AuthenticationException as e: params = e.get_error_dict() @@ -73,7 +73,7 @@ class MagicSignInEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "sign-in?" + urlencode(params) ) @@ -89,7 +89,7 @@ class MagicSignInEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "sign-in?" + urlencode(params) ) @@ -122,7 +122,7 @@ class MagicSignInEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "sign-in?" + urlencode(params) ) @@ -145,7 +145,7 @@ class MagicSignUpEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -159,7 +159,7 @@ class MagicSignUpEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) @@ -177,7 +177,7 @@ class MagicSignUpEndpoint(View): user_login(request=request, user=user, is_app=True) # Get the redirection path if next_path: - path = str(next_path) + path = str(validate_next_path(next_path)) else: path = get_redirection_path(user=user) # redirect to referer path @@ -187,7 +187,7 @@ class MagicSignUpEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = urljoin( base_host(request=request, is_app=True), "?" + urlencode(params) ) diff --git a/apiserver/plane/authentication/views/app/password_management.py b/apiserver/plane/authentication/views/app/password_management.py index 5b8d383c7..bbc5658c4 100644 --- a/apiserver/plane/authentication/views/app/password_management.py +++ b/apiserver/plane/authentication/views/app/password_management.py @@ -80,7 +80,7 @@ class ForgotPasswordEndpoint(APIView): if user: # Get the reset token for user uidb64, token = generate_password_token(user=user) - current_site = request.META.get("HTTP_ORIGIN") + current_site = base_host(request=request, is_app=True) # send the forgot password email forgot_password.delay( user.first_name, user.email, uidb64, token, current_site diff --git a/apiserver/plane/authentication/views/space/email.py b/apiserver/plane/authentication/views/space/email.py index 278cdf80b..6fa2d4517 100644 --- a/apiserver/plane/authentication/views/space/email.py +++ b/apiserver/plane/authentication/views/space/email.py @@ -17,6 +17,7 @@ from plane.authentication.adapter.error import ( AUTHENTICATION_ERROR_CODES, AuthenticationException, ) +from plane.utils.path_validator import validate_next_path class SignInAuthSpaceEndpoint(View): @@ -32,7 +33,7 @@ class SignInAuthSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -51,7 +52,7 @@ class SignInAuthSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -67,7 +68,7 @@ class SignInAuthSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -82,7 +83,7 @@ class SignInAuthSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -99,7 +100,7 @@ class SignInAuthSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -117,7 +118,7 @@ class SignUpAuthSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -135,7 +136,7 @@ class SignUpAuthSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) # Validate the email @@ -151,7 +152,7 @@ class SignUpAuthSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -166,7 +167,7 @@ class SignUpAuthSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -183,6 +184,6 @@ class SignUpAuthSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) diff --git a/apiserver/plane/authentication/views/space/github.py b/apiserver/plane/authentication/views/space/github.py index 1d9d1d4ee..fec71cb48 100644 --- a/apiserver/plane/authentication/views/space/github.py +++ b/apiserver/plane/authentication/views/space/github.py @@ -15,6 +15,7 @@ from plane.authentication.adapter.error import ( AUTHENTICATION_ERROR_CODES, AuthenticationException, ) +from plane.utils.path_validator import validate_next_path class GitHubOauthInitiateSpaceEndpoint(View): @@ -34,7 +35,7 @@ class GitHubOauthInitiateSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -66,7 +67,7 @@ class GitHubCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -77,7 +78,7 @@ class GitHubCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -93,6 +94,6 @@ class GitHubCallbackSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) diff --git a/apiserver/plane/authentication/views/space/gitlab.py b/apiserver/plane/authentication/views/space/gitlab.py index 9fb314442..4bdcf9514 100644 --- a/apiserver/plane/authentication/views/space/gitlab.py +++ b/apiserver/plane/authentication/views/space/gitlab.py @@ -15,6 +15,7 @@ from plane.authentication.adapter.error import ( AUTHENTICATION_ERROR_CODES, AuthenticationException, ) +from plane.utils.path_validator import validate_next_path class GitLabOauthInitiateSpaceEndpoint(View): @@ -34,7 +35,7 @@ class GitLabOauthInitiateSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -66,7 +67,7 @@ class GitLabCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -77,7 +78,7 @@ class GitLabCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -93,6 +94,6 @@ class GitLabCallbackSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) diff --git a/apiserver/plane/authentication/views/space/google.py b/apiserver/plane/authentication/views/space/google.py index 479a18883..03ad97793 100644 --- a/apiserver/plane/authentication/views/space/google.py +++ b/apiserver/plane/authentication/views/space/google.py @@ -15,6 +15,7 @@ from plane.authentication.adapter.error import ( AuthenticationException, AUTHENTICATION_ERROR_CODES, ) +from plane.utils.path_validator import validate_next_path class GoogleOauthInitiateSpaceEndpoint(View): @@ -33,7 +34,7 @@ class GoogleOauthInitiateSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -46,7 +47,7 @@ class GoogleOauthInitiateSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -65,7 +66,7 @@ class GoogleCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) if not code: @@ -75,7 +76,7 @@ class GoogleCallbackSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = next_path + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) try: @@ -89,6 +90,6 @@ class GoogleCallbackSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) diff --git a/apiserver/plane/authentication/views/space/magic.py b/apiserver/plane/authentication/views/space/magic.py index 7c23d5fc3..cb682137c 100644 --- a/apiserver/plane/authentication/views/space/magic.py +++ b/apiserver/plane/authentication/views/space/magic.py @@ -23,7 +23,7 @@ from plane.authentication.adapter.error import ( AuthenticationException, AUTHENTICATION_ERROR_CODES, ) - +from plane.utils.path_validator import validate_next_path class MagicGenerateSpaceEndpoint(APIView): permission_classes = [AllowAny] @@ -38,14 +38,14 @@ class MagicGenerateSpaceEndpoint(APIView): ) return Response(exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST) - origin = base_host(request=request, is_space=True) + email = request.data.get("email", "").strip().lower() try: validate_email(email) adapter = MagicCodeProvider(request=request, key=email) key, token = adapter.initiate() # If the smtp is configured send through here - magic_link.delay(email, key, token, origin) + magic_link.delay(email, key, token) return Response({"key": str(key)}, status=status.HTTP_200_OK) except AuthenticationException as e: return Response(e.get_error_dict(), status=status.HTTP_400_BAD_REQUEST) @@ -67,7 +67,7 @@ class MagicSignInSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -80,7 +80,7 @@ class MagicSignInSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -121,7 +121,7 @@ class MagicSignUpSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) # Existing User @@ -134,7 +134,7 @@ class MagicSignUpSpaceEndpoint(View): ) params = exc.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) @@ -152,6 +152,6 @@ class MagicSignUpSpaceEndpoint(View): except AuthenticationException as e: params = e.get_error_dict() if next_path: - params["next_path"] = str(next_path) + params["next_path"] = str(validate_next_path(next_path)) url = f"{base_host(request=request, is_space=True)}?{urlencode(params)}" return HttpResponseRedirect(url) diff --git a/apiserver/plane/authentication/views/space/password_management.py b/apiserver/plane/authentication/views/space/password_management.py index 710d0db2f..bff3e3485 100644 --- a/apiserver/plane/authentication/views/space/password_management.py +++ b/apiserver/plane/authentication/views/space/password_management.py @@ -90,7 +90,7 @@ class ForgotPasswordSpaceEndpoint(APIView): if user: # Get the reset token for user uidb64, token = generate_password_token(user=user) - current_site = request.META.get("HTTP_ORIGIN") + current_site = base_host(request=request, is_space=True) # send the forgot password email forgot_password.delay( user.first_name, user.email, uidb64, token, current_site diff --git a/apiserver/plane/authentication/views/space/signout.py b/apiserver/plane/authentication/views/space/signout.py index babd18ee9..11e617436 100644 --- a/apiserver/plane/authentication/views/space/signout.py +++ b/apiserver/plane/authentication/views/space/signout.py @@ -7,6 +7,7 @@ from django.utils import timezone # 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 class SignOutAuthSpaceEndpoint(View): @@ -21,8 +22,8 @@ class SignOutAuthSpaceEndpoint(View): user.save() # Log the user out logout(request) - url = f"{base_host(request=request, is_space=True)}{next_path}" + url = f"{base_host(request=request, is_space=True)}{str(validate_next_path(next_path)) if next_path else ''}" return HttpResponseRedirect(url) except Exception: - url = f"{base_host(request=request, is_space=True)}{next_path}" + url = f"{base_host(request=request, is_space=True)}{str(validate_next_path(next_path)) if next_path else ''}" return HttpResponseRedirect(url) diff --git a/apiserver/plane/bgtasks/magic_link_code_task.py b/apiserver/plane/bgtasks/magic_link_code_task.py index 848ea623f..1a0e9ba03 100644 --- a/apiserver/plane/bgtasks/magic_link_code_task.py +++ b/apiserver/plane/bgtasks/magic_link_code_task.py @@ -16,7 +16,7 @@ from plane.utils.exception_logger import log_exception @shared_task -def magic_link(email, key, token, current_site): +def magic_link(email, key, token): try: ( EMAIL_HOST, diff --git a/apiserver/plane/license/api/views/admin.py b/apiserver/plane/license/api/views/admin.py index 97f0e446e..e1e386082 100644 --- a/apiserver/plane/license/api/views/admin.py +++ b/apiserver/plane/license/api/views/admin.py @@ -33,6 +33,7 @@ from plane.authentication.adapter.error import ( AUTHENTICATION_ERROR_CODES, AuthenticationException, ) +from plane.utils.ip_address import get_client_ip class InstanceAdminEndpoint(BaseAPIView): @@ -217,7 +218,7 @@ class InstanceAdminSignUpEndpoint(View): user.is_active = True user.last_active = timezone.now() user.last_login_time = timezone.now() - user.last_login_ip = request.META.get("REMOTE_ADDR") + user.last_login_ip = get_client_ip(request=request) user.last_login_uagent = request.META.get("HTTP_USER_AGENT") user.token_updated_at = timezone.now() user.save() @@ -344,7 +345,7 @@ class InstanceAdminSignInEndpoint(View): user.is_active = True user.last_active = timezone.now() user.last_login_time = timezone.now() - user.last_login_ip = request.META.get("REMOTE_ADDR") + user.last_login_ip = get_client_ip(request=request) user.last_login_uagent = request.META.get("HTTP_USER_AGENT") user.token_updated_at = timezone.now() user.save() diff --git a/apiserver/plane/middleware/api_log_middleware.py b/apiserver/plane/middleware/api_log_middleware.py index c7a0841ad..299e66d22 100644 --- a/apiserver/plane/middleware/api_log_middleware.py +++ b/apiserver/plane/middleware/api_log_middleware.py @@ -1,5 +1,6 @@ +# Module imports from plane.db.models import APIActivityLog - +from plane.utils.ip_address import get_client_ip class APITokenLogMiddleware: def __init__(self, get_response): @@ -28,7 +29,7 @@ class APITokenLogMiddleware: response.content.decode("utf-8") if response.content else None ), response_code=response.status_code, - ip_address=request.META.get("REMOTE_ADDR", None), + ip_address=get_client_ip(request=request), user_agent=request.META.get("HTTP_USER_AGENT", None), ) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 57b11da00..7712da894 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -22,7 +22,7 @@ SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key()) DEBUG = int(os.environ.get("DEBUG", "0")) # Allowed Hosts -ALLOWED_HOSTS = ["*"] +ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "*").split(",") # Application definition INSTALLED_APPS = [ @@ -314,7 +314,7 @@ ADMIN_BASE_URL = os.environ.get("ADMIN_BASE_URL", None) SPACE_BASE_URL = os.environ.get("SPACE_BASE_URL", None) APP_BASE_URL = os.environ.get("APP_BASE_URL") LIVE_BASE_URL = os.environ.get("LIVE_BASE_URL") - +WEB_URL = os.environ.get("WEB_URL") HARD_DELETE_AFTER_DAYS = int(os.environ.get("HARD_DELETE_AFTER_DAYS", 60)) diff --git a/apiserver/plane/utils/host.py b/apiserver/plane/utils/host.py index 4046c1e20..c4914d7ff 100644 --- a/apiserver/plane/utils/host.py +++ b/apiserver/plane/utils/host.py @@ -1,18 +1,21 @@ -# Python imports -from urllib.parse import urlsplit - # Django imports from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.http import HttpRequest +# Third party imports +from rest_framework.request import Request -def base_host(request, is_admin=False, is_space=False, is_app=False): +# Module imports +from plane.utils.ip_address import get_client_ip + +def base_host(request: Request | HttpRequest, is_admin: bool = False, is_space: bool = False, is_app: bool = False) -> str: """Utility function to return host / origin from the request""" # Calculate the base origin from request - base_origin = str( - request.META.get("HTTP_ORIGIN") - or f"{urlsplit(request.META.get('HTTP_REFERER')).scheme}://{urlsplit(request.META.get('HTTP_REFERER')).netloc}" - or f"""{"https" if request.is_secure() else "http"}://{request.get_host()}""" - ) + base_origin = settings.WEB_URL or settings.APP_BASE_URL + + if not base_origin: + raise ImproperlyConfigured("APP_BASE_URL or WEB_URL is not set") # Admin redirections if is_admin: @@ -38,5 +41,5 @@ def base_host(request, is_admin=False, is_space=False, is_app=False): return base_origin -def user_ip(request): - return str(request.META.get("REMOTE_ADDR")) +def user_ip(request: Request | HttpRequest) -> str: + return get_client_ip(request=request) diff --git a/apiserver/plane/utils/path_validator.py b/apiserver/plane/utils/path_validator.py new file mode 100644 index 000000000..ba81e9cab --- /dev/null +++ b/apiserver/plane/utils/path_validator.py @@ -0,0 +1,21 @@ +# Python imports +from urllib.parse import urlparse + + +def validate_next_path(next_path: str) -> str: + """Validates that next_path is a valid path and extracts only the path component.""" + parsed_url = urlparse(next_path) + + # Ensure next_path is not an absolute URL + if parsed_url.scheme or parsed_url.netloc: + next_path = parsed_url.path # Extract only the path component + + # Ensure it starts with a forward slash (indicating a valid relative path) + if not next_path.startswith("/"): + return "" + + # Ensure it does not contain dangerous path traversal sequences + if ".." in next_path: + return "" + + return next_path diff --git a/deploy/selfhost/docker-compose.yml b/deploy/selfhost/docker-compose.yml index 0d12d529e..833c5a572 100644 --- a/deploy/selfhost/docker-compose.yml +++ b/deploy/selfhost/docker-compose.yml @@ -51,6 +51,7 @@ x-app-env: &app-env API_KEY_RATE_LIMIT: ${API_KEY_RATE_LIMIT:-60/minute} MINIO_ENDPOINT_SSL: ${MINIO_ENDPOINT_SSL:-0} + services: web: image: ${DOCKERHUB_USER:-makeplane}/plane-frontend:${APP_RELEASE:-stable}