Skip to content

feat: implement Log action for custom role permissions.#145

Open
Suryanshu185 wants to merge 3 commits into
mainfrom
feat/custom-roles-impl-log
Open

feat: implement Log action for custom role permissions.#145
Suryanshu185 wants to merge 3 commits into
mainfrom
feat/custom-roles-impl-log

Conversation

@Suryanshu185

@Suryanshu185 Suryanshu185 commented Oct 27, 2025

Copy link
Copy Markdown
Contributor

Add Log action to the system to enable selective audit logging for sensitive operations. The Log action allows access while generating audit trail entries.
Changes:

  • Add ROLE_PERMISSION_ACTION_LOG enum to protobuf definition (value 3)
  • Add RolePermissionActionLog constant to table package
  • Update permission evaluation to track log actions separately
  • Modify evaluateCustomRolePermissions to return (allowed, shouldLog) tuple - Add audit logging in performOrgUnitRoleCheck when Log action matches

Summary by CodeRabbit

  • New Features
    • Add CRUD endpoints to create, get, update, and delete custom org‑unit roles.
    • Introduce fine‑grained permission model with resource matching and Allow/Deny/Log actions.
    • Role listings now include built‑in and custom roles with metadata (type, displayName, created, createdBy).
    • Gateway enforces custom role permissions during access checks and logs decisions for auditability.

- Add org-unit-role.proto with 5 RPC methods
- Generate gRPC/gateway code and routes
- Add stub server implementation
- Update Swagger documentation
- ListOrgUnitRoles returns built-in roles (admin, auditor, default)
- CRUD operations return Unimplemented status

Signed-off-by: suryanshu185 <suryanshu.gupta@kluisz.ai>
Implement comprehensive custom role management system for organization
units with fine-grained permission-based access control (PBAC) and
response filtering capabilities.

Core Features:
- Full CRUD operations for custom roles (Create, Read, Update, Delete)
- Org-unit scoped roles with tenant isolation
- Dynamic role validation for user assignments
- Soft delete with binding checks (prevents deletion if users assigned)
- Response filtering for list operations based on role permissions

Signed-off-by: suryanshu185 <suryanshu.gupta@kluisz.ai>
@coderabbitai

coderabbitai Bot commented Oct 27, 2025

Copy link
Copy Markdown

Walkthrough

Adds support for org-unit scoped custom roles: protobuf RPCs and HTTP routes for CRUD, gateway RBAC evaluation for custom-role permissions, a persistent OrgUnitCustomRole table with soft-delete/binding checks and event logging, server handlers for role lifecycle, and initialization wiring.

Changes

