Skip to content

Conversation

@jordan-umusu
Copy link
Collaborator

@jordan-umusu jordan-umusu commented Jan 29, 2026

Summary by cubic

Adds a full RBAC system with service layer, REST APIs, and DB models for scopes, roles, groups, and role assignments. Seeds default scopes/roles and computes effective user scopes for access checks and the UI.

  • New Features

    • DB: Add RBAC tables; remove role from memberships/invitations; link roles via assignments.
    • Seeding: Seed system scopes and preset roles per org at startup; seed registry scopes during registry sync.
    • Service/API: Implement RBACService with CRUD and effective scope calculation; add EE routers for scopes/roles/groups/assignments and an OSS user scopes endpoint; protect routes with OrgUserRole and scope checks.
    • Auth/Audit: Add scope-based authorization (ScopeDeniedError) and rbac_* audit resources; expose RBAC feature flag.
    • Frontend: Generate TS schemas/types/services; update invites/members UI to use role_slug/role_name.
  • Migration

    • Run: uv run alembic upgrade head
    • Notes: Creates RBAC tables and drops role columns from membership/invitation tables with data migration to Role-based assignments.

Written for commit ac3b77b. Summary will update on new commits.

Copy link
Collaborator Author

jordan-umusu commented Jan 29, 2026

@jordan-umusu
Copy link
Collaborator Author

@cubic review

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 2, 2026

@cubic review

@jordan-umusu I have started the AI code review. It will take a few minutes to complete.

@jordan-umusu
Copy link
Collaborator Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

sa.UniqueConstraint(
"user_id",
"organization_id",
name=op.f("uq_user_role_assignment_user_id_organization_id"),
),

P1 Badge Allow multiple role assignments per org

The new user_role_assignment table enforces a unique constraint on (user_id, organization_id), which prevents a user from having more than one assignment within the same org. That makes it impossible to add a workspace-specific role for a user who already has an org-level role (the common case), e.g. accepting a workspace invitation will attempt to insert a workspace assignment and will hit a uniqueness violation. If the intent is “one role per workspace plus an optional org-wide assignment,” this constraint should be removed or replaced with a (user_id, workspace_id) unique plus the existing partial index for workspace_id IS NULL.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 22 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="tracecat/db/models.py">

<violation number="1" location="tracecat/db/models.py:475">
P2: The new bidirectional relationship is incomplete: User.role_assignments specifies back_populates="user", but UserRoleAssignment.user does not define the reciprocal back_populates. This can lead to SQLAlchemy warnings and unsynchronized relationship state when assigning roles. Add back_populates="role_assignments" on UserRoleAssignment.user (or remove back_populates here if you want a unidirectional relationship).</violation>
</file>

<file name="packages/tracecat-ee/tracecat_ee/rbac/router.py">

<violation number="1" location="packages/tracecat-ee/tracecat_ee/rbac/router.py:166">
P1: This will raise `TypeError: got multiple values for keyword argument 'scopes'` at runtime. When SQLAlchemy loads `r.scopes`, it's stored in `r.__dict__`, so the unpacked dict already contains `scopes`, conflicting with the explicit `scopes=` argument. Use explicit attribute mapping like `get_role` does.</violation>
</file>

<file name="tests/unit/test_rbac_service.py">

<violation number="1" location="tests/unit/test_rbac_service.py:55">
P2: OrganizationMembership no longer accepts a `role` argument. This fixture will raise a TypeError on instantiation, so the test setup fails before the RBAC service is exercised. Remove the role parameter or assign roles via the RBAC assignment tables.</violation>
</file>

<file name="packages/tracecat-ee/tracecat_ee/rbac/service.py">

<violation number="1" location="packages/tracecat-ee/tracecat_ee/rbac/service.py:380">
P1: Security: The comment states this should verify user belongs to the organization, but the query only checks if the user exists globally. This could allow adding users from other organizations to groups. Consider joining with an org membership table to verify the user belongs to `self.organization_id`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@jordan-umusu jordan-umusu force-pushed the feat/rbac-scope-seeding branch from f0f545f to 1b379d9 Compare February 2, 2026 22:34
@jordan-umusu jordan-umusu force-pushed the feat/rbac-scope-seeding branch from 1b379d9 to 7fc9fa9 Compare February 2, 2026 22:57
@jordan-umusu jordan-umusu marked this pull request as ready for review February 4, 2026 03:54
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef01108209

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 17 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/tracecat-ee/tracecat_ee/rbac/service.py">

