Rechte auf Datentabelle prüfen

Für die Entwicklung eigener Steuerelemente kann es vor allem für Ebene-1-Prüfungen nötig sein, zu prüfen, ob der Benutzer Datensätze einer Tabelle grundsätzlich benutzen darf. Dazu dient die Funktion

bool Api.User.CurrentUserIsAllowed(Guid dataSourceId, EnumTableOperations action).

Der Parameter dataSourceId ist die Datentabelle, für die die Berechtigung geprüft werden soll. Wenn hier eine Basis-Datentabelle angegeben wird, ist die Antwort immer „ja“.
Der Parameter action definiert, welche Berechtigung abgefragt werden soll (Create, Read, Edit, Delete). Implizite Berechtigungen werden dabei immer automatisch mit geprüft. Also wenn man Delete abfragt, bekommt man auch dann „nein“ als Antwort, wenn der User zwar Datensätze dieses Typs löschen dürfte, diese aber nicht bearbeiten darf (Edit), was zum Löschen erforderlich ist.

Beispiele:

bool userCanCreate = Api.User.CurrentUserIsAllowed(EnumDataSource.Email, EnumTableOperations.Create);

Es können auch mehrere Berechtigungen auf einmal geprüft werden:

bool userCanCreateAndEdit = Api.User.CurrentUserIsAllowed(EnumDataSource.Company, EnumTableOperations.Create | EnumTableOperations.Edit);

Das liefert nur dann „ja“, wenn der Benutzer Firmen anlegen und danach auch wieder editieren darf.

Datensatz-Rechte prüfen

Beim Implementieren von Aktionen oder Hintergrundprozessen (Ebene 2) muss man evtl. die Berechtigungen für einzelne Datensätze explizit prüfen. Dazu dient die Funktion

bool OrmBABase.IsAllowed(EnumTableOperations action).

Der Parameter action definiert, welche Berechtigung abgefragt werden soll (Read, Edit, Delete). Create ergibt an dieser Stelle keinen Sinn, siehe unten.
Die Berechtigungsprüfung ist sehr umfänglich und berücksichtigt alle direkten oder indirekten Abhängigkeiten. Diese sind

  1. Hat der Benutzer überhaupt Rechte auf die Datentabelle? (Basis-Rechte)
  2. Darf der Benutzer die jeweilige Aktion (Lesen oder Bearbeiten) für genau diesen Datensatz? (Falls er nicht bereits hinreichende Rechte für den Datentyp hat.) Es wird also ggf. geprüft, ob er unter den Lesern oder Autor/Bearbeitern ist.
  3. Benötigt der Benutzer für das angeforderte Recht weitere Rechte? Also z.B. Delete impliziert Edit und Read, und die beiden letzteren müssen ggf. auch für diesen Datensatz vorhanden sein.
  4. Handelt es sich um einen neuen, noch nicht gespeicherten Datensatz? In diesem Fall wird die Create-Berechtigung benötigt, wenn man Edit abfragt. Ein neuer Datensatz ist ein Datensatz, der nie gespeichert wurde.

Beispiele:
Leserechte auf einen Datensatz explizit prüfen: (Das macht BA nicht automatisch.)

OrmBABase orm = …;
if (!orm.IsAllowed(EnumTableOperations.Read))
    // Zugriff verweigert
Darf ich diesen Datensatz bearbeiten?
OrmBABase orm = …;
if (!orm.IsAllowed(EnumTableOperations.Edit))
    // Zugriff verweigert

Diese Prüfung funktioniert auch für neue Datensätze, die gerade angelegt werden. In diesem Fall wird nur die Create-Berechtigung für den Datentyp geprüft.

Leserechte in Datenprovider / Abfragen

Bei GetQuery()

Die API-Funktion Api.ORM.GetQuery liefert immer alle Datensätze unabhängig von den Leserechten. Wenn man die Leserechte berücksichtigen möchte, muss man das explizit tun. Dazu gibt es die Funktionen

IQueryable<T> Api.ORM.GetQueryWithReadPermissions<T>(Session session, IEnumerable<Type> dataSources = null) where T : OrmBABase
IQueryable<OrmBABase> Api.ORM.GetQueryWithReadPermissions(Guid type, Session session, IEnumerable<Type> dataSources = null)
IQueryable<OrmBABase> Api.ORM.GetQueryWithReadPermissions(Type ormType, Session session, IEnumerable<Type> dataSources = null)

Der Parameter T, type bzw. ormType gibt den gewünschten Datentyp an.
Der optionale Parameter dataSources kann (und sollte) benutzt werden, wenn eine Abfrage auf einen Basis-Datentabelle wie Standard (OrmBABase) oder Basis.Vorgang erfolgt, aber nicht alle potentiellen Datentypen gewünscht sind. Das kann beispielsweise vorkommen, wenn in einer kombinierten Ansicht einige einzelne Datentypen explizit ausgewählt sind.
Wenn der Parameter angegeben wird, wird die Ergebnismenge automatisch auf die entsprechenden Datentabellen eingeschränkt. Gleichzeitig kann die Prüfung der Leserechte für alle anderen Datentabellen entfallen. Das ist performanter.

Beispiele:
Im einfachsten Fall braucht man nur die Datensätze einer Datentabelle unter Berücksichtigung der Leserechte. Das kann auch eine Basis-Datentabelle sein.

IQueryable<OrmActivityBase> emailAddresses = Api.ORM.GetQueryWithReadPermissions<OrmActivityBase>(session);

Wenn der gewünschte Datentyp dynamisch aus der Konfiguration kommt:

IQueryable<OrmBABase> records = Api.ORM.GetQueryWithReadPermissions(wantedDataSourceGuid, session);

Wenn mehrere Datentypen dynamisch aus der Konfiguration kommen:

IEnumerable<Guid> configuredTypeGuids = …;
IEnumerable<Type> types = configuredTypeGuids.Select(ff => Api.ORM.GetOrmTypeCacheValue(ff).Type);
IQueryable<OrmBABase> records = Api.ORM.GetQueryWithReadPermissions<OrmBABase>(session, types)

Bei IQueryable<>

Falls man bereits eine fertige Query hat und die Prüfung der Leserechte nur nachträglich hinzufügen möchte:

IQueryable<T> Api.ORM.ApplyReadPermissions<T>(IQueryable<T> query, Session session, IEnumerable<Type> dataSources = null) where T : OrmBABase

Die Funktion verhält sich identisch wie GetQueryWithReadPermissions, nur dass sie auf einer bestehenden Query aufsetzt und auf lesbare Sätze sowie optional gewünschte Datentypen einschränkt.

Beispiel:
Um die Leserechte optional hinzuzufügen geht man so vor:

IQueryable<T> query = Api.ORM.GetQuery<T>(session).Where(ff => …);
if (readPermissions)
    query = Api.ORM.ApplyReadPermissions<T>(query, session);

Bei Formeln (CriteriaOperator)

In manchen Fällen benötigt man die Einschränkung für die Leserechte als Criteria Operator und nicht als LINQ-Expression. Dafür gibt es die Funktion

CriteriaOperator Api.ORM.ApplyReadPermissions(CriteriaOperator criteria, Type queryOrmType, GuidSet sourceTypes = null)

Diese Verhält sich analog zu ApplyReadPermissions<T> nur, dass hier ein bestehender Criteria Operator, der vom Typ Boolean sein muss, so modifiziert wird, dass die Leserechte berücksichtigt werden.
Der Parameter queryOrmType ist der Typ der Datensätze, auf die der Criteria Operator angewendet werden soll. Das kann eine Basis-Datentabelle sein, aber dann müssen die Rechte für alle davon erbenden Typen geprüft werden, falls dies nicht durch den Parameter sourceTypes weiter eingeschränkt wird.

Beispiel:

filterCriteria = Api.ORM.ApplyReadPermissions(filterCriteria, dataSourceType);

Benutzerrechte des aktuellen Benutzer prüfen

Um zu prüfen, ob der aktuelle Benutzer Mitglied einer oder mehrerer Rollen ist, gibt es die Funktionen

bool Api.User.CurrentUserIsInRole(Guid role, bool defaultAllowed = false)
bool Api.User.CurrentUserIsInRole(IReadOnlyCollection<Guid> roles, bool defaultAllowed = false)

Erstere prüft, ob der Benutzer genau eine Rolle hat, zweitere prüft, ob der User mindestens eine Rolle aus einer Menge hat.
Der Parameter defaultAllowed gibt an, wie sich die Funktionen verhalten sollen, wenn Guid.Empty bzw. gar keine Rolle oder null übergeben wird. Mit true kann man erreichen, dass in diesem Fall jeder Benutzer berechtigt sein soll.

Beispiele:

bool currentUserCanEdit = Api.User.CurrentUserIsInRole(EditorRoles, true);

In diesem Fall darf jeder bearbeiten, wenn EditorRoles leer ist.
Falls man die Rollen nur als String mit GUIDs zur Verfügung hat, kann man RoleSet.Parse verwenden.

