enlace_auth.resource_policy#
The OAuth per-connector allowlist: its shape, its parsing and its one rule.
[auth.oauth_server.resource_allowlist] maps a connector’s resource URL to the
emails allowed to authorize for it. The authorization server enforces it
(enlace_auth.auth.oauth_server), the config model validates it
(enlace_auth.config), and the admin dashboard describes it
(enlace_auth.admin.routes). All three go through parse_resource_allowlist()
here, so they cannot disagree about what a key means or what a value may hold.
The rule is fail closed, and fail loudly on anything malformed:
A listed resource admits exactly its listed emails;
["*"](ANY_ACCOUNT), on its own, admits every authenticated account.A key must be the resource URL exactly as a connector spells its audience (lowercase scheme and host, no default port, no
//,.or..), since a key in any other spelling can never match one.An unlisted resource is refused to everyone.
A server with no allowlist at all would admit everyone to every resource. That is never the result of a typo: it has to be asked for with
allow_unlisted_resources = true, and an allowlist that is present but malformed (a key that is not a URL, a value that is not a list of emails, a wildcard pattern, two keys that differ only by a trailing slash) is an error, not a guess.
The authorization server’s other config-time refusal lives here for the same
reason: check_require_consent() refuses to skip the consent screen, because
client registration is open to anyone (i2mint/enlace_auth#46).
Pure functions, no web or crypto imports, so the config model can use them.
Module Attributes
A |
Functions
|
Canonical form of a resource URL: the key both maps use, and the token |
|
Validate raw and return |
Validate raw and return |
- enlace_auth.resource_policy.ANY_ACCOUNT = '*'#
A
resource_allowlistentry that opens a resource to every authenticated account. Must be written out: an unlisted resource is refused (fail-closed).
- enlace_auth.resource_policy.normalize_resource(resource)[source]#
Canonical form of a resource URL: the key both maps use, and the token
aud.>>> normalize_resource("https://apps.example.com/api/x_mcp/mcp/") 'https://apps.example.com/api/x_mcp/mcp'
- Return type:
- enlace_auth.resource_policy.parse_resource_allowlist(raw, *, allow_unlisted_resources=False)[source]#
Validate raw and return
{normalized resource: lowercased emails}.Raises
ValueErrornaming the offending entry for anything that is not exactly the documented shape, and for an empty allowlist unless allow_unlisted_resources says an open server is intended. Setting that flag alongside a non-empty allowlist is also an error: the allowlist always refuses unlisted resources, so the flag would promise something that does not happen.>>> allowlist = parse_resource_allowlist( ... {"https://a.example/api/x/mcp/": ["Ann@Example.com", "bob@example.com"]} ... ) >>> {k: sorted(v) for k, v in allowlist.items()} {'https://a.example/api/x/mcp': ['ann@example.com', 'bob@example.com']} >>> parse_resource_allowlist({}, allow_unlisted_resources=True) {}