IDENTITY GOVERNANCE · LAB
Govern contractors, bring a returning person back, and suspend for a leave
Model non-employees with a sponsor and an end date, find expired and undated contractors, rehire a contractor without duplicating them, and suspend sign-in for a leave while keeping access.
Partly readyUses your lab tenant
The lesson
Builds on: Sources of truth.
New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.
Partly ready. Most of this lab runs today. Steps that wait on platform features are marked, and Missing infrastructure says what they need.
- G51 Effective-dated lifecycle (scheduled enable and lock, leave windows, activation invitations)
Your progress
Press Start before you begin. Only events your tenant records after that count, in the order below. Checking reads your tenant's Audit, so you need Audit read access in it.
Sign in to start this lab and check your progress. Log in or create an account.
Describe non-employees with a Workforce extension
Recorded as
tenant.schemas.createsucceeded.Register a contractor with a sponsor and end date
Recorded as
scim.user.createsucceeded forlab-provisioning.Lock the contractor whose end date has passed
Recorded as
scim.user.locksucceeded forlab-provisioning.Delete the duplicate an email match created
Recorded as
scim.user.deletesucceeded forlab-provisioning.Restore sign-in when the leave ends
Recorded as
scim.user.unlocksucceeded forlab-provisioning.A register ID cannot be reassigned
Recorded as
scim.user.patchrejected (mutability) forlab-provisioning.
Setup
Lab Photos also works with people who are not its employees: contract photographers, print partner engineers and interns. In this lab your tenant plays the contractor register as well as the directory.
Open a bash shell and set the variables and helpers from the directory lab, and look up Cora, who sponsors the contractors.
export WF=urn:example:labphotos:scim:schemas:extension:workforce:1.0:User
export CORA=$(scim -G "$SCIM/Users" --data-urlencode 'filter=userName eq "[email protected]"' | jq -r '.Resources[0].id')
export MOD=$(scim -G "$SCIM/Groups" --data-urlencode 'filter=displayName eq "Photo moderation"' | jq -r '.Resources[0].id')
Press Start on the lab page.
Walkthrough
Describe people outside HR. In User Management > Schemas, create a schema named
Workforcewith the URN in$WFand these attributes:workerType: text, allowed valuesEmployee,Contractor,Studentsponsor: text, the sponsor's useridcontractEnd: date-timeregisterId: text, unique in this tenant, mutability immutableleaveStatus: text, allowed valuesActive,OnLeave
Then ask the service to describe it: scim "$SCIM/ResourceTypes/User" | jq .schemaExtensions lists the enterprise extension and $WF, and scim "$SCIM/Schemas/$WF" | jq '.attributes[] | {name,type,mutability,uniqueness,canonicalValues}' shows each characteristic. Audit records tenant.schemas.create and tenant.schemas.attributes.create.
Why it matters: this is "People outside the HR system". The register's sponsor and end date need somewhere to live before anything can act on them.
Register a contractor and an intern. Lee Moreau is a contract photographer whose contract ended yesterday. Kiri Ueda is an intern with a sponsor and no end date. Both join
Photo moderation.
mkuser() { jq -n --arg wf "$WF" --arg cora "$CORA" --arg u "$1" --arg g "$2" --arg f "$3" --arg x "$4" --arg t "$5" --arg end "$6" \
'{schemas:["urn:ietf:params:scim:schemas:core:2.0:User",$wf], externalId:$x, userName:$u, name:{givenName:$g,familyName:$f},
emails:[{value:($u+"@example.com"),type:"work",primary:true}], active:true,
($wf):({workerType:$t, sponsor:$cora, registerId:$x} + (if $end == "" then {} else {contractEnd:$end} end))}' \
| scim -X POST "$SCIM/Users" --data-binary @- | jq -r .id; }
export LEE=$(mkuser lab-tmp-lee Lee Moreau C2291 Contractor "$(date -u -d yesterday +%Y-%m-%dT18:00:00Z)")
export KIRI=$(mkuser lab-tmp-kiri Kiri Ueda C2310 Student "")
for u in $LEE $KIRI; do jq -n --arg u "$u" '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:$u}]}]}' \
| scim -X PATCH "$SCIM/Groups/$MOD" --data-binary @- -o /dev/null -w '%{http_code}\n'; done
On macOS, use date -u -v-1d +%Y-%m-%dT18:00:00Z for yesterday.
Who is past their end date and still active?
scim -G "$SCIM/Users" --data-urlencode "filter=$WF:workerType ne \"Employee\" and $WF:contractEnd lt \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" and active eq true" \
--data-urlencode 'attributes=userName' | jq -r '.Resources[].userName'
Lee comes back. Lock him by replacing active with false.
Why it matters: this is "Sponsors and end dates". The end date is what makes ending the default, but only if something acts on it. Today that something is your query.
No end date, no account. Run the same search with
filter=$WF:workerType ne "Employee" and not ($WF:contractEnd pr) and active eq true. Kiri comes back. Lock her until her sponsor supplies a date. You cannot makecontractEndrequired in the schema, because a required extension attribute would apply to employees too.
Why it matters: the lesson's rule is blunt: no end date in the register, no account.
Sponsors who left. Point Kiri's
sponsorat the ID of the user you deleted in the joining lab (lab-tmp-eli, from Audit), or any ID that no longer exists. The tenant accepts it. Then check every contractor's sponsor.
scim -G "$SCIM/Users" --data-urlencode "filter=$WF:workerType ne \"Employee\"" --data-urlencode "attributes=userName,$WF:sponsor" \
| jq -r ".Resources[] | [.userName, .\"$WF\".sponsor] | @tsv" | while read u s; do
echo "$u sponsor $s: $(scim -o /dev/null -w '%{http_code}' "$SCIM/Users/$s")"; done
Lee's sponsor answers 200; Kiri's answers 404. Set Kiri's sponsor back to Cora.
Why it matters: a sponsor answers for the access. A departed sponsor answers for nothing, so the contractor needs a new one before any extension.
Note: custom attributes have no reference type, so sponsor cannot be validated as an existing user the way the enterprise manager is. Your loop is the check.
An extension is a decision. Cora confirms Kiri's internship runs for three more months. Replace Kiri's
$WF:contractEndwith that date andactivewithtrue, sending anX-Correlation-ID. Search Audit for it: the change, the attributes and the client are recorded. Who confirmed is not.
Why it matters: an extension moves an end date only after the sponsor decides. The record here proves the change, not the decision behind it, which is what G23 would add.
Coming back as an employee. Lab Photos hires Lee as employee
E11057. Look first, by both identifiers he might carry.
scim -G "$SCIM/Users" --data-urlencode "filter=externalId eq \"C2291\" or $WF:registerId eq \"C2291\"" | jq -r '.Resources[] | [.id, .userName, .active] | @tsv'
The existing account is found. Before enabling anything, remove his leftover Photo moderation membership (the lock kept it). Then convert the record in one PATCH: externalId to E11057, $WF:workerType to Employee, remove $WF:contractEnd, and set $ENT:employeeNumber to E11057. Same id, one history.
Why it matters: this is "Coming back". Reuse the identity, never the old access.
The email-matching failure. Search for Lee by a personal address:
filter=emails.value eq "[email protected]"returns 0. Create the "new hire" that way, withuserNamelab-tmp-lee2and that personal address. It succeeds, and you now have two records for one person. Find them withfilter=name.familyName eq "Moreau" and userName sw "lab-tmp-"(two results), then delete the duplicate.
Why it matters: the duplicate exists because the match used a value that was never stable. Step 7's register ID found the right person.
The source's own rehire. In the HR Simulator, start a Real-world edge cases run with
rehireandname_change. The rehire event creates a deleted person again with the sameexternalIdand gets201with a newid. Search Audit for the old and new IDs: the person's history is now split across two subjects. Compare with step 7.A leave of absence. Create Priya Nair as an employee with
export PRIYA=$(mkuser lab-tmp-priya Priya Nair E11100 Employee "")and add her toPhoto moderation. She starts parental leave: replaceactivewithfalseand$WF:leaveStatuswithOnLeave. Her groups stay, and her sessions and tokens end. On her return, replaceactivewithtrueandleaveStatuswithActive.
Why it matters: this is "Stepping away for a while". A leave sits between a leaver and doing nothing. The tenant shows the same "Locked" for a leaver and for Priya, so the reason has to be recorded somewhere, here in leaveStatus.
Planned walkthrough
Once G51 exists, the dates act by themselves.
Give
lab-tmp-leeacontractEndone hour away. At that time the tenant locks the account, ends its sessions and tokens, and records the lock with the reasoncontract_ended, with no query or PATCH from you.Two weeks before an end date, the tenant notifies the sponsor named in
sponsor. Moving the date afterwards records the sponsor's confirmation.Record Priya's leave as a window with a start and an expected return. Sign-in is suspended on the first day and restored on the return date, and both changes name the leave they belong to.
Break it
Replace Lee's
$WF:registerIdwithC9999. The answer is400withscimType: mutability: a register ID is assigned once.Set Kiri's
$WF:workerTypetoTemp. The answer is400 invalidValue, because it is not an allowed value.
Check your work
Press Check my progress. The checks look for, in order:
tenant.schemas.createsucceeded (step 1)scim.user.createsucceeded bylab-provisioning(step 2)scim.user.locksucceeded bylab-provisioning(step 3)scim.user.deletesucceeded bylab-provisioning(step 8)scim.user.unlocksucceeded bylab-provisioning(step 10)scim.user.patchrejected withmutability(Break it)
At the end, the step 3 and step 4 searches return nobody.
Cleanup
Delete
lab-tmp-lee,lab-tmp-kiriandlab-tmp-priya(andlab-tmp-lee2if it remains). Their history stays in Audit.Keep the
Workforceschema. The SCIM schemas lab reads it from discovery.
Missing infrastructure
G51:
contractEndand leave windows do not lock and restore accounts at the stated time, and nothing reminds the sponsor before an end date. Once they do, the Planned walkthrough replaces steps 3, 6 and 10's manual timing.