<violation number="1" location="packages/tracecat-ee/tracecat_ee/rbac/service.py:628">
P1: Security: Missing organization membership verification. This only checks if the user exists globally, but doesn't verify they belong to the current organization. This could allow role assignment to users outside the organization. The `add_group_member` method correctly checks `OrganizationMembership` - this should follow the same pattern.</violation>
</file>

<file name="tracecat/db/models.py">

<violation number="1" location="tracecat/db/models.py:481">
P2: Duplicate `role_assignments` relationship overrides the earlier attribute definition. Remove the duplicate block to avoid confusion and accidental overrides if parameters diverge later.</violation>
</file>

<file name="tracecat/organization/router.py">

<violation number="1" location="tracecat/organization/router.py:146">
P3: `scalar_one_or_none()` drops the RoleModel columns from a multi-column select, leaving the joins/columns unused and misleading. Either select only `User` or switch back to `result.tuples().one_or_none()` to match the multi-column query.

(Based on your team's feedback about using result.tuples() for multi-column selects.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@jordan-umusu jordan-umusu force-pushed the feat/rbac-scope-seeding branch from 7fc9fa9 to d685936 Compare February 4, 2026 14:45
@jordan-umusu jordan-umusu force-pushed the feat/rbac-scope-seeding branch 2 times, most recently from cdbf316 to 69e5c60 Compare February 5, 2026 18:05
@jordan-umusu jordan-umusu force-pushed the feat/rbac-scope-seeding branch from 998666b to 39c23c9 Compare February 5, 2026 19:02
@jordan-umusu jordan-umusu added the enterprise Enterprise Edition features and functionality label Feb 5, 2026
@jordan-umusu jordan-umusu changed the title feat(rbac): add RBAC service and API routers feat(rbac): Add RBAC service and API routers Feb 5, 2026
@jordan-umusu jordan-umusu force-pushed the feat/rbac-scope-seeding branch from 39c23c9 to 5dcdedf Compare February 9, 2026 15:14
@jordan-umusu jordan-umusu changed the base branch from feat/rbac-scope-seeding to graphite-base/1986 February 9, 2026 16:54
@jordan-umusu jordan-umusu changed the base branch from graphite-base/1986 to feat/rbac-scope-seeding February 9, 2026 21:20
@jordan-umusu jordan-umusu changed the base branch from feat/rbac-scope-seeding to graphite-base/1986 February 9, 2026 21:44
@jordan-umusu jordan-umusu changed the base branch from graphite-base/1986 to feat/rbac-scope-seeding February 9, 2026 22:40
Comment on lines +197 to +208
class GroupAssignmentRead(BaseModel):
"""Read schema for a group assignment."""

model_config = ConfigDict(from_attributes=True)

id: UUID
organization_id: UUID
group_id: UUID
workspace_id: UUID | None = None
role_id: UUID
assigned_at: datetime
assigned_by: UUID | None = None
Copy link
Contributor

Choose a reason for hiding this comment

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

naming consistency with db model i.e. GroupRoleAssignment*

Comment on lines +61 to +70
conditions = [Scope.organization_id == self.organization_id]

if include_system:
# Include system scopes (organization_id IS NULL)
conditions = [
(Scope.organization_id == self.organization_id)
| (Scope.organization_id.is_(None))
]

stmt = select(Scope).where(*conditions)
Copy link
Contributor

Choose a reason for hiding this comment

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

does this have to be wrapped in a list?

return scope

@audit_log(resource_type="rbac_scope", action="delete", resource_id_attr="scope_id")
async def delete_scope(self, scope_id: UserID) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

wrong type?

@audit_log(resource_type="rbac_role", action="update", resource_id_attr="role_id")
async def update_role(
self,
role_id: UserID,
Copy link
Contributor

Choose a reason for hiding this comment

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

wrong type?

Comment on lines +500 to +501
await self.get_group(group_id)
await self.get_role(role_id)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: prefer to check existence explicitly instead of fetch and discard

service = RBACService(session, role=role)
scopes = await service.list_scopes(include_system=include_system, source=source)
return ScopeList(
items=[ScopeRead.model_validate(s) for s in scopes],
Copy link
Contributor

Choose a reason for hiding this comment

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

if you have ScopeRead inherit from Schema it comes with a Schema.list_adapter() classmethod that lets us do this but with a single TypeAdapter call

# User Scopes Router (OSS - needed for ScopeProvider)
# =============================================================================

user_scopes_router = APIRouter(prefix="/users/me", tags=["users"])
Copy link
Contributor

Choose a reason for hiding this comment

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

does this overwrite /users/me in the fastapi users router?

UserRoleAssignment,
)
from tracecat.db.models import (
Role as RoleModel,
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe DBRole is more consistent with how we did DBChatMessage

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

Labels

enterprise Enterprise Edition features and functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants