Skip to content

Pragmatic.Authorization

Authorization engine for Pragmatic.Design.

Authorization in most .NET applications starts simple and deteriorates. Permission strings are hardcoded and scattered across controllers. Role-to-permission mapping lives in if-statements. Wildcard handling is hand-rolled and inconsistent. Instance-level checks (“can this user act on this specific invoice?”) are buried in business logic. When someone asks “what permissions does the booking-manager role have?”, the answer requires reading every controller in the project.

// Typical authorization: scattered, fragile, invisible
[HttpPost("{id}/cancel")]
public async Task<IActionResult> Cancel(Guid id, CancellationToken ct)
{
var permissions = User.Claims
.Where(c => c.Type == "permission")
.Select(c => c.Value).ToHashSet();
if (!permissions.Contains("booking.reservation.cancel")
&& !permissions.Any(p => p == "booking.reservation.*")
&& !permissions.Contains("*"))
return Forbid();
await _reservations.CancelAsync(id, ct);
return NoContent();
}

Declare what is protected and how. The framework handles resolution, matching, caching, and enforcement.

// Pragmatic: declarative, compile-safe, observable
[RequirePermission(BookingPermissions.Reservation.Cancel)]
[RequirePolicy<ReservationCancellationPolicy>]
public sealed class CancelReservationAction : VoidDomainAction
{
// Business logic only -- no authorization code here
}
// Roles compose from module definitions, declared once in Program.cs
app.UseAuthorization(authz =>
{
authz.MapRole<BookingManagerRole>(r => r
.IncludeDefinition<BookingOperator>()
.IncludeDefinition<CatalogReader>());
authz.MapGroup<CustomerCareGroup>();
authz.UsePermissionCache(TimeSpan.FromMinutes(5));
});

The SG generates permission constants (BookingPermissions.Reservation.Read), a permission registry for admin tooling, and optional role seeding from JSON. At runtime, an ordered provider chain resolves claims to permissions, WildcardMatcher handles * patterns consistently, CachedPermissionResolver caches per request and optionally cross-request, and OTel counters instrument every check.

  • Permission evaluation with wildcard matching
  • Role and group expansion (Claims -> Roles -> Permissions, Groups -> Roles -> Permissions)
  • Composable authorization policies (ResourcePolicy, AsyncResourcePolicy)
  • Resource-level authorization (ABAC via IResourceAuthorizer<T>)
  • Request-scoped and cross-request permission caching
  • Static and dynamic catalogs for admin tooling
  • Policy serialization for JSON storage
  • Built-in observability (OTel metrics and tracing)

See samples/Pragmatic.Authorization.Samples/ for 4 runnable scenarios: ResourcePolicy composition (operators, factories), WildcardMatcher (hierarchical permission patterns), roles and permissions (provider chain, ABAC), and policy evaluation (runnable checks against AnonymousUser/SystemUser).

If you are new to the stack, read in this order:

  1. This README for the mental model and the main APIs.
  2. docs/concepts.md for the full architecture and core concepts.
  3. docs/getting-started.md for a zero-to-running walkthrough.
  4. docs/permission-resolution.md for the provider chain internals.
  5. docs/policies.md for composable resource policies.
  6. docs/roles-and-groups.md for the role/group composition model.
  7. docs/stores.md for in-memory vs EF-backed stores.

When something goes wrong:

For architecture and cross-cutting topics:

Code wins over documents. Use this README and the package code as the source of truth for the current public surface.


There are two views of the system:

ViewPurpose
L1-L4 pipelineRuntime flow: authentication, permission gate, resource authorization, data filtering
L0-L3 permission scopePermission taxonomy: resource, CRUD, custom operations, data scope

Use L1-L4 when you want to understand request execution. Use L0-L3 when you want to name permissions or compose roles.

