User Lifecycle events
- 1 Membership lifecycle event definition (configure as many as you need)
- 2 Membership lifecycle action definitions (the steps a policy can take)
- 3 Membership lifecycle policy definitions (which actions run for which events)
- 4 Membership lifecycle policy part definitions
- 5 Lifecycle change requirements (assigned to groups or folders)
- 6 Worked example: 7-day grace period when an employee leaves
- 7 On-disk config key patterns
- 8 Example policies
In Grouper v6+, the User Lifecycle events feature allows Grouper to track lifecycle events and act on them. Generally this is for manual groups that are used in a policy.
Where to find it: from the Grouper UI top nav, Miscellaneous → User lifecycle admin. From there the four sub-screens are User lifecycle events, User lifecycle actions, User lifecycle policies, and User lifecycle policy parts.
Privilege required to configure: Grouper sysadmin (typically members of etc:sysadmingroup). Non-sysadmins do not see the User lifecycle admin menu entry.
Lifecycle event: a change in a user’s relationship to the institution. Each institution defines its own events based on group membership or data field/row changes. Examples:
The user no longer works at the institution
An affiliation changes (faculty, student, staff)
A supervisor changes
A work status changes (full time vs. part time)
An organization, department, or school changes
Key concepts:
Membership lifecycle event definition (configure as many as you need)
Config id — String, required. Alphanumeric key.
Name — String, required. Short human-readable name shown in dropdowns.
Description — String, textarea, required. Longer explanation of when this event fires.
Change magnitude — Float, required, default
0. Ranks how significant this event is when multiple events fire for the same user. Suggested scale:100leaves institution,50leaves department,10changes title.Trigger — Dropdown, required. Picks which change in Grouper fires this event. The fields shown below it change based on the selected value:
groupUserAdd— user was added to a specific group. Shows: Group id/name (path or UUID).groupUserRemove— user was removed from a specific group (e.g. an Active-employees group). Shows: Group id/name.groupUserRemoveFromFolder— user was removed from any group inside a folder (recursively). Shows: Folder id/name.dataFieldRemove— a value on a data field (or data-row column) was removed. Shows: Data field config (dropdown ofGrouperDataField).dataRowRemove— an entire data row was removed. Shows: Data row config (dropdown ofGrouperDataRow).
Description shown to privileged viewers — Template, textarea, required. Rendered when the viewer is a member of the privileged-viewers group below. Plain text passes through verbatim; each
${...}block is evaluated as a JEXL script. Available variables depend on the trigger:groupUserAdd/groupUserRemove:groupName,groupDisplayName,groupExtension,groupDisplayExtension,groupDescriptiongroupUserRemoveFromFolder: the fivegroup*variables above (for the specific child group) plusstemName,stemDisplayName,stemExtension,stemDisplayExtension,stemDescriptiondataFieldRemove:configId,valuedataRowRemove:configIdAlways available:
grouperUtil(e.g.grouperUtil.escapeHtml(value, true))
Examples:
Job loss— plain text passthroughJob loss from ${groupDisplayExtension}Job loss from ${grouperUtil.escapeHtml(groupDisplayExtension, true)}— HTML-escape variables that may contain user-supplied textJob loss from ${groupDisplayExtension}${groupDescription.contains('Restricted') ? ' (sensitive)' : ''}
Group of privileged viewers — String, required. Full group name (path) or UUID. Members of this group see the privileged template above; other viewers see the unprivileged template below.
Description shown to other viewers — Template, textarea, required. Same template rules as above, but typically kept generic to avoid leaking sensitive group/folder/attribute names. Example:
Job loss.
Save-time validation: when the event config is saved, both templates are evaluated against stub Group/Stem/GrouperDataField/GrouperDataRow objects that match the chosen trigger, in strict mode (lenient=false). A typo like ${groupDisplayExtensoin} fails at save time with a field-level error naming the missing variable, so it cannot ship to the daemon.
Storage: the configs and rendered descriptions are cached in two tables with a full daemon and an incremental daemon (described below):
grouper_lifecycle_event_config— columns:internal_id,config_id,group_internal_id,data_field_internal_id,data_row_internal_id,stem_id_index,created_on_microsgrouper_lifecycle_event— columns (all ≤ 30 chars):internal_id— PKgrpr_lcycl_evnt_cnfg_intrnl_id— FK togrouper_lifecycle_event_config.internal_idmember_internal_id— FK togrouper_members.internal_idevent_micros— micros since 1970, sourced from the relevant*_hsttablentrl_lng_priv_dic_intrnl_id— FK togrouper_dictionaryholding the rendered privileged textntrl_lng_unpriv_dic_intrnl_id— FK togrouper_dictionaryholding the rendered unprivileged text
Daemons: enabling User Lifecycle events activates two daemons, full and incremental. Job names (look for these in the daemon log / job status screen):
OTHER_JOB_userLifecycleFullDaemon— full sync, looks back roughly one year of historyOTHER_JOB_userLifecycleIncrementalDaemon— incremental sync, picks up new events since the last runOTHER_JOB_groupPolicyUserLifecycleFullDaemon— runs the configured actions (email, remove user, end-date membership) for the events the full/incremental daemon recorded
Each daemon looks at all event configs and queries the right history table for the chosen trigger:
groupUserAdd/groupUserRemove→grouper_sql_cache_mship_hstgroupUserRemoveFromFolder→grouper_sql_cache_mship_hst(joined to all child groups of the folder)dataFieldRemovewithfieldDataStructure = attribute→grouper_data_field_assign_hstdataFieldRemovewithfieldDataStructure = rowColumn→grouper_data_row_field_asn_hstdataRowRemove→grouper_data_row_assign_hst
For each matching history row, the daemon evaluates the privileged and unprivileged templates against the variables for the trigger and stores the rendered strings in grouper_dictionary. If the underlying object (group, field, row) is later removed, the rendered text in the dictionary is preserved — the daemon does not rewrite stored descriptions when the source is gone.
JEXL tester: the Grouper JEXL tester has a USER_LIFECYCLE_EVENT script type with built-in examples per trigger (plain text, simple interpolation, conditional output, HTML-escape, group + stem, data field, data row). Use it to develop or troubleshoot a template against sample data before saving the event config.
Deferred:
Free-form JEXL script trigger (in addition to the five trigger types above)
Hierarchy/precedence when two events apply to the same user
Time-buffer / debouncing across related events (e.g. position + job + relationship lost together)
Membership lifecycle action definitions (the steps a policy can take)
Name — String, required
Description — String, textarea, required
Action type — Dropdown, required:
emailManageremailUseremailGroupAdminremoveUserFromGroupaddEndDateOnMembership— adds an attribute with an end date
emailManageroptions:Data field config id — dropdown, required (the field that names the manager)
Subject id / subject identifier / subject id_or_identifier — dropdown, required
Subject source — dropdown, optional
Email subject line — String, required. Supports
${...}withgroupName,groupURL, etc.Email body — textarea, required. See Email body details below.
emailUseroptions: email subject line + email body (same variables as above)emailGroupAdminoptions: email subject line + email body (same variables as above)removeUserFromGroupoptions: noneaddEndDateOnMembershipoptions:Number of days in the future — int, required
Email body details
When the action type is emailUser, emailManager, or emailGroupAdmin, the daemon sends one email per recipient with bodies batched by event. The body is a template; ${...} blocks evaluate as JEXL scripts and the following variable is available:
listOfRecordMaps— a list of Java maps, one entry per event being batched. Each map contains:safeSubjectLifecycleUser— the user whose lifecycle changed (SafeSubject)safeSubjectRecipient— the user receiving the email (SafeSubject)groupId,groupName,groupDisplayName,groupExtension,groupDisplayExtension,groupDescription
Example body using GSH-style script directives outside ${...}:
$$ for (var recordMap : listOfRecordMaps) {
Recipient: ${recordMap.get('safeSubjectRecipient').getName()}
Subject of action: ${recordMap.get('safeSubjectLifecycleUser').getName()}
$$ }
Membership lifecycle policy definitions (which actions run for which events)
Config id — String, required
Name — String, required
Description — String, textarea, required
Is public — Boolean, required, default
false. Radio buttons.Who can use this — String, required (when
isPublicis false or null). Group id or name. Viewers with READ on the group see the policy; viewers with EDIT cannot change the assignment if they can’t see the group.Support instructions — String, textarea, optional. How a removed user can request privileges back, otherwise the group owners are contacted.
Membership lifecycle policy part definitions
Config id — String, required
Lifecycle policy — Dropdown, required
Number of lifecycle events — Int 1–10, required, dropdown. Shows that many event-config dropdowns; each must be unique.
Number of lifecycle actions — Int 1–10, required, dropdown. Shows that many action-config dropdowns; each must be unique.
Examples:
Medium security group: if a user leaves the institution, assign a 3-day grace period and notify their manager. If a user did not leave the institution but lost a department (e.g. job or affiliation change), assign a 7-day grace period and notify the user and their old and new managers.
Department loss or leaves organization: if a user loses a relevant affiliation (leaves a certain "Active" group) or leaves an organization (leaves any group in the org folder), notify the user’s managers and add a 7-day grace period.
Lifecycle change requirements (assigned to groups or folders)
Lifecycle change requirements are assigned to groups or folders on the group edit screen as a radio button (default: none).
Stored as an attribute on the group.
One notification per user’s lifecycle event.
If a user manually adds a direct membership and the target group has no lifecycle change requirement set, a note is shown asking them to set one (or explicitly choose “none”).
Memberships carry an attribute capturing the lifecycle event and the previous end date (if applicable).
A bulk-edit screen lets group managers add users back (optionally with the previous end date) or remove them.
The attestation screen and email call out users with recent lifecycle events.
Emails are batched per user per lifecycle event for the incremental run, and can be batched daily by recipient.
Open questions:
What happens when a user is not in a required lifecycle-defining group (e.g. the UI tries to add a non-employee to an employees-only group)? The lifecycle requirement should veto the membership (like the existing membership-requirement mechanism), and the veto message should help the UI user add the person to a temporary employee-service-eligible group somewhere.
Membership requirement on a group acting as a grace-period group.
Veto vs. requirement of auto-remove.
Example policy tiers (illustration only — each institution defines its own policies and assigns them to groups via lifecycle change requirements):
Group security level (policy) | Employee leaves the institution | Employee leaves dept | Employee leaves a position |
Low security | Membership set to expire in 5 days | No actions | No actions |
Medium security | 3-day grace period starts | Membership set to expire in 21 days | No actions |
High security | Immediate membership removal | Membership set to expire in 7 days | Membership set to expire in 14 days |
Worked example: 7-day grace period when an employee leaves
This walks through wiring up an event, an action, a policy, and a policy part for the scenario “when a user leaves the active-employee group, give them a 7-day grace period on their adobeUsers membership and email their manager.”
1. Lifecycle event (User lifecycle events → Add user lifecycle event)
Config id:
leaveInstitutionName: Leave institution
Description: User has left the institution (removed from the active-employee group)
Change magnitude:
100Trigger:
groupUserRemoveGroup id/name:
ref:affiliation:active_employeeDescription shown to privileged viewers:
Job loss from ${groupDisplayExtension}Group of privileged viewers:
etc:hrViewersDescription shown to other viewers:
Job loss
2. Lifecycle action (User lifecycle actions → Add user lifecycle action)
Config id:
gracePeriodSevenDaysName: 7-day grace period
Description: End-date the membership 7 days from the event
Action type:
addEndDateOnMembershipNumber of days in the future:
7
And a second action for the manager email:
Config id:
emailFormerManagerName: Email former manager
Action type:
emailManagerData field config id:
managerPennkey(whichever data field on the lifecycle user names their manager)Subject id/identifier:
subjectIdentifierEmail subject line:
${listOfRecordMaps.get(0).get('safeSubjectLifecycleUser').getName()} left the institutionEmail body: see Email body details above for the
$$ for ... $$ }pattern.
3. Lifecycle policy (User lifecycle policies → Add user lifecycle policy)
Config id:
mediumSecurityName: Medium security
Description: 7-day grace + email manager when the user leaves the institution