Skip to content

chore: update & optimize admin user flows#294

Open
vishnurk6247 wants to merge 5 commits into
developfrom
chore/rbac-updates
Open

chore: update & optimize admin user flows#294
vishnurk6247 wants to merge 5 commits into
developfrom
chore/rbac-updates

Conversation

@vishnurk6247

@vishnurk6247 vishnurk6247 commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • Refactor
    • Centralized admin authorization checks across the platform using a shared admin role helper for consistent access control.
    • Updated user and role/resource management to streamline admin handling and limit console-resource validation to non-admins.
    • Improved resource and role listing to support search, pagination, and total counts, with admins receiving full results while non-admins receive role-scoped data.
  • Bug Fixes
    • Improved Gmail OAuth handling: initialization now warns on incomplete credentials, while token retrieval still fails when credentials are missing.
    • Updated Superset authentication behavior: removed the prior missing-data_filters rejection for non-admins, retaining the later data_filters authorization/validation behavior.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Duplicated per-module check_admin functions are removed from plugins_module, auth_module, and llm_inference_config_module. A shared check_is_admin utility in user_management_module is updated to use new ADMIN_ROLE_NAME and SERVICE_AUTH_ROLE_ID constants. UserService gains get_all_resources and count_all_resources methods plus admin short-circuits in resource and lifecycle operations. Controllers across four modules are updated to call the centralized utility. Gmail credential validation is refactored to defer warnings from initialization to retrieval.

Changes

Admin Check Centralization and Resource Access

Layer / File(s) Summary
Admin constant and check_is_admin utility
user_management_module/.../constants/auth.py, user_management_module/.../utils/user_utils.py
Adds ADMIN_ROLE_NAME = 'admin' constant and refactors check_is_admin to resolve admin status via SERVICE_AUTH_ROLE_ID and ADMIN_ROLE_NAME instead of hardcoded strings; adds explanatory docstring.
UserService admin-aware resource queries
user_management_module/.../services/user_service.py
Expands imports to support SQL filtering; updates get_user_resources docstring; adds private _resource_filters helper and public get_all_resources and count_all_resources methods with optional scope/search/pagination. Implements admin short-circuit in get_user_role_for_scope that returns admin role id immediately when user has ADMIN_ROLE_NAME.
UserService admin-aware user lifecycle
user_management_module/.../services/user_service.py
reactivate_user computes is_reactivating_admin, skips console-resource validation for admin reactivations, and simplifies role assignment to always construct UserRole entries directly from input role ids.
User controller admin-aware operations
user_management_module/.../controllers/user_controller.py
create_user removes role_repository injection, computes is_creating_admin, guards console-resource validation for non-admin creation, and removes branched role assignment. get_resources endpoint injects user_service, computes is_admin, fetches either all dashboards (admin) or role-scoped dashboards plus data (non-admin), and returns resource object with lists.
Access controller admin check and resource pagination
user_management_module/.../controllers/access_controller.py
create_resource removes user_role_repository injection and UserRole mapping creation. get_resource adds search/limit/offset parameters, computes is_admin to gate access, calls get_all_resources, and returns response with total. get_role adds search/pagination parameters, applies search_filter, computes totals consistently, and returns {'roles': ..., 'total': ...}.
Plugins module consolidation to shared check_is_admin
plugins_module/.../services/datasource_services.py, plugins_module/.../controllers/datasource_controller.py, plugins_module/.../controllers/authenticator_controller.py
Removes check_admin function and auth imports from datasource_services. Updates datasource_controller to call check_is_admin across 15+ endpoints (add, update, delete, get, test, query, dynamic-query operations). Updates authenticator_controller to call check_is_admin across 7 endpoints.
auth_module and llm_inference_config_module migration
auth_module/.../controllers/superset_controller.py, llm_inference_config_module/.../controllers/llm_inference_config_controller.py
superset_controller removes local check_is_admin, imports shared utility, conditions resource fetch on is_admin (all resources for admins, user-scoped for non-admins), removes 400 validation branch for non-admin missing data_filters. llm_inference_config_controller removes local check_admin, imports check_is_admin, updates four endpoints to call shared helper.

Gmail Credential Validation Refactor