Request
|
v
L1 Authentication Who is the caller?
|
v
L2 Permission Gate Can the caller perform this operation?
| (CachedPermissionResolver + WildcardMatcher)
v
L3 Resource Authorization Can the caller act on THIS resource?
| (IResourceAuthorizer<T> + PolicyEvaluationFilter)
v
L4 Data-Level Filtering Which rows can the caller see?
(IQueryFilter<T>, IPermissionBasedFilter<T>)
L0: booking -- boundary (namespace)
L1: booking.reservation.read -- CRUD (SG-generated)
L2: billing.invoice.refund -- custom operation (IPermission)
L3: IQueryFilter<T> -- data-level scope

Reference Pragmatic.Authorization in your module’s .csproj. For the host, the SG auto-detects the package via FeatureDetector.

await PragmaticApp.RunAsync(args, app =>
{
app.UseAuthorization(authz =>
{
// Default policy for endpoints without explicit attributes
authz.DefaultPolicy = DefaultEndpointPolicy.RequireAuthenticated;
// Map application roles — compose from module definitions
authz.MapRole<AdminRole>();
authz.MapRole<ManagerRole>(r => r
.IncludeDefinition<BookingOperator>()
.IncludeDefinition<CatalogReader>());
// Map groups (expand to roles at runtime)
authz.MapGroup<CustomerCareGroup>();
// Instance-level authorization
authz.AddResourceAuthorizer<InvoiceAuthorizer>();
// Cross-request caching
authz.UsePermissionCache(TimeSpan.FromMinutes(5));
});
});
[RequirePermission("booking.reservation.read")]
public sealed class SearchReservationsQuery : IQuery<PagedResult<ReservationDto>>
{
// ...
}

Or with SG-generated constants:

[RequirePermission(BookingPermissions.Reservation.Create)]
[RequirePolicy<ReservationManagementPolicy>]
public sealed class CreateReservationMutation : Mutation<Reservation>
{
// ...
}
// In the module — permission template (IRoleDefinition)
public sealed class BookingOperator : IRoleDefinition
{
public static string Name => "booking-operator";
public static string? Description => "Full CRUD on reservations and guests";
public static IReadOnlyList<string> Permissions =>
[
BookingPermissions.Reservation.All,
BookingPermissions.Guest.All,
BookingPermissions.GuestPreferences.All
];
}
// In the host — application role (IRole)
public sealed class BookingManagerRole : IRole
{
public static string Name => "booking-manager";
public static string? Description => "Full booking operations with catalog context";
public static IReadOnlyList<string> DefaultPermissions => [];
// Composed via IncludeDefinition in Program.cs
}

When IUserAuthorization.HasPermission("booking.reservation.read") is called, the system resolves permissions through an ordered provider chain:

ClaimsPermissionProvider (Order 0)
| Reads "permission" claims from ICurrentUser
v
RoleExpansionProvider (Order 100)
| For each "role" claim, queries IRolePermissionStore
v
GroupExpansionProvider (Order 200)
| For each "group" claim, queries IGroupRoleStore -> IRolePermissionStore
v
[Custom providers] (Order N)
| Any IPermissionProvider you register
v
CachedPermissionResolver
| Merges all provider results (union)
| Caches per request (lazy, resolved on first access)
| Optionally caches cross-request via ICacheStack
v
WildcardMatcher
| Checks if any resolved permission matches the required one
| Supports: *, booking.*, booking.*.read, *.reservation.read
v
Result (bool)

All providers run asynchronously. Results are merged with union semantics (additive only). The resolved set is cached for the lifetime of the request scope. If UsePermissionCache is configured, the resolved set is also cached cross-request via ICacheStack (from Pragmatic.Caching), keyed by user ID (and tenant ID if multi-tenant).

PatternMatchesExample
*All permissionsSuper admin
booking.*All permissions starting with booking.Boundary admin
booking.reservation.*All reservation operationsEntity admin
booking.*.readRead on all entities in bookingBoundary reader
*.reservation.readRead Reservation across all boundariesCross-boundary reader

Wildcard matching is case-insensitive. A trailing * matches all remaining segments. A * in the middle matches exactly one segment.


AuthorizationBuilder is the fluent builder exposed by UseAuthorization. All methods return the builder for chaining.

