Skip to content
Open
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
3 changes: 2 additions & 1 deletion apps/sage_intacct/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,8 @@ def __get_entity_slide_preference(self) -> bool:
entity_slide_disabled = entity_slide_info['prefvalue'] == 'true'
except Exception as e:
logger.error(e.__dict__)

# TODO: Check by fetching locations scoped to one of the entities
# check the function check_entity_slide_error
return entity_slide_disabled

def sync_location_entities(self) -> None:
Expand Down
43 changes: 43 additions & 0 deletions apps/sage_intacct/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
from django_q.models import Schedule
from django.core.cache import cache

from cryptography.fernet import Fernet
from intacctsdk import IntacctRESTSDK
from sageintacctsdk import SageIntacctSDK

from apps.workspaces.enums import CacheKeyEnum
from apps.fyle.models import DependentFieldSetting
from apps.sage_intacct.utils import SageIntacctConnector
Expand Down Expand Up @@ -167,3 +171,42 @@ def validate_rest_api_connection(workspace_id: int) -> None:
sync_dimensions(workspace_id=workspace_id)
except Exception as e:
logger.info('REST API is not working for workspace_id - %s, error - %s', workspace_id, e)


def check_entity_slide_error(workspace_id: int, entity_id: str) -> None:
"""
Check if a location entity causes an entity slide error by attempting
to fetch locations scoped to that entity. Raises an exception if the
Sage Intacct API rejects the request (e.g. due to entity slide restrictions).
:param workspace_id: Workspace ID
:param entity_id: Location entity destination ID to validate
"""
migrated_to_rest_api = FeatureConfig.get_feature_config(workspace_id=workspace_id, key='migrated_to_rest_api')
entity_id_for_sdk = entity_id if entity_id != 'top_level' else None

if migrated_to_rest_api:
rest_connector = SageIntacctRestConnector(workspace_id=workspace_id)
connection = IntacctRESTSDK(
username=rest_connector.username,
access_token=rest_connector.connection.access_token,
entity_id=entity_id_for_sdk,
client_id=settings.INTACCT_CLIENT_ID,
client_secret=settings.INTACCT_CLIENT_SECRET
)
else:
sage_intacct_credentials = SageIntacctCredential.get_active_sage_intacct_credentials(workspace_id)
cipher_suite = Fernet(settings.ENCRYPTION_KEY)
decrypted_password = cipher_suite.decrypt(
sage_intacct_credentials.si_user_password.encode('utf-8')
).decode('utf-8')

connection = SageIntacctSDK(
sender_id=settings.SI_SENDER_ID,
sender_password=settings.SI_SENDER_PASSWORD,
user_id=sage_intacct_credentials.si_user_id,
company_id=sage_intacct_credentials.si_company_id,
user_password=decrypted_password,
entity_id=entity_id_for_sdk
)

connection.locations.count()
2 changes: 2 additions & 0 deletions apps/sage_intacct/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from apps.sage_intacct.views import (
AuthorizationCodeView,
DestinationAttributesView,
EntitySlideCheckView,
PaginatedDestinationAttributesView,
RefreshSageIntacctDimensionView,
SageIntacctAttributesCountView,
Expand All @@ -18,6 +19,7 @@
path('paginated_destination_attributes/', PaginatedDestinationAttributesView.as_view()),
path('credentials/authorization_code/', AuthorizationCodeView.as_view()),
path('attributes_count/', SageIntacctAttributesCountView.as_view()),
path('check_entity_slide/', EntitySlideCheckView.as_view()),
]