Cohort / File(s) Summary
API Protos & Routes
api/org-unit-role.proto, api/org-unit-role.pb.route.go
Added RPCs CreateCustomRole, UpdateCustomRole, GetCustomRole, DeleteCustomRole with HTTP mappings and per-RPC metadata; extended OrgUnitRolesListEntry with type/displayName/created/createdBy; new enums/messages for ResourceMatchCriteria, RolePermissionAction, ResourceMatch, RolePermission and request/response messages; added four route registrations.
API Docs (Swagger)
api/swagger/apidocs.swagger.json
Added CRUD endpoint definitions and request/response schemas for custom roles; introduced definitions for resource match, permission enums/actions; extended org unit roles list entry schema with new metadata fields.
Gateway Routing & RBAC
pkg/gateway/routes.go, pkg/gateway/server.go
routeData extended with resource and verb; matchRoute signature updated to return keys/values; gateway wired to ouCustomRoleTbl; added custom role permission evaluation (resource/instance/verb matching) with support for exact/prefix/suffix/regex/wildcard and logging; extracts resourceInstance from URL.
Server: Org Unit Role Handlers
pkg/server/org-unit-role.go
Added OrgUnitCustomRoleTable field to server; implemented CreateCustomRole, UpdateCustomRole, GetCustomRole, DeleteCustomRole; List includes built-in + custom roles; conversion helpers between proto and table types; request validation and error handling.
Server: Org Unit User
pkg/server/org-unit-user.go
Added customRoleTable field and isValidRole helper to validate built-in or custom roles; integrated into AddOrgUnitUser and UpdateOrgUnitUser; initialize customRoleTable in constructor.
Database Layer
pkg/table/const.go, pkg/table/org-unit-role.go
Added OrgUnitCustomRoleCollectionName constant; introduced OrgUnitCustomRoleTable, OrgUnitCustomRole, OrgUnitCustomRoleKey, ResourceMatchCriteria, RolePermissionAction, ResourceMatch, RolePermission types; methods for CRUD, queries, soft-delete, permanent delete with binding checks, cleanup, and event logger helpers.
Initialization
main.go
Locate OrgUnitCustomRole table and start its event logger during application startup (panic/log on failures).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Gateway
    participant RouteMatch
    participant PermEval as PermissionEval
    participant CustomRoleTbl as OrgUnitCustomRoleTable

    Client->>Gateway: HTTP request (method, path)
    Gateway->>RouteMatch: matchRoute(method, path)
    RouteMatch-->>Gateway: routeData, orgUnit, keys, values

    rect rgb(230, 245, 230)
    Note over Gateway,PermEval: Permission evaluation
    Gateway->>PermEval: check built-in roles
    alt built-in allows
        PermEval-->>Gateway: allow
    else built-in denies or no-match
        Gateway->>CustomRoleTbl: GetByOrgUnit(orgUnit)
        CustomRoleTbl-->>PermEval: custom roles
        PermEval->>PermEval: evaluate permissions (resource, instance, verb)
        alt match with DENY
            PermEval-->>Gateway: deny
        else match with ALLOW
            PermEval-->>Gateway: allow
        else match with LOG
            PermEval->>PermEval: log decision
        end
    end
    end

    alt allowed
        Gateway-->>Client: 200 / forwarded
    else denied
        Gateway-->>Client: 403 Forbidden
    end
Loading
sequenceDiagram
    participant Client
    participant Server as OrgUnitRoleServer
    participant Validator
    participant Tbl as OrgUnitCustomRoleTable

    Client->>Server: CreateCustomRoleReq
    Server->>Validator: validateCreateCustomRoleRequest
    Validator-->>Server: ok / error

    alt valid
        Server->>Tbl: FindAnyByNameAndOrgUnit(name)
        alt exists (active)
            Server-->>Client: AlreadyExists error
        else exists (soft-deleted, no bindings)
            Server->>Tbl: PermanentDelete(old)
            Tbl-->>Server: deleted
            Server->>Tbl: Insert new role
            Tbl-->>Server: created
            Server-->>Client: CreateCustomRoleResp
        end
    else invalid
        Server-->>Client: InvalidArgument error
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • Areas needing focused review:
    • pkg/gateway/server.go — permission evaluation logic (regex, wildcard, criteria precedence, logging, Deny precedence).
    • pkg/server/org-unit-role.go — validation, conversion between proto/table types, handling of soft-deleted roles and binding checks.
    • pkg/table/org-unit-role.go — MongoDB filter correctness, soft-delete/permanent-delete semantics, binding checks and cleanup logic.
    • pkg/gateway/routes.go — matchRoute signature change and all call sites.
    • main.go initialization — event logger startup error handling.

Poem

🐰 I hopped through routes and proto trees,

I stitched permissions with regex breeze.
Soft-deletes tucked in burrowed rows,
Custom roles bloom where the org-unit grows.
🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The PR title states "feat: implement Log action for custom role permissions," which focuses narrowly on a single action type within the permission system. However, the actual changeset implements a comprehensive custom roles feature that spans the entire system—including full CRUD operations, routing infrastructure, permission evaluation pipelines, resource matching logic, table abstractions, and audit logging support. While the Log action is indeed a real component mentioned in the PR objectives, it represents only one aspect of a much larger feature. The title is misleading because a developer scanning the PR history would expect primarily a logging-related change, but would instead find a major feature implementation that fundamentally extends the authorization system with custom, organization-unit-scoped roles. The title does not adequately capture the scope or main intent of the changeset.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/custom-roles-impl-log

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: 5

