Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

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.

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.

  1. Describe non-employees with a Workforce extension

    Recorded as tenant.schemas.create succeeded.

  2. Register a contractor with a sponsor and end date

    Recorded as scim.user.create succeeded for lab-provisioning.

  3. Lock the contractor whose end date has passed

    Recorded as scim.user.lock succeeded for lab-provisioning.

  4. Delete the duplicate an email match created

    Recorded as scim.user.delete succeeded for lab-provisioning.

  5. Restore sign-in when the leave ends

    Recorded as scim.user.unlock succeeded for lab-provisioning.

  6. A register ID cannot be reassigned

    Recorded as scim.user.patch rejected (mutability) for lab-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.

  1. 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')
  1. Press Start on the lab page.

Walkthrough

  1. Describe people outside HR. In User Management > Schemas, create a schema named Workforce with the URN in $WF and these attributes:

    • workerType: text, allowed values Employee, Contractor, Student

    • sponsor: text, the sponsor's user id

    • contractEnd: date-time

    • registerId: text, unique in this tenant, mutability immutable

    • leaveStatus: text, allowed values Active, 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.

  1. 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.

  1. 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.

  1. 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 make contractEnd required 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.

  1. Sponsors who left. Point Kiri's sponsor at 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.

  1. An extension is a decision. Cora confirms Kiri's internship runs for three more months. Replace Kiri's $WF:contractEnd with that date and active with true, sending an X-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.

  1. 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.

  1. 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, with userName lab-tmp-lee2 and that personal address. It succeeds, and you now have two records for one person. Find them with filter=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.

  1. The source's own rehire. In the HR Simulator, start a Real-world edge cases run with rehire and name_change. The rehire event creates a deleted person again with the same externalId and gets 201 with a new id. Search Audit for the old and new IDs: the person's history is now split across two subjects. Compare with step 7.

  2. A leave of absence. Create Priya Nair as an employee with export PRIYA=$(mkuser lab-tmp-priya Priya Nair E11100 Employee "") and add her to Photo moderation. She starts parental leave: replace active with false and $WF:leaveStatus with OnLeave. Her groups stay, and her sessions and tokens end. On her return, replace active with true and leaveStatus with Active.

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.

  1. Give lab-tmp-lee a contractEnd one hour away. At that time the tenant locks the account, ends its sessions and tokens, and records the lock with the reason contract_ended, with no query or PATCH from you.

  2. Two weeks before an end date, the tenant notifies the sponsor named in sponsor. Moving the date afterwards records the sponsor's confirmation.

  3. 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

  1. Replace Lee's $WF:registerId with C9999. The answer is 400 with scimType: mutability: a register ID is assigned once.

  2. Set Kiri's $WF:workerType to Temp. The answer is 400 invalidValue, because it is not an allowed value.

Check your work

Press Check my progress. The checks look for, in order:

  • tenant.schemas.create succeeded (step 1)

  • scim.user.create succeeded by lab-provisioning (step 2)

  • scim.user.lock succeeded by lab-provisioning (step 3)

  • scim.user.delete succeeded by lab-provisioning (step 8)

  • scim.user.unlock succeeded by lab-provisioning (step 10)

  • scim.user.patch rejected with mutability (Break it)

At the end, the step 3 and step 4 searches return nobody.

Cleanup

  1. Delete lab-tmp-lee, lab-tmp-kiri and lab-tmp-priya (and lab-tmp-lee2 if it remains). Their history stays in Audit.

  2. Keep the Workforce schema. The SCIM schemas lab reads it from discovery.

Missing infrastructure

  • G51: contractEnd and 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.

Back to all labs

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

The Lab