Änderungen auf dem TermStore mit dem System-User: Das kann doch nicht so schwer sein, oder?

So ähnlich war unsere erste Annahme bei der Planung eines eigenen Bearbeitungstools für den Terminologie-Store für SharePoint 2013. Dieses Tool ermöglicht die TermStore-Manipulation im Elevated-Code-Block. Daher war die Anforderung simpel: Von einem bestimmten Knoten weggehend sollte der TermStore für eine eingeschränkte Benutzergruppe bearbeitbar sein. Grund dafür ist die Menge an neu hinzugefügten Terms. Außerdem entsteht die Problematik, dass es SharePoint nicht mehr zulässt, diese später nochmals zu bearbeiten oder zu löschen, obwohl die TermSets geöffnet sind und die User Terme hinzufügen dürfen.

Also, was tun? Alle betreffenden User im TermStore berechtigen?

Dies kam in unserem Fall nicht in Frage, da nicht alle Terms im TermStore oder in einem TermSet für alle Benutzer veränderbar sein sollten. Schnell war klar, dass eine selbst entwickelte Oberfläche (auf einer Layoutspage) die Lösung ist. Dadurch wird die Bearbeitung eingeschränkter Terme möglich. In dieser Oberfläche sollen die User neue Terme anlegen, bestehende löschen und auch umbenennen können. Ebenfalls sollte es hier möglich sein, zusätzliche Eigenschaften hinzuzufügen.

Aber wie können User ohne Berechtigung Änderungen am Term Store vornehmen?

Und hier kommen wir zur Krux der ganzen Sache. Unsere Lösung war, den Code einfach mit Elevated-Privileges auszuführen und den System-User im TermStore zu berechtigen. Klingt zuerst einmal simpel und hatte auch funktioniert, solange wir mit Usern getestet haben, welche auch Berechtigung auf dem TermStore hatten. Die Probleme kamen erst mit Test-Usern, welche keine Berechtigung auf den TermStore hatten.

Aber wieso sollte das so sein? Es wird ja in beiden Fällen der System-User verwendet. Uns war aber nicht klar, warum es mit einem User, der Berechtigungen auf den TermStore hat, funktioniert und mit den anderen Usern eben nicht. Nach vielen Recherchen und langem Debugging war dann klar, dass das Problem die TaxonomySession istZuerst hatten wir den Code in etwa so wie in dem nachfolgenden Code-Snippet:

var webUrl = SPContext.Current.Web.Url;
SPSecurity.RunWithElevatedPrivileges(delegate
{
    using (SPSite site = new SPSite(webUrl))
    {
        using (SPWeb web = site.OpenWeb())
        {
            try
            {
                TaxonomySession session = new TaxonomySession(site);
                Guid termStoreId = Guid.Parse(termstoreguid);
                TermStore termStore = null;
                termStore = session.TermStores[termStoreId];
            }
            catch (Exception ex)
            {
            }
        }
    }
});

Die SPSite und das SPWeb werden innerhalb der Elevated-Privileges initialisiert. Daher war anzunehmen, dass auch die TaxonomySession, welche als Konstruktor-Argument die SPSite mitbekommt, ebenfalls unter dem Kontext des System-Users erstellt wird.

Wie beim Debugging aufgefallen ist, ist dies aber nicht der Fall. Anscheinend übernimmt die TaxonomySession die Daten des derzeitigen Users aus dem derzeitigen HttpContext. Das bringt mich auch schon zur Lösung des Problems:

var webUrl = SPContext.Current.Web.Url;
var context = HttpContext.Current;
HttpContext.Current = null;
SPSecurity.RunWithElevatedPrivileges(delegate
{
    using (SPSite site = new SPSite(webUrl))
    {
        using (SPWeb web = site.OpenWeb())
        {
            try
            {
                TaxonomySession session = new TaxonomySession(site);
                Guid termStoreId = Guid.Parse(termstoreguid);
                TermStore termStore = null;
                termStore = session.TermStores[termStoreId];
            }
            catch (Exception ex)
            {
            }
        }
    }
});
HttpContext.Current = context;

Um die TaxonomySession davon abzuhalten, den User aus dem HttpContext zu beziehen, setzen wir vor dem Elevated-Code-Block den HttpContext.Current auf null. Dies zwingt den TermStore die Userdaten aus der WindowsIdentity zu nehmen. Nun können wir im Kontext des System-Accounts nach Belieben Terms hinzufügen, entfernen oder bearbeiten!

Wichtig ist nur, dass man den HttpContext NACH dem Elevated-Code-Block wieder auf den vorherigen Wert setzt, da sonst einige Dinge wie z.B. der SPContext nicht mehr funktionieren.

Ich hoffe ich konnte euch ein bisschen Zeit sparen,

Liebe Grüße,
euer Josef