Class BowireTenancy
- Namespace
- Kuestenlogik.Bowire.Auth
- Assembly
- Kuestenlogik.Bowire.dll
Routes each call to the slot of whichever identity is currently being served (#97).
public sealed class BowireTenancy : IBowireUserStore, IBowireStorageRootProvider
- Inheritance
-
BowireTenancy
- Implements
- Inherited Members
Remarks
The stores are static — EnvironmentStore, RecordingStore and
the rest resolve their paths through BowireUserContext with
no request in sight. That was the point of the Phase B seam: the stores
keep their shape and the seam becomes identity-aware underneath them. Doing
that in a server means the "who" has to travel with the execution context
rather than through a parameter, so it rides an
AsyncLocal<T>. That is ambient state, which is normally worth
avoiding; here it is the only shape that reaches a static store from a
request without rewriting every store to take a user.
What happens with no identity. Background work has no request — the plugin update check, a warm-up, a hosted service. Those fall through to the store the host was using before tenancy was enabled, which keeps process-wide state process-wide instead of filing it under whoever happened to be served last. Requests do not take that path: in multi-tenant mode Bowire's endpoints require an authenticated caller, so a request without a subject has already been rejected before a store is touched.
Constructors
BowireTenancy(string, IBowireUserStore)
Tenancy rooted at storageRoot.
public BowireTenancy(string storageRoot, IBowireUserStore shared)
Parameters
storageRootstringThe data root — resolved once, before this replaces Current. Reading it afterwards would ask this very object where the root is.
sharedIBowireUserStoreWhere calls with no identity go. Normally the store the host had before, i.e. the flat single-user layout.
Properties
CurrentSubject
The subject being served on this execution context, or null
outside a request.
public static string? CurrentSubject { get; }
Property Value
StorageRoot
The storage root this store resolves under — the directory the Data scope means, not the per-identity slot inside it.
public string StorageRoot { get; }
Property Value
UsersRoot
The directory holding every identity's slot.
public string UsersRoot { get; }
Property Value
Methods
Enter(string)
Serve subject until the returned scope is disposed.
public static IDisposable Enter(string subject)
Parameters
subjectstring
Returns
Remarks
Restoring the previous value rather than clearing it: a nested scope is legitimate — an admin acting on another identity's behalf (#98) is exactly that — and clearing would silently drop the outer identity for the remainder of the request.
EnumerateSlots()
The slot directories that exist on disk, by name.
public IEnumerable<string> EnumerateSlots()
Returns
Remarks
Directory names, not subjects: the mapping only runs one way, which is what lets the slot name be readable without the subject having to be recoverable from disk. Callers that need the subject already have it.
For(string)
The store for subject, whoever is being served.
public ScopedBowireUserStore For(string subject)
Parameters
subjectstring
Returns
Remarks
Cached because GetUserPath(string) sits on read paths that run per request, and building a slot hashes the subject.
GetUserPath(string)
Absolute path to the file filename under the
active user's scope. Implementations are free to colocate users
(single shared dir, the default) or partition by identity
(per-user subdirectory, the multi-tenant case).
public string GetUserPath(string filename)
Parameters
filenamestringTrailing-component file name without any directory prefix -- e.g.
"environments.json","recordings.json". Implementations decorate it with whatever path makes sense for their model.