Class BowireScimStore

Namespace
Kuestenlogik.Bowire.Scim
Assembly
Kuestenlogik.Bowire.Scim.dll

The provisioned user and group list, on disk (#96).

public sealed class BowireScimStore
Inheritance
BowireScimStore
Inherited Members

Remarks

One file per resource under <storage root>/scim/, with the lookups an IdP needs held in memory. A single document holding every record would have to be rewritten on each of the ten thousand writes a first sync makes; a file per record makes a write cost one file and leaves the read path — which is what the round-trip latency is actually about — served from the index.

Every mutation also appends to scim/events.jsonl. Provisioning is the one surface where "who removed this person, and when" gets asked months later, and the resource files only ever show the current answer.

Constructors

BowireScimStore(string, TimeProvider?)

A store rooted at storageRoot.

public BowireScimStore(string storageRoot, TimeProvider? clock = null)

Parameters

storageRoot string

The data root — the same directory the identity slots live under, so deprovisioning can reach them.

clock TimeProvider

Injected in tests so a purge window can be crossed without waiting.

Fields

DirectoryName

The directory under the storage root that holds provisioning state.

public const string DirectoryName = "scim"

Field Value

string

Properties

EventLog

The append-only record of every provisioning decision.

public string EventLog { get; }

Property Value

string

Root

Where the provisioning state is kept.

public string Root { get; }

Property Value

string

StorageRoot

The data root this store's identities live under.

public string StorageRoot { get; }

Property Value

string

Methods

BindSubject(string, string)

Remember which token subject this record turned out to belong to.

public void BindSubject(string id, string subject)

Parameters

id string
subject string

CreateGroup(ScimGroup)

Provision a new group.

public ScimGroup CreateGroup(ScimGroup group)

Parameters

group ScimGroup

Returns

ScimGroup

Exceptions

ScimConflictException

The display name is already taken.

CreateUser(ScimUser)

Provision a new identity.

public ScimUserRecord CreateUser(ScimUser resource)

Parameters

resource ScimUser

Returns

ScimUserRecord

Exceptions

ScimConflictException

The login name is already taken.

DeleteGroup(string)

Remove a group. Groups carry no state of their own, so this is a hard delete.

public bool DeleteGroup(string id)

Parameters

id string

Returns

bool

DeleteUser(string)

Deprovision an identity: deactivate it and start the purge window.

public bool DeleteUser(string id)

Parameters

id string

Returns

bool

Whether there was anything to deprovision.

Remarks

A soft delete, because deprovisioning is routinely undone — a team change, a misfiring sync, an extended contract. Deleting on the DELETE makes those recoverable only from a backup, if one exists.

FindBySubject(string)

The record for a token subject, or null when the identity was never provisioned.

public ScimUserRecord? FindBySubject(string subject)

Parameters

subject string

Returns

ScimUserRecord

Remarks

Matched in the order the identifiers are trustworthy: the subject already bound to this record, then the IdP's own immutable id, then the login name. The last is the weakest — a person who changes their e-mail address changes their userName — which is exactly why the subject gets bound the first time it is seen.

FindByUserName(string)

The record with this login name, or null.

public ScimUserRecord? FindByUserName(string userName)

Parameters

userName string

Returns

ScimUserRecord

GetGroup(string)

The group with this SCIM id, or null.

public ScimGroup? GetGroup(string id)

Parameters

id string

Returns

ScimGroup

GetUser(string)

The record with this SCIM id, or null.

public ScimUserRecord? GetUser(string id)

Parameters

id string

Returns

ScimUserRecord

Groups()

Every provisioned group.

public IReadOnlyList<ScimGroup> Groups()

Returns

IReadOnlyList<ScimGroup>

IsMemberOf(string, string)

Whether the identity with this SCIM id is a member of adminGroup.

public bool IsMemberOf(string userId, string adminGroup)

Parameters

userId string
adminGroup string

Returns

bool

Purge(TimeSpan)

Delete every identity whose purge window has run out, and their state.

public int Purge(TimeSpan after)

Parameters

after TimeSpan

Returns

int

How many were purged.

ReplaceGroup(string, ScimGroup)

Replace a group, keeping its id and creation time.

public ScimGroup? ReplaceGroup(string id, ScimGroup group)

Parameters

id string
group ScimGroup

Returns

ScimGroup

ReplaceUser(string, ScimUser)

Replace an identity's attributes, keeping its id and creation time.

public ScimUserRecord? ReplaceUser(string id, ScimUser resource)

Parameters

id string
resource ScimUser

Returns

ScimUserRecord

Exceptions

ScimConflictException

The new login name belongs to somebody else.

UpdateGroup(string, Action<ScimGroup>)

Apply a change to a group in place.

public ScimGroup? UpdateGroup(string id, Action<ScimGroup> change)

Parameters

id string
change Action<ScimGroup>

Returns

ScimGroup

UpdateUser(string, Action<ScimUser>)

Record the outcome of a partial update the caller has already applied.

public ScimUserRecord? UpdateUser(string id, Action<ScimUser> change)

Parameters

id string
change Action<ScimUser>

Returns

ScimUserRecord

Users()

Every provisioned identity, newest last.

public IReadOnlyList<ScimUserRecord> Users()

Returns

IReadOnlyList<ScimUserRecord>