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

So oder zumindest so ähnlich, war unsere erste Annahme bei der Planung eines eigenen Bearbeitungstools für den Terminologie-Store für SharePoint 2013. Die Anforderung war 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, und die Problematik, dass SharePoint es nicht mehr zulässt – selbst wenn die TermSets geöffnet sind, und die User Terme hinzufügen dürfen – diese später nochmals zu bearbeiten oder zu löschen.

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 wurde klar, dass eine selbst entwickelte Oberfläche (auf einer Layoutspage), welche die Bearbeitung eingeschränkter Terme möglich macht, die Lösung ist. 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, also war uns 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 wurde 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)
            {
            }
        }
    }
});

Anzunehmen ist, dass – da die SPSite und das SPWeb innerhalb der Elevated-Privileges initialisiert werden – 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. Was mich auch zur Lösung des Problems bringt:

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, und wir können nun im Kontext des System-Accounts nach Belieben Terms hinzufügen, entfernen oder bearbeiten!

Wichtig ist nur, dass NACH dem Elevated-Code-Block der HttpContext wieder auf den vorherigen Wert gesetzt werden muss, 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