authz.DefaultPolicy = DefaultEndpointPolicy.RequireAuthenticated;
ValueBehavior
AllowAnonymousNo authorization required (default)
RequireAuthenticatedRequires a valid identity
RequirePermissionRequires the SG-generated CRUD permission based on HTTP verb and entity

Maps a strongly-typed IRole with its default permissions:

authz.MapRole<AdminRole>();

Maps a role with composition via RoleBuilder:

authz.MapRole<ManagerRole>(r => r
.IncludeDefinition<BookingOperator>()
.WithPermissions("custom.permission")
.WithAllPermissions<BookingBoundary>() // "booking.*"
.WithOperation<CatalogBoundary>(CrudOperation.Read) // "catalog.*.read"
.WithoutPermissions("booking.reservation.delete"));

Maps a role by name (string):

authz.MapRole("auditor", r => r
.WithOperation<BookingBoundary>(CrudOperation.Read)
.WithOperation<BillingBoundary>(CrudOperation.Read));
MethodDescription
WithPermissions(params string[])Adds explicit permissions
IncludeDefinition<T>()Includes all permissions from an IRoleDefinition
WithAllPermissions()Grants * (all permissions)
WithAllPermissions<TBoundary>()Grants {boundary}.*
WithOperation<TBoundary>(CrudOperation)Grants {boundary}.*.{operation}
WithoutPermissions(params string[])Removes specific permissions. Fails fast if excluded permission is covered by a wildcard
ClearDefaults()Clears default permissions from IRole.DefaultPermissions

Maps a strongly-typed IGroup:

authz.MapGroup<CustomerCareGroup>();

Maps with additional roles:

authz.MapGroup<CustomerCareGroup>(g => g
.WithRoles("extra-role")
.WithRole<AnotherRole>());

Maps by name:

authz.MapGroup("operations", g => g.WithRoles("front-desk", "catalog-viewer"));
MethodDescription
WithRoles(params string[])Adds roles by name
WithRole<TRole>()Adds a strongly-typed role
authz.UseRolePermissionStore<EfRolePermissionStore>();
authz.UseGroupRoleStore<EfGroupRoleStore>();

Custom stores override the in-memory stores created by MapRole/MapGroup.

authz.AddResourceAuthorizer<InvoiceAuthorizer>();

Discovers all IResourceAuthorizer<T> interfaces on the type and registers each in DI.

// Simple: cache for 5 minutes
authz.UsePermissionCache(TimeSpan.FromMinutes(5));
// Detailed: configure options
authz.UsePermissionCache(opts =>
{
opts.Expiration = TimeSpan.FromMinutes(10);
opts.KeyPrefix = "myapp-permissions";
});
authz.AddPermissionProvider<MyExternalPermissionProvider>();

If the project includes a roles.pragmatic.json file, the SG generates a SeedFromJson() extension method:

authz.SeedFromJson(); // Reads compile-time JSON, flattens inheritance

Example roles.pragmatic.json:

{
"roles": {
"front-desk": {
"description": "Front desk staff with basic booking access",
"permissions": ["booking.reservation.read", "booking.reservation.create", "booking.guest.read"]
},
"revenue-manager": {
"description": "Revenue management with full catalog and rate access",
"permissions": ["catalog.*", "booking.reservation.read"]
}
},
"groups": {
"operations": {
"description": "All operational staff",
"roles": ["front-desk", "catalog-viewer"]
}
}
}

Role inheritance is expanded at compile-time by the SG. The generated code has flattened permissions.


Composable authorization rules that evaluate against ICurrentUser. Analogous to Specification<T> but for authorization decisions.

