Class AuthorizationTeamServiceDecorator
Decorator over Tharga.Team.ITeamService that enforces the team-operation authorization model in the service layer (so the same checks protect the Blazor circuit and any consumer's REST controller). Reads the caller's claims via TeamAuthorizer:
- Create — authenticated AND
AllowTeamCreation(no scope; self-service). - Delete — (Owner of the team AND
AllowTeamCreation) ORteams:delete(system). - Rename / Consent —
team:manageon the team. - Custom-role CRUD — the configurable custom-role manage scope on the team (default
team:manage). - Member invite/remove/role/scope-overrides/display-name —
member:manageon the team. - Leave — no scope; the operation names no user but the caller. See LeaveTeamAsync(string).
- Remove user from all teams —
users:manage(system; backs user deletion). - Transfer ownership — passed through (Owner-only is enforced by the inner service).
Reads, consent-team lookup, last-seen touch, invite-code lookup and invitation responses pass through (self-service / not gated here).
public sealed class AuthorizationTeamServiceDecorator
- Inheritance
-
AuthorizationTeamServiceDecorator
- Inherited Members
Constructors
AuthorizationTeamServiceDecorator(ITeamService, TeamAuthorizer, TeamLifecycleOptions, IScopeRegistry, ITenantRoleRegistry, string, TeamPurgeCascade)
public AuthorizationTeamServiceDecorator(ITeamService inner, TeamAuthorizer authorizer, TeamLifecycleOptions lifecycle, IScopeRegistry scopeRegistry = null, ITenantRoleRegistry tenantRoleRegistry = null, string customRoleManageScope = null, TeamPurgeCascade purgeCascade = null)
Parameters
innerITeamServiceauthorizerTeamAuthorizerlifecycleTeamLifecycleOptionsscopeRegistryIScopeRegistrytenantRoleRegistryITenantRoleRegistrycustomRoleManageScopestringpurgeCascadeTeamPurgeCascade
Methods
AddMemberAsync(string, InviteUserModel)
public Task AddMemberAsync(string teamKey, InviteUserModel model)
Parameters
teamKeystringmodelInviteUserModel
Returns
ClearTeamIconAsync(string)
Clears the team's icon and deletes the stored bytes. Gated by team:manage.
public Task ClearTeamIconAsync(string teamKey)
Parameters
teamKeystring
Returns
CreateTeamAsync(string)
public Task<ITeam> CreateTeamAsync(string name)
Parameters
namestring
Returns
DeleteTeamAsync<TMember>(string)
public Task DeleteTeamAsync<TMember>(string teamKey) where TMember : ITeamMember
Parameters
teamKeystring
Returns
Type Parameters
TMember
ExtendInvitationAsync(string, string)
Gives an outstanding invitation a fresh lifetime, keeping its code.
public Task ExtendInvitationAsync(string teamKey, string inviteKey)
Parameters
Returns
Remarks
The point is what it does not do: mint a new code. A link that has already been mailed keeps working, so extending an invitation costs the recipient nothing and needs no second message. That is only possible because the expiry lives on the invitation record rather than being derived from its creation time — see ExpiresAt.
An operation rather than a settable expiry, so it can be authorized and audited as one fact.
The new expiry is now plus the configured Lifetime. Where no
lifetime is configured invitations do not expire, and extending clears any expiry the invitation was
carrying.
GetAllTeamsAsync()
Every team, regardless of membership. Requires the Read system scope.
public IAsyncEnumerable<ITeam> GetAllTeamsAsync()
Returns
Remarks
Discovery only — the returned teams carry no implied access. Acting inside a team the caller is not a member of still depends on that team's consent. Use Tharga.Team.ITeamService.GetTeamsAsync() for the caller's own teams; this method is for oversight surfaces (support, administration).
GetAllTeamsAsync<TMember>()
Every team, regardless of membership. Requires the Read system scope.
public IAsyncEnumerable<ITeam<TMember>> GetAllTeamsAsync<TMember>() where TMember : ITeamMember
Returns
- IAsyncEnumerable<ITeam<TMember>>
Type Parameters
TMember
Remarks
Discovery only — the returned teams carry no implied access. Acting inside a team the caller is not a member of still depends on that team's consent. Use Tharga.Team.ITeamService.GetTeamsAsync() for the caller's own teams; this method is for oversight surfaces (support, administration).
GetConsentedTeamsAsync(string[])
public IAsyncEnumerable<ITeam> GetConsentedTeamsAsync(string[] userRoles)
Parameters
userRolesstring[]
Returns
GetMembersAsync(string)
public IAsyncEnumerable<ITeamMember> GetMembersAsync(string teamKey)
Parameters
teamKeystring
Returns
GetTeamAsync<TMember>(string)
public Task<ITeam<TMember>> GetTeamAsync<TMember>(string teamKey) where TMember : ITeamMember
Parameters
teamKeystring
Returns
Type Parameters
TMember
GetTeamByKeyAsync(string)
A team by key, regardless of the caller's membership — a non-generic exact read for call sites with
no TMember to hand (e.g. the audit decorator capturing a "before" value for a consent change
made by a non-member acting through consent). Returns null when the team does not exist.
public Task<ITeam> GetTeamByKeyAsync(string teamKey)
Parameters
teamKeystring
Returns
GetTeamCustomRolesAsync(string)
public Task<IReadOnlyList<TenantRoleDefinition>> GetTeamCustomRolesAsync(string teamKey)
Parameters
teamKeystring
Returns
GetTeamKeyByInviteKeyAsync(string)
Unscoped, like SetInvitationResponseAsync(string, string, string, bool): the invite code is the check, and whoever follows an invitation link holds nothing on the team yet.
public Task<string> GetTeamKeyByInviteKeyAsync(string inviteKey)
Parameters
inviteKeystring
Returns
GetTeamMemberAsync(string, string)
public Task<ITeamMember> GetTeamMemberAsync(string teamKey, string userKey)
Parameters
Returns
GetTeamsAsync()
public IAsyncEnumerable<ITeam> GetTeamsAsync()
Returns
GetTeamsAsync<TMember>()
public IAsyncEnumerable<ITeam<TMember>> GetTeamsAsync<TMember>() where TMember : ITeamMember
Returns
- IAsyncEnumerable<ITeam<TMember>>
Type Parameters
TMember
GetTeamsForUserWithAccessLevelAsync(string, AccessLevel)
public Task<IReadOnlyList<ITeam>> GetTeamsForUserWithAccessLevelAsync(string userKey, AccessLevel accessLevel)
Parameters
userKeystringaccessLevelAccessLevel
Returns
Remarks
Gated on users:manage, the scope that already authorizes removing this user from every one
of these teams. Requiring teams:read instead would hide the warning from exactly the caller
about to cause the damage.
LeaveTeamAsync(string)
Deliberately unscoped, unlike RemoveMemberAsync(string, string) directly above it.
public Task LeaveTeamAsync(string teamKey)
Parameters
teamKeystring
Returns
Remarks
The signature carries no user key, so the caller can only remove themselves — the check is the
shape of the operation rather than a grant, which is the same reason an invitation is authorized
by its code. Requiring member:manage here is what stopped an ordinary member leaving a team
at all: it is registered at Administrator, so User and
Viewer never hold it.
No scope would serve. A suspended member holds none at all, and refusing them would make "suspended people cannot leave" an accident of the gate rather than a decision — while stranding nothing, since the Owner cannot be suspended.
The refusals live in the inner service and are the only ones: the Owner must transfer ownership first, and the last administrator of an ownerless team stays.
PurgeTeamAsync<TMember>(string)
Purging is the irreversible one and needs its own system scope, never a team-level grant.
public Task PurgeTeamAsync<TMember>(string teamKey) where TMember : ITeamMember
Parameters
teamKeystring
Returns
Type Parameters
TMember
Remarks
No AllowTeamCreation self-service path here, unlike RequireDeleteAsync(string).
A team administrator deleting their own team is recoverable and reasonable; destroying its storage
outright is not something a tenant should reach by holding team:manage.
RemoveMemberAsync(string, string)
public Task RemoveMemberAsync(string teamKey, string userKey)
Parameters
Returns
RemoveUserFromAllTeamsAsync(string)
Removes the user from every team they appear in, regardless of membership state. Backs user deletion (DeleteUserAsync(string, bool, CancellationToken)); requires the Manage system scope. Returns the number of teams the user was removed from.
public Task<int> RemoveUserFromAllTeamsAsync(string userKey)
Parameters
userKeystring
Returns
RenameTeamAsync<TMember>(string, string)
public Task RenameTeamAsync<TMember>(string teamKey, string name) where TMember : ITeamMember
Parameters
Returns
Type Parameters
TMember
RestoreTeamAsync<TMember>(string)
Restoring is authorized by the same rule as deleting — it undoes it.
public Task RestoreTeamAsync<TMember>(string teamKey) where TMember : ITeamMember
Parameters
teamKeystring
Returns
Type Parameters
TMember
SetInvitationResponseAsync(string, string, string, bool)
public Task SetInvitationResponseAsync(string teamKey, string userKey, string inviteCode, bool accept)
Parameters
Returns
SetMemberLastSeenAsync(string)
public Task SetMemberLastSeenAsync(string teamKey)
Parameters
teamKeystring
Returns
SetMemberNameAsync(string, string, string)
public Task SetMemberNameAsync(string teamKey, string userKey, string name)
Parameters
Returns
SetMemberRoleAsync(string, string, AccessLevel)
public Task SetMemberRoleAsync(string teamKey, string userKey, AccessLevel accessLevel)
Parameters
teamKeystringuserKeystringaccessLevelAccessLevel
Returns
SetMemberScopeOverridesAsync(string, string, string[])
public Task SetMemberScopeOverridesAsync(string teamKey, string userKey, string[] scopeOverrides)
Parameters
Returns
SetMemberSuspendedAsync(string, string, bool)
Suspends a member's access to the team, or restores it. The member keeps their membership, access level, roles and history, and still sees the team in the selector — they are simply granted no team scopes, so every scoped operation refuses.
public Task SetMemberSuspendedAsync(string teamKey, string userKey, bool suspended)
Parameters
Returns
Remarks
Reuses MemberManage, which already authorizes RemoveMemberAsync(string, string) — strictly more destructive, so a separate grant would guard the lesser act more carefully than the greater one.
The Owner cannot be suspended, and a member cannot suspend themselves. Both are refused by the service, not merely hidden in the UI.
Distinct from IUserManagementService.SetUserDisabledAsync, which blocks a person from the
whole application. This one is bounded to a single team.
SetMemberTenantRolesAsync(string, string, string[])
public Task SetMemberTenantRolesAsync(string teamKey, string userKey, string[] tenantRoles)
Parameters
Returns
SetOwnerAsync<TMember>(string, string)
public Task<SetOwnerResult> SetOwnerAsync<TMember>(string teamKey, string newOwnerUserKey) where TMember : ITeamMember
Parameters
Returns
Type Parameters
TMember
Remarks
A system grant only — there is no in-team fallback, deliberately, and now for two reasons.
On an ownerless team no in-team caller can exist. On a team that has an owner, the in-team caller
who should move ownership is the owner, and TransferOwnershipAsync<TMember>(string, string) is
already their path — an in-team fallback here would let an Administrator depose the owner, which
SetMemberRoleAsync exists to refuse.
An in-team scope of the same name must not satisfy it either, which is why this asks
HasSystemScopeAsync rather than checking a claim.
SetTeamConsentAsync(string, string[], AccessLevel?)
public Task SetTeamConsentAsync(string teamKey, string[] consentedRoles, AccessLevel? accessLevel = null)
Parameters
teamKeystringconsentedRolesstring[]accessLevelAccessLevel?
Returns
SetTeamCustomRolesAsync(string, IReadOnlyList<TenantRoleDefinition>)
public Task SetTeamCustomRolesAsync(string teamKey, IReadOnlyList<TenantRoleDefinition> customRoles)
Parameters
teamKeystringcustomRolesIReadOnlyList<TenantRoleDefinition>
Returns
SetTeamIconAsync(string, byte[], string)
Sets the team's icon from raw image bytes: stores them via the registered IIconStore,
persists the reference on the team, and deletes any previously-stored icon. Requires a registered
icon store. Gated by team:manage.
public Task SetTeamIconAsync(string teamKey, byte[] data, string contentType)
Parameters
Returns
TransferOwnershipAsync<TMember>(string, string)
public Task TransferOwnershipAsync<TMember>(string teamKey, string newOwnerUserKey) where TMember : ITeamMember
Parameters
Returns
Type Parameters
TMember
Events
SelectTeamEvent
public event EventHandler<SelectTeamEventArgs> SelectTeamEvent
Event Type
TeamsListChangedEvent
public event EventHandler<TeamsListChangedEventArgs> TeamsListChangedEvent