bool currentUserCanEdit = Api.User.CurrentUserIsInRole(RoleSet.Parse(EditorRolesString), true);

Die Funktion RoleSet.Parse kann auch mit leeren Strings und null umgehen. Ferner ist die Groß-Kleinschreibung egal, ebenso wie das Trennzeichen.

Effektive Berechtigungen eines beliebigen Benutzers

Um alle effektiven Berechtigungen eines beliebigen Benutzers (oder auch einer beliebigen Rolle) schnell abzufragen gibt es eine API-Funktion. Diese liefert ein Set aller Rollen-Guids, die ein Benuutzer effektiv, also auch inklusive geerbter Rollen hat. Das Ergebnis enthält immer auch den Benutzer selbst sowie die Everyone-Rolle.

if (!Api.User.GetEffectivePermissions(userProfileGuidToCheck).Overlaps(control.AllowedRoles))
    // Permission denied

Der Aufruf von GetEffectivePermissions(Guid) kann manchmal einen Datenbankzugriff auslösen. Die Methode sollte daher nicht in einer Schleife aufgrufen werden. In diesem Fall ist die Überladung GetEffectivePermissions(IReadOnlyCollection<Guid>) zu bevorzugen, die dasselbe mit maximal einem DB-Zugriff erledigt.

Eigene Rolle

Projekte können auch komplett eigene Rollen-Datentabellen anlegen. Dazu muss eine Datentabelle angelegt werden (wahlweise mit XPO-Designer), die von OrmRoleBase (Basis.Rolle) erbt.
Sobald Objekte dieser Klasse existieren, erscheinen diese Objekte auch in der Rollenverwaltung. Beim Doppelklick auf eine Zeile dieses Typs wird die Standardmaske laut Konfiguration geöffnet.
Des Weiteren können die Objekte auch an allen Stellen der Anwendung, wo Berechtigungen vergeben werden können, zugewiesen werden. Das sind

  • die Rollen eines Benutzerzugangs,
  • die Mitglieder von Rollen,
  • Berechtigungen in Datentypkonfigurationen,
  • Berechtigungen für Masken- und Ansichtensteuerelemente,
  • Leser und Bearbeiter von Datensätzen und
  • Berechtigungen für Auswahllistenwerte.

Bei Berechtigungen, die in Masken gepflegt werden, ist zu beachten, dass diese üblicherweise eine Liste von Ansichten hinterlegt haben, die für die Berechtigungsauswahl zur Verfügung stehen. Darin ist nicht unbedingt die projektspezifische Datentabelle enthalten. Die Maskenkonfigurationen müssen daher angepasst werden, um auch die neue Tabelle zu erlauben.

Berechtigungsprüfung für eigenen Datentyp überschreiben

In Einzelfällen kann es erforderlich sein, für einen Datensatz eine abweichende Berechtigungsprüfung zu implementieren. Zu diesem Zweck kann die Methode IsAllowed für eigene Datentabellen oder erweiterte Datentabellen überschrieben werden.
Eine solche Überschreibung ersetzt alle Berechtigungsprüfungen des Core auf Einzeldatensatzebene und nur diese. Die Maßnahme greift sehr tief in den Core ein und sollte daher mit Vorsicht eingesetzt werden. Wenn man die Methode überschreibt, muss man sich auch um die Berechtigungsprüfung auf Datentabellenebene kümmern.

Beispiel:

public override bool IsAllowed(EnumTableOperations action)
{
    Guid userGuid = Api.User.GetUserGuidByProfile(this);
    // System user profile should not be deleted
    if ((action & EnumTableOperations.Delete) != 0 &&
       (userGuid == Constants.SystemUser || userGuid == Api.User.CurrentUserGuid()))
        return false;
    if (userGuid == Api.User.CurrentUserGuid())
        action &= ~EnumTableOperations.Edit;
    return base.IsAllowed(action);
}

Im obigem Beispiel wird dem aktuellen Benutzer ein unbedingtes Bearbeiten-Recht auf das eigene Benutzerprofil eingeräumt. Darüber hinaus wird das Löschen des System-Users und des eigenen Benutzerprofils unterbunden.
Letzteres wird durch das erste if erreicht. Letzteres dadurch, dass der Prüfgrund Edit aus action entfernt wird, wenn es das eigene Benutzerprofil ist.
Action ist ein sogenanntes Flags-Enum, dass mehrere Werte gleichzeitig annehmen kann (Siehe).