MethodDescription
ResourcePolicy.AllowAlways allows (identity for OR)
ResourcePolicy.DenyAlways denies (identity for AND)
ResourcePolicy.RequirePermission(string)Single permission check
ResourcePolicy.RequireAnyPermission(params string[])Any of the permissions (OR)
ResourcePolicy.RequireAllPermissions(params string[])All of the permissions (AND)
ResourcePolicy.InRole(string)Role membership check
ResourcePolicy.InGroup(string)Group membership check
ResourcePolicy.HasClaim(string, string?)Claim existence / value check
ResourcePolicy.IsAuthenticated()Authentication check (singleton)
ResourcePolicy.HasPrincipalKind(PrincipalKind)Principal kind check
ResourcePolicy.Custom(Func<ICurrentUser, bool>)Delegate-based (not serializable)

Policies compose with & (AND), | (OR), and ! (NOT):

var policy = ResourcePolicy.RequirePermission("booking.reservation.create")
& ResourcePolicy.IsAuthenticated()
| ResourcePolicy.HasPrincipalKind(PrincipalKind.Service);
bool allowed = policy.Evaluate(currentUser);

Equivalent fluent API:

var policy = ResourcePolicy.RequirePermission("booking.reservation.create")
.And(ResourcePolicy.IsAuthenticated())
.Or(ResourcePolicy.HasPrincipalKind(PrincipalKind.Service));

Use the [RequirePolicy<T>] attribute:

[RequirePolicy<ReservationManagementPolicy>]
public sealed class CreateReservationMutation : Mutation<Reservation>
{
// ...
}

The policy type must have a parameterless constructor. It is instantiated once and cached. Evaluated by PolicyEvaluationFilter at Order 210 in the action pipeline.

For policies that require I/O (database lookups, external services):

var asyncPolicy = AsyncResourcePolicy.RequireExternalPermission(
async (user, ct) =>
{
// Call external permission service
return await externalService.CheckPermissionAsync(user.Id, ct);
});

Sync policies are implicitly convertible to async:

ResourcePolicy sync = ResourcePolicy.IsAuthenticated();
AsyncResourcePolicy async = sync; // implicit conversion via SyncToAsyncPolicy

Async composition supports short-circuit evaluation (AND returns false immediately if the left side is false, OR returns true immediately if the left side is true).

PolicySerializer converts between ResourcePolicy and PolicyExpression for JSON storage:

var policy = ResourcePolicy.RequirePermission("orders.read")
& ResourcePolicy.IsAuthenticated();
PolicyExpression expr = PolicySerializer.Serialize(policy);
// expr can be serialized to JSON, stored in database, etc.
ResourcePolicy restored = PolicySerializer.Deserialize(expr);

PolicyExpression is a record with a discriminated type (PolicyExpressionType) and child nodes for composite policies. Custom and delegate-based policies are not serializable.

Supported PolicyExpressionType values: Allow, Deny, Permission, AnyPermission, AllPermissions, Role, Group, Claim, Authenticated, PrincipalKind, And, Or, Not.


Interfaces (defined in Pragmatic.Abstractions)

Section titled “Interfaces (defined in Pragmatic.Abstractions)”

These interfaces live in the Pragmatic.Abstractions package so modules can depend on them without referencing Pragmatic.Authorization.

Strongly-typed application role with static abstract members:

public interface IRole
{
static abstract string Name { get; }
static abstract string? Description { get; }
static abstract IReadOnlyList<string> DefaultPermissions { get; }
}

Module-defined permission template (not an application role):

public interface IRoleDefinition
{
static abstract string Name { get; }
static abstract string? Description { get; }
static abstract IReadOnlyList<string> Permissions { get; }
}

Key distinction: IRoleDefinition belongs to modules and provides permission bundles. IRole belongs to the host and represents application roles. The host composes roles from definitions via IncludeDefinition<T>().

Strongly-typed group with default roles:

public interface IGroup
{
static abstract string Name { get; }
static abstract string? Description { get; }
static abstract IReadOnlyList<string> DefaultRoles { get; }
}

Strongly-typed permission for SG-driven registries:

public interface IPermission
{
static abstract string Name { get; }
static abstract string? Description { get; }
static abstract string? Category { get; }
}

Resolves permissions from an external source. Multiple providers compose in order:

