IDENTITY GOVERNANCE · LAB
Recover from lost answers, late writes, drift and a backlog
Settle a create whose response was lost, make a retry idempotent, stop a late write with If-Match, reconcile intended against actual group state, and drain a backlog under a SCIM rate limit you set.
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.
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.
Create a user whose answer you threw away
Recorded as
scim.user.createsucceeded forlab-provisioning.Reuse a correlation ID for a different request
Recorded as
scim.user.createrejected (command_conflict) forlab-provisioning.A delayed replace meets a newer version
Recorded as
scim.user.replacerejected (version_mismatch) forlab-provisioning.Make a change outside the provisioning service
Recorded as
tenant.groups.memberssucceeded.Set a SCIM request limit
Recorded as
tenant.provisioning.updatesucceeded.Requests beyond the limit are told to wait
Recorded as
scim.users.searchrejected (rate_limited) forlab-provisioning.
Request console
Requests in this lab can be sent from this page to your tenant: open one and choose Send. Fill in the values below first. They stay in this page's memory and are gone when you leave; secrets are never stored or sent anywhere except the request you send.
Setup
You play the identity system's provisioning service. Its intended state for one group is a file it owns.
Open a bash shell and set the variables and helpers from the directory lab.
Write the intended state for
Photo moderation:
cat > intended.json <<'EOF'
{"group": "Photo moderation", "members": ["[email protected]", "[email protected]"]}
EOF
export MOD=$(scim -G "$SCIM/Groups" --data-urlencode 'filter=displayName eq "Photo moderation"' | jq -r '.Resources[0].id')
In Groups, make
Photo moderationmatch the file: Ava and Cora only.Press Start on the lab page.
Walkthrough
A request with no answer. Send a create and throw the response away, as a timeout would.
newuser() { jq -n --arg u "$1" --arg x "$2" --arg g "$3" --arg f "$4" '{schemas:["urn:ietf:params:scim:schemas:core:2.0:User"], externalId:$x, userName:$u,
name:{givenName:$g,familyName:$f}, emails:[{value:($u+"@example.com"),type:"work",primary:true}], active:true}'; }
newuser lab-tmp-dana E10900 Dana Okafor | scim -o /dev/null -X POST "$SCIM/Users" --data-binary @-
You do not know whether it worked, and you have no id.
Look for your own key before anything else.
GET$ISSUER/scim/v2/Users?filter=externalId%20eq%20%22E10900%22
Open in console
GET $ISSUER/scim/v2/Users?filter=externalId%20eq%20%22E10900%22 HTTP/1.1
Authorization: Bearer $TOKEN
Accept: application/scim+jsontotalResults is 1: the create worked and only the answer was lost. Store the id with export DANA=<id>.
Why it matters: this is "A request with no answer". The outcome was unresolved until the search settled it. Only a search that finds nothing makes a second create safe.
Make a create idempotent. SCIM defines no idempotency key, but this tenant accepts
X-Correlation-IDon a create and returns the original result for a repeat.
export CID=$(uuidgen | tr A-Z a-z)
newuser lab-tmp-jo E10901 Jo Brandt | scim -X POST "$SCIM/Users" -H "X-Correlation-ID: $CID" --data-binary @- | jq -r .id
newuser lab-tmp-jo E10901 Jo Brandt | scim -X POST "$SCIM/Users" -H "X-Correlation-ID: $CID" --data-binary @- | jq -r .id
newuser lab-tmp-jo2 E10902 Jo Brandt | scim -X POST "$SCIM/Users" -H "X-Correlation-ID: $CID" --data-binary @- | jq .
The first two return 201 with the same id. Search Audit for $CID: the original scim.user.create and a second record whose detail says it was replayed, with no new user. The third, the same key with a different body, returns 409 with command_conflict.
Why it matters: this is "Safe retries". An idempotent request can simply be sent again after a timeout. A key reused for a different change is refused rather than guessed at.
A late write. Build a full replacement from Dana's current copy, but hold it back.
export E1=$(scim -i "$SCIM/Users/$DANA" | awk 'tolower($1)=="etag:" {print $2}' | tr -d '\r')
scim "$SCIM/Users/$DANA" | jq 'del(.meta, .groups, .id) | .title = "Print technician"' > dana.json
Now the deactivation arrives first: replace Dana's active with false. Then the held request finally arrives, with the version it was built from.
scim -X PUT "$SCIM/Users/$DANA" -H "If-Match: $E1" --data-binary @dana.json | jq .
The answer is 412. Read Dana again: she should stay disabled, so you rebuild the change from the current intended state, and there is nothing to send.
Why it matters: this is "Order and timing". The stale copy still said active: true. Without the version check, a late request would have enabled a leaver again.
Create drift. In the portal, add Ben to
Photo moderation, a manual edit outside your process. Then create[email protected](Locum Three) in the portal, an account your provisioning service never heard of.Reconcile. Compare intended with actual, and look for accounts no source has claimed.
ACTUAL=$(scim "$SCIM/Groups/$MOD" | jq -r '.members[].value' | while read id; do scim "$SCIM/Users/$id" | jq -r .userName; done | sort)
INTENDED=$(jq -r '.members[]' intended.json | sort)
comm -13 <(echo "$INTENDED") <(echo "$ACTUAL") | sed 's/^/extra member: /'
comm -23 <(echo "$INTENDED") <(echo "$ACTUAL") | sed 's/^/missing member: /'
scim -G "$SCIM/Users" --data-urlencode 'filter=active eq true and not (externalId pr)' | jq -r '.Resources[].userName' | sed 's/^/no externalId: /'
The report shows extra member: [email protected] and lists [email protected] among the unclaimed accounts. Decide each one. Ben's membership is access added outside the process: remove it, and find who added it by searching Audit for the group ID (tenant.groups.members by you). The locum account has no identity behind it, so it is an orphan to investigate, not to delete blindly.
Why it matters: this is "Reconciliation". It sees changes the provisioning service did not make, and each difference needs its own response.
A limit and a backlog. In User Management > Provisioning, set SCIM requests per minute to
10and save. Fire 20 reads.
for i in $(seq 1 20); do scim -o /dev/null -D - "$SCIM/Users?count=1" | grep -i -E '^(HTTP|retry-after)' | tr -d '\r' | tr '\n' ' '; echo; done
After about ten, the answers are 429 with Retry-After. Audit records scim.users.search rejected with rate_limited and the limit policy.
Drain in priority order. Queue three changes for Dana and the group and send them in this order, waiting as long as each
Retry-Aftersays: Dana's deactivation (already done, so confirm it), the removal of Ben fromPhoto moderation, then a title update for Dana.
Why it matters: this is "Limits and backlogs". A backlog waits as told and sends leavers and removals first. A delayed title is an inconvenience; a delayed removal is access that should be gone.
Restore: in Provisioning, clear SCIM requests per minute and save. A burst of 20 reads now succeeds.
Let the HR Simulator do it. Start a Real-world edge cases run with
stale_if_matchandduplicate_username, and burst on. Read each event:412followed by a re-read,409reported as expected,429with the simulator honoringRetry-After, and any event markedunresolvedreconciled before another attempt.
Break it
Send step 4's held PUT again, this time without
If-Match:scim -X PUT "$SCIM/Users/$DANA" --data-binary @dana.json | jq .active. It succeeds, and Dana is active again. A stale request just enabled a leaver.
Restore: replace Dana's active with false again.
Check your work
Press Check my progress. The checks look for, in order:
scim.user.createsucceeded bylab-provisioning(step 1 or 3)scim.user.createrejected withcommand_conflict(step 3)scim.user.replacerejected withversion_mismatch(step 4)tenant.groups.memberssucceeded (step 5)tenant.provisioning.updatesucceeded (step 7)scim.users.searchrejected withrate_limited(step 7)
Cleanup
Make sure Ben is out of
Photo moderationand the request limit is cleared.Delete
lab-tmp-dana,lab-tmp-joandlab-tmp-locum3, and deleteintended.jsonanddana.json.