Cette page n’est pas encore traduite : la version anglaise est affichée.
Access & Roles
Les rôles se composent, ils ne sont jamais codés en dur. Les permissions atomiques se combinent en rôles ; les rôles filtrent les données jusqu’à la ligne et à la colonne, et décident même quelles interfaces de module un build voit. Ce modèle remplace la séparation entre clé client et clé serveur : un identifiant désigne une identité, et ses rôles sont résolus à chaque requête.
Quand l’utiliser
- Il vous faut un identifiant (credential) plus restreint que « client » ou « serveur » : derrière lui, des rôles composés sont résolus à chaque requête.
- L’accès aux données doit s’arrêter aux lignes et aux colonnes : restriction par région, masquage des données personnelles, prestataires en lecture seule.
- Un build ne doit voir que les interfaces que son rôle débloque : pour un visiteur, expulser et fermer n’existent tout simplement pas.
- Votre UI doit griser les boutons avec exactitude :
CanIévalue la même politique que celle que le serveur appliquera. - Inutile si les presets fournis (
player,room-owner,seller, …) correspondent déjà à vos acteurs. Tous les modules les respectent par défaut ; le catalogue complet se trouve dans Concepts clés.
Qui fait quoi
| Acteur | Ce qu’il fait sur cette page |
|---|---|
operator | déclare les rôles et les politiques, fixe les limites par ligne et par colonne, attribue les rôles, émet les clés |
match-organizer | l’équipe du tournoi : détient une clé aux rôles composés, filtre les inscriptions, ne peut pas rembourser |
chaque acteur | vérifie CanI avant d’agir ; ne voit que les interfaces qu’il a débloquées |
En bref
entry-validator avec des limites par ligne et par colonne, l’attribuer, puis vérifier CanI avant d’agir[Role("entry-validator")]
public class EntryValidator
{
[Allow(Rooms.Membership.Administer)] public Permit GateEntries;
[Allow(Data.Records.Read, table: "player_profile", rows: "banned == false",
columns: "id, display_name")] public Permit SeeProfiles;
}
await PlayServ.Access.Grant(staffId, Roles.EntryValidator, Roles.MatchOrganizer);
var key = await PlayServ.Access.IssueKey(staffId); // the credential names no roles
// any actor, before attempting an operation:
if (await PlayServ.Access.CanI(Commerce.Orders.Administer)) Hud.ShowRefund();@Role('entry-validator')
export class EntryValidator {
@Allow(Rooms.membership.administer) gateEntries: Permit;
@Allow(Data.records.read, { table: 'player_profile', rows: 'banned == false',
columns: ['id', 'display_name'] }) seeProfiles: Permit;
}
await playserv.access.grant(staffId, Roles.entryValidator, Roles.matchOrganizer);
const key = await playserv.access.issueKey(staffId); // the credential names no roles
// any actor, before attempting an operation:
if (await playserv.access.canI(Commerce.orders.administer)) hud.showRefund();@role("entry-validator")
class EntryValidator:
gate_entries = allow(rooms.membership.administer)
see_profiles = allow(data.records.read, table="player_profile",
rows="banned == false", columns=["id", "display_name"])
await playserv.access.grant(staff_id, roles.ENTRY_VALIDATOR, roles.MATCH_ORGANIZER)
key = await playserv.access.issue_key(staff_id) # the credential names no roles
# any actor, before attempting an operation:
if await playserv.access.can_i(commerce.orders.administer):
hud.show_refund()La déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
// declarations compile into the same pushed model — playserv push from the UE project or CI
USTRUCT(PSRole = "entry-validator")
struct FEntryValidator
{
GENERATED_BODY()
UPROPERTY(PSAllow = (Atom = "Rooms.Membership.Administer"))
FPSPermit GateEntries;
UPROPERTY(PSAllow = (Atom = "Data.Records.Read", Table = "player_profile",
Rows = "banned == false", Columns = "id, display_name"))
FPSPermit SeeProfiles;
};
// granting is an operator act; a build checks what its identity resolves to
const FPSActor Me = Client->Whoami(); // which interfaces this actor unlocks
Client->Access->CanI(TEXT("Commerce.Orders.Administer"),
TPSOnResult<bool>::CreateWeakLambda(this, [this](const TPSResult<bool>& Result)
{
if (!Result.HasValue()) { return; }
if (Result.Value()) { Hud->ShowRefund(); }
}));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.// the same `[Role]` / `[Allow]` declaration as the server tab, on the Unity 2021.3 runtime
var me = playserv.Whoami(); // which interfaces this actor unlocks
if (await playserv.Access.CanI(Commerce.Orders.Administer)) hud.ShowRefund();Une permission atomique (atom) est une paire : une ressource et l’un de quatre verbes — read, write, execute, administer. L’ensemble des verbes est fixe : un cas qui n’y entre pas conduit à scinder la ressource, pas à allonger la liste. C’est pourquoi filtrer l’inscription de quelqu’un d’autre s’écrit Rooms.Membership.Administer et non avec un verbe ValidateEntry dédié : agir sur l’appartenance d’un autre acteur relève de l’administration, alors que rejoindre soi-même correspond à Rooms.Membership.Write sur la même ressource.
Le modèle
L’ACL des données, c’est rôle × opération × prédicat de lignes × masque de champs — un seul modèle, identique qu’il soit écrit dans le code, via l’API ou dans la grille des rôles du panneau.
De quoi l’accès est fait.
| Terme | Ce que c’est |
|---|---|
permission atomique | une paire ressource × verbe. Les quatre verbes sont read (récupérer, sélectionner, s’abonner), write (créer, modifier, supprimer, et agir pour soi-même — rejoindre, quitter), execute (invoquer une fonction, appliquer une capacité) et administer (agir sur les autres : expulser, fermer, forcer un changement d’état) |
rôle | un ensemble nommé de permissions atomiques. Il peut inclure un autre rôle |
preset de rôle | construit sur des permissions atomiques, il continue de fonctionner sans changement pour les consommateurs déjà déployés. C’est un point de départ, pas une contrainte : un projet déclare ses propres rôles à partir des mêmes permissions atomiques |
prédicat de lignes | quelles lignes — un prédicat booléen sur des valeurs de session |
masque de champs | quels champs, déclaré par rôle et par opération |
Ce en quoi un identifiant se résout.
| Identifiant | Ce qu’il débloque |
|---|---|
clé joueur | un build du moteur la porte ; le joueur derrière elle arrive avec la connexion (sign-in), et le build voit les lignes cl de chaque tableau d’opérations |
clé hôte | un serveur dédié ou un client maître (master-client) la détient, et ses rôles débloquent les lignes mc |
code publié | s’exécute avec le rôle backend-service du projet — c’est ce que vérifie Authoritative = true sur un classement (leaderboard) |
un hook enregistré | n’accorde rien de plus : votre fonction garde le rôle sous lequel elle a été déployée |
La légende des étiquettes (fn / cl / mc / adm) se trouve dans Concepts clés.
Ce qui vaut pour chaque vérification.
| Toujours | Ce que c’est |
|---|---|
un identifiant | ne porte aucune liste de rôles : il désigne une identité, et les rôles sont résolus côté serveur à chaque requête. Une révocation invalide immédiatement la résolution en cache, sans attendre l’expiration de son délai de fraîcheur déclaré |
la délégation | change la portée, jamais la capacité d’agir : agir au nom d’un joueur change quelles lignes sont visibles et à qui une écriture est attribuée, mais n’accorde aucune opération que l’acteur ne détenait pas déjà |
le verbe | dit quel type d’effet, et le prédicat dit quelles lignes. Si deux cas ne diffèrent que par à qui appartient la ligne, c’est un prédicat ; si l’effet lui-même diffère, c’est une autre opération et peut-être un autre verbe — c’est pourquoi « expulser » est administer et non write avec un prédicat large |
un propriétaire | se voit toujours lui-même, quoi que dise par ailleurs un prédicat |
la visibilité | n’est pas de la sécurité — une optimisation des canaux et une permission sont deux mécanismes différents, et aucun ne remplace l’autre |
un module désactivé | n’a aucune interface : l’activation d’un module est une propriété du build, donc la génération de code n’émet rien pour un module désactivé, et un appel indisponible est une erreur de compilation plutôt qu’un refus au runtime |
l'interface d'un module | suit l’acteur plutôt que le côté (voir Qui prend les décisions) : un build room-visitor voit rejoindre, quitter et lire, un build room-owner voit en plus expulser, fermer et configurer, et whoami indique quelles interfaces l’acteur courant débloque |
Qui attribue un rôle. Attribuer et révoquer un rôle à un joueur, ainsi que le rôle par défaut que le projet déclare pour un nouveau joueur, sont des opérations d’Auth & Players — ce module gère les identités, et un rôle se résout à partir de l’identité contenue dans l’identifiant.
Erreurs
- Ce qu’un prédicat masque répond
not found, pasforbidden: sinon, le refus lui-même révélerait à l’appelant que la chose existe. - Un droit que l’appelant ne détient pas répond
forbiddenlorsque l’existence de l’objet n’est pas un secret, et la réponse nomme ce qui manquait au lieu d’échouer sans explication. - Un champ hors du masque est absent de la réponse, et non présent mais vide : une valeur vide et une valeur masquée seraient impossibles à distinguer.
- Un rôle qui s’inclut lui-même, directement ou par une chaîne, est une erreur de configuration — refusé au moment de la déclaration plutôt que résolu au runtime.
- La délégation n’élargit jamais la capacité d’agir : un appel que l’acteur ne pourrait pas faire en son propre nom est refusé lorsqu’il est fait au nom d’un joueur.
Limites
Les chiffres arriveront avec le chapitre sur les limites de la plateforme ; ce qui se passe à chaque plafond est déjà fixé.
- La taille d’une sélection sous un prédicat de lignes est bornée, et le modèle d’ACL déclare cette borne au lieu de la découvrir. Une lecture qui dépasse le plafond reçoit autant de lignes que le plafond le permet, plus un marqueur indiquant la troncature — jamais une page raccourcie en silence.
- Le délai de fraîcheur d’une permission résolue est déclaré, et une révocation ne l’attend pas : elle invalide immédiatement.