diff --git a/apiserver/plane/app/views/external/base.py b/apiserver/plane/app/views/external/base.py index ae5c47f14..5643da226 100644 --- a/apiserver/plane/app/views/external/base.py +++ b/apiserver/plane/app/views/external/base.py @@ -3,7 +3,7 @@ import os from typing import List, Dict, Tuple # Third party import -import litellm +from openai import OpenAI import requests from rest_framework import status @@ -116,12 +116,14 @@ def get_llm_response(task, prompt, api_key: str, model: str, provider: str) -> T if provider.lower() == "gemini": model = f"gemini/{model}" - response = litellm.completion( + client = OpenAI(api_key=api_key) + chat_completion = client.chat.completions.create( model=model, - messages=[{"role": "user", "content": final_text}], - api_key=api_key, + messages=[ + {"role": "user", "content": final_text} + ] ) - text = response.choices[0].message.content.strip() + text = chat_completion.choices[0].message.content return text, None except Exception as e: log_exception(e) @@ -175,7 +177,7 @@ class WorkspaceGPTIntegrationEndpoint(BaseAPIView): @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") def post(self, request, slug): api_key, model, provider = get_llm_config() - + if not api_key or not model or not provider: return Response( {"error": "LLM provider API key and model are required"}, diff --git a/apiserver/plane/app/views/intake/base.py b/apiserver/plane/app/views/intake/base.py index 8647117c5..631fe80da 100644 --- a/apiserver/plane/app/views/intake/base.py +++ b/apiserver/plane/app/views/intake/base.py @@ -174,14 +174,17 @@ class IntakeIssueViewSet(BaseViewSet): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def list(self, request, slug, project_id): - intake_id = Intake.objects.filter( + intake = Intake.objects.filter( workspace__slug=slug, project_id=project_id ).first() + if not intake: + return Response({"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND) + project = Project.objects.get(pk=project_id) filters = issue_filters(request.GET, "GET", "issue__") intake_issue = ( IntakeIssue.objects.filter( - intake_id=intake_id.id, project_id=project_id, **filters + intake_id=intake.id, project_id=project_id, **filters ) .select_related("issue") .prefetch_related("issue__labels") diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index fbd4d7c50..48ea2f6bc 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -547,7 +547,7 @@ class IssueViewSet(BaseViewSet): ) """ - if the role is guest and guest_view_all_features is false and owned by is not + if the role is guest and guest_view_all_features is false and owned by is not the requesting user then dont show the issue """ @@ -1116,8 +1116,22 @@ class IssueMetaEndpoint(BaseAPIView): class IssueDetailIdentifierEndpoint(BaseAPIView): + def strict_str_to_int(self, s): + if not s.isdigit() and not (s.startswith('-') and s[1:].isdigit()): + raise ValueError("Invalid integer string") + return int(s) + def get(self, request, slug, project_identifier, issue_identifier): - + + # Check if the issue identifier is a valid integer + try: + issue_identifier = self.strict_str_to_int(issue_identifier) + except ValueError: + return Response( + {"error": "Invalid issue identifier"}, + status=status.HTTP_400_BAD_REQUEST, + ) + # Fetch the project project = Project.objects.get( identifier__iexact=project_identifier, @@ -1240,7 +1254,7 @@ class IssueDetailIdentifierEndpoint(BaseAPIView): ) """ - if the role is guest and guest_view_all_features is false and owned by is not + if the role is guest and guest_view_all_features is false and owned by is not the requesting user then dont show the issue """ diff --git a/apiserver/plane/app/views/module/issue.py b/apiserver/plane/app/views/module/issue.py index 06b0a2fb1..832e9a5cd 100644 --- a/apiserver/plane/app/views/module/issue.py +++ b/apiserver/plane/app/views/module/issue.py @@ -280,7 +280,7 @@ class ModuleIssueViewSet(BaseViewSet): issue_id=str(issue_id), project_id=str(project_id), current_instance=json.dumps( - {"module_name": module_issue.first().module.name} + {"module_name": module_issue.first().module.name if (module_issue.first() and module_issue.first().module) else None} ), epoch=int(timezone.now().timestamp()), notification=True, diff --git a/apiserver/plane/authentication/views/app/password_management.py b/apiserver/plane/authentication/views/app/password_management.py index cad498e63..5b8d383c7 100644 --- a/apiserver/plane/authentication/views/app/password_management.py +++ b/apiserver/plane/authentication/views/app/password_management.py @@ -100,8 +100,20 @@ class ResetPasswordEndpoint(View): def post(self, request, uidb64, token): try: # Decode the id from the uidb64 - id = smart_str(urlsafe_base64_decode(uidb64)) - user = User.objects.get(id=id) + try: + id = smart_str(urlsafe_base64_decode(uidb64)) + user = User.objects.get(id=id) + except (ValueError, User.DoesNotExist): + exc = AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD_TOKEN"], + error_message="INVALID_PASSWORD_TOKEN", + ) + params = exc.get_error_dict() + url = urljoin( + base_host(request=request, is_app=True), + "accounts/reset-password?" + urlencode(params), + ) + return HttpResponseRedirect(url) # check if the token is valid for the user if not PasswordResetTokenGenerator().check_token(user, token): diff --git a/apiserver/plane/bgtasks/issue_activities_task.py b/apiserver/plane/bgtasks/issue_activities_task.py index 1bc4817f8..dacb39236 100644 --- a/apiserver/plane/bgtasks/issue_activities_task.py +++ b/apiserver/plane/bgtasks/issue_activities_task.py @@ -9,10 +9,10 @@ from celery import shared_task from django.core.serializers.json import DjangoJSONEncoder from django.utils import timezone -from plane.app.serializers import IssueActivitySerializer -from plane.bgtasks.notification_task import notifications # Module imports +from plane.app.serializers import IssueActivitySerializer +from plane.bgtasks.notification_task import notifications from plane.db.models import ( CommentReaction, Cycle, @@ -32,7 +32,7 @@ from plane.settings.redis import redis_instance from plane.utils.exception_logger import log_exception from plane.bgtasks.webhook_task import webhook_activity from plane.utils.issue_relation_mapper import get_inverse_relation - +from plane.utils.valid_uuid import is_valid_uuid # Track Changes in name def track_name( @@ -1568,9 +1568,14 @@ def issue_activity( try: issue_activities = [] + # check if project_id is valid + if not is_valid_uuid(project_id): + return + project = Project.objects.get(pk=project_id) workspace_id = project.workspace_id + if issue_id is not None: if origin: ri = redis_instance() diff --git a/apiserver/plane/utils/valid_uuid.py b/apiserver/plane/utils/valid_uuid.py new file mode 100644 index 000000000..a44105136 --- /dev/null +++ b/apiserver/plane/utils/valid_uuid.py @@ -0,0 +1,8 @@ +import uuid + +def is_valid_uuid(uuid_str): + try: + uuid.UUID(uuid_str, version=4) + return True + except ValueError: + return False diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index ff4251865..41a5e0f13 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -4,7 +4,7 @@ Django==4.2.18 # rest framework djangorestframework==3.15.2 -# postgres +# postgres psycopg==3.1.18 psycopg-binary==3.1.18 psycopg-c==3.1.18 @@ -37,7 +37,7 @@ uvicorn==0.29.0 # sockets channels==4.1.0 # ai -litellm==1.51.0 +openai==1.63.2 # slack slack-sdk==3.27.1 # apm @@ -66,4 +66,4 @@ PyJWT==2.8.0 opentelemetry-api==1.28.1 opentelemetry-sdk==1.28.1 opentelemetry-instrumentation-django==0.49b1 -opentelemetry-exporter-otlp==1.28.1 \ No newline at end of file +opentelemetry-exporter-otlp==1.28.1 diff --git a/apiserver/requirements/local.txt b/apiserver/requirements/local.txt index 02792201b..2146554f6 100644 --- a/apiserver/requirements/local.txt +++ b/apiserver/requirements/local.txt @@ -2,4 +2,4 @@ # debug toolbar django-debug-toolbar==4.3.0 # formatter -ruff==0.4.2 \ No newline at end of file +ruff==0.9.7