feat(security): act as (delegated entry) - work the personal surfaces in a chosen identity's name - #6529
Merged
Merged
Conversation
… personal surfaces in a chosen identity's name The delegated-data-entry scenario: one manager enters timesheets, leave requests and expenses for workers who do not use computers. Instead of manager-twin UIs per module, an ADMINISTRATOR arms an acting identity for the SESSION and the existing personal machinery serves that person's world: - ActAsFacade (api-security): server-side session override, entitlement re-checked on EVERY read (a revoked role kills it mid-session), arm/disarm audit-logged. Deliberately NOT authentication-level impersonation - roles, security checks and audit stamping stay the REAL user's. - /services/core/actas (core-configurations): GET state (any user - the shells render from it), PUT arm (403 unentitled), DELETE disarm. - SDK User.getEffectiveName(): the acting identity when armed, else the real login - for personal-identity resolution ONLY. - Generated personal controllers (EntityMyController template): me() and /me resolve against the effective user; sensitive scrubbing, forced owner FK and personalReadOnly 403s hold unchanged underneath. - Inbox: the ASSIGNEE task query serves the acting identity's tasks (a personal-assigned submit task becomes completable by the delegate); candidate-group visibility stays the REAL user's roles - groups are never impersonated. CLAIM assigns to the acting identity. - Shells: My shell gains the switcher, a loud persistent banner and one-click exit; the Applications shell gains the 'Enter data as...' entry point that arms and lands in the My shell. en + bg catalog entries. IntentEmissionCoverageIT (outermost layer, session-pinned): arm -> the my list serves the acting person's rows with sensitive still stripped; a delegated POST carries owner = acting person AND CreatedBy = the real user; personalReadOnly still 403s while armed; the Inbox assignee query serves the acting person's personal-assigned confirm task; disarm restores self. Emission: the my controller resolves via User.getEffectiveName(). PERSONALIZATION_PLAN.md phase G. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| } | ||
| String acting = username.trim(); | ||
| HttpSessionFacade.setAttribute(SESSION_ATTRIBUTE, acting); | ||
| logger.info("Act-as ARMED: [{}] now acts as [{}] for this session", UserFacade.getName(), acting); |
| String acting = HttpSessionFacade.getAttribute(SESSION_ATTRIBUTE); | ||
| HttpSessionFacade.removeAttribute(SESSION_ATTRIBUTE); | ||
| if (acting != null && !acting.isBlank()) { | ||
| logger.info("Act-as DISARMED: [{}] no longer acts as [{}]", UserFacade.getName(), acting); |
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.
The scenario
Small real-world businesses (construction crews, workshops, field teams) often have ONE person - the manager - entering everything: the workers do not use computers. The personal ("My") surfaces are exactly the right UX for that data (my timesheet, my leave request, my expenses), but they are hard-scoped to the logged-in user, and a personal-assigned process task (`assignee: personal`) is visible only in the owner's own Inbox - the manager cannot drive the flow at all.
The feature
An entitled user (ADMINISTRATOR) arms an acting identity for the current session and works the personal surfaces in that person's name:
Deliberately NOT authentication-level impersonation. Roles, security checks and audit stamping stay the REAL user's - `CreatedBy`/`UpdatedBy` keep recording who really performed the write while the record's owner reference carries the acting identity, so a delegated record shows whose it is AND who really entered it, forever.
Coverage (IT, outermost layer)
`IntentEmissionCoverageIT`, session-pinned sequence: arm -> the my list serves the acting person's rows with the sensitive field still stripped; a delegated POST carries owner = the acting person AND `CreatedBy` = the real user; `personalReadOnly` still 403s while armed; the Inbox assignee query serves the acting person's personal-assigned confirm task; disarm restores self. Emission assert: the generated my controller resolves via `User.getEffectiveName()`. Ran green locally (1/1). PERSONALIZATION_PLAN.md gains phase G.
🤖 Generated with Claude Code