Security Trimmed Custom Actions

In my Securing SharePoint Application Pages post, I showed you how to check for SharePoint permissions, and custom permissions in a custom application page. But we did not deal with custom actions security.

Most of the time application pages are clicked-through from custom actions. You might be clicking a link from the site settings page or a menu item from the site actions menu and navigating to an application page, only to get an access denied error message if you are not authorized to use the application page. So, custom actions should also be security trimmed. Adding security to custom actions is quite simple, just add the “Rights” attribute and list all the SharePoint permissions you need to check in the element manifest file. Then your custom action user interface will only show up when user is authorized to hit the application page.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="AppPageNavigationMenu" 
                GroupId="SiteActions" 
                Location="Microsoft.SharePoint.StandardMenu" 
                Sequence="1001" 
                Title="AppPage"
                Description="Navigate to AppPage"
                Rights="BrowseUserInfo,CreateGroups,ManageLists">
    <UrlAction Url="~sitecollection/_layouts/ApplicationPageSecurity/AppPage.aspx"  />
  </CustomAction>
</Elements>
 

Don’t be too happy, what about the custom permissions we wanted to check? You cannot put that in the element manifest file! But you can actually use your own custom security trimmed web control that your custom action can use to render itself. In the SharePoint Object model there is one Web control we can take advantage of, that is SPSecurityTrimmedControl from Microsoft.SharePoint.WebControls namespace. As its name implies, the control has the ability to check for SharePoint permissions and hides itself when a user does not have enough permission to use the custom action.

The key property in the SPSecurityTrimmedControl that will do the trick is a Boolean internal property named “ShouldRender”. SPSecurityTrimmedControl checks for SharePoint permissions and if user does not meet them, it sets ShouldRender to false, then skips the rendering part and makes itself invisible.

Render from SPSecurityTrimmedControl (Thanking Red Gate’s .NET Reflector):
protected override void Render(HtmlTextWriter output)    
{
        if (this.ShouldRender)
        {
            base.Render(output);
        }
}

In fact, it is the ShouldRender property itself that makes sure that the user is authorized. So it comes down to this: you inherit from SPSecurityTrimmedControl, assign Permissions property with your new SharePoint permissions before ShouldRender is called (on OnInit will do), and only render if user passes through the SharePoint Permissions and your custom access check logic by overriding Render. Finally, in the element manifest file, refer to the class and assembly the security trimmed control is defined in. And, this is what you should get:

Manifest File referring to a custom control
<?xml version="1.0" encoding="utf-8"?>
 
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="AppPageNavigationMenu" 
                GroupId="SiteActions" 
                Location="Microsoft.SharePoint.StandardMenu" 
                ControlAssembly="Bamboo.ApplicationPageSecurity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8c72b2.."
                ControlClass="Bamboo.ApplicationPageSecurity.WebControls.AppPageActionControl"  />
</Elements>
Custom Security Trimmed Control
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint;
using System.Reflection;
 
namespace Bamboo.ApplicationPageSecurity.WebControls
{
    public class AppPageActionControl : SPSecurityTrimmedControl
    {
        protected MenuItemTemplate menuItem;
        private bool shouldRender = false;
        protected bool CustomShouldRender
        {
            get
            {
                bool hasCustomRights = this.HasCustomRights();
 
                Type baseType = this.GetType().BaseType;
                object obj = baseType.InvokeMember(
                    "ShouldRender", 
                    BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance,
                    null, this, new object [] {});
 
                if (obj != null)
                {
                    return (bool)obj && hasCustomRights;
                }
                else
                {
                    return false;
                }
            }
        }
 
        protected virtual SPBasePermissions AppPagePermissions
        {
            get
            {
                SPBasePermissions permissions = base.Permissions 
                | SPBasePermissions.ViewFormPages // Don't forget this, if custom action takes to an application page
                | SPBasePermissions.BrowseUserInfo
                | SPBasePermissions.CreateGroups
                | SPBasePermissions.ManageLists;
 
                return permissions;
            }
        }
 
