Thursday, March 08, 2012

SharePoint 'Approve on behalf of' for Publishing Pages

Using require approval and the approval workflow in SharePoint 2010 publishing, or just approval on the pages library in the simple publishing configuration, is straight forwards when using the browser as you're only logged in as one person with a limited set of roles and thus set of rights. Typically a page author can edit a page and submit it for approval, but not actually approve the page to be published on the site. When you need to approve, you have to log on as a user with approval rights.

Sometimes you need to extend the user experience to allow an author to make simple changes to an already published page, such as extending the publishing end date, and republish it directly without having to do all the approval procedures all over again. So you create a custom "extend expiry date" ribbon button, elevate the privileges from code and call the Page ListItem File Approve method, only to get an access denied error.

In SharePoint 2010, it is not sufficient to be the app-pool identity (SHAREPOINT\System) or even a site-collection admin, you have to run the approval procedures as a user with approval privileges. So your code needs to impersonate a user in the "Approvers" group to be able to approve an item on behalf of the current user.

public static void ApproveOnBehalfOfUser(
SPListItem item, string approversGroupName, string userName, string comment)
{
    Guid siteId = item.ParentList.ParentWeb.Site.ID;
    Guid webId = item.ParentList.ParentWeb.ID;
    Guid listId = item.ParentList.ID;
    Guid itemId = item.UniqueId;
 
    SPUserToken approveUser = GetApproverUserToken(siteId, webId, approversGroupName);
    if (approveUser == null)
        throw new ApplicationException(String.Format(
"The group '{0}' has no members of type user, cannot approve item on behalf of '{1}'"
approversGroupName, userName));
 
    using (SPSite site = new SPSite(siteId, approveUser))
    {
        using (SPWeb web = site.OpenWeb(webId))
        {
            var approveItem = web.Lists[listId].Items[itemId];
            approveItem.File.Approve(comment);
        }
    }
}
 
private static SPUserToken GetApproverUserToken(
Guid siteId, Guid webId, string approversGroupName)
{
    SPUserToken token = null;
    SPSecurity.RunWithElevatedPrivileges(() =>
    {
        using (SPSite site = new SPSite(siteId))
        {
            using (SPWeb web = site.OpenWeb(webId))
            {
                var group = web.SiteGroups[approversGroupName];
                if (group != null)
                {
                    foreach (SPUser user in group.Users)
                    {
                        if (!user.IsDomainGroup && !user.IsSiteAdmin)
                        {
                            token = web.GetUserToken(user.LoginName);
                            break;
                        }
                    }
                }
            }
        }
    });
    return token;
}

The code picks a user from the approvers group, and then impersonates that user using a SPUserToken object. From within the impersonated user token scope, the given page list item is opened again with the permissions of an approver, and finally the page is approved on behalf of the given page author.