public interface IPermissionProvider
{
int Order { get; }
ValueTask<IReadOnlySet<string>> ResolvePermissionsAsync(
ICurrentUser user, CancellationToken ct = default);
}

Built-in providers: ClaimsPermissionProvider (Order 0), RoleExpansionProvider (Order 100), GroupExpansionProvider (Order 200).

Runtime authorization interface, accessed via ICurrentUser.Authorization:

public interface IUserAuthorization
{
IReadOnlyCollection<string> Roles { get; }
IReadOnlySet<string> Permissions { get; }
IReadOnlyCollection<string> Groups { get; }
IReadOnlyCollection<string> Scopes { get; }
bool HasPermission(string permission);
bool HasAnyPermission(IEnumerable<string> permissions);
bool HasAllPermissions(IEnumerable<string> permissions);
bool IsInRole(string role);
bool IsInGroup(string group);
bool HasScope(string scope);
}

Implemented by CachedPermissionResolver at runtime.

Instance-level authorization (ABAC). Contravariant on TResource:

public interface IResourceAuthorizer<in TResource>
{
ValueTask<bool> CanAccessAsync(
ICurrentUser user, TResource resource, string action,
CancellationToken ct = default);
}

Async permission checker for scenarios requiring I/O:

public interface IPermissionChecker
{
ValueTask<bool> HasPermissionAsync(string permission, CancellationToken ct = default);
ValueTask<bool> HasAnyPermissionAsync(IEnumerable<string> permissions, CancellationToken ct = default);
ValueTask<bool> HasAllPermissionsAsync(IEnumerable<string> permissions, CancellationToken ct = default);
}

Applied to actions, mutations, and queries. Requires ALL specified permissions (AND logic):

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class RequirePermissionAttribute(params string[] permissions) : Attribute
{
public string[] Permissions { get; }
public string? Description { get; set; } // Mode 2: SG generates a constant
public string? Category { get; set; } // Override for generated permission
}

public interface IRolePermissionStore
{
ValueTask<IReadOnlySet<string>> GetPermissionsForRoleAsync(string roleName, CancellationToken ct = default);
ValueTask<IReadOnlyList<string>> GetAllRolesAsync(CancellationToken ct = default);
}
public interface IGroupRoleStore
{
ValueTask<IReadOnlySet<string>> GetRolesForGroupAsync(string groupName, CancellationToken ct = default);
ValueTask<IReadOnlyList<string>> GetAllGroupsAsync(CancellationToken ct = default);
}

InMemoryRolePermissionStore and InMemoryGroupRoleStore are populated automatically by AuthorizationBuilder.MapRole and MapGroup. Thread-safe via ConcurrentDictionary and per-set locking. Registered as singletons.

InterfaceExtendsPurpose
ITemporalRolePermissionStoreIRolePermissionStorePoint-in-time permission resolution
ITenantRolePermissionStoreIRolePermissionStoreTenant-specific permissions
ITemporalGroupRoleStoreIGroupRoleStorePoint-in-time group resolution
ITenantGroupRoleStoreIGroupRoleStoreTenant-specific groups
InterfacePurpose
IDynamicPermissionStoreRuntime-created permissions
IDynamicRoleStoreRuntime-created roles
IResourcePolicyStoreDynamic policy-to-resource assignments

IPermissionCatalog and IResourceCatalog are discovery APIs for admin tooling and inspection. They merge static (SG-generated) and dynamic (database-backed) sources.

public interface IPermissionCatalog
{
ValueTask<IReadOnlyList<PermissionInfo>> GetAllPermissionsAsync(CancellationToken ct = default);
ValueTask<IReadOnlyList<RoleInfo>> GetAllRolesAsync(CancellationToken ct = default);
ValueTask<IReadOnlySet<string>> GetEffectivePermissionsAsync(string userId, CancellationToken ct = default);
}

DefaultPermissionCatalog merges IReadOnlyList<PermissionInfo> (static, from SG) with IDynamicPermissionStore (optional). GetEffectivePermissionsAsync returns an empty set in the default implementation (deny-by-default). Full cross-user resolution requires the RBAC Management package.

