Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 32 additions & 27 deletions .github/workflows/django.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,24 @@ on:
branches: ["master"]
workflow_dispatch:

permissions:
contents: read
pull-requests: write
issues: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 20

strategy:
fail-fast: false
matrix:
python-version: [3.10.5, 3.11.2, 3.11.7, 3.12.3, 3.12.7]
python-version: [3.10.4, 3.12.10, 3.13.3]

services:
postgres:
Expand All @@ -33,44 +44,38 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip

- name: Cache pip packages
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pip-tools
if [ ! -f requirements.txt ]; then
echo "Generating requirements.txt..."
echo "django>=5.0,<6.0" >> requirements.in
echo "djangorestframework>=3.12.0,<4.0" >> requirements.in
echo "psycopg2>=2.9.0" >> requirements.in
echo "django-filter>=23.2,<24.0" >> requirements.in
echo "python-decouple>=3.8" >> requirements.in
echo "django-cors-headers>=4.6.0" >> requirements.in
echo "drf-yasg==1.21.10" >> requirements.in
pip-compile requirements.in
fi
pip install -r requirements.txt
run: pip install --upgrade pip && pip install -r requirements.txt

- name: Set Environment Variables
- name: Set up environment
run: |
echo "SECRET_KEY=${{ secrets.SECRET_KEY || 'test-secret-key' }}" >> $GITHUB_ENV
echo "DB_NAME=test_db" >> $GITHUB_ENV
echo "DB_USER=postgres" >> $GITHUB_ENV
echo "DB_PASSWORD=password" >> $GITHUB_ENV
echo "DB_HOST=localhost" >> $GITHUB_ENV
echo "DB_PORT=5432" >> $GITHUB_ENV
echo DB_NAME=test_db >> $GITHUB_ENV
echo DB_USER=postgres >> $GITHUB_ENV
echo DB_PASSWORD=password >> $GITHUB_ENV
echo DB_HOST=localhost >> $GITHUB_ENV
echo DB_PORT=5432 >> $GITHUB_ENV

- name: Wait for PostgreSQL to be ready
run: |
until pg_isready -h localhost -p 5432 -U postgres; do
echo "Waiting for PostgreSQL to be ready..."
sleep 5
done
until pg_isready -h localhost -U postgres; do sleep 1; done

- name: Run migrations
run: python manage.py migrate
Expand Down
29 changes: 24 additions & 5 deletions api/utils.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import logging
import traceback
from typing import Optional
from rest_framework.response import Response
from rest_framework import status

def error_response(message, http_status=status.HTTP_400_BAD_REQUEST):
logger = logging.getLogger(__name__)


def error_response(
message: Optional[str] = None,
http_status: int = status.HTTP_400_BAD_REQUEST,
*,
exc: Optional[Exception] = None
):
"""
Generates a response with the ‘error’ field and the specified HTTP status
Generates a generalized Response with the 'error' field.
If exc is passed, it logs the stack trace on the server
"""
return Response({"error": message}, status=http_status)
if exc is not None:
# Logging of a full stack trade
logger.error("Internal error: %s\n%s", exc, traceback.format_exc())

# We return only a general message to the client
safe_message = message or "An internal server error occurred"
return Response({"error": safe_message}, status=http_status)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 12 months ago

To fix the issue, we need to ensure that sensitive exception details are not exposed to external users. Instead of directly passing str(e) as the message argument to error_response, we should always provide a generic error message for the client. The detailed exception information should only be logged on the server for debugging purposes. This can be achieved by modifying the calls to error_response in users/views.py to use a generic error message, while still passing the exception object (exc) for logging purposes.


Suggested changeset 1
users/views.py
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/users/views.py b/users/views.py
--- a/users/views.py
+++ b/users/views.py
@@ -53,6 +53,6 @@
         except ValueError as e:
-            return error_response(str(e), exc=e)
+            return error_response("An error occurred while processing your request.", exc=e)
         except Exception as e:
             return error_response(
-                "An unexpected error occured",
+                "An unexpected error occurred.",
                 status.HTTP_500_INTERNAL_SERVER_ERROR,
EOF
@@ -53,6 +53,6 @@
except ValueError as e:
return error_response(str(e), exc=e)
return error_response("An error occurred while processing your request.", exc=e)
except Exception as e:
return error_response(
"An unexpected error occured",
"An unexpected error occurred.",
status.HTTP_500_INTERNAL_SERVER_ERROR,
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated


def status_response(message, http_status=None):
"""
Generates a response with the ‘status’ field and the specified HTTP status
"""
return Response({"status": message}, status=http_status)
"""
return Response({"status": message}, status=http_status)
3 changes: 1 addition & 2 deletions projects/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import viewsets, status
from rest_framework.exceptions import ValidationError
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
Expand Down Expand Up @@ -133,7 +132,7 @@ def assign_role(self, request, pk=None):
ProjectMembershipService.assign_role(project, target, role)
except Exception as e:
message = getattr(e, 'detail', str(e))
return error_response(message)
return error_response(message, exc=e)

return status_response("Role assigned")

Expand Down
16 changes: 10 additions & 6 deletions tasks/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,11 @@ def toggle_favorite(self, request, pk=None):
"is_favorite": updated_task.is_favorite,
})
except Exception as e:
logger.error(f"Error toggling favorite for task {pk}: {e}")
logger.exception(f"Error toggling favorite for task {pk}")
return error_response(
"Failed to update favorite status",
status.HTTP_500_INTERNAL_SERVER_ERROR,
exc=e
)

@action(
Expand Down Expand Up @@ -143,10 +144,11 @@ def toggle_completed(self, request, project_pk=None, pk=None):
}
)
except Exception as e:
logger.error(f"Error toggling completion for task {pk}: {e}")
logger.exception(f"Error toggling completion for task {pk}")
return error_response(
"Failed to update completion status",
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
status.HTTP_500_INTERNAL_SERVER_ERROR,
exc=e
)

@action(
Expand Down Expand Up @@ -186,11 +188,13 @@ def move_task(self, request, project_pk=None, pk=None):
TaskService.move_task_to_project(task, project_id, request.user)
return status_response("Task moved successfully")
except ValueError as e:
return error_response(str(e), status.HTTP_404_NOT_FOUND)
return error_response(str(e), status.HTTP_404_NOT_FOUND, exc=e)
except Exception as e:
logger.error(f"Error moving task: {e}", exc_info=True)
logger.exception(f"Error moving task: {e}")
return error_response(
"Failed to move task", status.HTTP_500_INTERNAL_SERVER_ERROR
"Failed to move task",
status.HTTP_500_INTERNAL_SERVER_ERROR,
exc=e,
)

@action(
Expand Down
11 changes: 6 additions & 5 deletions users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class AuthViewSet(viewsets.GenericViewSet):
def get_serializer_class(self):
if getattr(self, 'swagger_fake_view', False):
return serializers.Serializer

if self.action == 'register':
return UserRegistrationSerializer
elif self.action == 'login':
Expand Down Expand Up @@ -51,13 +51,14 @@ def logout(self, request):
data = UserService.logout_user(refresh_token)
return Response(data, status=status.HTTP_200_OK)
except ValueError as e:
return error_response(str(e))
return error_response(str(e), exc=e)
except Exception as e:
return error_response(
f"Unexpected error {str(e)}",
"An unexpected error occured",
status.HTTP_500_INTERNAL_SERVER_ERROR,
exc=e,
)


class UserViewSet(viewsets.GenericViewSet):
"""
Expand All @@ -74,4 +75,4 @@ def profile(self, request):
@action(detail=False, methods=['put', 'patch'])
def update_profile(self, request):
data = UserService.update_profile(request.user, request.data)
return Response(data)
return Response(data)