Service-Zugriffe auf SharePoint-Ressourcen

17. November 2021

Die Integration von SharePoint in andere Services gestaltet sich deutlich anders als der traditionelle Zugriff auf externe Dienste. In der “guten alten Zeit” hat man einfach einen “technischen Benutzer” angelegt, wenn man von einem Service aus auf eine Ressource eines anderen Service zugreifen wollte. Den konnte man dann so berechtigen, wie es nötig war (auch wenn wohl “früher” die meisten technischen User einfach Vollzugriff bekommen haben). Das geht heutzutage im Cloud-Bereich nicht mehr so einfach: ein Windows-Service oder eine Azure Function können keine 2-Faktor-Authentifizierung per Authenticator-App auf dem Handy nutzen.

In Azure legt man nun also statt eines Applikations-Users eine “App Registration” an und diese hat dann ein “Application Secret” statt eines Passwortes. Das hört sich erst mal nach einer recht marginalen Umstellung an. Interessant wird es dann bei der Einrichtung des ersten “Application Secret”: das braucht nämlich immer ein Ablaufdatum – und das darf nicht weiter als 2 Jahre in der Zukunft sein. Aber es gibt viele weitere Unterschiede. Z.B. wurde das “Application Secret” nicht einfach “Application Passwort” genannt, weil es eben kein Passwort in dem Sinne ist:

  • nicht der Admin bestimmt, wie das Secret lautet, sondern das Secret wird vom AAD generiert
  • das Secret kann nicht geändert werden, sondern es muss zur Änderung ein neues erzeugt werden
  • dieselbe Applikation kann viele Secrets mit unterschiedlichen Start/Ende-Zeitpunkten haben, die sich beliebig überlappen können

Das Secret ist aber nicht der einzige Unterschied zum guten alten Applikations-User: eine registrierte App kann sich als solche auch per Zertifikat authentifizieren (das ging vielfach auch schon früher bei Applikations-Usern) und die “App Registrations” finden sich nicht in den Benutzern und/oder Gruppen des AAD.

Gerade letzteres hat dramatische Auswirkungen auf Berechtigungen in allen möglichen Systemen. Während in Dynamics eine AAD-App noch über ihre “Application ID” in den Benutzern hinterlegt werden und dann (fast) wie ein ganz normaler Benutzer konfiguriert werden kann, kennt SharePoint kein solches Mapping zu “eigenen” Benutzer-Entitäten. Eine registrierte Applikation taucht in der “normalen” Verwaltung von SharePoint einfach nicht auf.

Zur Rechte-Konfiguration von solchen registrierten Applikationen gehören “API Permissions”. In diesen gibt es auch einen Bereich für SharePoint:

Wenn man sich jetzt die verfügbaren “Permissions” so anschaut, fällt dem aufmerksamen Administrator eventuell eine Sache auf: Am Ende jeder “Permission” findet sich das Wort “.All”.

Das, was man hier konfiguriert, sind Zugriffs-Rechte auf APIs, nicht auf Ressourcen. Diese Rechte werden im Authentication Token hinterlegt – was auch der Grund dafür ist, dass hier keine Ressource-Rechte konfiguriert werden, da sonst das Token viel zu groß und zu komplex werden würde.

Wirklich interessant wird es dann, wenn man damit zum Betrieb geht und sagt: “Ich entwickle hier die Applikation XYZ, die SharePoint-Zugriffe nutzt und brauche jetzt Applikations-Zugriff auf die SharePoint Site https://companyabc.sharepoint.com/sites/ProjektName_Test/, denn das können die für die SharePoint-API gar nicht im AAD konfigurieren. Wie im dem Screen-Shot zu sehen ist, wird immer Vollzugriff auf alle Sites eines SharePoint für die App gegeben. Hat der Entwickler die App unter Kontrolle, dann hat er auch Vollzugriff auf den kompletten SharePoint – und damit Vollzugriff auf alle Teams und deren Dateien, denn diese werden eben in SharePoint Sites gespeichert … und auch auf alle OneDrive-for-Business-Laufwerke des Unternehmens, denn auch die sind nur Sites in SharePoint. Die Erweiterung der Rechte-Konfiguration für die SharePoint-API, so dass auch einzelne Sites freigegeben werden können, ist “in Entwicklung” und sollte schon letzten Monat deployed werden … Stand heute steht es nun also für diesen Monat an und irgendwie habe ich nicht das Gefühl, dass ich das nächsten Monat nutzen könnte.

