SharePoint 2013 – Custom Claim Based Permissions Teil 1

09. Dezember 2015

Da sich mir erstmalig die Herausforderung stellte Daten aus einem Content Management System, welches über eigenes Permission-Handling verfügt in den SharePoint Index zu bekommen, habe ich etwas Nachforschung betrieben und einen Prototypen entwickelt.

Die Ausgangssituation für meinen sehr simplen Prototyp habe ich Sveinar Rasmussen zu verdanken, dieser hat sich kritisch in 2 Posts mit Thema auseinandergesetzt:

Creating Custom Connector Sending Claims with SharePoint 2013

Creating a Custom Pre-Security Trimmer for SharePoint 2013

In diesen Posts wird darüber berichtet, wie man einen eigenen Connector und einen Pre-Security Trimmer implementiert und dabei eigene Claims vergibt, absolut gelungene Posts, von denen ich viel lernen konnte! Ein paar offene Fragen hatte ich nach dem Lesen dennoch, deswegen möchte ich diesen Post nutzen, um eine Zusammenfassung über die wichtigsten technischen Aspekte zu liefern und um außerdem für mich damals bis dato ungelöste Fragen zu beantworten. Da die genannten Artikel schon sehr ausführlich sind werde ich nur das wichtigste zusammenfassen, und auf die Kernelemente beschränken und meine eigenen Erkenntnisse ergänzen, so dass jeder Leser im Anschluss dazu in der Lage sein sollte, seine eigenen Custom Claim based Permissions in den SharePoint Index zu bekommen, und diese auch wieder in einer Suche aufzulösen und zu finden!

Der Blog ist in folgende Sektionen untergliedert:

Warum eigene Claims?
Aufbau und Funktionsweise der Komponenten
Implementierung Connector
Implementierung Pre-Security Trimmer
Resultat / Praxistest

Warum eigene Claims?

Fangen wir mal von vorne an, warum braucht man so etwas überhaupt?

Wie schon in den referenzierten Blogs erwähnt, ist es ein unglaublich angenehmer Weg Content von externen System mit Berechtigungen zu versehen. Ich möchte es gerne mit einem Beispiel erklären, das deutlich machen sollte, welchen Mehrwert man davonträgt.

Man hat ein externes CMS, welches seine eigenen Gruppen benutzt um ein Berechtigungsmodel abzubilden. Hinter diesen Gruppen stecken Active Directory Nutzer.

Natürlich gibt es an dieser Stelle direkt die Möglichkeit beim Feeden der Contents vom CMS in SharePoint die Gruppen aufzulösen und die AD User an das Item zu hängen. Es würde funktionieren, zumindest begrenzt. Doch was passiert, wenn am System Gruppenzusammenstellungen anpasst und User Berechtigungen geändert werden? Das Item im Index wird sich nicht ändern, da keine Anpassung am eigentlichem geschehen ist! Die User die es finden werden bleiben die gleichen, obwohl der Stand im CMS ein ganz anderer ist, eine fatales Sicherheitsproblem tritt ein!

Was kann man also machen, um dieses zu umgehen? Ganz einfach, man schreibt die tatsächlichen Berechtigungen in Form von Custom Claims an das Dokument. Auf diesem Weg ist egal, welche Active Directory Nutzer hinter der Gruppe stecken, dies wird zur Zeit der Suche aufgelöst und ist daher in jedem Fall aktuell, zumindest so aktuell, wie sich eine Anbindung und Auflösung der Berechtigungen durch das CMS umsetzten lässt. Es kristallisiert sich also der riesige Vorteil heraus, dass man die Security Informationen am Dokument im SharePoint Index immer auf dem gleichen stand hat, wie das Ausgangs-CMS und die Auflösung zur Zeit der Suche durchgeführt wird. Es ist also auch nie ein Refeed der Dokumente nötig, solange sich nichts am Dokument selbst ändert.

Die Auflösung der Security Informationen geschieht wie beschrieben zur Zeit der Suche. Der Pre-Security Trimmer erfragt im genannten Beispiel, auf welche CMS Gruppen der AD User Zugriff hat. Diese werden ihm als Claims zugeschrieben und er findet Dokumente, auf die er berechtigt ist.

Sollten Informationen aus einem CMS oder anderem System indexiert werden, welche über keine Active Directory Anbindung, und somit auch über keine Möglichkeit zur Auflösung verfügen, ist dies kein Problem, auch hier bieten sich eigen definierte Claims an. So es ist es möglich beim Feeden Berechtigungen zu vergeben und diese durch ein Mapping im Pre-Security Trimmer aufzulösen.

Mein Beispiel 

In meinem Programm Beispiel habe ich ein XML Dokument als Ausgangsbasis genutzt. Es sind Nodes hinterlegt, die später als sepearate Dokumente mit Security im Index auftauchen sollen. Für den Security Trimmer habe ich ein Mapping hinterlegt, das Active Directory Nutzer auf im XML definierte Gruppen mapt, dies benötigt zwar manuelle Administration, deckt aber viele Anwendungsfälle ab.

Aufbau und Funktionsweise der Komponenten

Bevor ich zur technischen Seite komme, möchte ich kurz den Aufbau des Gesamtkonstrukts eingehen. Die folgende Abbildung ist eine sehr grobe Übersicht, aber sie zeigt eindeutig, in welchen Bereichen die Komponenten ihre Arbeit verwirken.

BCS Connector

Der Connector ist dafür zuständig Dokumente zu beschaffen. Hier müssen die Security Informationen am Dokument abgelegt werden, diese werden dann in den Index übernommen.

