Profiles and groups#
The UserProfile and UserGroup content types, and the rules that govern them.
For the reasoning behind any of it, read About profiles and groups. For the contract a site's own types must meet instead, read Users and groups as content.
The two types#
|
|
|
|---|---|---|
Portal type |
|
|
Schema |
|
|
Marker it provides |
|
|
Workflow |
|
|
Identifier |
the object's id in its container is the userid |
the object's id is the group id |
Membership |
|
the same field, naming the groups this group is nested inside |
Containment |
nothing may be added inside a Profile |
|
Neither type declares group_ids. Both enable the behavior, which is what lets a
site's own user type gain membership without declaring the field, its
vocabulary, and its two permissions a second time.
A UserGroup does not store its own members. Membership is stored on each
member.
UserProfile fields#
Field |
Type |
Required |
Read permission |
Write permission |
|---|---|---|---|---|
|
|
yes |
View Profile |
Edit Profile |
|
|
yes |
View Profile |
Edit Profile |
|
|
yes |
View PII |
Edit Profile |
|
|
no, read-only |
View PII |
— |
|
|
no |
View Profile |
Edit Profile |
|
|
no |
View Profile |
Edit Profile |
|
|
no |
View Profile |
Edit Profile |
|
|
no |
View Profile |
Edit Profile |
|
from the behavior |
no |
View Profile |
Edit Profile Group Membership |
Permission titles are shortened here; see Permissions for the ids.
UserGroup fields#
Field |
Type |
Required |
|---|---|---|
|
|
yes |
|
|
no |
|
from the behavior |
no |
Addresses#
emails is an ordered tuple and is the required field. email is derived from
it and read-only:
the first verified address, or the first address at all when none is verified.
Rule |
Behaviour |
|---|---|
What counts as verified |
This site holds an |
What creates one |
A magic link, or a login through a provider the operator marked as trusting. |
A provider asserting |
Nothing, unless that provider is marked as trusted here. |
Writing |
Moves that address to the front of |
Writing an empty |
Ignored. |
Where |
Catalog metadata. Confirming or removing an email identity updates the owner's catalog entry. |
|
Absent. |
What a login writes#
Event |
Effect on |
|---|---|
First login |
Every address the provider reports, in the provider's order: primary first, then the ones it says it verified. |
A later login |
Appends only addresses no provider has offered before. |
An address you deleted |
Stays deleted. |
The order you chose |
Not rearranged. |
A provider changing your address |
The new one is added beside the old. |
Which address stands for a person is that order, so choosing one is moving it to
the front. The edit form's list does that, and so does Make preferred
on the Sign-in methods page. A verified address still wins over an
unverified one above it, because that is what email is derived from.
Workflow#
Profile states#
stateDiagram-v2
direction LR
[*] --> incomplete
incomplete --> complete: complete
complete --> incomplete: reopen
incomplete --> deactivated: deactivate
complete --> deactivated: deactivate
deactivated --> incomplete: reactivate
State |
Meaning |
Enumerated? |
|---|---|---|
|
Missing information the site requires. The initial state. |
yes |
|
Nothing required is missing. |
yes |
|
Excluded from enumeration and from property lookup. The object and its data are kept. |
no |
Which states count is profile_enumeration_states, ('incomplete', 'complete')
by default. See Settings.
Transition |
To |
Guarded by |
So the user themselves… |
|---|---|---|---|
|
|
|
can make it |
|
|
|
can make it |
|
|
|
cannot |
|
|
|
cannot |
Reactivating returns a profile to incomplete, never to complete.
Nothing asks a user to press a button: the package moves a profile between
incomplete and complete itself, whenever the profile is written to and
whenever its owner signs in. deactivated is never entered or left by that.
Who holds what, per state#
Every permission is mapped with acquired="False" in every state.
Permission |
|
|
|
|---|---|---|---|
|
Manager, Site Administrator, Owner |
+ Member |
Manager, Site Administrator |
|
Manager, Site Administrator, Owner |
+ Member |
Manager, Site Administrator |
|
Manager, Site Administrator, Owner |
same |
Manager, Site Administrator |
Edit Profile |
Manager, Site Administrator, Owner |
same |
Manager, Site Administrator |
View Profile |
Manager, Site Administrator, Owner |
+ Member |
Manager, Site Administrator |
View PII |
Manager, Site Administrator, Owner |
same |
Manager, Site Administrator |
Edit Profile Group Membership |
Manager, Site Administrator |
same |
same |
|
Manager, Site Administrator |
same |
same |
|
Manager, Site Administrator |
same |
same |
|
Manager, Site Administrator |
same |
same |
|
Manager, Site Administrator |
same |
same |
|
Manager, Site Administrator |
same |
same |
|
Manager, Site Administrator |
same |
same |
|
Manager, Site Administrator |
same |
same |
|
Manager, Site Administrator |
same |
same |
Two things to read off this table:
Ownernever holdsDelete objects, in any state. Stock Plone would grant it with acquisition on; the workflow states it and stops it at the site administrator.Ownernever holds Edit Profile Group Membership, in any state. Writinggroup_idsis granting yourself roles.
A user gets Owner on their own UserProfile and nothing on anybody else's. The
role is computed by a local role provider rather than assigned at creation.
Group states#
stateDiagram-v2
direction LR
[*] --> active
active --> deactivated: deactivate
deactivated --> active: reactivate
State |
|
Enumerated, and grants membership? |
|---|---|---|
|
Manager, Site Administrator, Member |
yes |
|
Manager, Site Administrator |
no |
Modify portal content and Delete objects are Manager and Site Administrator
in both states. Both transitions are guarded by Manage users.
Deactivating a group stops it being enumerated and stops it granting membership,
without deleting it and without editing a single UserProfile. Reactivating
restores exactly the membership it had.
Which states count is group_enumeration_states, ('active',) by default.
Where principals are stored#
Eight registry records: parent path, id, title and content type, for profiles and again for groups. The group records default to the profile container's, so a site filing principals together sets none of them. See Settings.
The catalog is not scoped to the container. It indexes a UserProfile
wherever the object actually is, so reorganizing content is not a
deauthentication.
Where principals may be created#
Only in that container.
Each type has its own add permission, and rolemap.xml grants both to no role
at all:
pas.plugins.identity.userprofile.add
pas.plugins.identity.usergroup.add
What makes either type addable is a local grant on the container itself, which the package writes when it creates the folder, when it installs into a site where the folder already exists, and when a folder appears at the configured path.
So nobody creates a UserProfile in an ordinary folder or pastes one into it,
and that includes a Manager. Neither type appears in the add menu anywhere
else. To file principals somewhere else as well, grant the permission on that
folder.
The two permissions are separate so that a site filing groups apart from users can open each container to one kind only.
The profile gate#
While a profile is incomplete, every page its owner asks for is answered with a
redirect to its edit form. Subscribed to IPubAfterTraversalEvent.
Controlled by enforce_required_profile_fields, on by default.
What counts as complete#
required_profile_fields names the fields. Empty, which is how it ships, means
the fields the profile type itself marks required: login, emails and
fullname for the type in this package.
Value |
Counts as filled? |
|---|---|
|
no |
|
no |
an empty collection |
no |
|
yes |
|
yes |
The record only ever adds. A field the type marks required stays required whatever the record says.
A field named here need not be required on the type: @types/UserProfile reports
the site's required fields alongside the type's, so the edit form asks for
everything the flow insists on.
What is never gated#
The checks run in this order, cheapest first, and the first match lets the request through.
# |
Passes when |
Why |
|---|---|---|
1 |
The request is a |
Volto fetches the edit form over the API. |
2 |
It is not a |
A gate on every request is a gate on every stylesheet. |
3 |
There is no Plone site yet |
Nothing to answer for. |
4 |
The user is anonymous |
The gate is about a profile's owner. |
5 |
The last path segment is exempt |
See the two lists below. |
6 |
The user holds |
A required field nobody can supply must not lock the site. |
7 |
The gate is switched off |
|
8 |
The user has no incomplete profile |
Nothing to hold them for. |
9 |
The profile is already in the traversed path |
Its edit form, its widgets, its save. Redirecting the target is a loop. |
Exempt path segments:
login @@login login_form require_login @@require_login
logout @@logout logged_out
Exempt prefixes:
@@oauth-
Name any other view that must not be interrupted in gate_exempt_paths, matched
against the last segment of the path.
Claims refresh#
On every sign-in the provider's claims refresh the fields that provider still
owns, and only those. The rule is one comparison: the UserProfile remembers
what the provider last wrote, and the provider may write a field only while the
current value still equals that.
Situation |
Written? |
|---|---|
Fresh |
yes |
The provider changed the claim since the last sign-in |
yes |
The user edited the field |
no |
The user cleared the field |
no |
An administrator typed the value in by hand |
no |
login is never synced. It is half of the case-folded index that user
enumeration queries.
Provider avatars#
Off by default: sync_portraits.
When on, a changed picture_url claim is fetched during claims sync and stored
as the user's portrait.
Guard |
Value |
|---|---|
Scheme |
HTTPS only |
Timeout |
|
Size cap |
|
Content type |
Refused unless the server claims an image |
Warning
Read About profiles and groups before enabling this. picture_url is
a claim, and at many providers a claim is whatever the user typed. Turning it
into a server-side fetch makes the sign-in path a request forger.
Membership#
Membership is stored in group_ids on each member. Two paths write it, and they
reach the same place.
Path |
Notes |
|---|---|
|
The ordinary Plone API, and the Users and Groups control panel that calls it. Reaches the plugin through PlonePAS's group tool. |
Editing the |
The Groups field on the edit form. Needs Edit Profile Group Membership. |
Situation |
Result |
|---|---|
A group is deleted |
No |
The group is recreated |
Exactly the membership it had. |
A |
Grants nothing; the consistency check reports it. |
A group inside a group |
Allowed. |
A group inside itself |
Refused. |
An inactive group |
Grants nothing and passes nothing through. |
Nesting is closed over when the question is asked rather than stored expanded, so removing an edge takes effect everywhere at once.
Maintenance#
Step |
Does |
Does not |
|---|---|---|
The consistency check |
Reports drift |
Repair anything |
|
Repairs |
Report anything |
They are kept apart so the check can be scheduled read-only. The check reports
catalog entries missing, entries whose object is gone, brains that disagree with
their object, duplicate user ids or login names, and UserProfile objects naming
groups that do not exist.
Coexistence with the stock plugins#
source_users, source_groups and auto_group keep working.
A user known to both this package and source_users appears once in a
search, because both return the same canonical user id and every consumer merges
on it. Group memberships are the union, with no duplicates.