1

I have a custom ribbon icon "Enlist" deployed to each document library. A normal (i.e. non-admin) user can click this icon; when he does, I would like to permanently save the document library information (ID, name) somewhere. A central admin can then (via a custom central admin page) see a list of all document libraries enlisted by users.

I'm thinking about using SPPersistedObject to store the document library information when a user clicks on the ribbon icon. However, afaik this requires farm admin privileges, which means that normal users will encounter an error when clicking "Enlist". Would running the code that writes SPPersistedObject with elevated privileges resolve this problem, or could an error still occur? This is what I mean:

// Execute this code when a ribbon icon is clicked by a normal user
SPSecurity.RunWithElevatedPrivileges(delegate()
{
    // Write DocumentLibrary Id and Name to some custom SPPersistedObject
});

MSDN says that RunWithElevatedPrivileges() "runs under the Application Pool identity, which has site collection administrator privileges on all site collections hosted by that application pool." Is this enough to update SPPersistedObject?

Should I override SPPersistedObject.HasAdditionalUpdateAccess to always return true, or would this still not be enough for least-privileged environments? If not, what options do I have to store such information other than a custom database?

  • Are your document libraries all in the same site? in the same site collection? in different site collections? Have you considered just saving this information to a SharePoint list instead of with SPPersistedObject? Is there something specific that is leading you towards an SPPersistedObject approach? – lwbecker2 Mar 24 '14 at 3:21
  • The document libraries are all across the farm - no limitations here, they can be in different site collections in different content databases in different web applications. The reason I'm not storing this in a list is that 1) supposedly, it's not a good idea if you're dealing with many items, e.g. 100k (perhaps this is more of a myth?) and 2) where would I store such a list to make it directly accessible via central admin? Wouldn't I still need to store its location somewhere? – w128 Mar 24 '14 at 8:19
6
+50

Elevated privileges is not enough to write to the configuration database. With elevated privileges you will use the the web application app pool’s identitty, which has only read permission to the config db by default. Of course admin user (which runs the central admin should have read and write permissions).

See more at: http://joelblogs.co.uk/2010/10/10/persisting-configuration-data-in-sharepoint-2010-with-sppersistedobject/

SPPersistedObject.HasAdditionalUpdateAccess

still needs the non farm admin user to be impersonated to farm admin user.

One final topic that requires some coverage is security. As described earlier, the HOS exists in the farm configuration database. Strictly speaking it exists to support administrative functionality however it has uses beyond this scope. For example, if we’re building a custom timer job and we want to have the ability to submit tasks to our job for completion. It’s most likely that these tasks will originate within a non-administrative scope. Possibly as the result of a user clicking a button on a webpart on some other site collection. With previous versions of SharePoint, this simply wasn’t possible. Only farm administrators could update the HOS. However with SharePoint 2010, an addition has been made to the base SPPersistedObject that opens up the HOS to other users. To see how this works, we can modify our Main function as follows:

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace PersistedObjectDemo
{
    internal class Program
    {
        private const int LOGON_TYPE_INTERACTIVE = 2;
        private const int LOGON_TYPE_PROVIDER_DEFAULT = 0;

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool LogonUser(string userName, string domain, string password, int logonType,
                                            int logonProvider, ref IntPtr accessToken);

        private static void Main(string[] args)
        {
            try
            {
                IntPtr accessToken = IntPtr.Zero;
                if (LogonUser("non-farm-admin-username", "your-domain-name", "your-password", LOGON_TYPE_INTERACTIVE,
                              LOGON_TYPE_PROVIDER_DEFAULT, ref accessToken))
                {
                    using (var identity = new WindowsIdentity(accessToken))
                    {
                        using (identity.Impersonate())
                        {
                            MyPersistedObject obj = MyPersistedObject.Local;
                   //delete existing object and recreate to keep things simple
                            obj.Delete();
                            obj = MyPersistedObject.Local;
                            obj.Foo = "Some updated value";
                            obj.Update();
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                Console.Write(ex);
            }

            Console.WriteLine("Press return to exit");
            Console.ReadLine();
        }
    }
}

This code simulates an attempt by a non-farm admin user to update the HOS. If we run this we’ll see the following exception which is our gotcha number 3:

System.Security.SecurityException: Access denied. at Microsoft.SharePoint.Administration.SPPersistedObject.BaseUpdate() at PersistedObjectDemo.MyPersistedObject.Update()

Unsurprisingly, trying to update the HOS using a non-farm admin user throws an exception. However, if we add the following code to MyPersistedObject.cs and run our program again, we can see that the HOS is updated appropriately.

protected override bool HasAdditionalUpdateAccess()
{
    return true;
}

Of course, in a real-world application, we’d probably want to add some security checking in this function rather than simply returning true and allowing anybody to update our object but hopefully this illustrates the point sufficiently.

There is one other gotcha with regard to security – when SharePoint is 2010 installed, by default only the farm admin account has read/write access to the config database. In a least-privilege installation, all web applications other then Central Administration, will be running under their own service account and these accounts will not have write access to the config database. Consequently, adding, deleting and updating objects in the HOS will not be possible. Although overriding HasAdditionalUpdateAccess allows us a bit more control over who can update the HOS, if the underlying app pool account doesn’t have the appropriate database permissions we’re going to come unstuck pretty quickly.

  • nice answer, a very useful reminder – Salvatore Di Fazio Mar 24 '14 at 8:29
  • 1
    So to summarize, the general, least-assuming answer to the original question is "no". The way I see it, this means that SPPersistedObjects are useless for passing information farm-wide between normal and farm admin users. Are there any other built-in alternatives for this? There are e.g. site property bags, but there we have another problem: how to make a farm admin know which sites' property bags to check for stored values? – w128 Mar 26 '14 at 10:26
  • SPPersitedObjects are still the best there is for farm wide settings. web.config is pretty ugly, but so is giving RW access to your config DB for your webapp accounts... – Dennis G Sep 25 '14 at 15:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.