Driver contract#
What a driver must declare and satisfy.
To write one, follow Write a driver. This page is the checklist.
The interface#
Attributes#
Attribute |
Purpose |
|---|---|
|
Unique id, such as |
|
Shown in the control panel. |
|
The schema the configuration form is generated from. |
|
Tuple of scopes requested when the provider names none. |
|
Claim keys tried, in order, to find the subject. |
|
The endpoint set, for a provider that publishes fixed URLs. Empty means discovery. |
|
The issuer to discover from, when the driver fixes it. Empty means the operator types it. |
|
Claim path → Profile field, seeded into a new provider. |
|
The claim groups arrive in, or empty for a provider with none. |
|
Provider group → local group. Empty for every shipped driver. |
|
Whether this provider's verification counts by default. |
|
Whether an identity may be linked from the account page. |
Base class defaults (core/drivers/base.py): settings_schema is
IOAuth2Settings, default_scope is (), subject_keys is ('sub',),
static_metadata is {}, issuer is '', default_group_claim is '',
default_trust_email_verification is False.
Methods#
Method |
Contract |
|---|---|
|
Turn the provider's payload into this package's claim names. |
|
The stable identifier. Raises when no key in |
BaseDriver also provides enrichment_endpoint, merge_enrichment,
reported_addresses and _email_verified for drivers that need a second call to
the provider.
The rules#
Each is a rule a driver must satisfy, and what catches a violation.
# |
Rule |
Enforced by |
|---|---|---|
1 |
A field marked secret stays secret: masked on the way out of every API surface, omitted from GenericSetup export. Do not invent your own credential storage. |
the serializers and the export tests |
2 |
Every field has an |
the driver contract test—a tie fails it rather than falling back to the alphabet |
3 |
|
the contract test |
4 |
|
the contract test, against the same fields the login filters on—and the registry, which refuses any other target |
5 |
|
the contract test |
6 |
|
convention; every shipped driver is empty |
7 |
|
the contract test and |
8 |
|
review; only |
9 |
A driver names one metadata source, not both: endpoints it publishes, or an issuer to discover from. |
the contract test |
10 |
Every URL a driver declares—each |
the contract test |
Why order is a number#
A configuration schema travels as a JSON object, and plone.restapi serializes
those with sorted keys, so the order fields are declared in is gone by the time
the control panel builds a form. The number is what survives.
The inherited fields are spaced by ten, so a new field can be slotted between two of them.
Why only True counts#
Several providers send the string "true", and several send 1. Everything that
reads the flag refuses anything that is not literally True, because a forged
unverified address that reads as truthy is an account takeover.
A provider that really does send a string is handled by the per-provider
accept_string_booleans switch, not by a driver being lenient.
Why the endpoints live on the driver#
A driver is everything this package knows about a kind of provider, and a
provider's endpoints are the most basic thing there is to know. They were a
dict keyed by driver id in core/flows/metadata.py until 1.0.0a5, which had
two costs: GitHubDriver.enrichment_endpoint read a key another module
defined, and a driver shipped by anybody else could not add a row to it.
metadata.py keeps every resolving concern—the discovery fetch, the
per-issuer cache, its TTL, forget(). Which of the two sources applies is the
driver's answer.
An empty static_metadata means discovery, which is the answer for every
OpenID Connect provider: the endpoints come from the issuer's document and
change when the provider says so. github fills it in because GitHub is plain
OAuth2 and publishes no discovery document at all.
An empty issuer means the operator supplies one, and that is decided by the
issuer field in settings_schema rather than by a list of driver ids—so a
subclass of GenericOIDCDriver this package has never heard of is discovered
exactly as its parent is. A driver declaring neither is refused rather than
sent to discovery with an empty URL: email, whose magic link never leaves the
site, is that case.
Why an empty group claim is not neutral#
Leaving default_group_claim empty switches the feature off for that driver: no
group_claim field appears in the configuration form, and nobody is asked to map
the groups of a provider that has none.
A map stored against such a provider grants nothing rather than guessing at a claim name.
Operators can override the claim with a dotted path, so a provider nesting groups
under realm_access.roles needs no driver of its own.
Registration#
A driver is a named utility providing IDriver, registered under its
driver_id. The control panel lists whatever is registered, and the form comes
from settings_schema, so a third-party driver needs no frontend change.