Layer / File(s) Summary
Gmail credential deferred validation
user_management_module/.../services/email_service.py
GmailEmailService logs a warning instead of raising at initialization when required OAuth credentials are incomplete. Credential validation is extracted to _assert_credentials() helper and invoked in get_credentials() before Credentials construction.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • vizsatiz

Poem

🐰 A rabbit once hopped through a field full of checks,
Each module had one—what a tangle of specs!
Now one little util rules all the land,
check_is_admin—one truth, single hand.
The admin hops free, skipping joins and their weight,
And Gmail logs gently—one authoritative gate! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the pull request, which is to centralize and optimize admin-check logic across multiple modules by removing duplicate implementations and using a shared utility.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/rbac-updates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
wavefront/server/modules/auth_module/auth_module/controllers/superset_controller.py (1)

56-62: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unreachable condition: data_filters and len(data_filters) < 1 is always False.

The condition if data_filters and len(data_filters) < 1 can never be True:

  • If data_filters is truthy (e.g., a non-empty list), then len(data_filters) >= 1
  • If data_filters is falsy (empty list or None), the first part short-circuits

This appears to be leftover from the removal of the 400 branch for non-admin missing data_filters mentioned in the AI summary. The intended check was likely if not data_filters or len(data_filters) < 1 or simply if not data_filters.