Pre-Security Trimmer

Der Pre-Security Trimmer wird durchlaufen, nachdem eine Suche ausgelöst wurde. Dabei werden die Userrelevanten Claims aufgelöst und als Information übergeben. Das hat zur Folge, dass in einem der nächsten Schritte auch die Dokumente im Index gefunden werden können, an denen nicht unmittelbar der User oder eine seiner Gruppen, sondern auch andere Claims vorhanden sind.

Ein Pre Security Trimmer bietet sich immer an, wenn die Berechtigungen im Quellsystem keinen hochfrequenten Änderungen unterzogen werden. Sollte dies der Fall sein gibt es auch die möglichkeit einen Post Security Trimmer zu implementieren. Dieser prüft nach einer durchgeführten Suche die gefunden Ergebnisse erneut und checkt, ob der Benutzter auch tatsächlich noch die entsprechenden Berechtigungen verfügt.

Implementierung Connector

In den genannten Blogposts ist eine sehr detaillierte Beschreibung zu finden, wie ein möglicher Connector zu implementieren ist, daher möchte ich auf dieser Stelle auf umschweifende Anleitungen verzichten und mich auf die spannenden Zeilen Code beschränken, die dieses Gesamtkonzept erst möglich machen!

Ich habe mich inspirieren lassen und meinen Connector so aufgebaut, dass er ein XML auslesen soll, welches wir hier sehen:

<?xml version="1.0" encoding="utf-8" ?>
<entries>
            <entry>
                        <id>exSec_0</id>
                        <title>exSec_Title0</title>
                        <permission>gruppe1,gruppe2</permission>
            </entry>
            <entry>
                        <id>exSec_1</id>
                        <title>exSec_Title1</title>
                        <permission>gruppe10,gruppe2</permission>
            </entry>
            <entry>
                        <id>exSec_2</id>
                        <title>exSec_Title2</title>
                        <permission>gruppe100,gruppe2</permission>
            </entry>

</entries> 

Warum ich dies als mein Beispiel gewählt habe? Dafür gibt es drei Gründe:

  1. „Keep it simple“, und simpler geht’s fast nicht - weniger Fehlerquellen sind immer gut
  2. Es bietet die Grundlage für eine einfache Erweiterung
  3. Multiple Claims pro Dokument sind kein Problem

 

Mein Ziel war also ein Connector, welcher pro XML Node „entry“ ein Dokument erstellt, mit Titel und ID versieht du das Ganze in den SharePoint Index lädt. Die „permission“ Einträge enthalten die Einträge, die als Claims im Index hinterlegt sein sollen.

Die Herausforderung besteht an der Stelle eindeutig darin, die Claims richtig zu erzeugen, dieser Blog handelt ja schließlich nicht vom Auslesen von XML Files.

Um die Claims zu erstellen, sollten wir verstehen, wie sie aufgebaut sind, diese geht aus der folgenden Tabelle gut hervor. Ein Claim besteht aus:

Nutzt man dieses Wissen und erstellt daraus eine Funktion, ergibt das genau den im verwiesenen Blog genutzeten Code: 

binaryWriter.Write(isDeny ? (byte)1 : (byte)0); // Allow = 0, Deny = 1
binaryWriter.Write((byte)1); // Indicate that this is a non-NT claim type
 
// Claim Value
binaryWriter.Write((Int32)claimvalue.Length);
binaryWriter.Write(Encoding.Unicode.GetBytes(claimvalue));
 
// Claim Type
binaryWriter.Write((Int32)claimtype.Length);
binaryWriter.Write(Encoding.Unicode.GetBytes(claimtype));
 
// Claim Data Value Type
binaryWriter.Write((Int32)datatype.Length);
binaryWriter.Write(Encoding.Unicode.GetBytes(datatype));
 
// Claim Original Issuer
binaryWriter.Write((Int32)claimissuer.Length);
binaryWriter.Write(Encoding.Unicode.GetBytes(claimissuer));
 

So einfach lassen sich einzelne Claims zusammensetzten. Doch was tun, wenn man pro Dokument mehrere Claims hinterlegen möchte? Das gestaltet sich auch nicht sonderlich schwerer.

Dafür habe ich diese Funktion erstellt, sie erlaubt die Übergabe von einem Claim Value Array. Für jeden Value wird ein Claim erstellt und an das Dokument gehangen. Hier der Programmcode:

        private static byte[] GetSecurityAcl(string claimtype,string[] claimvalues,string claimissuer)
        {
            Byte[] sharePointACLs = null;
            MemoryStream aclStream = new MemoryStream();
            if (!string.IsNullOrEmpty(claimtype) &&
                !string.IsNullOrEmpty(claimissuer) &&
                claimvalues != null & (claimvalues.Length>0))
            {
                foreach(String claimvalue in claimvalues)
                {
                    using (BinaryWriter dest = new BinaryWriter(aclStream))
               {
                         AddClaimAcl(dest, false, claimtype, claimvalue, claimissuer);
                    }
                }  
               
            }
            sharePointACLs = aclStream.ToArray();
            return sharePointACLs;
        }
 

Diese beiden Funktionen enthalten alle relevanten Teile. Für mein Beispiel habe ich für jede am Dokument angehangene Permissionen ein Claim erstellt, und diesen in den ByteArray geschrieben.

Damit wäre auch schon der schwierigste Teil abgeschlossen, im Folgeblogpost werde ich auf die Implementierung des Pre-Security Trimmers kommentieren und den wichtigsten Punkt – das Ergebnis – vorstellen.

Neuen Kommentar schreiben