Interface IUserManagementService
- Namespace
- Tharga.Team
- Assembly
- Tharga.Team.dll
User administration operations: directory verification and user deletion. Authorization is enforced
in the service layer by an authorization decorator; the [RequireScope] attributes here document
the scope each operation requires. All operations require the Manage
system scope. Directory-backed operations require a registered IUserDirectoryService.
public interface IUserManagementService
Methods
DeleteUserAsync(string, bool, CancellationToken)
Delete a user: removes the user from every team and deletes the user record (audited).
With deleteFromDirectory the user is also deleted from the external directory;
a directory failure does not roll back the local delete — it is reported on the result.
[RequireScope("users:manage")]
Task<UserDeleteResult> DeleteUserAsync(string userKey, bool deleteFromDirectory = false, CancellationToken cancellationToken = default)
Parameters
userKeystringdeleteFromDirectoryboolcancellationTokenCancellationToken
Returns
GetDirectoryOnlyUsersAsync(CancellationToken)
List directory users that have no matching local user (matched by directory id, falling back to email), streamed as directory pages arrive.
[RequireScope("users:manage")]
IAsyncEnumerable<DirectoryUser> GetDirectoryOnlyUsersAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationToken
Returns
GetOwnedTeamsAsync(string, CancellationToken)
The teams this user owns — the teams that deleting them would leave with no owner.
[RequireScope("users:manage")]
Task<IReadOnlyList<ITeam>> GetOwnedTeamsAsync(string userKey, CancellationToken cancellationToken = default)
Parameters
userKeystringcancellationTokenCancellationToken
Returns
Remarks
Meant to be asked before confirming a delete, so the operator can transfer ownership
instead of learning afterwards that a team is unrecoverable: TransferOwnershipAsync
requires the caller to be the owner, so once the owner is gone only a holder of
SetOwner can repair it.
On IUserManagementService rather than ITeamService deliberately. The question
is "what will deleting this user break", which is user administration; and it keeps the delete
dialog off the internal team contract, which no component should inject.
GetSignInIdentitiesAsync(IEnumerable<string>, CancellationToken)
How each of the given users signs in, read from the directory in one batch. One entry per user that is linked to a directory account the directory still knows; unknown and unlinked users are left out.
[RequireScope("users:manage")]
Task<UserSignInIdentities[]> GetSignInIdentitiesAsync(IEnumerable<string> userKeys, CancellationToken cancellationToken = default)
Parameters
userKeysIEnumerable<string>cancellationTokenCancellationToken
Returns
Remarks
Requires a registered IUserIdentityDirectory; without one the result is empty. Meant for a list surface reading the rows it shows, so pass a page of keys rather than every user.
The default implementation returns nothing, so a host's own implementation of this interface keeps compiling; such a host shows no sign-in methods until it implements this.
SetUserDisabledAsync(string, bool, CancellationToken)
Disables the user, or enables them again. A disabled user keeps their record, their memberships and their history — the reversible alternative to DeleteUserAsync(string, bool, CancellationToken).
[RequireScope("users:manage")]
Task SetUserDisabledAsync(string userKey, bool disabled, CancellationToken cancellationToken = default)
Parameters
userKeystringdisabledboolcancellationTokenCancellationToken
Returns
Remarks
A caller cannot disable themselves. Enforced here rather than only hidden in the UI: an administrator who locks themselves out needs a second administrator to undo it, and refusing the self-case also guarantees somebody is left holding Manage.
This is not Disabled, which means disabled in the external directory. This one blocks the user from this application only, and the two are shown separately.
It does not cascade to the user's API keys. A key is not a session — it is an independent credential with its own lifecycle, and retiring a person's integrations is a separate deliberate act (which is also what keeps each one reversible on its own).
A signed-in user is not evicted instantly; they are signed out within
ClaimRevalidationOptions.Interval.
Exceptions
- InvalidOperationException
The caller is the user being disabled.
SetUserEmailAsync(string, string, CancellationToken)
Set a user's email address. It stays local: a directory-backed user's address is usually owned by the organisation, so it is never written back to the directory.
[RequireScope("users:manage")]
Task<UserEmailChangeResult> SetUserEmailAsync(string userKey, string email, CancellationToken cancellationToken = default)
Parameters
userKeystringemailstringcancellationTokenCancellationToken
Returns
Remarks
The address is trimmed and must be a single, plain address. Nothing is sent to it, and it is not checked against other users: more than one user may hold the same address. It is what the user is mailed at, what an invitation to them matches, and what their avatar and fallback name come from, so the change records the address it replaced.
A host that refreshes the profile from trusted sign-ins (UserServiceBase.ShouldRefreshProfile)
will have the address set back to the one the identity provider sends the next time they sign in.
Exceptions
- ArgumentException
The address is empty or not a single plain address.
- InvalidOperationException
The user was not found.
- NotSupportedException
The host's user service does not implement it.
SetUserNameAsync(string, string, bool, CancellationToken)
Set a user's display name, and, when writeToDirectory is true, write it to the
external directory too.
[RequireScope("users:manage")]
Task<UserNameChangeResult> SetUserNameAsync(string userKey, string name, bool writeToDirectory, CancellationToken cancellationToken = default)
Parameters
userKeystringThe user to rename.
namestringThe new name. Null or empty clears the local name, and cannot be written to the directory.
writeToDirectoryboolWhether to write the name to the directory as well. The caller decides on every rename;
o.Blazor.WriteNameToDirectoryonly allows it.cancellationTokenCancellationTokenCancels the directory write.
Returns
Remarks
A write to the directory that cannot be honoured is refused before anything changes: the host has not allowed it, no directory is registered, the user is not linked to a directory account, or the name is a placeholder (IsPlaceholder(string)). Once the local write has happened, the directory write is best-effort and its outcome is reported on the result rather than rolled back into the local one: they fail independently, and coupling them would let a directory outage block renaming a user here.
Administrative rename only. The self-service path (IUserService.SetUserNameAsync) stays
local deliberately — a user editing their own display name in this application should not silently
rewrite the organization's directory.
Exceptions
- InvalidOperationException
The user was not found, or
writeToDirectoryis true and the write cannot be honoured.- NotSupportedException
writeToDirectoryis true and no directory is registered.
UseDirectoryNameAsync(string, CancellationToken)
Replace the user's local name with the display name the directory holds for them, and return it.
[RequireScope("users:manage")]
Task<string> UseDirectoryNameAsync(string userKey, CancellationToken cancellationToken = default)
Parameters
userKeystringcancellationTokenCancellationToken
Returns
Remarks
The directory is read here rather than the name being passed in, so the operation can only ever take what the directory actually says. Refused when the directory name is a placeholder (IsPlaceholder(string)): taking "unknown" would overwrite a real name with none. Local only — the name came from the directory, so nothing is written back.
Exceptions
- InvalidOperationException
The user is not found in the directory, or the directory holds only a placeholder name.
- NotSupportedException
The host's user management service does not implement it.
VerifyAllAsync(CancellationToken)
Verify all local users against the external directory, streamed as results arrive.
[RequireScope("users:manage")]
IAsyncEnumerable<UserVerificationResult> VerifyAllAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationToken
Returns
VerifyUserAsync(string, CancellationToken)
Verify a local user against the external directory. When the user resolves via email fallback, the found directory id is persisted on the user (relink).
[RequireScope("users:manage")]
Task<DirectoryVerificationResult> VerifyUserAsync(string userKey, CancellationToken cancellationToken = default)
Parameters
userKeystringcancellationTokenCancellationToken