Skip to content

intent: permissions.can never reaches the generated app's access gates #6760

Description

@delchev

Problem

An intent's permissions: block is the one place an author states who may do what:

permissions:
  - { role: ProvisioningAdmin,  can: [Tenant:read, TenantApplication:create] }
  - { role: ProvisioningViewer, can: [Tenant:read] }

The generated application never checks those roles. Two disjoint namespaces exist side by side:

  • AuthoredPermissionIntentGenerator emits <intent>.roles from permissions[].role. Its
    own javadoc is explicit that the can: tokens are not translated into anything: "the tokens are
    informational; document them but do not generate .access from them."
  • EnforcedEdmIntentGenerator gives every entity convention-derived gates
    (roleRead = <project>.<perspective>.<Entity>ReadOnly,
    roleWrite = <project>.<perspective>.<Entity>FullAccess, generateDefaultRoles = true).
    ModelParameterProcessor.collectRoles materialises those into the app's own roles, and the
    generated controller enforces them in checkPermissions("read"|"write").

So granting ProvisioningAdmin grants nothing at all — the gate the controller actually consults is
<project>.<perspective>.TenantApplicationFullAccess, a name no intent construct mentions. The
author's stated access model is silently unconsumed, which is the failure class of #6646: it does
not error, it does not warn, and the roles show up in the UI so it looks wired.

The web/UI surface has no gate at all: nothing emits an .access. Note .access is already listed
in IntentGenerationService.INTENT_OWNED_EXTENSIONS, so a hand-authored .access at the project
root is scrubbed by the next Generate — it survives only under custom/, which is not
documented anywhere and is discovered the hard way.

This needs no spec change. The specification deliberately leaves URL-shaped rules to "whichever
downstream template materialises the UI, because only that template knows the paths it publishes" —
Dirigible is that template, and it knows exactly which paths it publishes.

Ask

  1. Bind can: to the entity gates. When any can: token names an entity, that entity's
    roleRead / roleWrite should be the authored roles rather than (or in addition to) the
    convention names, so the role the author declared is the role the controller checks. Entities no
    token names keep today's convention behaviour, so existing generated apps are unchanged.
  2. Emit <intent>.access for the paths the templates publish — the REST base and per-entity
    controller paths (/services/java/<project>/gen/<module>/api/<perspective>/<Entity>Controller),
    the generated SPA pages, the report pages — from the same tokens. Opt-in through the project's
    generation recipe in .settings, not a new DSL key (the DSL surface is the spec's; the
    path mapping is ours).
  3. Say that root-level .access is scrub-owned in the intent docs, and keep custom/ as the
    documented home for hand-authored access constraints.

Worth deciding as part of this: the action vocabulary. The controller gate is binary (read / write)
while can: tokens are free-form (create, approve, …); a mapping — or a rejection of tokens
that cannot map — is better than quietly dropping the ones that do not fit.

Reported from a service whose whole domain layer is intent-first and which had to hand-author
custom/security/*.access to gate its endpoints at all.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions