Dev#33
Conversation
…n-updating-or-deleting-image fix: remove old image from s3 when updating or deleting image
…-pages-and-email feat: implement api crud for auth pages and email
…d-and-theme-color feat: implement api crud for brand and theme color
feat: implement custom emails models
…template-folder feat: update the email ui in the template folder
fix: remove logo and favicon default
…pi-performance-monitoring-and-profiling feat: implement django silk for api performance monitoring and profiling
fix: rename url for silk
chore: improve query performance
…k-org fix: update response for api check org
There was a problem hiding this comment.
Code Review
This pull request introduces customization features for organizations by adding the custom_email, custom_page, and organization_setting apps, allowing custom templates, themes, and logos. It also integrates django-silk for profiling in development. The review feedback identifies critical issues, including a potential TypeError in the _get_instance helper when a default callable is missing, an Insecure Direct Object Reference (IDOR) vulnerability in the organization settings update view, a duplicate email_type field in OrganizationEmailSerializer, and a Django migration anti-pattern where a service is imported at the top level of a migration.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| def _get_instance(self, queryset, data, fallback_field, default=None): | ||
| data = data.copy() | ||
| object_id = data.pop("id", None) | ||
| fallback_value = data.get(fallback_field) | ||
| instance = queryset.filter(id=object_id).first() if object_id else None | ||
| if instance is None and fallback_value: | ||
| instance = queryset.filter(**{fallback_field: fallback_value}).first() | ||
| return instance or default(data) |
There was a problem hiding this comment.
The _get_instance helper method defines default=None as a parameter. However, when it is called for custom_pages (line 160), no default argument is provided. If a matching custom page is not found in the database, default(data) will be evaluated, raising a TypeError: 'NoneType' object is not callable and crashing the request.
To fix this, check if default is callable before executing it, and return None otherwise.
| def _get_instance(self, queryset, data, fallback_field, default=None): | |
| data = data.copy() | |
| object_id = data.pop("id", None) | |
| fallback_value = data.get(fallback_field) | |
| instance = queryset.filter(id=object_id).first() if object_id else None | |
| if instance is None and fallback_value: | |
| instance = queryset.filter(**{fallback_field: fallback_value}).first() | |
| return instance or default(data) | |
| def _get_instance(self, queryset, data, fallback_field, default=None): | |
| data = data.copy() | |
| object_id = data.pop("id", None) | |
| fallback_value = data.get(fallback_field) | |
| instance = queryset.filter(id=object_id).first() if object_id else None | |
| if instance is None and fallback_value: | |
| instance = queryset.filter(**{fallback_field: fallback_value}).first() | |
| if instance is not None: | |
| return instance | |
| return default(data) if callable(default) else None |
| def get_object(self): | ||
| organization = get_object_or_404( | ||
| Organization, | ||
| slug_name=self.request.headers.get("X-Organization"), | ||
| is_active=True, | ||
| ) | ||
| return get_object_or_404(self.get_queryset(), organization=organization) |
There was a problem hiding this comment.
The get_object method retrieves the organization solely based on the X-Organization header without verifying if the requesting user (identified by the X-User-ID header) is actually associated with or has a role in that organization. This creates an Insecure Direct Object Reference (IDOR) vulnerability, allowing any authenticated user to modify another organization's settings by spoofing the header.
To secure this endpoint, ensure that the query filters the organization by the user's ID.
| def get_object(self): | |
| organization = get_object_or_404( | |
| Organization, | |
| slug_name=self.request.headers.get("X-Organization"), | |
| is_active=True, | |
| ) | |
| return get_object_or_404(self.get_queryset(), organization=organization) | |
| def get_object(self): | |
| user_id = self.request.headers.get("X-User-ID") | |
| organization_slug = self.request.headers.get("X-Organization") | |
| organization = get_object_or_404( | |
| Organization, | |
| slug_name=organization_slug, | |
| organization_role__organization_role_user__root_user_id=user_id, | |
| is_active=True, | |
| ) | |
| return get_object_or_404(self.get_queryset(), organization=organization) |
| "email_type", | ||
| "email_type", |
| @@ -0,0 +1,43 @@ | |||
| from django.db import migrations | |||
|
|
|||
| from apps.custom_email.service import get_default_organization_emails | |||
There was a problem hiding this comment.
Importing from apps.custom_email.service at the top level of a migration is a Django anti-pattern. The service file imports live models (e.g., OrganizationEmail), which can cause migration failures in the future if those models are modified or deleted. Consider moving the default email data to a standalone constants file that does not import any models, or defining the default data directly within the migration.
What?
Why?
How?
Testing?
Anything Else?