        private bool HasCustomRights()
        {
            bool hasCustomRights = false;
 
            //write here a custom logic to check if user has enough rights to access application page
            //if yes, set userCheckedForCustomLogic to true;
 
            hasCustomRights = true;
            return hasCustomRights;
        }
 
        private void SetAppPagePermissionProperties()
        {
            base.Permissions = this.AppPagePermissions;
 
            // DoesUserHavePermission is called on the current SPWeb
            base.PermissionContext = PermissionContext.CurrentSite;
 
            // It make sure user have all permissions
            base.PermissionMode = PermissionMode.All;
 
            //By default any users (included AnonymousUsersOnly), but I only wanted to deal with Authenticated Users only
            base.AuthenticationRestrictions = AuthenticationRestrictions.AuthenticatedUsersOnly;
        }
 
        private void CreateAndAddMenuItem()
        {
            this.menuItem = new MenuItemTemplate();
            this.menuItem.Title = "AppPage";
            this.menuItem.Text = "AppPage";
            this.menuItem.Description = "AppPage Description";
            this.menuItem.Sequence = 1040;
            this.menuItem.ClientOnClickNavigateUrl = "~site/_layouts/Bamboo.ApplicationPageSecurity/AppPage.aspx";
            this.Controls.Add(menuItem);
        }
 
        protected override void OnInit(EventArgs e)
        { 
            this.SetAppPagePermissionProperties();
            base.OnInit(e);
        }
 
        protected override void OnLoad(EventArgs e)
        {
            base.EnsureChildControls();
            base.OnLoad(e);
        }
 
        protected override void CreateChildControls()
        {
            this.CreateAndAddMenuItem();
        }
 
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if (this.CustomShouldRender == true)
            {
                this.shouldRender = true;
            }
            else
            {
                this.shouldRender = false;
                base.Visible = false;
            }
        }
        protected override void Render(System.Web.UI.HtmlTextWriter output)
        {
            if (this.shouldRender == true)
            {
                base.Render(output);
            }
            else
            {
                base.Visible = false;
            }
        }
    }
}

 

Yes I know, am using reflection to read an internal property, and that breaks encapsulation. Unlikely that it will dissappear one day, but you can always write yourself a routine that checks for the SharePoint permissions and avoids calling ShouldRender but, then again, why the need to inherit from SPSecurityTrimmedControl in the first place? Mais Voila! You have a security trimmed custom action with SharePoint permissions and custom permissions checking ability (and that is what we want).

Sincerely your code,

NaT


Posted Oct 15 2008, 05:07 PM by Natnael K. Gebremariam

Comments

erugalatha wrote re: Security Trimmed Custom Actions
on Fri, Nov 14 2008 7:45 AM

Custom Actions do not appear on the Data View Web Part dropdown menus.

Why?

Jaap Vossers wrote re: Security Trimmed Custom Actions
on Thu, Dec 4 2008 11:00 AM

As an alternative, you could also create a wrapper control (have manifest point to this control), to which you add a SecurityTrimmedControl as a child in CreateChildControls, and subsequently you add your actual control to the Controls collection of your SecurityTrimmed control. No need to use reflection :)

Natnael K. Gebremariam wrote re: Security Trimmed Custom Actions
on Thu, Dec 4 2008 11:29 AM

- Yep, it should work too.

- How are you going to apply the custom permissions? You're going to change the visibility of the SecurityTrimmedControl in my controls collection, depending on the custom permissions validation results, right?

Jaap Vossers wrote re: Security Trimmed Custom Actions
on Fri, Dec 5 2008 9:27 AM

Ah, sorry, I must have read your blog post too quickly and missed your point because of that. Ignore my previous post please :)

Jaap

Add a Comment

Please sign into Bamboo Nation to leave a comment.

Blogs

    The Bamboo Team Blog
  • Home

Proud Sponsor of:

SPTechCon Boston

Subscribe by Email

Syndication

Bamboo Nation Almost Everywhere

Bamboo Solutions on Facebook

Bamboo Solutions on Google+

Bamboo Solutions on LinkedIn

Bamboo Solutions on YouTube

Bamboo Now in Alltop!

Featured in Alltop

Bamboo Solutions Corporation, 2002-2014