Docs / Architecture / Identity
Explanation For operators

Identity

Who may use the application, and how a person becomes a role.

Five modes, one environment variable, exactly one of them on. create_app refuses to start on a combination that has no single right reading rather than picking one — a deployment that was told two contradictory things and guessed is a deployment whose identity story nobody can state a year later.

DPF_IDENTITY=open        no door. A laptop, and the default.
DPF_IDENTITY=site        the site this is mounted inside signs you in (dpf/web/gate.py).
DPF_IDENTITY=link        an unlisted link: one key in the URL, once, then a cookie.
DPF_IDENTITY=site+link   either of the two above.
DPF_IDENTITY=entra       Microsoft Entra ID (dpf/web/entra.py). What a customer runs.

On site, link and site+link the door is not the whole story: every request must also be a registered person (dpf/web/accounts.py). An administrator adds people by email on the Users page; each gets a one-time link to set a password, or signs in with the site's session. The first administrator, and the way back in when every administrator is locked out, is the shell: python -m dpf user add|invite EMAIL.

Systems do not use any of these doors. The read API at /api/v1 has its own — a service token — and a token opens nothing outside it. See the read API. The documentation at /docs, and the stylesheets it needs, is open on every door.

PATHDOORWHO YOU AREYOUR ROLEdpf/web/api_v1.py/api/v1and MCP overHTTPdpf/web/machine.pyThe token doora service token,kept as a hashdpf/tokens.pyA systemservice:NAME,nobody's accountNoneit reads, and can changenothingEvery otherpageall but /api/v1 and/docsdpf/web/gate.pyopenno door, for a laptopdpf/web/gate.pysite · link ·site+linkthe host site's sign-in,an unlisted link, oreitherdpf/web/entra.pyentraMicrosoft Entra IDNobody signs inwhoever is at thekeyboarddpf/web/accounts.pyA registered personan email, then apassword or thesite's sessionThe person Entrasigned inwith the groups ontheir tokenAny of the fourchosen in the sidebar, onthe recordThe role on theiraccountonly an administrator mayact as anotherThe narrowest mappedrolefrom the groups; switchingis refused
Doors and roles. Which door a request meets, who that makes you, and what decides your role.

DPF_GATE=1 is the older spelling of site and still works; set alongside a different DPF_IDENTITY it is a startup error, not a preference.

What changes between them is who decides your role, not only who you are. In open mode the four roles are a switcher: one click, on the record, honest about being a device for trying the factory — principal and same_hand are what stop that from being one person approving their own request. On site, link and site+link a person acts in the role on their account, and only an administrator may switch, to walk the roles; the record keeps who they are. Under entra the role is an assertion Entra made about you, role switching is refused with a 403 that names the group, and /governance/users becomes a read-only register of who has signed in.

Groups become roles

DPF_ENTRA_ROLE_MAP=use_case=<guid>,data_hub=<guid>,admin=<guid>,source_system:pol=<guid>

A group id (or a name, if the tenant emits names) on the right; a role on the left, with the source system as part of the key for source owners — having the source-system role is not the same as owning the data, and rbac.owns_system compares the system on the row against the system being asked about. A role may appear more than once; two groups granting data_hub is ordinary in a directory that has grown.

Three rules, each of them a tested failure mode:

  • In no mapped group is no access at all. Not a default role, not read-only. The row is not created and nothing is switched to. Failing open on identity is a default role wearing a small hat.
  • In several mapped groups gives the narrowest role, not the widest. Somebody in the source-system group because they own a system and in the use-case group because their team consumes one would otherwise become the person who can both ask and answer — a single-person approval assembled out of two legitimate memberships. Narrowest is read off the permission table (rbac.breadth), so placing a new decision against a role re-ranks it. The users page shows every mapped group the person carries, not only the one that won.
  • The map is re-read per request. Removing a group from DPF_ENTRA_ROLE_MAP takes effect on the person's next click, without a deploy — exactly as gate.allowed() already does for emails. Membership itself is as fresh as the token, which is the directory's clock: DPF_ENTRA_SESSION_HOURS (default 8) is how long a removed leaver keeps what their last token said.

Settings

Every variable Entra needs, with its default, is in Configuration. Missing a required one, the application does not start: a workspace that starts and refuses everybody looks, from the outside, exactly like a workspace that is broken.

Registering the application in Entra, and what to tell the identity team once it is, is in Turn on Microsoft Entra ID.

Documentation for version 0.2.0 Reviewed 2026-10-04 Covers dpf/web/gate.py, dpf/web/accounts.py, dpf/web/entra.py, dpf/web/machine.py