Class CorePermissionEnforcer

java.lang.Object
com.kingsrook.qqq.backend.core.actions.permissions.CorePermissionEnforcer

public final class CorePermissionEnforcer extends Object
Permission enforcement inside the core actions (issues #1061 and #1062). Historically, table/process/widget/report permissions were checked only by the middlewares (Javalin, API, picocli, Slack) before they called a core action - so any other entry point (MCP, an AI assistant, custom routes, scripts) bypassed them, and FieldSecurityLock was not enforced at all. The core actions now call this class. Whether a given call is enforced: - input.getPermissionEnforcement() == SKIP (set only through withPermissionBypass(reason)): never enforced. - == ENFORCE: always enforced - even when nested inside another action, and even if the instance setting is off. - == DEFAULT: enforced only for an "outermost" call (one not running inside another core action - see QContext.getAuthorizedActionScopeDepth) and only if the instance setting enforcePermissionsInCoreActions is on. Calls nested inside a core action (pre/post customizers, process steps, audits, possible-value translation, widget renderers, async children of those) are framework-internal and trusted - the outer action already applied the check owed to its caller. This preserves QQQ's existing model where, e.g., a process the user may run can read tables the user can't. System user sessions (QSystemUserSession) pass every permission check and field lock, so scheduled jobs, automations and ESB handlers that run as the system user are unaffected; the framework's own schedulers, automation and queue pollers also mark their process runs with withPermissionBypass. Record security locks are not handled here - they are enforced (as before) inside the backend modules and the DML actions, for every call.
  • Field Details

  • Method Details

    • isInstanceEnforcementEnabled

      public static boolean isInstanceEnforcementEnabled()
      The effective instance-level setting: QInstance.enforcePermissionsInCoreActions if set; else the system property / environment variable if set; else false.
    • shouldEnforce

      public static boolean shouldEnforce(AbstractActionInput input)
      Should permissions (and field security locks) be enforced for this input, at this point (e.g., before its action opens its authorized scope)?
    • shouldEnforceFieldAccess

      public static boolean shouldEnforceFieldAccess(AbstractActionInput input)
      Field policy opt-in does not opt an application into core table permissions.
    • checkTablePermissions

      public static boolean checkTablePermissions(AbstractTableActionInput input, TablePermissionSubType... permissionSubTypes) throws QException
      If enforcing for this input, check the table permission(s) - throwing QPermissionDeniedException if denied. Returns whether enforcement applied (so the caller can also apply field security locks and the like).
      Throws:
      QException
    • checkReadCriteria

      public static FieldAccessHelper.PendingExactMatch checkReadCriteria(AbstractTableActionInput input, List<QueryJoin> queryJoins, QQueryFilter filter, Collection<String> additionalFieldNames) throws QException
      For read-style actions that are being enforced: check READ on every joined table (explicit or implied by the filter), and that the filter / additional field names satisfy field access policy. Validate literal-only restrictions before session expressions are expanded by the underlying action, preserving reusable filters.
      Throws:
      QException
    • checkRestoreWriteFlag

      public static void checkRestoreWriteFlag(AbstractTableActionInput input, Set<ActionFlag> flags) throws QPermissionDeniedException
      Reject a write that carries a RestoreWriteFlag unless the session has the RestoreWriteFlag.REQUIRED_PERMISSION permission. Unlike the other checks in this class, this one is NOT conditional on the instance setting, on the nesting of the call, or on a permission bypass: the flag relaxes integrity behaviors (automations, customizers, audit, events), so it is an escalation that must always be earned. QSystemUserSession has all permissions, so framework-internal system work is unaffected.
      Throws:
      QPermissionDeniedException
    • checkProcessPermission

      public static boolean checkProcessPermission(AbstractActionInput input, String processName, Map<String,Serializable> processValues) throws QException
      If enforcing for this input, check the process permission (using the input's process values, for custom permission checkers that consider them).
      Throws:
      QException
    • checkAssociatedWrites

      public static void checkAssociatedWrites(AbstractTableActionInput input) throws QException
      For write actions that are being enforced: check permissions (and field security locks) for the associated child records the write will insert, update or delete - including an update's omitted children and a delete's cascades. Discovery queries run inside an authorized scope; the permission checks themselves are explicit.
      Throws:
      QException
    • openAuthorizedScope

      public static CorePermissionEnforcer.AuthorizedScope openAuthorizedScope()
      Open an authorized action scope, to be closed via try-with-resources. Core actions do this after applying whatever enforcement is owed to their caller, so the actions they call internally are trusted.
    • runTrusted

      public static <T, E extends Exception> T runTrusted(String reason, UnsafeSupplier<T,E> supplier) throws E
      Run framework- or application-internal code as trusted: core actions called by the supplier (with PermissionEnforcement.DEFAULT) are not subject to the instance-level permission enforcement. The reason is required, and logged at debug level - grep for runTrusted / withPermissionBypass to audit.
      Throws:
      E