public interface IResourceCatalog
{
ValueTask<IReadOnlyList<ProtectedResource>> GetAllResourcesAsync(CancellationToken ct = default);
ValueTask<PolicyExpression?> GetResourcePolicyAsync(string resourceIdentifier, CancellationToken ct = default);
}

DefaultResourceCatalog merges static resources with IResourcePolicyStore (optional).

public sealed record ProtectedResource(
string ResourceType, // "Endpoint", "Action", "Mutation"
string Identifier, // route pattern or action FQN
string DisplayName,
string? Category)
{
public IReadOnlyList<string> StaticPermissions { get; init; } = [];
public PolicyExpression? PolicyExpression { get; init; }
public string? ResourceGroup { get; init; }
}
public sealed record PermissionInfo(string Name, string? Description, string? Category);
public sealed record RoleInfo(string Name, string? Description, IReadOnlyList<string> DefaultPermissions);

AuthorizationDiagnostics provides OTel-compatible instrumentation:

InstrumentNameDescription
ActivitySourcePragmatic.AuthorizationDistributed tracing
Counter<long>pragmatic.authorization.permission_checksTotal permission checks
Counter<long>pragmatic.authorization.permission_deniedDenied permission checks
Counter<long>pragmatic.authorization.cache_hitsCache hits (resolved set already available)
Counter<long>pragmatic.authorization.cache_missesCache misses (full provider chain invoked)

UseAuthorization / AddPragmaticAuthorization registers:

RegistrationLifetimeNotes
IPermissionProvider (ClaimsPermissionProvider)ScopedAlways registered
IPermissionProvider (RoleExpansionProvider)ScopedWhen roles are mapped or custom store used
IPermissionProvider (GroupExpansionProvider)ScopedWhen groups are mapped or custom store used
IRolePermissionStore (InMemoryRolePermissionStore)SingletonWhen MapRole used without custom store
IGroupRoleStore (InMemoryGroupRoleStore)SingletonWhen MapGroup used without custom store
IUserAuthorization (CachedPermissionResolver)ScopedAlways registered
IPermissionCatalog (DefaultPermissionCatalog)SingletonTryAdd (SG-generated takes precedence)
IResourceCatalog (DefaultResourceCatalog)SingletonTryAdd (SG-generated takes precedence)
AuthorizationOptionsOptionsVia IOptions<AuthorizationOptions>

The SG auto-detects Pragmatic.Authorization via FeatureDetector and generates:

  • PermissionConstants: Nested static classes per entity (BookingPermissions.Reservation.Read, .Create, .Update, .Delete, .All).
  • PermissionRegistry: IReadOnlyList<PermissionInfo> with all known permissions (static + IPermission).
  • RoleSeedingExtensions: SeedFromJson() if roles.pragmatic.json exists. Expands inheritance at compile-time.

The SG auto-generates permission constants following a hierarchical naming convention:

{boundary}.{entity-kebab}.{operation}
ExampleMeaning
booking.reservation.createCreate a reservation
booking.reservation.readRead reservations
booking.guest.updateUpdate guest details
catalog.amenity.deleteDelete an amenity
billing.invoice.refundCustom permission (from [RequirePermission])

CRUD operations (create, read, update, delete, list) are auto-generated for every entity with [Entity<T>]. Custom permissions are generated from [RequirePermission("...")] attributes on actions/mutations.


  • WithoutPermissions(...) is safe only with explicit permissions. If you try to exclude something covered by a wildcard, the builder fails fast at startup.
  • Internal calls skip the permission and policy gates through ICallContext, but they still preserve user context for downstream resource and data checks.
  • IPermissionCatalog is reliable for listing known permissions and roles. The default catalog does not yet provide full cross-user effective permission resolution; for the current user prefer ICurrentUser.Authorization, and for admin-side lookup prefer the management actions.

PackagePurpose
Pragmatic.AuthorizationEngine: policies, evaluation, caching, stores
Pragmatic.Authorization.ManagementOptional RBAC package: CRUD actions, EF entities, dynamic stores

