Skip to content

Permissions

The entity system's authorization layer is built on three pieces:

  1. The Authorize DSL (in vyuh_entity_annotations) — declarative expressions like Authorize.permission('lms.area.view'), Authorize.allOf([...]), Authorize.self().
  2. An AuthorizationProvider — synchronously holds the current actor's permission / role / user-group state and evaluates Authorize expressions.
  3. UI AuthorizeGuard widgets and an AuthorizationGate shell for app-wide loading / refresh state.

This replaces the older PermissionProvider / EntityPermissionService / PermissionGuard API.

AuthorizationProvider

abstract class AuthorizationProvider — synchronous authorization provider for the entity system. The provider is the single entry point gates use to evaluate an Authorize expression. Concrete implementations populate cached state on login and refresh on demand; evaluation is pure and synchronous from there.

Cached state (abstract)

PropertyTypeDescription
permissionsSet<String>Permission strings (e.g. 'lms.batch.view')
rolesSet<String>Role codes (e.g. 'qa_lead')
userGroupsSet<String>User-group IDs
currentUserIdString?Authenticated user id, or null when anonymous

Synchronous primitives (abstract)

MethodReturnsDescription
hasPermission(String perm)boolTrue when the actor holds perm
hasRole(String code)boolTrue when the actor holds the role
isMemberOf(String groupId)boolTrue when the actor is a group member

Readiness

MemberTypeDescription
isReadyboolTrue after the first refresh attempt resolves (success or failure). Stays true.
isRefreshingboolTrue while a refresh is in flight (concurrent calls dedupe through one Completer).
changesStream<void>Broadcasts on every refresh start/end and state transition.
whenReadyFuture<void>Resolves when isReady becomes true. Idempotent.
hasFreshDataboolTrue when the last refresh fetched from server (vs storage / cache fallback).
lastFreshAtDateTime?Timestamp of the last successful server fetch.

Evaluation

dart
bool can(Authorize expr, {Object? subject, EntityOwnership? ownership});

Evaluates an Authorize expression against the current cached state. subject and ownership are required when the expression contains Authorize.self(). On denial, fires the onDeny callback.

Refresh

dart
Future<void> refresh();
StreamSubscription<void> bindRefreshStream(Stream<void> events);

refresh() is the single async surface — call on login, logout, or any event that may change the actor's authorization state. bindRefreshStream subscribes to a Stream<void> and triggers a refresh on every emission. EntitySystemPlugin automatically binds it to vyuh.auth.userChanges so login/logout transitions refresh the cache.

Denial logging

dart
void Function(Authorize expression, AuthorizationContext context)? onDeny;

Set once at app startup to receive a callback every time can returns false. Fired synchronously after evaluation; implementations should dispatch async logging via unawaited(...).


OpenAuthorizationProvider

final class OpenAuthorizationProvider extends AuthorizationProvider — built-in provider that allows everything. Used by default when none is supplied to EntitySystemPlugin.

dart
final class OpenAuthorizationProvider extends AuthorizationProvider {
  // permissions / roles / userGroups: returns {'*'}
  // hasPermission / hasRole / isMemberOf: always true
  // can(...): always true
  // isReady / hasFreshData: true
  // refresh(): no-op
}

AuthorizeGuard

final class AuthorizeGuard extends StatelessWidget (in vyuh_entity_system_ui) — renders child when an Authorize expression passes, otherwise falls back to a surface-appropriate visual. Replaces the old PermissionGuard.

Constructor

dart
const AuthorizeGuard({
  super.key,
  required this.authorize,
  this.evaluator,
  required this.child,
  this.fallback,
  this.defaults = const AuthorizeDefaults(),
  this.subject,
});

Surface factories

Each factory resolves a different default fallback slot from AuthorizeDefaults.

FactorySurfaceDefault fallback
AuthorizeGuard.routeRoute contentplaceholder ("no access" page)
AuthorizeGuard.menuMenu itemshide
AuthorizeGuard.tabTabshide
AuthorizeGuard.sectionDetail sections / panelsplaceholder
AuthorizeGuard.actionAction buttonsdisable
AuthorizeGuard.fieldReadField readredact (••••)
AuthorizeGuard.fieldWriteField writedisable (read-only)
AuthorizeGuard(...) (unnamed)Generic widgethide

Properties

PropertyTypeDescription
authorizeAuthorizeExpression to evaluate
evaluatorAuthorizeEvaluator?Custom evaluator. Defaults to vyuh.entity?.authorizationProvider?.can
childWidgetRendered when authorization passes
fallbackAuthorizeFallback?Override the surface default
defaultsAuthorizeDefaultsApp-level fallback defaults
subjectObject?Subject passed to the evaluator (used by Authorize.self() / ownership checks)
dart
typedef AuthorizeEvaluator = bool Function(
  Authorize authorize, {
  Object? subject,
});