🐛 Proposed fix
-    if data_filters and len(data_filters) < 1:
+    if not data_filters:
         return JSONResponse(
             status_code=status.HTTP_403_FORBIDDEN,
             content=response_formatter.buildErrorResponse(
                 'Data access not set for user'
             ),
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/auth_module/auth_module/controllers/superset_controller.py`
around lines 56 - 62, The condition `if data_filters and len(data_filters) < 1:`
in the response block for checking data access is logically impossible and will
never execute. When data_filters is truthy, its length is always at least 1,
making the second part always false. Replace this condition with `if not
data_filters:` to correctly check if data_filters is empty or None, which
appears to be the intended behavior for denying access when no data filters are
set for the user.
wavefront/server/modules/llm_inference_config_module/llm_inference_config_module/controllers/llm_inference_config_controller.py (1)

107-137: ⚠️ Potential issue | 🟡 Minor

Add admin check to get_llm_inference_config endpoint or document the intentional design.

The endpoint GET /v1/llm-inference-configs/{config_id} lacks an admin check (lines 109-118), while all other endpoints in this controller—including the list endpoint GET /v1/llm-inference-configs (line 84)—require admin access. This creates an authorization inconsistency: users can retrieve a specific config by ID without admin privileges, but cannot list all configs.

Either add the admin check to align with the list endpoint and other operations, or if this is intentional design (allowing non-admins to read configs), document the reasoning and consider restricting the response to sanitize sensitive fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/llm_inference_config_module/llm_inference_config_module/controllers/llm_inference_config_controller.py`
around lines 107 - 137, The get_llm_inference_config endpoint lacks an admin
authorization check that is present in other endpoints of the same controller
(such as the list endpoint). Add an admin permission check at the beginning of
the get_llm_inference_config function to enforce consistent authorization across
all endpoints, similar to how other operations in the controller are protected.
Alternatively, if allowing non-admin access to specific configs is intentional,
add clear documentation explaining the design decision and consider sanitizing
sensitive fields from the response before returning it to the client.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@wavefront/server/modules/user_management_module/user_management_module/services/user_service.py`:
- Line 150: At line 150, replace the role ID comparison logic
`current_admin_role_id in new_user_data.role_id` with a name-based check that
verifies if any assigned role is actually an admin role. Query the database for
all role IDs where Role.name equals ADMIN_ROLE_NAME (following the same pattern
as used in get_user_role_for_scope at line 107), then check if any role ID in
new_user_data.role_id matches one of those admin role IDs using set
intersection. This ensures the logic correctly identifies admin role assignments
by name rather than relying on a specific role ID.

---

Outside diff comments:
In
`@wavefront/server/modules/auth_module/auth_module/controllers/superset_controller.py`:
- Around line 56-62: The condition `if data_filters and len(data_filters) < 1:`
in the response block for checking data access is logically impossible and will
never execute. When data_filters is truthy, its length is always at least 1,
making the second part always false. Replace this condition with `if not
data_filters:` to correctly check if data_filters is empty or None, which
appears to be the intended behavior for denying access when no data filters are
set for the user.

In
`@wavefront/server/modules/llm_inference_config_module/llm_inference_config_module/controllers/llm_inference_config_controller.py`:
- Around line 107-137: The get_llm_inference_config endpoint lacks an admin
authorization check that is present in other endpoints of the same controller
(such as the list endpoint). Add an admin permission check at the beginning of
the get_llm_inference_config function to enforce consistent authorization across
all endpoints, similar to how other operations in the controller are protected.
Alternatively, if allowing non-admin access to specific configs is intentional,
add clear documentation explaining the design decision and consider sanitizing
sensitive fields from the response before returning it to the client.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d0f8161-e302-4d8b-b3c1-b367b1763c37

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb1d9b and eb9ca86.

📒 Files selected for processing (10)
  • wavefront/server/modules/auth_module/auth_module/controllers/superset_controller.py
  • wavefront/server/modules/llm_inference_config_module/llm_inference_config_module/controllers/llm_inference_config_controller.py
  • wavefront/server/modules/plugins_module/plugins_module/controllers/authenticator_controller.py
  • wavefront/server/modules/plugins_module/plugins_module/controllers/datasource_controller.py
  • wavefront/server/modules/plugins_module/plugins_module/services/datasource_services.py
  • wavefront/server/modules/user_management_module/user_management_module/constants/auth.py
  • wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py
  • wavefront/server/modules/user_management_module/user_management_module/controllers/user_controller.py
  • wavefront/server/modules/user_management_module/user_management_module/services/user_service.py
  • wavefront/server/modules/user_management_module/user_management_module/utils/user_utils.py
💤 Files with no reviewable changes (1)
  • wavefront/server/modules/plugins_module/plugins_module/services/datasource_services.py

current_admin_role_id: str,
response_formatter: ResponseFormatter,
) -> JSONResponse:
is_reactivating_admin = current_admin_role_id in new_user_data.role_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there can be multiple admin roles or if admin role is unique
rg -n "ADMIN_ROLE_NAME" --type py -C 3
# Check how admin roles are created/managed
ast-grep --pattern 'Role($$$name=$$$"admin"$$$)'

Repository: rootflo/wavefront

Length of output: 4420


🏁 Script executed:

#!/bin/bash
# Get the full reactivate_user method context around line 150
sed -n '140,160p' wavefront/server/modules/user_management_module/user_management_module/services/user_service.py

# Also check the method signature and how new_user_data is structured
sed -n '143,155p' wavefront/server/modules/user_management_module/user_management_module/services/user_service.py

# Check what new_user_data.role_id actually is
rg -A 5 "new_user_data\s*=" wavefront/server/modules/user_management_module/user_management_module/services/user_service.py | head -20

Repository: rootflo/wavefront

Length of output: 1379


🏁 Script executed:

#!/bin/bash
# Check the NewUser model/dataclass to confirm role_id is a list
rg -A 10 "class NewUser" wavefront/server/modules/user_management_module/ --type py

# Check if there's any constraint on unique role names
rg -n "role.*name.*unique\|unique.*role.*name" wavefront/ --type py -i

# Check how roles are created/managed to see if multiple admin roles are possible
rg -B 3 -A 3 "name.*=.*['\"]admin['\"]" wavefront/ --type py

# Check Role model for unique constraints
fd -e py -path "*models*" -name "*role*" | xargs grep -l "class Role" | head -3

Repository: rootflo/wavefront

Length of output: 6095


🏁 Script executed:

#!/bin/bash
# Check how is_reactivating_admin is used in reactivate_user after line 150
sed -n '150,200p' wavefront/server/modules/user_management_module/user_management_module/services/user_service.py

# Check the Role model to see if name is unique
fd -e py -name "*.py" | xargs grep -l "class Role" | head -3

Repository: rootflo/wavefront

Length of output: 2841


🏁 Script executed:

#!/bin/bash
# Check the Role model definition
find . -name "*.py" -type f | xargs grep -l "class Role" | xargs grep -A 20 "class Role"

# Check if role.name has a unique constraint
find . -name "*.py" -type f | xargs grep -B 5 -A 15 "class Role"

Repository: rootflo/wavefront

Length of output: 10845


🏁 Script executed:

#!/bin/bash
# Check if there are any unique constraints on the role name field
cat ./wavefront/server/modules/db_repo_module/db_repo_module/models/role.py

# Check how roles are created to see if name uniqueness is enforced elsewhere
grep -r "unique.*role\|role.*unique" wavefront/server/modules/db_repo_module --include="*.py" -i

# Check how the admin role is managed during initialization/migration
grep -B 5 -A 5 "admin_role_id" wavefront/server/modules/db_repo_module/db_repo_module/alembic/versions/*.py | head -50

Repository: rootflo/wavefront

Length of output: 5386


Use name-based check to detect if any assigned role is an admin role.

Line 150's check current_admin_role_id in new_user_data.role_id skips console resource validation only if the caller's specific role ID is assigned to the new user. This logic is fragile because:

  1. The Role.name field has no unique constraint—multiple roles could theoretically have name='admin'
  2. It doesn't verify if any assigned role is actually an admin role; it only checks if the caller's specific role ID is present
  3. If a non-admin role ID is accidentally passed as current_admin_role_id, console validation would be incorrectly skipped

Instead, query for admin roles by name (as in get_user_role_for_scope at line 107), checking if any role in new_user_data.role_id has name == ADMIN_ROLE_NAME:

Suggested approach
# Query for admin role IDs
admin_stmt = (
    select(Role.id)
    .where(Role.name == ADMIN_ROLE_NAME)
)
admin_result = await session.execute(admin_stmt)
admin_role_ids = {str(role_id) for role_id in admin_result.scalars().all()}

# Check if any assigned role is an admin role
is_reactivating_admin = bool(set(new_user_data.role_id) & admin_role_ids)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/user_management_module/user_management_module/services/user_service.py`
at line 150, At line 150, replace the role ID comparison logic
`current_admin_role_id in new_user_data.role_id` with a name-based check that
verifies if any assigned role is actually an admin role. Query the database for
all role IDs where Role.name equals ADMIN_ROLE_NAME (following the same pattern
as used in get_user_role_for_scope at line 107), then check if any role ID in
new_user_data.role_id matches one of those admin role IDs using set
intersection. This ensures the logic correctly identifies admin role assignments
by name rather than relying on a specific role ID.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@wavefront/server/modules/user_management_module/user_management_module/services/email_service.py`:
- Around line 150-156: Change the exception type raised in the
_assert_credentials method from generic Exception to ValueError. The downstream
error handler in user_controller.py (line 486) specifically catches ValueError
for credential validation failures, and using the correct exception type ensures
the error handling contract is properly maintained.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 495f7f3c-0c93-4b47-9f2c-2a247896e912

📥 Commits

Reviewing files that changed from the base of the PR and between eb9ca86 and 1f819c2.

📒 Files selected for processing (1)
  • wavefront/server/modules/user_management_module/user_management_module/services/email_service.py

Comment on lines +150 to 156
def _assert_credentials(self):
if not all(
[self.client_id, self.client_secret, self.refresh_token, self.email_sender]
):
raise Exception(
'Gmail OAuth requires client_id, client_secret, refresh_token, and email_sender'
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find catch blocks that expect ValueError from email service operations.

# Search for ValueError exception handlers near email service calls
rg -nP -A5 -B5 'except\s+ValueError' --type=py | rg -A5 -B5 'email_service'

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

# First, let's locate the user_controller.py file mentioned in the review
fd -t f 'user_controller.py' -x cat -n {} | head -50

Repository: rootflo/wavefront

Length of output: 1958


🏁 Script executed:

# Check the email_service.py file to see the _assert_credentials method and its context
fd -t f 'email_service.py' -x wc -l {}

Repository: rootflo/wavefront

Length of output: 164


🏁 Script executed:

# Find all files in user_management_module to understand the structure
fd -t f . 'wavefront/server/modules/user_management_module' | head -20

Repository: rootflo/wavefront

Length of output: 1904


🏁 Script executed:

# Let's check user_controller.py around the lines mentioned (410-493)
cat -n wavefront/server/modules/user_management_module/user_management_module/controllers/user_controller.py | sed -n '400,500p'

Repository: rootflo/wavefront

Length of output: 4538


🏁 Script executed:

# Let's search for all calls to _assert_credentials to see how it's used
rg -n '_assert_credentials' --type=py -A3 -B3

Repository: rootflo/wavefront

Length of output: 1898


🏁 Script executed:

# Let's also search for where email_service is used and what exceptions are caught
rg -n 'email_service' --type=py -A5 -B5

Repository: rootflo/wavefront

Length of output: 20649


🏁 Script executed:

# Let's look at the send_forget_password_email method in email_service.py
rg -n 'send_forget_password_email' --type=py -A15

Repository: rootflo/wavefront

Length of output: 12637


🏁 Script executed:

# Let's also look at the get_credentials method to confirm it calls _assert_credentials
rg -n 'def get_credentials' --type=py -A15

Repository: rootflo/wavefront

Length of output: 5675


🏁 Script executed:

# Let's check the _get_gmail_service method to see if it calls get_credentials
rg -n '_get_gmail_service' --type=py -A20

Repository: rootflo/wavefront

Length of output: 7042


🏁 Script executed:

# Let's also read the full email_service.py to understand both implementations completely
cat -n wavefront/server/modules/user_management_module/user_management_module/services/email_service.py | head -100

Repository: rootflo/wavefront

Length of output: 4302


🏁 Script executed:

# Let's see the complete send_forget_password_email method for GmailEmailService with its exception handling
sed -n '177,210p' wavefront/server/modules/user_management_module/user_management_module/services/email_service.py

Repository: rootflo/wavefront

Length of output: 1568


🏁 Script executed:

# Let's also check if there's an except clause for send_forget_password_email
sed -n '177,220p' wavefront/server/modules/user_management_module/user_management_module/services/email_service.py

Repository: rootflo/wavefront

Length of output: 1800


Change _assert_credentials() to raise ValueError instead of generic Exception.

The method raises a generic Exception, but the reset-password endpoint in user_controller.py (line 486) specifically catches ValueError to handle credential validation failures. This exception type mismatch breaks the error handling contract—credential failures should be raised as ValueError to be properly caught by downstream handlers expecting that specific type.

Proposed fix
    def _assert_credentials(self):
        if not all(
            [self.client_id, self.client_secret, self.refresh_token, self.email_sender]
        ):
-            raise Exception(
+            raise ValueError(
                'Gmail OAuth requires client_id, client_secret, refresh_token, and email_sender'
            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/user_management_module/user_management_module/services/email_service.py`
around lines 150 - 156, Change the exception type raised in the
_assert_credentials method from generic Exception to ValueError. The downstream
error handler in user_controller.py (line 486) specifically catches ValueError
for credential validation failures, and using the correct exception type ensures
the error handling contract is properly maintained.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
wavefront/server/modules/user_management_module/user_management_module/controllers/user_controller.py (1)

81-81: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Same fragile admin detection as reactivate_user.

The check role_id in new_user.role_id only verifies whether the caller's specific role ID is being assigned, not whether any assigned role is actually an admin role. This mirrors the issue flagged in user_service.py line 216. Consider applying a name-based admin check consistently in both locations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/user_controller.py`
at line 81, The admin detection check in the method containing the
`is_creating_admin = role_id in new_user.role_id` statement is fragile because
it only verifies whether the caller's specific role ID is being assigned, rather
than checking if any of the assigned roles are actually admin roles by name.
Replace this check with a name-based admin role validation that inspects the
actual role names in the new_user.role_id assignment to determine if any of them
designate admin privileges, ensuring consistent admin detection logic across
both this location and the similar check in the reactivate_user method.
wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py (3)

91-101: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Redundant session.commit() inside session.begin() context manager.

The async with session.begin() context manager automatically commits on successful exit. The explicit await session.commit() on line 101 is redundant and may cause warnings in some SQLAlchemy configurations.

Proposed fix
     async with resource_repository.session() as session:
         async with session.begin():
             await resource_repository.create_all(
                 resources, replace=True, session=session
             )
             await role_repository.create_all(roles, replace=True, session=session)
             await role_resource_repository.create_all(
                 role_resources, replace=True, session=session
             )

-            await session.commit()
-
             resource_count = len(payload.resources)
+
+        return JSONResponse(
+            status_code=status.HTTP_201_CREATED,
+            content=response_formatter.buildSuccessResponse(
+                data={'message': f'Created {resource_count} resources successfully'}
+            ),
+        )
-            return JSONResponse(
-                status_code=status.HTTP_201_CREATED,
-                content=response_formatter.buildSuccessResponse(
-                    data={'message': f'Created {resource_count} resources successfully'}
-                ),
-            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py`
around lines 91 - 101, The explicit `await session.commit()` call is redundant
because the `async with session.begin()` context manager automatically commits
when exiting the block successfully. Remove the `await session.commit()` line at
the end of the session.begin() block (after the
role_resource_repository.create_all() call) to eliminate the redundancy and
avoid potential SQLAlchemy warnings about double commits.

266-276: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Column validation raises AttributeError instead of returning 400.

getattr(Role, item) raises AttributeError for non-existent attributes rather than returning a falsy value. The current check if not getattr(Role, item) will never catch invalid columns—it will crash with an unhandled exception, resulting in a 500 error instead of the intended 400 response.

Proposed fix using hasattr
     item_to_select = select_item.split(',') if select_item else []
     valid_columns = []
     for item in item_to_select:
-        if not getattr(Role, item):
+        if not hasattr(Role, item):
             return JSONResponse(
                 status_code=status.HTTP_400_BAD_REQUEST,
                 content=response_formatter.buildErrorResponse(
                     error=f'Invalid column {item}'
                 ),
             )
         valid_columns.append(getattr(Role, item))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py`
around lines 266 - 276, The column validation logic uses getattr(Role, item)
without a default value, which raises AttributeError for non-existent attributes
instead of returning a falsy value. This causes an unhandled exception and 500
error instead of the intended 400 response. Replace the getattr(Role, item)
check in the condition with hasattr(Role, item) to safely verify the attribute
exists before accessing it, ensuring invalid columns are properly caught and the
JSONResponse with HTTP 400 is returned as intended.

206-227: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Type mismatch: scopes annotation is list[str] but service expects List[ResourceScope].

The scopes parameter is annotated as list[str], but the default uses ResourceScope enum values and user_service.get_all_resources expects List[ResourceScope]. When clients pass query parameters (e.g., ?scopes=DASHBOARD), FastAPI deserializes them as strings, not enum values, which may cause filtering to fail silently or behave incorrectly.

Proposed fix
-    scopes: list[str] = Query(
+    scopes: list[ResourceScope] = Query(
         default=[ResourceScope.DASHBOARD, ResourceScope.CONSOLE],
         description='The scopes of the resources to fetch',
     ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py`
around lines 206 - 227, The scopes parameter type annotation is list[str] but
should be List[ResourceScope] or list[ResourceScope] to match what the
user_service.get_all_resources method expects and to properly align with the
default values using ResourceScope enum members like ResourceScope.DASHBOARD and
ResourceScope.CONSOLE. Change the type annotation from list[str] to the
appropriate ResourceScope collection type so that FastAPI will automatically
deserialize incoming query string parameters into ResourceScope enum values
rather than leaving them as strings, ensuring the service receives the correct
type and filtering works as intended.
wavefront/server/modules/auth_module/auth_module/controllers/superset_controller.py (1)

59-65: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Authorization check is unreachable due to always-false condition.

The condition data_filters and len(data_filters) < 1 can never be true:

  • If data_filters is empty ([]), it's falsy → condition short-circuits to False
  • If data_filters is non-empty, len(data_filters) < 1 is False → condition is False

With the prior 400 validation branch removed, this unreachable 403 check is the only protection for non-admin users without data access. They will incorrectly proceed to guest token generation.

🔒 Proposed fix
-    if data_filters and len(data_filters) < 1:
+    if not is_admin and not data_filters:
         return JSONResponse(
             status_code=status.HTTP_403_FORBIDDEN,
             content=response_formatter.buildErrorResponse(
                 'Data access not set for user'
             ),
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/auth_module/auth_module/controllers/superset_controller.py`
around lines 59 - 65, The authorization check at the superset_controller.py file
uses a flawed logical condition with `and` that makes it unreachable. When
data_filters is empty it's falsy (short-circuiting the condition to False), and
when non-empty the length check becomes False. Change the logical operator from
`and` to `or` in the condition so it properly validates whether data_filters is
either empty or has zero elements, ensuring non-admin users without data access
are correctly blocked from proceeding to guest token generation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@wavefront/server/modules/auth_module/auth_module/controllers/superset_controller.py`:
- Around line 59-65: The authorization check at the superset_controller.py file
uses a flawed logical condition with `and` that makes it unreachable. When
data_filters is empty it's falsy (short-circuiting the condition to False), and
when non-empty the length check becomes False. Change the logical operator from
`and` to `or` in the condition so it properly validates whether data_filters is
either empty or has zero elements, ensuring non-admin users without data access
are correctly blocked from proceeding to guest token generation.

In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py`:
- Around line 91-101: The explicit `await session.commit()` call is redundant
because the `async with session.begin()` context manager automatically commits
when exiting the block successfully. Remove the `await session.commit()` line at
the end of the session.begin() block (after the
role_resource_repository.create_all() call) to eliminate the redundancy and
avoid potential SQLAlchemy warnings about double commits.
- Around line 266-276: The column validation logic uses getattr(Role, item)
without a default value, which raises AttributeError for non-existent attributes
instead of returning a falsy value. This causes an unhandled exception and 500
error instead of the intended 400 response. Replace the getattr(Role, item)
check in the condition with hasattr(Role, item) to safely verify the attribute
exists before accessing it, ensuring invalid columns are properly caught and the
JSONResponse with HTTP 400 is returned as intended.
- Around line 206-227: The scopes parameter type annotation is list[str] but
should be List[ResourceScope] or list[ResourceScope] to match what the
user_service.get_all_resources method expects and to properly align with the
default values using ResourceScope enum members like ResourceScope.DASHBOARD and
ResourceScope.CONSOLE. Change the type annotation from list[str] to the
appropriate ResourceScope collection type so that FastAPI will automatically
deserialize incoming query string parameters into ResourceScope enum values
rather than leaving them as strings, ensuring the service receives the correct
type and filtering works as intended.

In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/user_controller.py`:
- Line 81: The admin detection check in the method containing the
`is_creating_admin = role_id in new_user.role_id` statement is fragile because
it only verifies whether the caller's specific role ID is being assigned, rather
than checking if any of the assigned roles are actually admin roles by name.
Replace this check with a name-based admin role validation that inspects the
actual role names in the new_user.role_id assignment to determine if any of them
designate admin privileges, ensuring consistent admin detection logic across
both this location and the similar check in the reactivate_user method.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ecc0167-0008-403e-b76a-6aac232c0c5b

📥 Commits

Reviewing files that changed from the base of the PR and between 1f819c2 and 46bb947.

📒 Files selected for processing (4)
  • wavefront/server/modules/auth_module/auth_module/controllers/superset_controller.py
  • wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py
  • wavefront/server/modules/user_management_module/user_management_module/controllers/user_controller.py
  • wavefront/server/modules/user_management_module/user_management_module/services/user_service.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py`:
- Around line 206-215: The scopes parameter in the controller function accepts
strings from the HTTP query (list[str]) but the service method expects
ResourceScope enum members (List[ResourceScope]). Locate where scopes are
extracted from the query parameter in the function signature and add conversion
logic to transform the string values into ResourceScope enum members before
passing them to the user_service.get_all_resources() call. This conversion
should handle the case where scopes is None or empty, using a pattern like
iterating through the scopes list and constructing ResourceScope enum values
from each string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 312f6a92-d4af-45f6-9686-eb8e3d9e58b0

📥 Commits

Reviewing files that changed from the base of the PR and between 46bb947 and 647080a.

📒 Files selected for processing (1)
  • wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py

Comment on lines +206 to +215
scopes: Optional[list[str]] = Query(
default=None,
description='Scopes of the resources to fetch (all scopes when omitted)',
),
search: Optional[str] = Query(
None, description='Search by key, value or description'
),
limit: Optional[int] = Query(
None, description='Maximum number of resources to return (all when omitted)'
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ResourceScope enum backing type to confirm string coercion works
echo "=== Checking ResourceScope enum definition ==="
rg -n -A10 'class ResourceScope' --type py

echo -e "\n=== Checking _resource_filters implementation for scope handling ==="
ast-grep --pattern $'def _resource_filters($$$):
    $$$
'

Repository: rootflo/wavefront

Length of output: 1322


🏁 Script executed:

#!/bin/bash
# Find get_all_resources function signature
echo "=== Finding get_all_resources function ==="
rg -n "def get_all_resources" --type py -A 5

echo -e "\n=== Finding _resource_filters function ==="
rg -n "def _resource_filters" --type py -A 15

echo -e "\n=== Checking imports in access_controller.py to see what ResourceScope is used ==="
head -50 wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py | grep -n "import\|from"

Repository: rootflo/wavefront

Length of output: 4670


Type annotation mismatch requires fixing: scopes parameter accepts list[str] but service expects List[ResourceScope].

The controller accepts scopes: Optional[list[str]] from the HTTP query and passes it directly to user_service.get_all_resources(scopes=scopes, ...), which expects scopes: Optional[List[ResourceScope]]. While ResourceScope(str, Enum) means enum values are strings at runtime and Resource.scope.in_(scopes) will accept string values, the type annotation mismatch indicates missing validation/conversion logic.

Convert the query string values to ResourceScope enum members before passing to the service (e.g., using a FastAPI validator or list comprehension like [ResourceScope(s) for s in scopes if s]).

Regarding B008 warning: This is a false positive. FastAPI requires Query() calls in function parameter defaults for dependency injection—this is the documented pattern.

🧰 Tools
🪛 Ruff (0.15.15)

[warning] 206-209: Do not perform function call Query in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py`
around lines 206 - 215, The scopes parameter in the controller function accepts
strings from the HTTP query (list[str]) but the service method expects
ResourceScope enum members (List[ResourceScope]). Locate where scopes are
extracted from the query parameter in the function signature and add conversion
logic to transform the string values into ResourceScope enum members before
passing them to the user_service.get_all_resources() call. This conversion
should handle the case where scopes is None or empty, using a pattern like
iterating through the scopes list and constructing ResourceScope enum values
from each string.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py (1)

252-254: 💤 Low value

Type annotation and default value inconsistency for scopes.

The default value [ResourceScope.CONSOLE] uses the enum type, but the type annotation declares list[str]. This works at runtime because ResourceScope extends str, but creates inconsistency for type checkers and IDE tooling.

Consider using Optional[list[str]] with a None default and handling the default value inside the function, or explicitly document that enum values are acceptable:

-    scopes: list[str] = Query(
-        default=[ResourceScope.CONSOLE], description='The scopes of the roles to fetch'
-    ),
+    scopes: Optional[list[str]] = Query(
+        default=None, description='The scopes of the roles to fetch (defaults to CONSOLE)'
+    ),

Then apply the default inside the function:

scopes = scopes or [ResourceScope.CONSOLE.value]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py`
around lines 252 - 254, The `scopes` parameter in the access_controller.py file
has a type annotation inconsistency where it declares `list[str]` but uses
`ResourceScope.CONSOLE` (an enum) as the default value. To fix this, change the
type annotation for the `scopes` parameter to `Optional[list[str]]` with a
default value of `None`, then add logic inside the function body to set `scopes`
to `[ResourceScope.CONSOLE.value]` when the parameter is `None` (using a pattern
like `scopes = scopes or [ResourceScope.CONSOLE.value]`). This ensures the type
annotation accurately reflects what the function accepts and properly handles
the default case inside the function logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py`:
- Around line 252-254: The `scopes` parameter in the access_controller.py file
has a type annotation inconsistency where it declares `list[str]` but uses
`ResourceScope.CONSOLE` (an enum) as the default value. To fix this, change the
type annotation for the `scopes` parameter to `Optional[list[str]]` with a
default value of `None`, then add logic inside the function body to set `scopes`
to `[ResourceScope.CONSOLE.value]` when the parameter is `None` (using a pattern
like `scopes = scopes or [ResourceScope.CONSOLE.value]`). This ensures the type
annotation accurately reflects what the function accepts and properly handles
the default case inside the function logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9dd753ca-78de-4ec9-b730-780a62435d93

📥 Commits

Reviewing files that changed from the base of the PR and between 647080a and 606ef60.

📒 Files selected for processing (1)
  • wavefront/server/modules/user_management_module/user_management_module/controllers/access_controller.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant