Sécurité
PowerPortalsPro propose un modèle de sécurité flexible piloté par le code qui vous permet de contrôler l’accès à la fois au niveau de la table et du niveau des enregistrements. La sécurité est mise en œuvre en créant des classes de gestionnaire de permissions et en les enregistrant dans le conteneur d’injection de dépendances.
Aperçu
Il existe deux interfaces que vous pouvez implémenter selon le niveau de contrôle dont vous avez besoin :
ITablePermissionHandler— Contrôle si un utilisateur peut effectuer des opérations Créer, Lire, Mettre à jour, Supprimer, Ajouter ou Ajouter sur une table donnée. Les décisions sont basées uniquement sur l’identité de l’utilisateur.ITableRecordPermissionHandler— S’étendITablePermissionHandleravec les surcharges au niveau des enregistrements qui reçoivent l’opération réelleTableRecord, permettant des règles précises telles que « les utilisateurs ne peuvent modifier que leurs propres enregistrements ».
Conseil
Si vous n’avez besoin que de vérifications au niveau de la table (par exemple « les utilisateurs authentifiés peuvent lire ce tableau »), utilisez
ITablePermissionHandler. Si vous avez besoin de logique au niveau des enregistrements (par exemple « les utilisateurs ne peuvent supprimer que les enregistrements qu’ils possèdent »), utilisezITableRecordPermissionHandler.
Bonne pratique — filtrer à la source
Les gestionnaires d’autorisations imposent l’accès, mais ils ne remplacent pas le filtrage. Les gestionnaires au niveau des enregistrements s’exécutent après qu’une page d’enregistrements a été récupérée depuis Dataverse, donc compter sur eux pour masquer les lignes déforme la pagination de la grille (une page de 50 peut en montrer moins) et le nombre total, et gaspille des requêtes en récupérant des lignes qui sont ensuite rejetées. Chaque fois que des données doivent être attribuées à l’utilisateur actuel, filtrez au niveau de la requête — avec une
IFetchXmlQueryFilterInterceptorvue (voir ci-dessous) ou personnalisée qui tient compte de l’utilisateur actuel — afin que Dataverse ne rende que les lignes que l’utilisateur doit voir et que la page grille correctement. Gardez les responsables des permissions comme garantie d’application.
Dataverse-Native Security (Aperçu)
Le modèle de sécurité recommandé pour la suite permet à Dataverse lui-même d’assurer la sécurité pour les utilisateurs de votre portail, basé sur la fonctionnalité de sécurité unifiée Dataverse de Power Pages. Au lieu d’écrire des gestionnaires de permissions en code, vous gérez l’accès avec des rôles de sécurité standards Dataverse — les mêmes rôles, privilèges et outils administratifs utilisés pour les utilisateurs internes. Le modèle est actuellement en version preview — à la fois la fonctionnalité de Microsoft et le support de Power Portals Pro — et Power Portals Pro suivra le calendrier de sortie de Microsoft jusqu’à la disponibilité générale.
Une fois activé, PowerPortalsPro fournit automatiquement un système géré (C2) systemuser pour chaque contact portail — à l’enregistrement pour les nouveaux contacts, et paresseusement à la connexion pour les existants — ainsi que l’enregistrement powerpagesusermapping qui relie le contact, l’utilisateur système et votre site Power Pages. Chaque appel Dataverse pour la session se fait alors passer pour cet utilisateur système, de sorte que les rôles de sécurité que Power Pages synchronise à partir de vos rôles web et vos permissions de table régissent exactement ce que l’utilisateur peut voir et faire. La session comporte un PortalUserType de ContactStubUser afin que votre interface puisse toujours distinguer ces utilisateurs provisionnés des utilisateurs internes réels.
Note
Pour utiliser ce modèle de sécurité, vous devez créer un site web Power Pages dans votre environnement Dataverse — c’est ce qui installe les composants du cadre Microsoft et permet d’activer la fonctionnalité de sécurité unifiée. Vous n’avez pas besoin d’un forfait Power Pages payant, et vous pouvez supprimer le site web lui-même immédiatement après avoir installé et activé la fonctionnalité. L’enregistrement de site Power Pages (
powerpagesite) référencé parPowerPagesSiteIddoit exister dans l’environnement, car les mappages utilisateur et la synchronisation sécurité-rôle se résolvent contre celui-ci.
Activation de la sécurité Dataverse Native
Activez le modèle dans SecurityOptions le fichier de Program.csvotre serveur , en fournissant l’identifiant de votre site Power Pages :
builder.Services.Configure<SecurityOptions>(options =>
{
options.DataverseSecurity.ProvisionSystemUsers = true;
options.DataverseSecurity.PowerPagesSiteId = new Guid("<your powerpagesite id>");
});
La configuration est validée au démarrage de l’application : lorsqu’elle ProvisionSystemUsers est activée, PowerPagesSiteId elle doit être définie et doit se résoudre à un enregistrement existant powerpagesite , sinon l’application ne démarre pas avec des instructions.
Accordez l’accès exactement comme le fait Power Pages : configurez les rôles web et les permissions de table dans l’application Power Pages Management, et attribuez des rôles web aux contacts (via l’application Management ou de manière programmatique). La plateforme synchronise ces rôles en rôles de sécurité générés automatiquement (nommés PowerPages_*) et les attribue automatiquement au fur et à mesure que l’adhésion au rôle web change — comme la configuration est entièrement dans Power Pages, votre portail peut se déplacer vers ou depuis un site Power Pages réel sans aucune friction. Notez que les changements de rôle et de privilège peuvent prendre de quelques secondes à quelques minutes pour se propager dans les caches de Dataverse avant d’entrer en vigueur.
Suivez le guide de configuration étape par étape
Commencer
1. Créer un gestionnaire de permissions
Créez une classe qui implémente ITablePermissionHandler ou ITableRecordPermissionHandler. Définissez la Table propriété au nom logique de la table Dataverse à laquelle le gestionnaire s’applique, et implémentez chaque méthode d’autorisation.
Voici un exemple simple qui accorde un accès complet à tous les utilisateurs pour une table spécifique :
public class MyTablePermissionHandler : ITablePermissionHandler
{
public string Table => "my_customtable";
public Task<bool> CanCreateAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanUpdateAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanDeleteAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanAppendAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanAppendToAsync(Guid? userId) => Task.FromResult(true);
}
2. Enregistrer dans l’injection de dépendance
Enregistrez votre gestionnaire dans le conteneur DI du Program.cs fichier de votre projet (ou une méthode d’extension de collection de services) :
builder.Services.AddSingleton<ITablePermissionHandler, MyTablePermissionHandler>();
Puisque les gestionnaires sont résolus depuis le conteneur DI, vous pouvez injecter tous les services nécessaires (comme un contexte de base de données ou un service utilisateur) via l’injection de constructeurs.
Sécurité au niveau des enregistrements
Pour les scénarios où l’accès dépend de l’enregistrement spécifique sur lequel on travaille, implémentez ITableRecordPermissionHandler. Cette interface s’étend ITablePermissionHandler avec les surcharges qui reçoivent le TableRecord.
L’exemple suivant permet à tous les utilisateurs de lire les enregistrements globalement, mais limite les opérations d’écriture aux enregistrements appartenant à l’utilisateur actuel :
public class ContactPermissionHandler : ITableRecordPermissionHandler
{
public string Table => "contact";
public List<string> RequiredColumns => [];
// Niveau table : permettre un accès large
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanCreateAsync(Guid? userId)
=> Task.FromResult(userId != null && userId != Guid.Empty);
// Niveau enregistrement : restreindre les écritures au propriétaire du disque
public Task<bool> CanUpdateAsync(
Guid? userId, TableRecord recordWithUpdates, TableRecord currentRecord)
{
return Task.FromResult(userId == currentRecord.Id);
}
public Task<bool> CanDeleteAsync(Guid? userId, TableRecord record)
{
return Task.FromResult(userId == record.Id);
}
// ... Implémenter les méthodes restantes
}
Note
La
CanUpdateAsyncsurcharge au niveau de l’enregistrement fournit à la fois à l’enregistrement les mises à jour proposées (recordWithUpdates) et à l’enregistrement dans son état actuellement persisté (currentRecord), ce qui vous permet de comparer des valeurs ou de valider des modifications spécifiques du champ.
Colonnes requises
La RequiredColumns propriété spécifie quelles colonnes doivent être récupérées pour que le gestionnaire puisse évaluer les permissions au niveau des enregistrements. Le framework ajoute automatiquement ces colonnes aux requêtes FetchXML lorsqu’elles ne sont pas déjà incluses, garantissant que le gestionnaire dispose toujours des données dont il a besoin — même si la vue ou la requête n’inclut pas ces colonnes.
L’exemple suivant utilise la ppp_owningportaluserid colonne de recherche pour restreindre l’accès aux enregistrements appartenant à l’utilisateur actuel :
public class RegionPermissionHandler : ITableRecordPermissionHandler
{
public string Table => "ppp_region";
public List<string> RequiredColumns => ["ppp_owningportaluserid"];
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanCreateAsync(Guid? userId) => Task.FromResult(userId != null);
public Task<bool> CanReadAsync(Guid? userId, TableRecord record)
{
return Task.FromResult(
record.GetValueOrDefault<LookupValue>("ppp_owningportaluserid")?.Value == userId);
}
// ... Implémenter les méthodes restantes
}
Note
Si
RequiredColumnsretourne une liste vide et que les méthodes au niveau de l’enregistrement du gestionnaire n’utilisentrecord.Idque , aucune colonne supplémentaire n’est ajoutée à la requête. Laownerpropriété est toujours fournie par la plateforme, quel que soitRequiredColumnsle facteur .
Intercepteurs de filtres de requête FetchXML
En plus de la sécurité au niveau des tables et des enregistrements, PowerPortalsPro prend en charge IFetchXmlQueryFilterInterceptor — une interface qui vous permet de modifier les requêtes FetchXML avant leur exécution. Cela est utile pour imposer un filtrage au niveau des lignes, par exemple pour s’assurer qu’un utilisateur ne puisse voir que les enregistrements qui lui appartiennent ou qui lui appartiennent à son équipe.
IFetchXmlQueryFilterInterceptor étend ITablePermissionHandler, donc chaque intercepteur définit également des permissions au niveau de la table via la Table propriété et les méthodes d’autorisation standard. La méthode de l’intercepteur OnQueryAsync n’est appelée que pour les requêtes qui ciblent sa table spécifiée.
Contrairement aux gestionnaires de permissions au niveau des enregistrements qui évaluent chaque enregistrement après récupération, les intercepteurs filtres de requête modifient la requête avant qu’elle n’atteigne Dataverse. Les enregistrements filtrés ne sont jamais récupérés, ce qui améliore les performances et la sécurité — et, surtout, permet de garder la pagination en grille et les comptages totaux précis, car le filtrage appliqué après la récupération laisserait les pages courtes et ferait mal compter les totaux de la situation.
1. Créer un intercepteur de filtre de requête
Implémentez IFetchXmlQueryFilterInterceptor, définissez la Table propriété au nom logique de la table, implémentez les méthodes de permission, et utilisez l’in FetchXMLBuilder OnQueryAsync pour ajouter des filtres à la requête. L’enregistrement FetchXmlParameters fournit le EntityReferencefichier .
FetchXMLBuilder
L’exemple suivant restreint les requêtes sur la contact table pour ne retourner que l’enregistrement correspondant à l’utilisateur actuel :
public class ContactQueryFilterInterceptor : IFetchXmlQueryFilterInterceptor
{
public string Table => "contact";
public Task<bool> CanCreateAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanUpdateAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanDeleteAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanAppendAsync(Guid? userId) => Task.FromResult(false);
public Task<bool> CanAppendToAsync(Guid? userId) => Task.FromResult(false);
public Task<FetchXMLBuilder> OnQueryAsync(
FetchXMLBuilder fetchXmlBuilder, FetchXmlParameters parameters)
{
var filter = fetchXmlBuilder.Fetch.Entity.AddFilter();
var condition = filter.AddCondition();
condition.Column = "contactid";
condition.Operator = ConditionOperator.Equal;
condition.Value = parameters.CurrentUser.Id.ToString();
return Task.FromResult(fetchXmlBuilder);
}
}
2. Enregistrer dans l’injection de dépendance
Enregistrez votre intercepteur à la fois comme an IFetchXmlQueryFilterInterceptor et an ITablePermissionHandler afin que les permissions de filtrage des requêtes et de niveau tableau soient appliquées. Plusieurs intercepteurs peuvent être enregistrés et chacun sera exécuté pour des requêtes ciblant sa table.
builder.Services.AddTransient<ContactQueryFilterInterceptor>();
builder.Services.AddTransient<ITablePermissionHandler>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
builder.Services.AddTransient<IFetchXmlQueryFilterInterceptor>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
Conseil
Parce que
IFetchXmlQueryFilterInterceptorétendITablePermissionHandler, une seule classe gère à la fois les permissions au niveau de la table et le filtrage au niveau requête. LaOnQueryAsyncméthode n’est invoquée que pour les requêtes correspondant à la propriété de l’intercepteurTable, il n’est donc pas nécessaire de vérifier le nom de la table à l’intérieur de la méthode.
Classes de base intégrées
Le cadre fournit des classes de base pour simplifier les schémas de sécurité courants :
AnonymousPermissionHandler— Accorde toutes les opérations à tous les utilisateurs (y compris anonymes). Utile pour les tables accessibles au public. Il suffit d’hériter et de poser laTablepropriété.
Référence API
ITablePermissionHandler Interface
Propriétés
Nom | Type | Par défaut | Description |
|---|---|---|---|
Table | string | Nom logique de la table à laquelle ce gestionnaire de permissions s’applique. |
TableMéthodes
Nom | Paramètres | Type | Description |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir ajouter un enregistrement. |
CanAppendToAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur devrait pouvoir ajouter à un enregistrement. |
CanCreateAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir créer un enregistrement. |
CanDeleteAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir supprimer un enregistrement. |
CanReadAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir lire un enregistrement. |
CanUpdateAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir mettre à jour un enregistrement. |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncITableRecordPermissionHandler Interface
Propriétés
Nom | Type | Par défaut | Description |
|---|---|---|---|
RequiredColumns | List<string> | Liste des noms logiques de colonnes à récupérer pour que ce gestionnaire puisse évaluer les permissions au niveau de l’enregistrement. | |
Table | string | Nom logique de la table à laquelle ce gestionnaire de permissions s’applique. |
RequiredColumnsTableMéthodes
Nom | Paramètres | Type | Description |
|---|---|---|---|
CanAppendAsync | Guid? userId TableRecord record | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir ajouter l’enregistrement fourni. |
CanAppendToAsync | Guid? userId TableRecord record | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir ajouter à l’enregistrement fourni. |
CanCreateAsync | Guid? userId TableRecord record | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir créer l’enregistrement fourni. |
CanDeleteAsync | Guid? userId TableRecord record | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir supprimer l’enregistrement fourni. |
CanReadAsync | Guid? userId TableRecord record | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir lire l’enregistrement fourni. |
CanUpdateAsync | Guid? userId TableRecord recordWithUpdates TableRecord currentRecord | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir mettre à jour l’enregistrement fourni. |
CanAppendAsyncTableRecord record
CanAppendToAsyncTableRecord record
CanCreateAsyncTableRecord record
CanDeleteAsyncTableRecord record
CanReadAsyncTableRecord record
CanUpdateAsyncTableRecord recordWithUpdates
TableRecord currentRecord
IFetchXmlQueryFilterInterceptor Interface
Propriétés
Nom | Type | Par défaut | Description |
|---|---|---|---|
Table | string | Nom logique de la table à laquelle ce gestionnaire de permissions s’applique. |
TableMéthodes
Nom | Paramètres | Type | Description |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir ajouter un enregistrement. |
CanAppendToAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur devrait pouvoir ajouter à un enregistrement. |
CanCreateAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir créer un enregistrement. |
CanDeleteAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir supprimer un enregistrement. |
CanReadAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir lire un enregistrement. |
CanUpdateAsync | Guid? userId | Task<bool> | Méthode pour déterminer si un utilisateur doit pouvoir mettre à jour un enregistrement. |
OnQueryAsync | FetchXMLBuilder fetchXmlBuilder FetchXmlParameters parameters | Task<FetchXMLBuilder> | Appelé pour permettre la modification d’une requête FetchXML avant son exécution. |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncOnQueryAsyncFetchXmlParameters parameters