When evaluator is null, the guard delegates to vyuh.entity?.authorizationProvider?.can — the common case for production apps that register EntitySystemPlugin. Falls through to "allow" when no provider is registered.

Example

dart
AuthorizeGuard.action(
  authorize: Authorize.permission('equipment.edit'),
  child: EditButton(onPressed: _openEditor),
)

AuthorizeGuard.route(
  authorize: Authorize.allOf([
    Authorize.permission('lms.batch.approve'),
    Authorize.not(Authorize.self()),
  ]),
  subject: batch,
  child: BatchApprovalPage(batch: batch),
)

AuthorizeFallback

sealed class AuthorizeFallback (in vyuh_entity_annotations) — what to show when an AuthorizeGuard denies.

VariantVisual
FallbackHide()SizedBox.shrink()
FallbackDisable({title, message})CDX DisabledOverlay over the child
FallbackPlaceholder({title, message})CDX LockedSurface placeholder
FallbackRedact({mask})CDX RedactedValue (defaults to ••••)
FallbackWidget(Type fallbackWidget)Custom widget resolved from AuthorizeFallbackRegistry

The top-level helper Widget renderAuthorizeFallback(fallback, authorize, child) exposes the same rendering pipeline so menu builders and route gates can render the vocabulary consistently.


AuthorizeFallbackRegistry

class AuthorizeFallbackRegistry (in vyuh_entity_system_ui) — maps a custom AuthorizeFallbackWidget subclass to a builder. Register once at app startup before any guard renders.

dart
abstract class AuthorizeFallbackWidget extends StatelessWidget {
  final Authorize authorize;
  const AuthorizeFallbackWidget({super.key, required this.authorize});
}

class AuthorizeFallbackRegistry {
  static void register<T extends AuthorizeFallbackWidget>(
    Type type,
    T Function(Authorize authorize) builder,
  );

  static AuthorizeFallbackWidget lookup(Type type, Authorize authorize);
}

Throws StateError if lookup is called for an unregistered type — fail fast at the call site rather than render a silent placeholder.


AuthorizationGate

class AuthorizationGate extends StatelessWidget — top-level shell gate that ties the routed UI to the lifecycle of the AuthorizationProvider. Place it in your MaterialApp.router builder.

dart
const AuthorizationGate({
  super.key,
  required this.child,
  required this.loaderBuilder,
  this.showRefreshingIndicator = true,
});
PropertyTypeDescription
childWidgetThe routed child
loaderBuilderWidgetBuilderApp-specific loading view shown while the provider hydrates
showRefreshingIndicatorboolWhether to overlay a thin progress bar during background refreshes

Behaviour:

  • Anonymous user (login screen) → renders child unmodified.
  • Authenticated, provider not yet ready → renders loaderBuilder.
  • Ready → renders child; if isRefreshing, overlays AuthorizationRefreshingIndicator.
dart
MaterialApp.router(
  builder: (context, child) => AuthorizationGate(
    loaderBuilder: (_) => const MyAppBrandedLoader(),
    child: child!,
  ),
)

AuthorizationRefreshingIndicator

A 2px (configurable via height) progress strip used by AuthorizationGate while the provider is mid-refresh. Public so apps can use it standalone (e.g., on a settings screen with a manual "Refresh permissions" button).


EntityRoutePermissions

The configuration surface for declaring permissions on entity routes. Documented under EntityConfiguration.

When a route has permissions set, the standard route builder wraps the route body in AuthorizeGuard.route(authorize: Authorize.anyPermission(perms)) so users who fail the check see the route's "no access" placeholder.


EntityPermissionCache

class EntityPermissionCache — static cache for entity permission results. Used internally by EntityLayouts to cache detail-tab permission filtering and by other places that need a typed permission cache scoped per entity identifier.

Static methods

MethodDescription
getDetailTabs(String entityIdentifier)Cached tab identifiers for an entity
setDetailTabs(String entityIdentifier, List<String>)Cache tab identifiers
hasDetailTabs(String entityIdentifier)Whether tab cache exists
getActions(String entityIdentifier)Cached action identifiers
setActions(String entityIdentifier, List<String>)Cache action identifiers
hasActions(String entityIdentifier)Whether action cache exists
getRoutePermission(String entityIdentifier, String routeName)Cached route permission result
setRoutePermission(String entityIdentifier, String routeName, bool allowed)Cache a route permission
hasRoutePermissions(String entityIdentifier)Whether route cache exists
clearEntityType(String entityIdentifier)Clear cache for one entity type
clearAll()Clear all cached permissions
getStats()Cache statistics for debugging

Clearing on logout

dart
EntityConfiguration.clearAllPermissionCaches();

This delegates to EntityPermissionCache.clearAll().

See Also