Pragmatic.Abstractions (Authorization namespace)

Section titled “Pragmatic.Abstractions (Authorization namespace)”
TypeKindPurpose
IRoleInterfaceStrongly-typed application role
IRoleDefinitionInterfaceModule permission template
IGroupInterfaceStrongly-typed group
IPermissionInterfaceStrongly-typed permission
IPermissionProviderInterfacePermission resolution provider
IUserAuthorizationInterfaceRuntime authorization interface
IPermissionCheckerInterfaceAsync permission checker
IResourceAuthorizer<T>InterfaceInstance-level authorization
RequirePermissionAttributeAttributePermission enforcement on actions
PermissionInfoRecordPermission descriptor
RoleInfoRecordRole descriptor
TypeKindPurpose
ResourcePolicyAbstract classComposable sync authorization rule
AsyncResourcePolicyAbstract classComposable async authorization rule
RequirePolicyAttribute<T>AttributePolicy enforcement on actions
AuthorizationBuilderClassFluent configuration builder
RoleBuilderClassInline role configuration
GroupBuilderClassInline group configuration
AuthorizationOptionsClassConfiguration options
PermissionCacheOptionsClassCache configuration
DefaultEndpointPolicyEnumDefault endpoint authorization modes
CrudOperationEnumStandard CRUD operations
CachedPermissionResolverClassRequest-scoped permission resolver (implements IUserAuthorization)
WildcardMatcherStatic classPermission wildcard matching (internal)
IRolePermissionStoreInterfaceRole-to-permissions store
IGroupRoleStoreInterfaceGroup-to-roles store
InMemoryRolePermissionStoreClassIn-memory role store
InMemoryGroupRoleStoreClassIn-memory group store
ITemporalRolePermissionStoreInterfaceTemporal role store (forward-compat)
ITenantRolePermissionStoreInterfaceTenant-scoped role store (forward-compat)
ITemporalGroupRoleStoreInterfaceTemporal group store (forward-compat)
ITenantGroupRoleStoreInterfaceTenant-scoped group store (forward-compat)
IDynamicPermissionStoreInterfaceRuntime permission management
IDynamicRoleStoreInterfaceRuntime role management
IResourcePolicyStoreInterfaceDynamic policy-resource mapping
IPermissionCatalogInterfacePermission/role discovery
IResourceCatalogInterfaceResource discovery
DefaultPermissionCatalogClassDefault catalog (static + dynamic)
DefaultResourceCatalogClassDefault resource catalog
ProtectedResourceRecordResource descriptor
PolicyExpressionRecordSerializable policy tree
PolicyExpressionTypeEnumPolicy node discriminator
PolicySerializerStatic classPolicy serialization/deserialization
AuthorizationDiagnosticsStatic classOTel metrics and tracing
PragmaticBuilderAuthorizationExtensionsStatic classUseAuthorization / AddPragmaticAuthorization

58 unit tests covering:

  • CachedPermissionResolverTests — Provider chain, caching, permissions resolution
  • CachedPermissionResolverCacheTests — Cross-request ICacheStack integration
  • CompositePermissionProviderTests — Multi-provider merging
  • InMemoryRolePermissionStoreTests — Thread-safe role store
  • InMemoryGroupRoleStoreTests — Group store
  • GroupExpansionProviderTests — Group -> Role -> Permission chain
  • AuthorizationBuilderTests — Builder configuration, store selection
  • ResourcePolicyTests — All policy factory methods
  • ResourcePolicyCompositionTests — AND, OR, NOT composition
  • AsyncResourcePolicyTests — Async policies with short-circuit evaluation
  • PolicySerializerTests — Round-trip serialization
  • WildcardMatcherTests — All wildcard patterns
  • DefaultPermissionCatalogTests — Static + dynamic merging
  • DefaultResourceCatalogTests — Resource catalog
  • RoleBuilderExtensionsTests — WithAllPermissions, WithOperation, WithoutPermissions