Compare commits

...

1 Commits

Author SHA1 Message Date
mohammad-hamid
a3ae83a37a IAM: Add disabled field to role permissions
This change adds support for disabling permissions without removing them
from roles. This allows users to revoke permissions while signaling to
apps that the permission should not be re-granted.

Key changes:
- Add disabled boolean field to RolespecPermission in CUE schema
- Update generated Go types for Role, CoreRole, and GlobalRole
- Add disabled column to permission table via migration
- Filter disabled permissions in Zanzana tuple conversion
- Filter disabled permissions in legacy RBAC queries
- Update SQL queries to persist and retrieve disabled field

When a permission has disabled: true:
- It remains visible in the role definition
- It is NOT written to Zanzana (authorization engine)
- It is NOT returned in RBAC permission queries

This prevents apps from re-granting permissions that users have
explicitly revoked, while maintaining visibility of the permission
in the role.
2025-12-16 11:54:31 -05:00
8 changed files with 29 additions and 6 deletions

View File

@@ -6,6 +6,8 @@ RoleSpec: {
action: string
// RBAC scope (e.g: "dashboards:uid:dash1")
scope: string
// When true, the permission is disabled and not granted to the role
disabled: bool | *false
}
// Display name of the role

View File

@@ -8,6 +8,8 @@ type CoreRolespecPermission struct {
Action string `json:"action"`
// RBAC scope (e.g: "dashboards:uid:dash1")
Scope string `json:"scope"`
// When true, the permission is disabled and not granted to the role
Disabled bool `json:"disabled"`
}
// NewCoreRolespecPermission creates a new CoreRolespecPermission object.

View File

@@ -8,6 +8,8 @@ type GlobalRolespecPermission struct {
Action string `json:"action"`
// RBAC scope (e.g: "dashboards:uid:dash1")
Scope string `json:"scope"`
// When true, the permission is disabled and not granted to the role
Disabled bool `json:"disabled"`
}
// NewGlobalRolespecPermission creates a new GlobalRolespecPermission object.

View File

@@ -8,6 +8,8 @@ type RolespecPermission struct {
Action string `json:"action"`
// RBAC scope (e.g: "dashboards:uid:dash1")
Scope string `json:"scope"`
// When true, the permission is disabled and not granted to the role
Disabled bool `json:"disabled"`
}
// NewRolespecPermission creates a new RolespecPermission object.

View File

@@ -17,10 +17,16 @@ import (
// convertRolePermissionsToTuples converts role permissions (action/scope) to v1 TupleKey format
// using the shared zanzana.ConvertRolePermissionsToTuples utility and common.ToAuthzExtTupleKeys
// Disabled permissions are filtered out and not converted to tuples
func convertRolePermissionsToTuples(roleUID string, permissions []iamv0.CoreRolespecPermission) ([]*v1.TupleKey, error) {
// Convert IAM permissions to zanzana.RolePermission format
// Convert IAM permissions to zanzana.RolePermission format, filtering out disabled permissions
rolePerms := make([]zanzana.RolePermission, 0, len(permissions))
for _, perm := range permissions {
// Skip disabled permissions - they should not be added to Zanzana
if perm.Disabled {
continue
}
// Split the scope to get kind, attribute, identifier
kind, _, identifier := accesscontrol.SplitScope(perm.Scope)
rolePerms = append(rolePerms, zanzana.RolePermission{

View File

@@ -67,7 +67,9 @@ func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query acces
permission.scope
FROM permission
INNER JOIN role ON role.id = permission.role_id
` + filter
` + filter + `
AND (permission.disabled = 0 OR permission.disabled IS NULL)
`
if len(query.RolePrefixes) > 0 {
rolePrefixesFilter, filterParams := accesscontrol.RolePrefixesFilter(query.RolePrefixes)
@@ -132,6 +134,7 @@ func (s *AccessControlStore) GetTeamsPermissions(ctx context.Context, query acce
WHERE tr.team_id IN(?` + strings.Repeat(", ?", len(teams)-1) + `)
AND tr.org_id = ?
) as all_role ON role.id = all_role.role_id
WHERE (permission.disabled = 0 OR permission.disabled IS NULL)
`
params := make([]any, 0)

View File

@@ -203,10 +203,11 @@ type BuiltinRole struct {
// Permission is the model for access control permissions
type Permission struct {
ID int64 `json:"-" xorm:"pk autoincr 'id'"`
RoleID int64 `json:"-" xorm:"role_id"`
Action string `json:"action"`
Scope string `json:"scope"`
ID int64 `json:"-" xorm:"pk autoincr 'id'"`
RoleID int64 `json:"-" xorm:"role_id"`
Action string `json:"action"`
Scope string `json:"scope"`
Disabled bool `json:"disabled" xorm:"disabled"`
Kind string `json:"-"`
Attribute string `json:"-"`

View File

@@ -218,4 +218,9 @@ func AddMigration(mg *migrator.Migrator) {
mg.AddMigration("Remove permission role_id index", migrator.NewDropIndexMigration(permissionV1, &migrator.Index{
Cols: []string{"role_id"},
}))
// Add disabled column to permission table
mg.AddMigration("add column disabled to permission table", migrator.NewAddColumnMigration(permissionV1, &migrator.Column{
Name: "disabled", Type: migrator.DB_Bool, Nullable: false, Default: "0",
}))
}