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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
+2
@@ -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.
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:"-"`
|
||||
|
||||
@@ -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",
|
||||
}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user