sage_intacct_dimension_paths = [
Expand Down
42 changes: 41 additions & 1 deletion apps/sage_intacct/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from sageintacctsdk.exceptions import InvalidTokenError
from intacctsdk.exceptions import InvalidTokenError as IntacctRESTInvalidTokenError

from apps.sage_intacct.helpers import sync_dimensions
from apps.sage_intacct.helpers import sync_dimensions, check_entity_slide_error
from apps.sage_intacct.models import SageIntacctAttributesCount
from apps.sage_intacct.serializers import SageIntacctAttributesCountSerializer, SageIntacctFieldSerializer
from apps.workspaces.enums import CacheKeyEnum
Expand Down Expand Up @@ -343,3 +343,43 @@
serializer_class = SageIntacctAttributesCountSerializer
lookup_field = 'workspace_id'
lookup_url_kwarg = 'workspace_id'


class EntitySlideCheckView(generics.CreateAPIView):
"""
Check if a location entity causes an entity slide error
"""
def post(self, request: Request, *args, **kwargs) -> Response:
entity_id = request.data.get('entity_id')
workspace_id = kwargs['workspace_id']

assert_valid(entity_id is not None, 'Entity ID is required')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate entity_id is non-empty, not just non-null.

Line 356 accepts "" / whitespace, which can lead to invalid SDK calls instead of a clean request validation failure.

Proposed fix
-        assert_valid(entity_id is not None, 'Entity ID is required')
+        assert_valid(
+            entity_id is not None and str(entity_id).strip() != '',
+            'Entity ID is required'
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert_valid(entity_id is not None, 'Entity ID is required')
assert_valid(
entity_id is not None and str(entity_id).strip() != '',
'Entity ID is required'
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/sage_intacct/views.py` at line 356, The current validation uses
assert_valid(entity_id is not None, 'Entity ID is required') which allows empty
strings/whitespace; update the check to ensure entity_id is a non-empty,
non-whitespace string by validating entity_id and entity_id.strip() before
calling assert_valid so that empty or whitespace-only values fail with the same
'Entity ID is required' message (target the entity_id variable and the
assert_valid call).


try:
check_entity_slide_error(workspace_id=workspace_id, entity_id=entity_id)
return Response(
data={'entity_slide_error': False},
status=status.HTTP_200_OK
)
except SageIntacctCredential.DoesNotExist:
logger.info('Sage Intacct credentials not found for workspace_id - %s', workspace_id)
return Response(
data={'message': 'Sage Intacct credentials not found'},
status=status.HTTP_400_BAD_REQUEST
)
except (InvalidTokenError, IntacctRESTInvalidTokenError):
invalidate_sage_intacct_credentials(workspace_id)
logger.info('Invalid Sage Intacct token for workspace_id - %s', workspace_id)
return Response(
data={'message': 'Invalid Sage Intacct token'},
status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
logger.info(
'Entity slide error for workspace_id - %s, entity_id - %s: %s',
workspace_id, entity_id, str(e)
)
return Response(
data={'entity_slide_error': True, 'message': str(e)},

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 4 days ago

In general, to fix information exposure via exceptions, avoid sending raw exception objects or their messages to the client. Instead, log the full details on the server (possibly with stack trace) and return a generic, non-sensitive message to the user. If you need to signal specific conditions to the client, use predefined safe codes/flags instead of arbitrary exception text.

For this specific view (EntitySlideCheckView.post in apps/sage_intacct/views.py), the best minimal fix is:

  • Keep logging the exception server-side (possibly upgrading to logger.exception so a stack trace is captured for debugging).
  • Stop including str(e) in the response body.
  • Replace it with a generic, non-sensitive message such as "An internal error occurred while checking entity slide error" while still returning 'entity_slide_error': True so the client can react correctly.
  • Do not change the HTTP status code or structure of the JSON other than the textual message, to avoid breaking clients.

Changes needed:

  • In apps/sage_intacct/views.py, within EntitySlideCheckView.post, modify the final except Exception as e: block so that:
    • The log line no longer needs str(e) explicitly if we use logger.exception; otherwise, we can keep the current log but that is not required for the CodeQL fix.
    • The Response data uses a generic message string independent of e.

No new imports or helper methods are required; logging is already imported and configured.

Suggested changeset 1
apps/sage_intacct/views.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/sage_intacct/views.py b/apps/sage_intacct/views.py
--- a/apps/sage_intacct/views.py
+++ b/apps/sage_intacct/views.py
@@ -375,11 +375,14 @@
                 status=status.HTTP_400_BAD_REQUEST
             )
         except Exception as e:
-            logger.info(
-                'Entity slide error for workspace_id - %s, entity_id - %s: %s',
-                workspace_id, entity_id, str(e)
+            logger.exception(
+                'Entity slide error for workspace_id - %s, entity_id - %s',
+                workspace_id, entity_id
             )
             return Response(
-                data={'entity_slide_error': True, 'message': str(e)},
+                data={
+                    'entity_slide_error': True,
+                    'message': 'An internal error occurred while checking entity slide error'
+                },
                 status=status.HTTP_200_OK
             )
EOF
@@ -375,11 +375,14 @@
status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
logger.info(
'Entity slide error for workspace_id - %s, entity_id - %s: %s',
workspace_id, entity_id, str(e)
logger.exception(
'Entity slide error for workspace_id - %s, entity_id - %s',
workspace_id, entity_id
)
return Response(
data={'entity_slide_error': True, 'message': str(e)},
data={
'entity_slide_error': True,
'message': 'An internal error occurred while checking entity slide error'
},
status=status.HTTP_200_OK
)
Copilot is powered by AI and may make mistakes. Always verify output.
status=status.HTTP_200_OK
Comment on lines +377 to +384
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not expose raw exceptions in API responses.

Line 383 returns str(e) to clients, which can leak internal details. Also, Line 384 reports unexpected failures as HTTP 200.

Proposed fix
-        except Exception as e:
-            logger.info(
-                'Entity slide error for workspace_id - %s, entity_id - %s: %s',
-                workspace_id, entity_id, str(e)
-            )
-            return Response(
-                data={'entity_slide_error': True, 'message': str(e)},
-                status=status.HTTP_200_OK
-            )
+        except Exception:
+            logger.exception(
+                'Unexpected error while checking entity slide for workspace_id - %s, entity_id - %s',
+                workspace_id, entity_id
+            )
+            return Response(
+                data={'message': 'Unable to validate entity access at this time'},
+                status=status.HTTP_500_INTERNAL_SERVER_ERROR
+            )
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 383-383: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.

🪛 Ruff (0.15.2)

[warning] 377-377: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/sage_intacct/views.py` around lines 377 - 384, The except block that
currently logs and returns raw exception text should be changed to avoid
exposing internal errors: replace logger.info(...) with logger.exception(...) to
record the stack trace, and in the Response (the block returning
{'entity_slide_error': True, 'message': ...}) remove str(e) and return a generic
message (e.g., "Internal server error processing entity slide") and an
appropriate error status (e.g., status.HTTP_500_INTERNAL_SERVER_ERROR) instead
of HTTP 200; update the code around the except handler in
apps/sage_intacct/views.py where workspace_id/entity_id are handled to implement
these changes.

)
Loading