Der bisher einzige Weg, einzelne Sites für eine Applikation freizugeben, ist: nutze die Graph-API statt der SharePoint-API … gleiches Backend aber unterschiedliche API-Implementierung.

Für die Graph-API gibt es Recht “https://graph.microsoft.com/Sites.Selected” (das soll auch irgendwann für die SharePoint-API kommen). Dieses Recht reicht aber nicht aus, denn es ermöglicht nur einen Zugriff auf die API, nicht aber auf die Ressourcen. Per PowerShell (eine Web-Oberfläche gibt es dafür bisher nicht) muss dann noch konfiguriert werden, auf welche Site(s) welcher Zugriff (“lesen” oder “lesen und schreiben”) gewährt wird:

Install-Module -Name PnP.PowerShell
Connect-PnPOnline https://mycomany.sharepoint.com -Interactive
Grant-PnPAzureADAppSitePermission -AppId '12345678-1234-1234-1234-123456789012' `
                                  -DisplayName 'TEST Applikation' `
                                  -Site 'https://mycomany.sharepoint.com/sites/Test_Site' `
                                  -Permissions Read,Write

Der Zugriff über die Graph-API mit “Microsoft.Graph” gestaltet sich übrigens recht “interessant”, denn die API basiert auf IDs, nicht auf Pfaden, so dass ich erst die Site-ID, die Library-ID und die Drive-ID herausfinden muss, um dann ein Request-Objekt zu erzeugen, auf dem ich dann asynchron die Methode “.AddAsync” aufrufe (warum ich da ein Suffix “Async” habe, obwohl in der API solche Methoden gar nicht synchron existieren, kann mir wohl keiner zufriedenstellend erklären). Einen neuen Folder erzeugt man übrigens, indem das übergebene “DriveItem” ein Property “Folder” mit dem Wert “new Folder()” hat.

Mein naiver erster Ansatz für das Erstellen eines Ordners “Hello World” in “Dokumente” war:

await this.graphClient
    .Sites["root"]
    .SiteWithPath("/sites/DAAS_Test")
    .Drives["Documents"]
    .Root
    .ItemWithPath("/")
    .Children
    .Request()
    .AddAsync(new DriveItem
    {
        Name = "Hello World",
        Folder = new Folder(),
        AdditionalData = ReplaceConflicting,
    });

Das resultiert aber in der URL:  https://graph.microsoft.com:443/v1.0/sites/root:/sites/DAAS_Test:/drives/Documents/root:/:/children

Die ist “natürlich” ungültig – wurde ja auch von einem einfachen Aufruf über die API erzeugt … die korrekte API-URL muss “natürlich” lauten: https://graph.microsoft.com:443/v1.0/sites/tenant.sharepoint.com/drives/b![eine lange ID]/root/children

Für die Ermittlung der Drive-ID (über Namen konnte ich nicht darauf zugreifen) sind wieder zusätzliche Requests nötig, so dass ich Euch den Code hier erspare.

An allen möglichen Ecken und Enden ist die API so gestrickt, dass zumindest ich immer erstmal in irgendwelche Fehler vom Server tappe, weil der Syntax der API sehr viele Ausdrücke zulässt, die in nicht gültigen URLs beim Aufruf resultieren (eine “Microsoft.Graph.ServiceException: Code: BadRequest” mit “Message: Url specified is invalid.”).

Wer mit “Microsoft.Graph” arbeitet, dem kann ich nur raten, alle Zugriffe noch mal zu abstrahieren, so dass der nächste Entwickler, der sich den Code anschaut nicht durch triviale Änderungen in der Business-Logik die API-Zugriffe zerstört.

autor Sven Erik Matzen

Sven Erik Matzen

Chief eXpert