Sicherheit
PowerPortalsPro bietet ein flexibles, codebasiertes Sicherheitsmodell, das es Ihnen ermöglicht, den Zugriff sowohl auf Tabellen- als auch auf Datensatzebene zu steuern. Sicherheit wird implementiert, indem Berechtigungshandler-Klassen erstellt und im Dependency Injection-Container registriert werden.
Überblick
Es gibt zwei Schnittstellen, die du je nach benötigtem Kontrollniveau implementieren kannst:
ITablePermissionHandler— Kontrolliert, ob ein Benutzer die Operationen Erstellen, Lesen, Aktualisieren, Löschen, Anhängen oder Anhängen auf einer gegebenen Tabelle ausführen kann. Entscheidungen basieren ausschließlich auf der Identität des Nutzers.ITableRecordPermissionHandler— ErweitertITablePermissionHandlersich um Datensatzebene-Überlastungen, die das tatsächlichTableRecordbearbeitete Werk empfangen, wodurch feingradige Regeln wie "Benutzer können nur ihre eigenen Datensätze bearbeiten" ermöglichen.
Tipp
Wenn Sie nur Kontrolle auf Tabellenebene benötigen (z. B. "authentifizierte Benutzer können diese Tabelle lesen"), verwenden Sie
ITablePermissionHandler. Wenn Sie Datensatz-Logik benötigen (z. B. "Benutzer können nur Datensätze löschen, die sie besitzen"), verwendenITableRecordPermissionHandlerSie .
Best Practice – an der Quelle filtern
Berechtigungshandler erzwingen den Zugriff, aber sie ersetzen das Filtern nicht. Datensatz-Handlers laufen, nachdem eine Seite mit Datensätzen aus Dataverse abgerufen wurde, daher verzerrt das Verstecken von Zeilen das Rasterpaging (eine Seite mit 50 kann weniger anzeigen) und die Gesamtanzahl und verschwendet Abfragen, die Zeilen abrufen, die dann verworfen werden. Wann immer Daten auf den aktuellen Benutzer beschränkt werden müssen, filtern Sie stattdessen auf Abfrageebene – mit einer
IFetchXmlQueryFilterInterceptor(siehe unten) oder einer benutzerdefinierten Ansicht, die den aktuellen Benutzer berücksichtigt – sodass Dataverse nur die Zeilen zurückgibt, die der Benutzer sehen sollte, und die Rasterseite genau anpasst. Behalte die Berechtigungsbearbeiter als deine Durchsetzungsgarantie.
Dataverse-Native Security (Vorschau)
Das empfohlene Sicherheitsmodell für die Zukunft ermöglicht es Dataverse selbst , die Sicherheit für Ihre Portalnutzer durchzusetzen, basierend auf der einheitlichen Sicherheitsfunktion von Power Pages. Anstatt Berechtigungshandler im Code zu schreiben, verwalten Sie den Zugriff mit Standard-Dataverse-Sicherheitsrollen – denselben Rollen, Privilegien und Admin-Tools wie für interne Benutzer. Das Modell befindet sich derzeit in der Vorschau – sowohl Microsofts Funktion als auch Power Portals Pros Unterstützung dafür – und Power Portals Pro wird Microsofts Veröffentlichungsplan zur allgemeinen Verfügbarkeit folgen.
Wenn aktiviert, stellt PowerPortalsPro automatisch ein systemverwaltetes (C2) systemuser für jeden Portalkontakt bereit – bei der Registrierung für neue Kontakte und faul bei der Anmeldung für bestehende – zusammen mit dem powerpagesusermapping Datensatz, der den Kontakt, den Systemnutzer und Ihre Power Pages-Website verknüpft. Jeder Dataverse-Aufruf für die Sitzung imitiert dann diesen Systembenutzer, sodass die Sicherheitsrollen, die Power Pages mit deinen Webrollen und Tabellenberechtigungen synchronisiert, genau bestimmen, was der Nutzer sehen und tun kann. Die Sitzung trägt ein PortalUserType Off ContactStubUser , sodass Ihre Benutzeroberfläche diese bereitgestellten Benutzer immer von echten internen Benutzern unterscheiden kann.
Anmerkung
Um dieses Sicherheitsmodell zu verwenden, müssen Sie eine Power Pages-Website in Ihrer Dataverse-Umgebung erstellen – diese installiert die Microsoft-Framework-Komponenten und ermöglicht es Ihnen, die unified-security-Funktion zu aktivieren. Sie benötigen keinen bezahlten Power Pages-Plan und können die Website direkt nach der Installation und Aktivierung der Funktion löschen. Der von von belegte
PowerPagesSiteIdPower Pages-Site-Eintrag (powerpagesite) muss in der Umgebung vorhanden sein, da Benutzerzuordnungen und Synchronisation mit Sicherheitsrollen gegen ihn aufgelöst werden.
Aktivierung von Dataverse-Native-Sicherheit
Aktivieren Sie das System SecurityOptions auf Ihrem Server Program.csund geben Sie die ID Ihres Power Pages-Website-Datensatzes an:
builder.Services.Configure<SecurityOptions>(options =>
{
options.DataverseSecurity.ProvisionSystemUsers = true;
options.DataverseSecurity.PowerPagesSiteId = new Guid("<your powerpagesite id>");
});
Die Konfiguration wird beim Anwendungsstart überprüft: Wenn ProvisionSystemUsers aktiviert ist, PowerPagesSiteId muss gesetzt und auf einen bestehenden powerpagesite Datensatz aufgelöst werden, andernfalls startet die Anwendung nicht mit der Leitung.
Gewähren Sie Zugriff genau so wie Power Pages: Konfigurieren Sie Webrollen und Tabellenberechtigungen in der Power Pages Management App und weisen Sie Webrollen Kontakten zu (über die Management-App oder programmatisch). Die Plattform synchronisiert diese in automatisch generierte Sicherheitsrollen (benannt PowerPages_*) und weist sie automatisch zu, wenn sich die Web-Mitgliedschaft ändert – da die Konfiguration vollständig in Power Pages liegt, kann Ihr Portal ohne Widerstand zu einer tatsächlichen Power-Pages-Seite wechseln oder von ihnen zurückgehen. Beachten Sie, dass Rollen- und Privilegienänderungen von wenigen Sekunden bis zu ein paar Minuten dauern können, um durch die Dataverse-Caches zu laufen, bevor sie wirksam werden.
Folgen Sie der Schritt-für-Schritt-Einrichtungsanleitung
Anfang
1. Einen Berechtigungshandler erstellen
Erstelle eine Klasse, die oder ITableRecordPermissionHandlerimplementiertITablePermissionHandler. Setze die Eigenschaft Table auf den logischen Namen der Dataverse-Tabelle, auf die der Handler angewendet wird, und implementiere jede Berechtigungsmethode.
Hier ist ein einfaches Beispiel, das allen Benutzern für eine bestimmte Tabelle vollen Zugriff gewährt:
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. Registrieren in der Abhängigkeitsinjektion
Registrieren Sie Ihren Handler im DI-Container in der Program.cs Datei Ihres Projekts (oder in einer Service Collection Extension Methode):
builder.Services.AddSingleton<ITablePermissionHandler, MyTablePermissionHandler>();
Da Handler aus dem DI-Container aufgelöst werden, können Sie alle erforderlichen Services (wie Datenbankkontext oder Benutzerdienst) per Konstruktor-Injektion injizieren.
Rekordsicherheit auf Rekordniveau
Für Szenarien, in denen der Zugriff von dem spezifischen Datensatz abhängt, der bearbeitet wird, implementieren ITableRecordPermissionHandlerSie . Diese Schnittstelle erstreckt ITablePermissionHandler sich mit Überlastungen, die das empfangen.TableRecord
Das folgende Beispiel erlaubt es allen Benutzern, Datensätze global zu lesen, beschränkt jedoch die Schreibvorgänge auf Datensätze, die dem aktuellen Benutzer gehören:
public class ContactPermissionHandler : ITableRecordPermissionHandler
{
public string Table => "contact";
public List<string> RequiredColumns => [];
// Tischniveau: Zugang allgemein erlauben
public Task<bool> CanReadAsync(Guid? userId) => Task.FromResult(true);
public Task<bool> CanCreateAsync(Guid? userId)
=> Task.FromResult(userId != null && userId != Guid.Empty);
// Datensatzebene: Beschränke Schreibvorgänge auf den Datensatzbesitzer
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);
}
// ... Implementiere die verbleibenden Methoden
}
Anmerkung
Die
CanUpdateAsyncDatensatzebene-Überladung stellt sowohl den Datensatz mit den vorgeschlagenen Aktualisierungen (recordWithUpdates) als auch den Datensatz im aktuell gespeicherten Zustand bereit (currentRecord), sodass Sie Werte vergleichen oder spezifische Feldänderungen validieren können.
Erforderliche Spalten
Die Eigenschaft RequiredColumns legt fest, welche Spalten abgerufen werden müssen, damit der Handler die Berechtigungen auf Datensatzebene auswerten kann. Das Framework fügt diese Spalten automatisch zu FetchXML-Abfragen hinzu, wenn sie noch nicht enthalten sind, sodass der Handler immer die benötigten Daten hat – selbst wenn die Ansicht oder Abfrage diese Spalten nicht enthält.
Das folgende Beispiel verwendet die ppp_owningportaluserid Nachschlagespalte, um den Zugriff auf Datensätze des aktuellen Benutzers einzuschränken:
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);
}
// ... Implementiere die verbleibenden Methoden
}
Anmerkung
Wenn
RequiredColumnseine leere Liste zurückgegeben wird und die Methoden auf Datensatzebene des Handlers nur verwendenrecord.Id, werden keine zusätzlichen Spalten zur Abfrage hinzugefügt. Die Eigenschaftownerwird immer von der Plattform bereitgestellt, unabhängig davon.RequiredColumns
FetchXML Abfragefilter-Interceptoren
Zusätzlich zur Sicherheit auf Tabellen- und Datensatzebene unterstützt IFetchXmlQueryFilterInterceptor PowerPortalsPro – eine Schnittstelle, mit der Sie FetchXML-Abfragen vor deren Ausführung anpassen können. Dies ist nützlich, um Zeilen-Filterung durchzusetzen, etwa indem sichergestellt wird, dass ein Benutzer nur Datensätze sehen kann, die ihm oder seinem Team gehören.
IFetchXmlQueryFilterInterceptorITablePermissionHandlererweitert , sodass jeder Abfanger auch tabellenbezogene Berechtigungen über die Table Eigenschaft und die Standardberechtigungsmethoden definiert. Die OnQueryAsync Methode des Interceptors wird nur für Abfragen aufgerufen, die auf die angegebene Tabelle abzielen.
Im Gegensatz zu Datensatz-Berechtigungshandlern, die jeden Datensatz nach dem Abruf evaluieren, ändern Abfragefilter-Interceptoren die Abfrage, bevor sie Dataverse erreicht. Herausgefilterte Datensätze werden nie abgerufen, was die Leistung und Sicherheit verbessert – und, was wichtig ist, die Raster-Auslagerung und die Gesamtzählung genau hält, da eine nach der Abruf angewandte Filterung Seiten zu kurz und Fehlzählungen führen würde.
1. Erstellen Sie einen Abfragefilter-Interceptor
Implementiere IFetchXmlQueryFilterInterceptor, setze die Table Eigenschaft auf den logischen Namen der Tabelle, implementiere die Berechtigungsmethoden und nutze die FetchXMLBuilder In, OnQueryAsync um Filter zur Abfrage hinzuzufügen. Der FetchXmlParameters Datensatz liefert das aktuelle Benutzer.EntityReference
FetchXMLBuilder
Das folgende Beispiel beschränkt Abfragen in der contact Tabelle darauf, nur den Datensatz zurückzugeben, der dem aktuellen Benutzer entspricht:
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. Registrieren in der Abhängigkeitsinjektion
Registrieren Sie Ihren Interceptor sowohl als ein IFetchXmlQueryFilterInterceptor als auch als an ITablePermissionHandler , damit sowohl die Abfragefilterung als auch die Berechtigungen auf Tabellenebene angewendet werden. Mehrere Abfangsender können registriert werden, und jeder wird für Abfragen ausgeführt, die auf seine Tabelle abzielen.
builder.Services.AddTransient<ContactQueryFilterInterceptor>();
builder.Services.AddTransient<ITablePermissionHandler>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
builder.Services.AddTransient<IFetchXmlQueryFilterInterceptor>(
sp => sp.GetRequiredService<ContactQueryFilterInterceptor>());
Tipp
Da
IFetchXmlQueryFilterInterceptorerweitertITablePermissionHandler, übernimmt eine einzelne Klasse sowohl Berechtigungen auf Tabellenebene als auch Abfragefilterung. Die MethodeOnQueryAsyncwird nur für Abfragen aufgerufen, die mit derTableEigenschaft des Interceptors übereinstimmen, sodass der Tabellenname innerhalb der Methode nicht überprüft werden muss.
Eingebaute Basisklassen
Das Framework stellt Basisklassen bereit, um gängige Sicherheitsmuster zu vereinfachen:
AnonymousPermissionHandler— Gewährt alle Operationen allen Nutzern (einschließlich Anonymen). Nützlich für öffentlich zugängliche Tabellen. Einfach erben und setzen Sie die EigenschaftTable.
API-Referenz
ITablePermissionHandler Interface
Eigenschaften
Name | Typ | Default | Beschreibung |
|---|---|---|---|
Table | string | Logischer Name der Tabelle, auf die dieser Berechtigungshandler angewendet wird. |
TableMethoden
Name | Parameter | Typ | Beschreibung |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer einen Datensatz anhängen kann. |
CanAppendToAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer in der Lage sein sollte, einen Datensatz anzuhängen. |
CanCreateAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer in der Lage sein sollte, einen Datensatz zu erstellen. |
CanDeleteAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer einen Datensatz löschen kann. |
CanReadAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer einen Datensatz lesen kann. |
CanUpdateAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer einen Datensatz aktualisieren kann. |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncITableRecordPermissionHandler Interface
Eigenschaften
Name | Typ | Default | Beschreibung |
|---|---|---|---|
RequiredColumns | List<string> | Liste von logischen Spaltennamen, die für diesen Handler abgerufen werden müssen, um Datensatzberechtigungen zu bewerten. | |
Table | string | Logischer Name der Tabelle, auf die dieser Berechtigungshandler angewendet wird. |
RequiredColumnsTableMethoden
Name | Parameter | Typ | Beschreibung |
|---|---|---|---|
CanAppendAsync | Guid? userId TableRecord record | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer den bereitgestellten Datensatz anhängen kann. |
CanAppendToAsync | Guid? userId TableRecord record | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer in der Lage sein sollte, den bereitgestellten Datensatz anzuhängen. |
CanCreateAsync | Guid? userId TableRecord record | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer den bereitgestellten Datensatz erstellen kann. |
CanDeleteAsync | Guid? userId TableRecord record | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer den bereitgestellten Datensatz löschen kann. |
CanReadAsync | Guid? userId TableRecord record | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer den bereitgestellten Datensatz lesen kann. |
CanUpdateAsync | Guid? userId TableRecord recordWithUpdates TableRecord currentRecord | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer den bereitgestellten Datensatz aktualisieren kann. |
CanAppendAsyncTableRecord record
CanAppendToAsyncTableRecord record
CanCreateAsyncTableRecord record
CanDeleteAsyncTableRecord record
CanReadAsyncTableRecord record
CanUpdateAsyncTableRecord recordWithUpdates
TableRecord currentRecord
IFetchXmlQueryFilterInterceptor Interface
Eigenschaften
Name | Typ | Default | Beschreibung |
|---|---|---|---|
Table | string | Logischer Name der Tabelle, auf die dieser Berechtigungshandler angewendet wird. |
TableMethoden
Name | Parameter | Typ | Beschreibung |
|---|---|---|---|
CanAppendAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer einen Datensatz anhängen kann. |
CanAppendToAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer in der Lage sein sollte, einen Datensatz anzuhängen. |
CanCreateAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer in der Lage sein sollte, einen Datensatz zu erstellen. |
CanDeleteAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer einen Datensatz löschen kann. |
CanReadAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer einen Datensatz lesen kann. |
CanUpdateAsync | Guid? userId | Task<bool> | Methode, um zu bestimmen, ob ein Benutzer einen Datensatz aktualisieren kann. |
OnQueryAsync | FetchXMLBuilder fetchXmlBuilder FetchXmlParameters parameters | Task<FetchXMLBuilder> | Aufruft, um die Änderung einer FetchXML-Abfrage vor deren Ausführung zu ermöglichen. |
CanAppendAsyncCanAppendToAsyncCanCreateAsyncCanDeleteAsyncCanReadAsyncCanUpdateAsyncOnQueryAsyncFetchXmlParameters parameters
