dRBACs.Client 4.3.0
dRBACs.Client
Libreria client per l'integrazione del sistema RBAC (Role-Based Access Control) nei progetti di ASLRoma1.
Installazione
Per utilizzare questa libreria, aggiungi il riferimento al progetto o installa il package NuGet (quando disponibile).
Compatibilità
Il pacchetto è multi-target sulle sole release LTS:
| TFM | Note |
|---|---|
net6.0 |
LTS scaduta (nov 2024), inclusa per i progetti non ancora migrati |
net8.0 |
LTS |
net10.0 |
LTS |
NuGet seleziona automaticamente l'assembly giusto e non serve alcuna configurazione nel progetto consumer. Un progetto su una release STS funziona comunque: risolve all'LTS compatibile più vicina — per esempio un progetto net9.0 usa lib/net8.0.
Il codice sorgente è identico per tutti i target — nessuna #if di compilazione condizionale, quindi il comportamento non cambia da un framework all'altro.
Aggiornamento a v4.3.0 — permission dell'utente (perimetro)
Novità additiva per chi non la usa: un'app che non chiama AddRbacPermissions() non cambia
comportamento e non paga alcuna chiamata in più.
Il problema
Il ruolo dice cosa puoi fare; serviva un modo di dire su quale sottoinsieme. Il caso reale: un operatore assegnato a uno o più presidi deve vedere solo i ricoveri di quei presidi, e chi non ha presidi assegnati continua a vederli tutti.
Il perimetro è un attributo dell'utente, non del ruolo — «questo operatore lavora sui presidi X e Y» non è una capacità condivisa da tutti quelli che hanno il suo stesso ruolo. Con le permission sul ruolo l'unica strada sarebbe stata un ruolo per presidio, che gonfia il catalogo e fa smettere ai ruoli di descrivere capacità. Da questa versione le permission stanno quindi sull'associazione utente-ruoli.
Convenzione sui valori
<DOMINIO>:<VALORE> es. PRESIDIO:120026, ASL:RM1, DISTRETTO:3
Maiuscolo, separatore :, valore senza spazi, massimo 128 caratteri, confronto
case-insensitive — il servizio normalizza in maiuscolo sia in scrittura sia in lettura. Il
significato del valore non viene validato: lo conosce solo l'applicativo che lo legge.
Il dominio serve anche quando un'istanza del servizio RBAC è condivisa fra progetti: ogni applicativo legge le dimensioni che gli competono e ignora le altre.
Registrazione
builder.Services.AddRbacClient(builder.Configuration);
builder.Services.AddRbacPermissions(); // <-- opt-in
AddRbacPermissions() registra una seconda IClaimsTransformation che, a ogni richiesta
autenticata, risolve le permission dell'utente e ne emette una per claim sotto il tipo
permission. Ha cache e finestra di backoff proprie (rbac_perms_{CF}), con gli stessi parametri
Rbac:CacheTtlMinutes e Rbac:FailureRetrySeconds dei ruoli.
È opt-in perché aggiunge una chiamata al servizio RBAC per ogni richiesta: chi non usa il
perimetro non deve pagarla. I metodi su IRbacClient ci sono comunque, anche senza questa
chiamata: quello che si aggiunge qui è solo la proiezione automatica sui claim.
Lettura
using dRBACs.Client.Security;
// tutte le permission
IReadOnlyList<string> tutte = PermissionClaims.Read(User);
// una in particolare (confronto case-insensitive)
bool puo = PermissionClaims.Has(User, "PRESIDIO:120026");
// solo i valori di un dominio, senza prefisso: ["120026", "120285"]
IReadOnlyList<string> presidi = PermissionClaims.ValuesOf(User, "PRESIDIO");
Cosa sapere
- Un guasto del servizio non diventa mai "nessuna permission". La trasformazione lascia il
principal intatto quando il servizio non risponde, e il fallimento non va in cache: va in cache
solo un successo. Qui l'invariante pesa più che sui ruoli, perché "nessuna permission" di solito
significa "vede tutto": trattare un'avaria come assenza di perimetro lo aprirebbe in silenzio.
L'applicativo deve quindi distinguere claim assente da claim presente e vuoto, e decidere di
conseguenza (nel caso d'uso originale:
503). - Cambio di contratto sui DTO.
UserRoleDtoacquistaPermissions;RbacRoleDtola perde. Quel campo era popolato ma non lo leggeva nessun applicativo, e un campo JSON in più nella risposta viene semplicemente ignorato dalla deserializzazione — chi legge i ruoli non se ne accorge. - Serve un
dRBACs.Servercon gli endpointapi/user-permissions. Contro una versione precedente del servizio le letture rispondono 404 e arrivano comeRbacResultfallito, cioè come "perimetro non determinabile". - La cache è per processo, come quella dei ruoli: una scrittura fatta dall'app A non invalida quella dell'app B.
Aggiornamento a v4.2.0 — Blazor Server e rendering interattivo
Novità additiva: un'app che non chiama AddRbacBlazorServer() non cambia comportamento.
Il problema
Un componente Blazor con rendering interattivo gira due volte: una durante il prerender,
dentro la richiesta HTTP, e una sul circuito SignalR, dove non esiste nessuna richiesta HTTP.
Sul circuito IHttpContextAccessor.HttpContext è null, e con esso saltano le due cose che nel
pacchetto ci passavano attraverso:
// Funziona nel prerender, poi smette. Con il ?. davanti all'await non restituisce
// nemmeno null: await di un Task nullo è una NullReferenceException.
_accessToken = await HttpContextAccessor.HttpContext?.GetTokenAsync("access_token");
IUserContext è il caso peggiore dei due, perché non fallisce: degrada in silenzio ai propri
valori di comodo — UserId diventa "dev-user", Email "dev@example.com", IsAdmin falso.
Le policy di autorizzazione continuano a funzionare, perché leggono l'AuthenticationState e non
questo servizio: il difetto non si manifesta come accesso negato, ma come dati scritti a nome
dell'utente sbagliato.
La soluzione
builder.Services.AddIamAuthentication(builder.Configuration)
.WithClaimsAugmenter<EmailClaimAugmenter>();
// Dopo AddRbacClient / AddIamAuthentication.
builder.Services.AddRbacBlazorServer();
La chiamata fa tre cose:
- Aggancia agli eventi del cookie il deposito server-side dell'access token
(
IRbacAccessTokenStore). Il token viene depositato a ogni richiesta HTTP in cui il cookie viene autenticato — compresa la negoziazione del circuito — e non lascia mai il server: è il motivo per cui non si usaPersistentComponentState, che lo serializzerebbe dentro l'HTML della pagina. - Registra
IRbacAccessTokenProvider, che recupera il token da dentro o fuori una richiesta HTTP. - Sostituisce
IUserContextcon un'implementazione che risolve il principal dallaHttpContextquando c'è e dall'AuthenticationStateProviderquando non c'è.
Nei componenti, al posto di IHttpContextAccessor:
@inject IRbacAccessTokenProvider TokenProvider
@inject IRbacClient RbacClient
@code {
protected override async Task OnInitializedAsync()
{
string? token = await TokenProvider.GetAccessTokenAsync();
RbacResult<IReadOnlyList<RbacRoleDto>> roles = await RbacClient.GetAllRolesAsync(token);
if (!roles.IsSuccess)
{
// Il servizio non ha risposto: roles.Value è null, non una lista vuota.
return;
}
_availableRoles = roles.Value!.ToList();
}
}
IUserContext non richiede invece alcuna modifica al codice che lo inietta: cambia solo
l'implementazione registrata.
Cosa sapere
- Il deposito è in memoria, per processo, come la cache dei ruoli. Dopo un riavvio
dell'applicazione un circuito già aperto non trova più il token: la richiesta HTTP successiva
lo rideposita, quindi un ricaricamento della pagina lo rimette a posto senza rifare login.
Un'app su più nodi senza affinità di sessione può reimplementare
IRbacAccessTokenStoresu un deposito condiviso e registrarla al posto di quella predefinita. - La chiave del deposito è
sid, altrimentisub, altrimentiNameIdentifier, altrimenti il codice fiscale. Ripiegando susub, due login dello stesso utente da dispositivi diversi condividono la voce e l'ultimo sovrascrive il token del primo — sono token equivalenti per il servizio RBAC. Quello che non può succedere è che la chiave di un utente risolva al token di un altro: ogni anello della catena è un claim di identità. - Il logout scarta il token dal deposito, senza aspettarne la scadenza.
- Le opzioni (
RbacBlazorOptions) permettono di cambiare lo schema del cookie, disattivare la cattura o la sostituzione diIUserContext, e regolare la durata di ripiego in cache.
Aggiornamento a v3.3.0 — anagrafica dipendenti (opzionale)
Novità additiva e disattivata per default: chi non la configura non deve cambiare nulla.
L'IAM identifica l'utente con il codice fiscale, mentre le applicazioni preesistenti lo
identificano con lo username aziendale — quello con cui hanno scritto anni di dati. Senza un
ponte fra i due, ogni scrittura fatta dopo il passaggio all'IAM finisce sotto una chiave utente
diversa da tutte quelle già a database. Il lookup risolve il problema chiedendo a un servizio di
anagrafica, dato il codice fiscale, la posizione del dipendente, e ne emette lo USERNAME come
claim preferred_username.
{
"Iam": {
"EmployeeDirectory": {
"Enabled": true,
"BaseUrl": "https://url-del-servizio-anagrafica",
"LookupPath": "api/dipendenti/{cf}",
"CacheTtlMinutes": 60
}
}
}
Token di servizio
L'anagrafica non accetta il token dell'utente: autentica l'applicazione, con un token proprio che
si chiede in POST autenticandosi in Basic. Il token e' opaco e ha una scadenza assoluta, che il
pacchetto legge dalla risposta e usa per rinnovarlo — con un margine, perche' un token scaduto per
una frazione di secondo si manifesta come un 401 intermittente.
{
"Iam": {
"EmployeeDirectory": {
"AuthTokenUrl": "https://admin-mia.aslroma1.it/auth/token",
"AuthBasicCredentials": "<gia' codificate in base64>",
"TokenExpiryMarginSeconds": 60
}
}
}
In alternativa a AuthBasicCredentials si possono passare AuthClientId e AuthClientSecret in
chiaro. Lasciando AuthTokenUrl vuoto si inoltra all'anagrafica il token dell'utente, che resta il
comportamento adatto ai servizi che lo accettano.
Il token e' dell'applicazione e non dell'utente: sta in un singleton, condiviso da tutte le richieste, e il rinnovo e' serializzato — alla scadenza tutte le richieste in volo ripartono insieme, ed e' il momento in cui conviene meno moltiplicare le chiamate.
Il claim viene aggiunto prima degli augmenter e prima dei ruoli, quindi un augmenter lo
trova già fra i claim. Il pacchetto emette solo lo username: il resto dell'anagrafica —
nome, cognome, recapiti, struttura — è a disposizione dell'app in
IamClaimsAugmentationContext.Employee, perché quali altri dati diventino claim è una decisione
applicativa e non del pacchetto.
⚠️ Il campo GRUPPO dell'anagrafica è mappato ma deliberatamente inutilizzato, e non va
trasformato in un ruolo: i ruoli li decide il servizio RBAC, e una seconda sorgente
riaprirebbe esattamente il problema che IgnoreIdentityProviderRoles esiste per chiudere.
Un'anagrafica irraggiungibile non fa fallire il login: si prosegue senza username, coerentemente con quanto già fa la risoluzione dei ruoli. Esiti positivi e finestre di backoff sono in cache come per i ruoli.
Host a token (API, microservizi)
Un'app a cookie risolve lo username una volta sola al login e se lo porta nel cookie. Un'API
quel momento non ce l'ha: deve ricavarlo dal token a ogni richiesta, e per questo si registra
diversamente — senza OIDC, con una IClaimsTransformation:
builder.Services.AddIamEmployeeDirectory(builder.Configuration);
builder.Services.AddRbacClient(builder.Configuration); // se servono anche i ruoli
Richiede SaveToken = true sull'handler JWT: senza, non c'è access token da inoltrare né
all'anagrafica né al servizio RBAC.
Aggiornamento a v3.2.0 — i ruoli dell'IdP non sono più attendibili per default
Quando i ruoli li decide il servizio RBAC (UseRbacRoles = true, il default), i claim di ruolo
presenti nel token vengono scartati prima che l'identity finisca nel cookie.
Il motivo è di sicurezza, non di igiene dei claim. Un IdP può emettere ruoli non pertinenti
all'applicazione — o, come osservato sull'IAM ASLROMA1, lo stesso ruolo admin a ogni
utente che effettua l'accesso. E c'è un secondo motivo, meno evidente: un ruolo lasciato nel
cookie sopravvive a un'indisponibilità del servizio RBAC, perché
RemoteRolesClaimsTransformation restituisce il principal intatto quando il servizio non
risponde. Senza lo scarto, un'interruzione del servizio ruoli diventerebbe una promozione.
Lo scarto copre identity.RoleClaimType e i tipi noti (role, roles, URI WS-Federation), ed
è verificato a posteriori: se un claim di ruolo sopravvive, il login fallisce invece di
proseguire con un cookie potenzialmente privilegiato.
Gli IIamClaimsAugmenter girano dopo lo scarto: un ruolo aggiunto da un innesto applicativo
è una decisione dell'app, non un'affermazione dell'IdP, e resta in piedi.
// Per tornare al comportamento precedente — cioè fidarsi dei ruoli dell'IdP
// quanto di quelli RBAC:
builder.Services.AddIamAuthentication(builder.Configuration, o =>
{
o.IgnoreIdentityProviderRoles = false;
});
L'opzione non ha effetto con UseRbacRoles = false: senza una sorgente alternativa, svuotare i
ruoli lascerebbe ogni utente senza permessi.
Aggiornamento a v3.1.3
Un ruolo già presente nel principal sotto un solo claim type non veniva normalizzato sugli
altri. La verifica di presenza era globale — "questo ruolo c'è già da qualche parte?" — e un
riscontro positivo saltava l'emissione di tutti i tipi, incluso identity.RoleClaimType, che
è quello su cui IsInRole e [Authorize(Roles = ...)] decidono.
Si manifesta con un host che ha MapInboundClaims = true: il claim role del token viene
rimappato sull'URI WS-Federation, mentre RoleClaimType resta "role". Il ruolo si vede
benissimo fra i claim e IsInRole risponde comunque false.
Ora la verifica è per singolo claim type: si aggiungono i tipi mancanti senza duplicare quelli presenti.
Nota di configurazione, indipendente da questa correzione: RoleClaimType e NameClaimType
vanno tenuti coerenti con MapInboundClaims. Con la mappatura inbound attiva i claim del token
arrivano in forma WS-Federation, quindi i valori giusti sono ClaimTypes.Role e
ClaimTypes.Name, non "role" ed "email".
Aggiornamento a v3.1.2
Configure<RbacOptions> chiamata dopo AddRbacClient (o dopo AddIamAuthentication, che
la chiama internamente) non aveva effetto sull'indirizzo usato dal client: il base url veniva
letto subito da Rbac:BaseUrl e catturato nella closure di AddHttpClient. Un'app che tiene
la configurazione in variabili d'ambiente piatte finiva così per chiamare il default
https://localhost:7171.
Sintomo: a log l'indirizzo giusto, e a runtime
Risoluzione ruoli RBAC non riuscita ...: Connection refused (localhost:7171).
Ora il base url si risolve da IOptions<RbacOptions> alla creazione del client, quindi
entrambe le forme funzionano:
// sezione di configurazione
builder.Services.AddRbacClient(builder.Configuration); // legge Rbac:BaseUrl
// oppure valore da variabile d'ambiente piatta
builder.Services.Configure<RbacOptions>(o => o.BaseUrl = Environment.GetEnvironmentVariable("RBAC_BASE_URL")!);
Un BaseUrl esplicitamente vuoto ora fallisce con un messaggio chiaro invece di ripiegare in
silenzio su localhost.
Aggiornamento a v3.1.1
Correzione obbligatoria per chi usa AddIamAuthentication o comunque la risoluzione ruoli:
in v3.1.0 RemoteRolesClaimsTransformation entrava in ricorsione infinita alla prima
richiesta autenticata.
Il token per il servizio RBAC si legge con HttpContext.GetTokenAsync("access_token"), che
internamente chiama AuthenticateAsync, che rinvoca la claims transformation: senza una
guardia il giro non finisce mai. Il risultato è una StackOverflowException — non
intercettabile — quindi il processo terminava subito dopo il login senza lasciare nulla nei
log, e il container ripartiva.
Sintomo tipico: il login sull'IAM va a buon fine, il browser viene rediretto all'applicazione
e l'applicazione si riavvia. Nessun aggiornamento di configurazione è necessario: basta
passare a 3.1.1.
Migrazione a v3.0.0
IRbacClient restituisce RbacResult<T> invece di liste e bool. Il motivo: prima un
servizio irraggiungibile e un utente senza ruoli restituivano entrambi una lista vuota, e la
claims transformation metteva quell'esito in cache — un blip di rete lasciava l'utente senza
permessi per l'intera durata della cache, e una revoca era indistinguibile da un guasto.
// prima (v2.x)
List<RbacRoleDto> roles = await client.GetUserRolesAsync(cf);
// una lista vuota poteva voler dire "nessun ruolo" oppure "servizio giù": impossibile saperlo
// ora (v3.0.0)
RbacResult<IReadOnlyList<RbacRoleDto>> result = await client.GetUserRolesAsync(cf);
if (!result.IsSuccess)
{
// result.Error, result.StatusCode
return;
}
IReadOnlyList<RbacRoleDto> roles = result.Value!;
Altri cambiamenti, senza impatto sul codice consumer:
- il token di accesso viaggia sulla singola richiesta e non più su
HttpClient.DefaultRequestHeaders; - la claims transformation non muta più il principal ricevuto e non emette il claim
RBAC_TRANSFORMED, che se persistito in un cookie avrebbe congelato i ruoli fino al logout; - nuova opzione
Rbac:FailureRetrySeconds(default 15): attesa prima di riprovare dopo un guasto. Impostarla a0disattiva il backoff. AncheRbac:CacheTtlMinutesa0disattiva la cache dei ruoli.
Migrazione da AslRoma1.Rbac.Client (v1.x → v2.0.0)
Il pacchetto è stato rinominato. Per aggiornare un progetto consumer:
| Prima (v1.x) | Ora (v2.0.0) |
|---|---|
PackageReference Include="AslRoma1.Rbac.Client" |
PackageReference Include="dRBACs.Client" |
using AslRoma1.Rbac.Client.Extensions; |
using dRBACs.Client.Extensions; |
using AslRoma1.Rbac.Client.Security; |
using dRBACs.Client.Security; |
using AslRoma1.Rbac.Client.Services; |
using dRBACs.Client.Services; |
AddAslRoma1RbacClient(configuration) |
AddRbacClient(configuration) |
Nessuna chiave di configurazione è cambiata: la sezione Rbac resta identica.
Configurazione
Aggiungi la configurazione RBAC nel file appsettings.json:
{
"Rbac": {
"BaseUrl": "https://url-del-servizio-rbac",
"CacheTtlMinutes": 10,
"FailureRetrySeconds": 15
}
}
Autenticazione IAM (v3.1.0)
Per autenticarsi contro l'IAM ASLROMA1 (LDAP, SPID, CIE) e risolvere i ruoli in un colpo solo:
using dRBACs.Client.Extensions;
builder.Services.AddIamAuthentication(builder.Configuration);
Restano a carico dell'app app.UseAuthentication() e app.UseAuthorization(): l'ordine dei
middleware è una decisione della pipeline, non del pacchetto.
{
"Iam": {
"Authority": "https://iam.aslroma1.it",
"ClientId": "...",
"ClientSecret": "...",
"Scopes": "openid profile",
"ValidationMode": "Strict"
}
}
Al termine del login l'utente ha: anagrafica da SPID/CIE, codice fiscale su
ClaimTypes.SerialNumber, email e PEC estratte dall'assertion SAML, e i ruoli dal servizio
RBAC — quindi IsInRole(...) e [Authorize(Roles = "...")] funzionano senza altro codice.
Modalità di validazione
| Modalità | Discovery | Firma verificata | Issuer / nonce |
|---|---|---|---|
Strict (default) |
sì | sì, via JWKS | validati |
Discovery |
sì | sì, via JWKS | rilassati |
Compatible |
no, endpoint cablati | no | rilassati |
Strict è il default perché è stato verificato con login reali contro l'IAM di produzione,
un gradino per volta: il jwks_uri esiste e pubblica una chiave RSA 2048 con kid, le firme
RS256 verificano, l'iss coincide con l'authority, l'aud è il client_id e il nonce torna
riflesso nell'id_token.
Discovery verifica la firma ma lascia rilassati issuer e nonce: serve a isolare un guasto,
non come destinazione. Compatible non verifica nulla — un id_token contraffatto verrebbe
accettato — ed esiste solo come via di fuga se l'IAM smettesse di pubblicare il JWKS. In quel
caso la cosa giusta è segnalarlo all'IAM, non restare lì.
Nota su RequireState: resta disattivato in tutte e tre le modalità, e non è una deroga. Il
default del framework è true, ma OpenIdConnectHandler non assegna mai lo State del
contesto di validazione: lasciato a true il login fallisce con IDX21329 contro qualunque
IdP. La protezione CSRF sul callback la fa il cookie di correlazione, che il handler valida
per conto suo.
Cache dei ruoli e invalidazione
I ruoli risolti restano in cache in memoria per Rbac:CacheTtlMinutes (default 10). Ogni
scrittura fatta con IRbacClient — AddRoleToUserAsync, RemoveRoleFromUserAsync — scarta la
cache di quel codice fiscale non appena il servizio conferma: l'utente vede i ruoli nuovi alla
richiesta successiva, senza aspettare il TTL e senza rifare login. Si invalida solo dopo un
esito positivo, perché se la scrittura non e' passata i ruoli in cache sono ancora quelli giusti.
Un'app che modifica i ruoli per una strada propria — una API amministrativa che parla
direttamente al servizio RBAC invece di passare da IRbacClient — puo' iniettare
IRbacRoleCache e invalidare da se':
public class RuoliController(IRbacRoleCache cache)
{
public async Task Assegna(string cf, string roleId)
{
await _mioServizio.AssegnaAsync(cf, roleId);
cache.Invalidate(cf); // maiuscole e spazi non contano
}
}
Limite da conoscere: la cache vive dentro un solo processo. Una scrittura fatta dall'app A
non invalida la cache dell'app B, che continuera' a servire i ruoli vecchi fino alla scadenza
del TTL. Per l'utente modificato dall'interno della propria applicazione — il caso normale —
l'aggiornamento e' invece immediato. Se serve che la modifica si propaghi subito ovunque,
l'unica leva oggi e' abbassare Rbac:CacheTtlMinutes.
Durata della sessione
Il cookie eredita la scadenza del token e non si rinnova (UseTokenLifetime = true,
SlidingExpiration = false): lasciando i default del framework durerebbe 14 giorni
rinnovandosi a ogni richiesta, e sopravviverebbe per settimane a un token scaduto o revocato.
L'id_token dell'IAM vive un'ora, e quella diventa la durata della sessione.
Il discovery dichiara solo authorization_code, ma il token endpoint restituisce anche un
refresh token. Il pacchetto non lo usa: nulla qui rinnova la sessione.
Iam:CookieLifetimeFollowsToken = false disattiva il vincolo, ma è una responsabilità che
ci si assume consapevolmente.
Claim applicativi
Per i claim che il pacchetto non può conoscere — il codice struttura di un'app, un profilo
amministrativo — si implementa IIamClaimsAugmenter:
builder.Services.AddIamAuthentication(builder.Configuration)
.WithClaimsAugmenter<CirCodeAugmenter>();
Adattare i default a un'app esistente
I default seguono l'integrazione collaudata (MapInboundClaims = false, nome da email,
ruoli da role). Un'app che si aspetta la forma WS-Federation li ribalta:
builder.Services.AddIamAuthentication(builder.Configuration, o =>
{
o.MapInboundClaims = true;
o.NameClaimType = "preferred_username";
});
Altre opzioni: ApiPathPrefixes (default ["/api"], sotto cui si risponde 401/403 invece di
redirigere al login), UseRbacRoles (per chi si autorizza diversamente),
EmployeeDirectory (vedi sopra), AccessDeniedPath,
e LogUserInfoPayload — che registra il payload grezzo dello userinfo per riallineare i DTO
quando lo schema IAM cambia. ⚠️ Quel payload contiene dati personali (codice fiscale,
indirizzo, telefono, estremi del documento): va acceso solo il tempo necessario.
Registrazione dei servizi
Nel file Program.cs, utilizza il metodo di estensione per registrare i servizi necessari:
using dRBACs.Client.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Registra i servizi RBAC
builder.Services.AddRbacClient(builder.Configuration);
var app = builder.Build();
// ...
Utilizzo
IUserContext
Puoi iniettare IUserContext per ottenere informazioni sull'utente corrente:
public class MyService
{
private readonly IUserContext _userContext;
public MyService(IUserContext userContext)
{
_userContext = userContext;
}
public void DoSomething()
{
var cf = _userContext.CodiceFiscale;
var isAdmin = _userContext.IsAdmin;
// ...
}
}
IRbacClient
Puoi iniettare IRbacClient per interagire direttamente con il servizio RBAC:
public class MyAdminService
{
private readonly IRbacClient _rbacClient;
public MyAdminService(IRbacClient rbacClient)
{
_rbacClient = rbacClient;
}
public async Task<bool> AssignRole(string cf, string roleId)
{
var result = await _rbacClient.AddRoleToUserAsync(cf, roleId);
return result.IsSuccess;
}
}
Ogni metodo restituisce un RbacResult<T>, che tiene distinti «il servizio ha risposto, e la
risposta è vuota» e «il servizio non ha risposto»: il primo è un IsSuccess con un valore
vuoto, il secondo un fallimento con Error e — quando c'è — StatusCode. Collassare i due casi è
il difetto che questo tipo esiste per impedire.
| Metodo | Chiamata al servizio |
|---|---|
GetUserRolesAsync(cf) |
GET api/user-roles/{cf} |
GetAllRolesAsync() |
GET api/roles |
GetAllUserRolesAsync() |
GET api/user-roles |
AddRoleToUserAsync(cf, roleId) |
POST api/user-roles/{cf}/roles/{roleId} |
RemoveRoleFromUserAsync(cf, roleId) |
DELETE api/user-roles/{cf}/roles/{roleId} |
GetUserPermissionsAsync(cf) |
GET api/user-permissions/{cf} |
ReplaceUserPermissionsAsync(cf, permissions) |
PUT api/user-permissions/{cf} |
AddPermissionToUserAsync(cf, permission) |
POST api/user-permissions/{cf} |
RemovePermissionFromUserAsync(cf, permission) |
DELETE api/user-permissions/{cf}/{permission} |
Ogni metodo accetta un accessToken facoltativo: quando lo si omette, la richiesta parte senza
bearer e il servizio RBAC risponde 401. Dentro una richiesta HTTP il token lo mette la claims
transformation; da un componente Blazor interattivo lo si prende da IRbacAccessTokenProvider.
Le scritture sono idempotenti: assegnare una permission già presente o rimuoverne una assente non è un errore. Ogni scrittura riuscita invalida la cache di quel codice fiscale.
No packages depend on dRBACs.Client.