feat: Role inheritance for entity permissions + dab configure --show-effective-permissions#3164
Open
feat: Role inheritance for entity permissions + dab configure --show-effective-permissions#3164
dab configure --show-effective-permissions#3164Conversation
… with a-z entity ordering Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Implement role inheritance for entity permissions
feat: Role inheritance for entity permissions + Feb 25, 2026
dab configure --show-effective-permissions
Contributor
|
@copilot I pushed changes which try to eliminate the parallel logic we had here for auth inheritance, instead having a single source of truth, can you review these changes please? |
Contributor
|
Also @copilot please update the description to match the new functionality with a single source of truth. |
|
Azure Pipelines successfully started running 6 pipeline(s). |
Aniruddh25
reviewed
Mar 9, 2026
Aniruddh25
approved these changes
Mar 9, 2026
Contributor
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
Collaborator
|
Why are so many of the Configuration tests removed in the latest commit? |
…:Azure/data-api-builder into copilot/add-role-inheritance-permissions
Contributor
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
Contributor
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
Collaborator
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
Aniruddh25
reviewed
Mar 10, 2026
Aniruddh25
reviewed
Mar 10, 2026
Aniruddh25
reviewed
Mar 10, 2026
…:Azure/data-api-builder into copilot/add-role-inheritance-permissions
Contributor
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why make this change?
Developers were required to repeat identical permission configurations across every role, leading to verbose configs and unexpected access denials. This implements role inheritance so unconfigured roles fall back through the chain:
named-role → authenticated → anonymous → none.What is this change?
Role inheritance at runtime (
AuthorizationResolver)GetEffectiveRoleName(entityName, roleName)private helper implementing the inheritance chainAreRoleAndOperationDefinedForEntity,AreColumnsAllowedForOperation,GetDBPolicyForRequest,GetAllowedExposedColumns,IsStoredProcedureExecutionPermittedanonymous,authenticated) always resolve to themselves — no inheritance applies to themRoleMetadatawhen copyinganonymous → authenticatedto prevent shared mutable state between the two roles (RoleMetadata.DeepClone()andOperationMetadata.DeepClone()added toAuthorizationMetadataHelpers)GraphQL
@authorizedirective support — single source of truth (IAuthorizationResolver,AuthorizationResolver,GraphQLAuthorizationHandler)IsRoleAllowedByDirective(clientRole, directiveRoles)as an abstract interface method onIAuthorizationResolver(not a default interface method, keeping auth policy logic in the concrete class and preserving clean mockability)AuthorizationResolver— the single source of truth for directive-level role checking with the following inheritance rules:authenticatedin directive + clientRole isauthenticated→ allowed (authenticatedinherits fromanonymous)authenticatedoranonymous+ clientRole is an unconfigured named role (not present in any entity's explicit permission map) → allowed (inheritance for truly unconfigured roles)GraphQLAuthorizationHandlernow injectsIAuthorizationResolverand delegates all directive role checks toIsRoleAllowedByDirective, eliminating duplicated inheritance logicIsInHeaderDesignatedRoleprivate static method fromGraphQLAuthorizationHandlerwhich previously duplicated the inheritance logic_explicitlyConfiguredNamedRolesHashSet<string>(case-insensitive, O(1) lookup) is built once duringSetEntityPermissionMapalongsideEntityPermissionsMapand used byIsRoleAllowedByDirectiveto distinguish unconfigured roles from explicitly restricted ones; both fields are swapped atomically to ensure consistency during hot-reload and to remove stale entries when entities are removed from the configCLI:
dab configure --show-effective-permissions(ConfigureOptions,ConfigGenerator)authenticated→anonymousinheritance when applicableRules implemented
authenticatedinherits fromanonymousat startup when not explicitly configured (existing behavior)authenticatedat lookup timeauthenticatedis also absent, unconfigured named roles inherit fromanonymous(via the setup-time copy)How was this tested?
TestAuthenticatedRoleWhenAnonymousRoleIsDefinedto reflect that named roles now inheritTestNamedRoleInheritsFromAuthenticatedRole— validates rule 3TestNamedRoleInheritsNothingWhenNoSystemRolesDefined— validates rule 5TestNamedRoleInheritsFromAnonymousViaAuthenticated— validates rule 4 (chain through both system roles)TestExplicitlyConfiguredNamedRoleDoesNotInheritBroaderPermissions— security test validating that a named role with explicitly restricted permissions does not escalate to broaderauthenticatedpermissionsTestIsRoleAllowedByDirective— 11-case data-driven test covering the full directive inheritance chain for unconfigured roles: explicit match, named→authenticated, authenticated→anonymous, named→anonymous (via chain), deny cases, and case-insensitivityTestIsRoleAllowedByDirective_ExplicitlyConfiguredRoleUsesStrictMatching— 4-case data-driven test validating that explicitly configured named roles use strict directive matching and do not inherit from system roles at the directive level--show-effective-permissionsinConfigureOptionsTests.cs:TestShowEffectivePermissions— parameterized test covering alphabetical entity ordering, config file immutability, authenticated-inherits-anonymous line, and inheritance notesTestShowEffectivePermissions_EntitiesSortedAlphabetically— validates a-z entity orderingTestShowEffectivePermissions_RolesSortedAlphabeticallyWithinEntity— validates a-z role ordering within each entityTestShowEffectivePermissions_AuthenticatedInheritsAnonymousNote— validates the inherited-authenticated display line and inheritance noteTestShowEffectivePermissions_NoInheritanceNoteWhenAuthenticatedExplicitlyConfigured— validates note is suppressed when authenticated is explicitly configuredTestShowEffectivePermissions_ReturnsFalseWhenConfigMissing— validates error handling for missing configSample Request(s)
Config with only
anonymousdefined —authenticatedand any unconfigured named role (e.g.editor) both get Read access:CLI usage:
Original prompt
This section details on the original issue you should resolve
<issue_title>[Enh]: Implement role inheritance for entity permissions</issue_title>
<issue_description>## Problem
Today, a developer is required to repeat permissions across all possible roles.
Desired Behavior
Introduce role inheritance that let's unlisted roles inherit from roles with fewer permissions.
Specific-role -(not found)-> Authenticated -(not found)-> Anonymous -(not found)-> NoneRules
permissions, that role always gets its that configuration.authenticatedis not configured,authenticatedinherits the permissions ofanonymous, if present.named-roleis not configured, it inherits the permissions ofauthenticated, if present.named-roleis not configured and neither isauthenticated, it inherits the permissions ofanonymous, if present.named-roleis not configured and neither isauthenticatedoranonymous, it inherits nothing.actions,policiesandfields`.Command line
We need to ensure the developer always has a way to know and understand inheritance.
dab configure --show-effective-permissions <role-name>.Note: In this release, this feature does not work with auto-entities.
Output
Example Matrix
Note: none of the examples include
executebelow, but the behavior for stored procedures would be the same.1. All roles configured:
{ "permissions": { "anonymous": [ "read" ], "authenticated": [ "update" ], "special-role": [ "delete" ] } }2.
special-rolemissing{ "permissions": { "anonymous": [ "read" ], "authenticated": [ "update" ] } }3.
authenticatedandspecial-rolemissing{ "permissions": { "anonymous": [ "read" ] } }4. Only a custom role defined
{ "permissions": { "jerry-role": [ "read" ] } }Coding considerations
The implementation of
[CopyOverPermissionsFromAnonymousToAuthenticatedRole](https://github.com/Azure/data-api-builder/blob/29b0e6eee594027e0787b3ce9c9aace015128f49/src/Core/Authorization/AuthorizationResolver.cs#L398-L427)already exists. This is a nice start, but not the complete story. It has a bug: This is a reference assignment, not a deep copy. Both authenticated and anonymous share the same RoleMetadata object. If any downstream code ever mutates the inherited permissions for one role (e.g., appending an action), it silently mutates the other. Extending this pattern to named roles creates a three-way shared reference chain, a subtle and dangerous source of bugs. We want to fix this and not repeat it.The method
GetRolesForEntity(string entityName)would return the wrong result. This is used by GraphQL to build @authorize directives on object types. With inheritance, you'd need to materialize all possible roles (including those that aren't explicitly configured but would inherit), which is unbounded, DAB can't know what named roles a JWT might carry ahead of time. This is fundamentally different from today, where every role that can access an entity is explicitly listed. The GraphQL schema generation would break or become incomplete.The method
AreRoleAndOperationDefinedForEntity()would need to implement the fallback chain (named-role → authenticated → anonymous). But if you materialize everything at startup (like the current anon...💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.