Caution

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

⚠️ Outside diff range comments (1)
pkg/gateway/routes.go (1)

78-146: Critical: Returning pointer to map value copy creates memory safety violation.

The review comment is correct. The current code at line 144 returns &data, where data is a local copy from reading the map. In Go, map reads return value copies, and taking their address creates an unsafe pointer to a stack variable.

The proposed fix is sound:

  • Change type routeNodes from map[route.MethodType]routeData to map[route.MethodType]*routeData
  • Update assignments at lines 46–50 and 60 to wrap values with &routeData{...}
  • Change line 144 from return &data, ... to return data, ... (data becomes *routeData from map read)

All affected code paths are in this file and have been verified. No external code depends on the routeData type directly.

🧹 Nitpick comments (9)
pkg/server/org-unit-user.go (2)

29-47: Normalize role and avoid string literals for built-ins.

Trim/EqualFold role names and use shared constants to prevent casing/whitespace issues and drift.

Apply:

@@
-import (
+import (
   "context"
   "log"
   "time"
+  "strings"
@@
-func (s *OrgUnitUserServer) isValidRole(ctx context.Context, tenant, orgUnitId, role string) error {
+func (s *OrgUnitUserServer) isValidRole(ctx context.Context, tenant, orgUnitId, role string) error {
+    r := strings.TrimSpace(role)
+    if r == "" {
+        return status.Errorf(codes.InvalidArgument, "Role must be non-empty")
+    }
     // Check if it's a built-in role
-    if role == "admin" || role == "auditor" {
+    if strings.EqualFold(r, "admin") || strings.EqualFold(r, "auditor") {
         return nil
     }
     // Check if it's a valid custom role
-    _, err := s.customRoleTable.FindByNameAndOrgUnit(ctx, tenant, orgUnitId, role)
+    _, err := s.customRoleTable.FindByNameAndOrgUnit(ctx, tenant, orgUnitId, r)
@@
-            return status.Errorf(codes.InvalidArgument, "Invalid role: '%s'. Must be 'admin', 'auditor', or a valid custom role", role)
+            return status.Errorf(codes.InvalidArgument, "Invalid role: '%s'. Must be 'admin', 'auditor', or a valid custom role", r)

Optionally centralize built-ins in a const slice to reuse across packages.


86-90: Role validation integration — OK. Add brief context on failure.

Calls correctly propagate status errors. Consider wrapping NotFound with role and OU for easier client diagnosis.

Also applies to: 124-128

pkg/gateway/server.go (3)

353-365: Escape regex meta in wildcard patterns to avoid unintended matches.

Replacing “” with “.” without escaping other meta chars makes patterns like bucket.prod match as regex. Quote the pattern first.

Apply:

-    // Convert wildcard pattern to regex
-    pattern := strings.ReplaceAll(match.Key, "*", ".*")
-    pattern = "^" + pattern + "$"
+    // Convert wildcard pattern to safe regex
+    // 1) Quote meta, 2) turn escaped \* back into .*
+    pattern := regexp.QuoteMeta(match.Key)
+    pattern = strings.ReplaceAll(pattern, `\*`, ".*")
+    pattern = "^" + pattern + "$"

252-255: Use structured zap logger for audit events (consistent format/rotation).

Switch from log.Printf to zap fields; same logger used in access logs.

Apply:

-  log.Printf("[LOG] User: %s, Tenant: %s, OrgUnit: %s, Role: %s, Resource: %s, Verb: %s, Instance: %s, Method: %s, Path: %s, Allowed: %v",
-    authInfo.UserName, authInfo.Realm, ou, ouUser.Role, resource, verb, resourceInstance, r.Method, r.URL.Path, allowed)
+  logger.Info("custom_role_audit",
+    zap.String("username", authInfo.UserName),
+    zap.String("tenant", authInfo.Realm),
+    zap.String("org_unit", ou),
+    zap.String("role", ouUser.Role),
+    zap.String("resource", resource),
+    zap.String("verb", verb),
+    zap.String("instance", resourceInstance),
+    zap.String("method", r.Method),
+    zap.String("path", r.URL.Path),
+    zap.Bool("allowed", allowed),
+  )

392-398: Resource instance extraction is brittle.

Hard-coding keys ("name", "id", "username", "bucket", "object") will miss routes with different param names. Consider reading a resourceInstanceKey from route metadata or falling back to the last path parameter.

api/swagger/apidocs.swagger.json (2)

2508-2532: Add maxItems to permissions to match server limit and satisfy Checkov.

Server caps permissions to 20; reflect in schema.

Apply:

         "permissions": {
           "type": "array",
+          "maxItems": 20,
           "items": {
             "type": "object",
             "$ref": "#/definitions/apiRolePermission"
           },
           "title": "List of permissions granted by this custom role"
         }

2533-2554: Add maxItems to update schema as well.

Apply:

         "permissions": {
           "type": "array",
+          "maxItems": 20,
           "items": {
             "type": "object",
             "$ref": "#/definitions/apiRolePermission"
           },
           "title": "Updated list of permissions granted by this custom role"
         }
pkg/server/org-unit-role.go (1)

56-71: List roles returns up to 1000 custom roles without pagination.

Risk of large payloads. Consider adding optional offset/limit to OrgUnitRolesListReq and swagger, or server-side cap lower than 1000.

pkg/table/org-unit-role.go (1)

174-194: Binding check consistency.

If org-unit user entries have an “active” flag, filter it to avoid preventing deletion due to stale bindings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 9233c9a and 678cc7d.

⛔ Files ignored due to path filters (3)
  • api/org-unit-role.pb.go is excluded by !**/*.pb.go
  • api/org-unit-role.pb.gw.go is excluded by !**/*.pb.gw.go
  • api/org-unit-role_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (10)
  • api/org-unit-role.pb.route.go (1 hunks)
  • api/org-unit-role.proto (4 hunks)
  • api/swagger/apidocs.swagger.json (8 hunks)
  • main.go (1 hunks)
  • pkg/gateway/routes.go (7 hunks)
  • pkg/gateway/server.go (9 hunks)
  • pkg/server/org-unit-role.go (1 hunks)
  • pkg/server/org-unit-user.go (6 hunks)
  • pkg/table/const.go (1 hunks)
  • pkg/table/org-unit-role.go (1 hunks)
🧰 Additional context used
🪛 Checkov (3.2.334)
api/swagger/apidocs.swagger.json

[medium] 2522-2529: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🔇 Additional comments (10)
pkg/gateway/routes.go (2)

26-27: Good addition: route metadata carried through.

Carrying resource and verb on routeData enables precise auth/log decisions. Looks correct.


124-142: Code is correct as-is; review comment is based on incorrect assumption.

The matchRoute() function is specifically designed to extract the organization unit from OU-scoped routes. Evidence confirms:

  • Scope limitation is intentional: All routes in the codebase (org-unit-role.proto, org-unit-user.proto) define only scope: "ou"
  • Only "ou" scope exists: Comprehensive search found zero other scopes anywhere in the codebase
  • Function purpose is OU-specific: The logic extracts orgUnit and validates it's present when a single "ou" scope is declared

The hard-coded "ou" check is not rejecting "valid non-OU routes" because no such routes exist. The proto definitions enforce this constraint at the schema level. The code correctly implements the business logic for the current system design.

Likely an incorrect or invalid review comment.

api/org-unit-role.pb.route.go (1)

20-46: CRUD route registrations look correct.

Resource "org-unit-role", scope "ou", verbs set per RPC. Matches list route style; good for auth and audit.

pkg/server/org-unit-user.go (1)

25-27: Server now holds customRoleTable — good.

Field wiring enables custom role validation centrally.

pkg/table/const.go (1)

33-35: Collection name constant verified — LGTM.

Confirmed:

  • OrgUnitCustomRoleCollectionName defined consistently and used correctly in pkg/table/org-unit-role.go:296
  • OrgUnitCustomRoleKey struct contains the required fields (Tenant, OrgUnitId, Name) for tenant+ou+name uniqueness
  • No duplicate collection names or conflicting definitions found
  • Database and collection setup follows established patterns
main.go (1)

437-446: Now I need to verify EventLogger implementation and main.go context directly.Based on the comprehensive verification from web search results, I can now definitively assess the review comment. The evidence is conclusive:

<anit context.Background() is never canceled, meaning any goroutines listening on that context will persist indefinitely. The review comment identifies a legitimate resource leak concern.

StartEventLogger without shutdown — add stop/close on exit.

Context.Background() returns a non-nil, empty Context that is never canceled, has no values, and has no deadline. When logger.Start(context.Background()) is called at lines 437-446, any goroutines spawned by that method cannot be signaled to stop through context cancellation. Without storing a reference to the logger and calling an explicit Stop/Close method during application shutdown, these goroutines will leak.

Verify:

  • Whether db.EventLogger.Start() spawns background goroutines
  • If so, whether EventLogger has a Stop() or Close() method available
  • Where application-level shutdown/cleanup occurs (e.g., defer blocks, signal handlers in main.go)
  • Wire the stop call into the shutdown sequence to match patterns used for other tables
pkg/gateway/server.go (1)

617-620: Initialization order check for custom role table.

GetOrgUnitCustomRoleTable() returns NotFound until Locate… is called (done in main per PR). Confirm main initializes the table before New() to avoid panic at startup.

Would you like a repo-wide check to confirm init order?

api/org-unit-role.proto (1)

130-140: Spec states UNSPECIFIED is invalid; ensure server enforces this.

After fixing validators/evaluator (see other comments), the spec and implementation will align. No proto changes needed.

pkg/table/org-unit-role.go (2)

115-123: Add/confirm index on key.tenant, key.orgUnitId, key.name.

These fields are hot in reads and deletes. Ensure Initialize creates a compound index for efficient lookups and uniqueness per OU.

I can draft an index spec if Initialize() doesn’t do this.


164-171: Partial update semantics assumption.

SoftDelete uses Update with only a subset of fields. Confirm table.Update performs partial $set; otherwise you may overwrite documents.

Comment thread api/swagger/apidocs.swagger.json Outdated
Comment thread pkg/gateway/routes.go
Comment on lines +54 to 56
resource: r.Resource,
verb: r.Verb,
},

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

Propagate resource/verb for all nodes.

Fields are set for both new and existing entries. Ensure r.Resource and r.Verb are never empty in route registrations to avoid nil semantics at auth time.

Run to spot any empty resource/verb:

Also applies to: 67-69


🏁 Script executed:

#!/bin/bash
rg -nP --type=go -C1 '\bNewRoute\(|Resource\s*=|Verb\s*=' api/ | sed -n 'p'

Length of output: 21767


Add Resource and Verb assignments to all route registrations to avoid empty values at auth time.

The following files have routes missing Resource and Verb assignments that must be populated:

  • api/tenant-user.pb.route.go: 10 routes (all GetUsers, CreateUser, GetUser, EnableUser, DisableUser, UpdateUser, DeleteUser, ListUserSessions, LogoutUserSession, ListTenantUserOrgUnits)
  • api/myaccount.pb.route.go: 14 routes (all GetMyInfo through ListMyAzs)
  • api/resdef.pb.route.go: 1 route (GetResources)
  • api/register.pb.route.go: 1 route (GetRegisterOtp)

Each route must explicitly set route.Resource and route.Verb before being added to the route collection to prevent empty/nil values during authorization checks.

🤖 Prompt for AI Agents
In pkg/gateway/routes.go around lines 54 to 56, the route registrations are
being added without setting route.Resource and route.Verb which leads to empty
values at auth time; update each registration in the listed generated route
files (api/tenant-user.pb.route.go — all 10 routes, api/myaccount.pb.route.go —
all 14 routes, api/resdef.pb.route.go — GetResources, api/register.pb.route.go —
GetRegisterOtp) to set route.Resource and route.Verb explicitly (e.g.,
route.Resource = "<resource-name>" and route.Verb = "<verb>" appropriate for
that handler) immediately before the route is appended to the collection so no
route has nil/empty Resource or Verb during authorization checks.

Comment thread pkg/gateway/server.go
Comment on lines +296 to +306
switch perm.Action {
case table.RolePermissionActionDeny:
denyMatched = true
case table.RolePermissionActionLog:
// Log action allows access but marks it for audit logging
logMatched = true
allowMatched = true
case table.RolePermissionActionAllow, table.RolePermissionActionUnspecified:
// Treat UNSPECIFIED or empty as Allow (default permissive behavior)
allowMatched = true
}

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

Unspecified action improperly grants access; align with proto/swagger and reject/ignore UNSPECIFIED.

Proto/swagger say UNSPECIFIED is invalid. Here it defaults to Allow, which is permissive and can leak access. Treat UNSPECIFIED as invalid/ignored and only Allow/Log/Deny explicitly.

Apply:

-    case table.RolePermissionActionAllow, table.RolePermissionActionUnspecified:
-      // Treat UNSPECIFIED or empty as Allow (default permissive behavior)
-      allowMatched = true
+    case table.RolePermissionActionAllow:
+      allowMatched = true
+    case table.RolePermissionActionUnspecified:
+      // Invalid/misconfigured permission; ignore defensively
+      continue
📝 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
switch perm.Action {
case table.RolePermissionActionDeny:
denyMatched = true
case table.RolePermissionActionLog:
// Log action allows access but marks it for audit logging
logMatched = true
allowMatched = true
case table.RolePermissionActionAllow, table.RolePermissionActionUnspecified:
// Treat UNSPECIFIED or empty as Allow (default permissive behavior)
allowMatched = true
}
switch perm.Action {
case table.RolePermissionActionDeny:
denyMatched = true
case table.RolePermissionActionLog:
// Log action allows access but marks it for audit logging
logMatched = true
allowMatched = true
case table.RolePermissionActionAllow:
allowMatched = true
case table.RolePermissionActionUnspecified:
// Invalid/misconfigured permission; ignore defensively
continue
}
🤖 Prompt for AI Agents
In pkg/gateway/server.go around lines 296 to 306, the switch currently treats
RolePermissionActionUnspecified as Allow which is incorrect per the
proto/swagger; change the logic so only explicit RolePermissionActionAllow sets
allowMatched, RolePermissionActionLog sets logMatched and allowMatched (if log
is intended to allow) and RolePermissionActionDeny sets denyMatched, while
RolePermissionActionUnspecified (and any unknown values) must NOT set
allowMatched — instead ignore or log the invalid/unspecified action (do not
grant access) so UNSPECIFIED cannot inadvertently permit access.

Comment thread pkg/server/org-unit-role.go Outdated
Comment thread pkg/server/org-unit-role.go Outdated
Add Log action to the system to enable
selective audit logging for sensitive operations. The Log action allows access
while generating audit trail entries.

Changes:
- Add ROLE_PERMISSION_ACTION_LOG enum to protobuf definition (value 3)
- Add RolePermissionActionLog constant to table package
- Update permission evaluation to track log actions separately
- Modify evaluateCustomRolePermissions to return (allowed, shouldLog) tuple
- Add audit logging in performOrgUnitRoleCheck when Log action matches

Audit log format includes:
- User, Tenant, OrgUnit, Role
- Resource, Verb, Instance
- Access result (allowed/denied)

Precedence rules:
- Deny > Log/Allow (deny takes priority)
- Log action allows access +  marks for audit

Logs are written to stdout/access.log with [LOG] prefix.

Signed-off-by: suryanshu185 <suryanshu.gupta@kluisz.ai>
@Suryanshu185 Suryanshu185 force-pushed the feat/custom-roles-impl-log branch from 678cc7d to f450149 Compare October 27, 2025 09:19

@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: 0

♻️ Duplicate comments (3)
pkg/gateway/server.go (1)

303-306: Critical: UNSPECIFIED action grants access improperly; align with proto/swagger.

Per proto (line 132) and swagger, UNSPECIFIED is invalid and must be rejected. Currently, it defaults to Allow, creating a permissive security hole. Only Allow, Deny, and Log are valid actions.

Apply this diff:

-    case table.RolePermissionActionAllow, table.RolePermissionActionUnspecified:
-      // Treat UNSPECIFIED or empty as Allow (default permissive behavior)
-      allowMatched = true
+    case table.RolePermissionActionAllow:
+      allowMatched = true
+    case table.RolePermissionActionUnspecified:
+      // Invalid/misconfigured permission; ignore defensively
+      continue
pkg/server/org-unit-role.go (2)

412-419: Critical: Validator incorrectly allows UNSPECIFIED and rejects LOG.

Lines 412-414 check for Unspecified but then allow it if the subsequent check passes, while LOG is rejected by line 415-418. Per proto (line 132-139) and swagger, UNSPECIFIED is invalid and LOG is valid.

Apply this diff:

     if perm.Action == api.RolePermissionActionDefs_Unspecified {
-      return fmt.Errorf("permission %d: action must be specified (Allow, Deny, or Log)", i+1)
+      return fmt.Errorf("permission %d: action cannot be Unspecified (must be Allow, Deny, or Log)", i+1)
     }
     if perm.Action != api.RolePermissionActionDefs_Allow &&
       perm.Action != api.RolePermissionActionDefs_Deny &&
       perm.Action != api.RolePermissionActionDefs_Log {
       return fmt.Errorf("permission %d: invalid action value: %v", i+1, perm.Action)
     }

451-459: Critical: Apply the same fix to UpdateCustomRole validator.

Same issue as CreateCustomRole: validator allows UNSPECIFIED and rejects LOG.

Apply this diff:

     if perm.Action == api.RolePermissionActionDefs_Unspecified {
-      return fmt.Errorf("permission %d: action must be specified (Allow, Deny, or Log)", i+1)
+      return fmt.Errorf("permission %d: action cannot be Unspecified (must be Allow, Deny, or Log)", i+1)
     }
     if perm.Action != api.RolePermissionActionDefs_Allow &&
       perm.Action != api.RolePermissionActionDefs_Deny &&
       perm.Action != api.RolePermissionActionDefs_Log {
       return fmt.Errorf("permission %d: invalid action value: %v", i+1, perm.Action)
     }
🧹 Nitpick comments (2)
pkg/gateway/server.go (1)

389-398: ResourceInstance extraction is fragile; consider explicit route metadata.

The code heuristically looks for common keys like name, id, username, etc. in path parameters. This is brittle and may not scale as new resources are added. Consider encoding the resource instance parameter name explicitly in route metadata (similar to how resource and verb are already provided).

pkg/server/org-unit-role.go (1)

304-310: Unspecified action defaults to Deny for security; verify alignment.

Lines 304-306 default UNSPECIFIED to Deny, which is defensive. However, since validators should prevent UNSPECIFIED from reaching this code, consider logging a warning when this branch is hit to catch misconfiguration.

Add:

   case api.RolePermissionActionDefs_Unspecified:
     // UNSPECIFIED action - default to Deny for security
     log.Printf("Unspecified action encountered, defaulting to Deny")
+    log.Printf("Warning: UNSPECIFIED action should be caught by validator")
     return table.RolePermissionActionDeny
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 678cc7d and f450149.

⛔ Files ignored due to path filters (1)
  • api/org-unit-role.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (5)
  • api/org-unit-role.proto (4 hunks)
  • api/swagger/apidocs.swagger.json (9 hunks)
  • pkg/gateway/server.go (9 hunks)
  • pkg/server/org-unit-role.go (1 hunks)
  • pkg/table/org-unit-role.go (1 hunks)
🧰 Additional context used
🪛 Checkov (3.2.334)
api/swagger/apidocs.swagger.json

[medium] 2518-2525: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🔇 Additional comments (11)
pkg/gateway/server.go (2)

617-620: LGTM: Custom role table correctly wired into gateway.

The ouCustomRoleTbl is properly initialized and integrated into the gateway's authorization flow, enabling runtime permission evaluation for org-unit custom roles.


260-315: Verify Log action semantics: does Log imply Allow?

Lines 299-302 set both logMatched = true and allowMatched = true when action is Log, meaning Log grants access while generating an audit trail. Confirm this is the intended behavior per product requirements, as the comment suggests "Log action allows access but marks it for audit logging."

If the intent is confirmed, the logic is correct. Otherwise, consider whether Log should only record without granting access.

pkg/server/org-unit-role.go (1)

92-125: Soft-deleted role handling is thorough and correct.

The logic properly checks for existing active and soft-deleted roles, performs binding checks, and permanently deletes orphaned soft-deleted roles before allowing recreation. This prevents name conflicts and maintains referential integrity.

api/swagger/apidocs.swagger.json (1)

2598-2608: Swagger correctly documents action semantics.

The enum properly marks Unspecified as invalid and includes Log as a valid action. Server validators must be updated to match (see comments on pkg/server/org-unit-role.go).

api/org-unit-role.proto (2)

129-141: Proto correctly defines action semantics.

The enum properly documents Unspecified as invalid (line 132) and Log as a valid action that allows access with audit logging (line 138-139). Implementation must align with this specification.


27-75: CRUD RPCs are well-designed with proper REST mappings.

The new RPCs follow RESTful conventions, use appropriate HTTP methods, and include proper resource/scope/verb annotations for authorization. The API surface is clean and consistent.

pkg/table/org-unit-role.go (5)

96-110: LGTM: Query correctly filters for active roles.

The filter "active": bson.M{"$ne": false} properly includes roles where active is true or nil (default active), excluding only explicitly soft-deleted roles.


171-192: HasBindings implementation is correct and efficient.

The method properly queries the OrgUnitUserTable to check for role assignments. Using Count is more efficient than fetching all bindings.


199-214: Intelligent deletion strategy is well-implemented.

DeleteCustomRoleWithBindingCheck correctly soft-deletes roles with bindings (preserving referential integrity) and permanently deletes orphaned roles (keeping the database clean).


239-271: Cleanup continues on errors; verify this is intentional.

Lines 257 and 263-266 continue on errors during cleanup, allowing the operation to process remaining roles despite individual failures. If cleanup must be atomic (all-or-nothing), consider collecting errors and returning them.

If best-effort cleanup is acceptable, the current implementation is fine. Otherwise, collect errors:

+  var errs []error
   for _, role := range softDeletedRoles {
     hasBindings, err := t.HasBindings(ctx, tenant, orgUnitId, role.Key.Name)
     if err != nil {
+      errs = append(errs, err)
       continue
     }
     if !hasBindings {
       err = t.PermanentDelete(ctx, role.Key)
       if err != nil {
+        errs = append(errs, err)
         continue
       }
     }
   }
+  if len(errs) > 0 {
+    return fmt.Errorf("cleanup encountered %d errors: %v", len(errs), errs)
+  }
   return nil

52-56: RolePermissionAction constants match proto/table requirements.

The string constants correctly define the allowed actions. Ensure that the gateway and server converters handle these consistently with the proto definitions.

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