Python API#
Everything this package offers Python code, at one import path.
from pas.plugins.identity import api
api.IProfileEnricher # register against
api.IdentityLinked # subscribe to
api.UserProfile # type hint, adapt
api.profile.get_current() # do
Write against pas.plugins.identity.api.
The module a name happens to live in under core or server is where it is implemented, not where it is published, and it moves without notice.
Note
from pas.plugins.identity import api shadows from plone import api.
A module that wants both aliases one:
from pas.plugins.identity import api as identity_api
Conventions#
These hold across every name below, so that one worked example teaches the rest.
Arguments |
A userid where the question is about a person; the object where it is about a Profile |
Returns |
A lookup answers |
Names |
Verb first, no redundant noun: |
Current user |
Never implicit. |
The last one is the one to read twice.
api.profile.get(userid) has no default, and that is deliberate: a default falling back to the current user would make None mean two things at once—"I did not say whose" and "the lookup missed"—and only the caller knows which.
A caller passing a userid read from catalog metadata that turned out to be missing would be handed their own Profile, and every check downstream would pass.
Vocabulary#
What a policy package registers against. These are the same objects the layer defines, not copies, so an adapter registered through the façade is registered for what the package actually fires.
Kind |
Names |
|---|---|
Content classes |
|
Content interfaces |
|
Extension points |
|
Catalog |
|
Event classes |
|
Event interfaces |
|
Driver authoring |
|
Records and types |
|
Exceptions |
|
Constants |
|
Subscribe to an event interface, not to an event class—that is what a
ZCML <subscriber> registration and @adapter both take.
The classes are here for the code that constructs or type-hints one.
api.profile#
Call |
Answers |
|---|---|
|
The Profile, or |
|
The current user's Profile, or |
|
The Profile, minting one if this is a first sign-in; |
A Profile wakes when you fetch it, so these are for paths that are going to read a field or write one. A check that only needs one value should go through the catalog.
Questions the Profile answers itself#
There is no api.profile.email(userid) and no api.profile.url(userid), on purpose.
A question about one Profile that the Profile can answer is a property on it:
profile = api.profile.get(userid)
profile.email # the address that stands for this person
profile.verified_emails # the ones this site has proved
profile.absolute_url() # where it lives
api.portrait#
Keyed by userid rather than by Profile, because a user with no Profile can still have a member portrait—so profile.has_picture() could not express an answer of True.
Call |
Answers |
|---|---|
|
Whether a picture exists in either store |
|
The URL of the Profile's picture, or |
|
Nothing. Stores image bytes the caller already holds, where a login would put them |
|
Whether a picture was stored. Fetches the URL with a login's checks first |
The first two are not the same question: has_picture also looks in portal_memberdata.
The two writes are for two kinds of caller.
store is for one that downloaded its pictures beforehand, such as an import that caches them on disk.
It puts the picture on the Profile, unless the user has no Profile or chose a picture of their own there; then it goes to portal_memberdata.
Passing url lets the Profile remember where the picture came from, so a later login can replace it.
sync_portrait is for a caller that holds only a URL.
It fetches nothing unless the site-wide portrait switch is on, refuses plain HTTP unless allow_http is set, and applies the site's timeout and size limit.
sync_portrait is the one exception to the rule that an operation which cannot proceed raises.
It answers False instead, because it is the same call a login makes, and a login must not fail over an avatar.
A batch import learns why a picture was refused only from the log.
api.provider#
Call |
Answers |
|---|---|
|
One configured provider, or |
|
Every configured provider, enabled or not, in the order the sign-in page offers them |
|
This package's PAS plugin, or |
Read-only. Registering or editing a provider goes through the control panel or a GenericSetup profile, so that every route in is held to the same validation.
api.plugin#
Call |
Answers |
|---|---|
|
The PAS plugin with that id in |
api.claims#
Only on a site running the authorization server.
Importing api.claims is safe anywhere; calling into it needs pas.plugins.identity[server] installed and its profile applied.
Call |
Answers |
|---|---|
|
The claims this server releases, with |
|
Every scope the server will release claims for |
|
The claim names one scope releases, with no user in hand |
|
The scopes the registered serializers declare |
|
The claims those serializers declare |
declared_scopes() is a subset of get_scopes(): openid is supported and declares no claims of its own.
For writing a serializer of your own, the same group carries what you subclass and register against:
Name |
Is |
|---|---|
|
The base class a new scope subclasses |
|
The interface a scope serializer is registered under |
|
The shipped serializers, to subclass when adding a claim to a scope this package already releases |
See Serialize a claim.
api.clients#
Also [server] only.
The one group here whose operations raise rather than answer None—registering a client that already exists has no sensible empty answer.
Call |
Answers |
|---|---|
|
One registered client, or |
|
Every registered client, in registry order |
|
The stored client and its plaintext secret |
|
Nothing; raises when the client is not registered |
|
A fresh secret, discarding the old one |
|
The client, or |
A minted secret is returned once and hashed on the way in. It cannot be read back—not here, not from the control panel, not from the registry.
Nothing in the package imports this#
api spans every layer, so anything under core or server importing it would collapse the boundary those layers are kept apart by.
An import-linter contract enforces that, and make check-imports runs it.
The same is true of plone.api, which nothing in Plone core imports.
Inside this package the layers keep importing each other directly; the façade is for consumers.
Next steps#
Add a field to a Profile and fill it at login: Write a profile enricher.
Release a claim about it: Serialize a claim.
What is settled and what is not: Stability.