Second Life of a Hungarian SharePoint Geek

July 9, 2015

Creating custom SharePoint permission levels via PowerShell

Filed under: Permissions, PowerShell, Security, SP 2010 — Tags: , , , — Peter Holpar @ 15:36

Today I had to create some custom permissions via code, however the best post I found in this theme included only C# code, and I was to use PowerShell. Although it is not very difficult to translate the code, it is not trivial as well. So I thought I share my “translation”, and hope somebody find it useful.

Copy a permission level:
$contributorRole = $web.RoleDefinitions.GetByType([Microsoft.SharePoint.SPRoleType]::Contributor)
$customRole = New-Object Microsoft.SharePoint.SPRoleDefinition($contributorRole)

Add a permission:
$customRole.BasePermissions = $customRole.BasePermissions -bor [Microsoft.SharePoint.SPBasePermissions]::CreateGroups

Add multiple permissions:
$customRole.BasePermissions = $customRole.BasePermissions -bor ([Microsoft.SharePoint.SPBasePermissions]::ApplyStyleSheets -bor [Microsoft.SharePoint.SPBasePermissions]::ApproveItems)

Add all permissions:
$customRole.BasePermissions = $customRole.BasePermissions -bor [Microsoft.SharePoint.SPBasePermissions]::FullMask

Remove a permission:
$customRole.BasePermissions = $customRole.BasePermissions -band -bnot [Microsoft.SharePoint.SPBasePermissions]::DeleteListItems

Remove multiple permissions:
$customRole.BasePermissions = $customRole.BasePermissions -band -bnot ([Microsoft.SharePoint.SPBasePermissions]::DeleteVersions -bor [Microsoft.SharePoint.SPBasePermissions]::EditListItems)

Remove all permissions:
$customRole.BasePermissions = $customRole.BasePermissions -band [Microsoft.SharePoint.SPBasePermissions]::EmptyMask

Test for a permission:
$permissionTest = ($customRole.BasePermissions -band [Microsoft.SharePoint.SPBasePermissions]::DeleteListItems) -eq [Microsoft.SharePoint.SPBasePermissions]::DeleteListItems

Save a permission level:
$customRole.Name = "Your custom permission"
$customRole.Description = "Description of your custom permission level"
$web.RoleDefinitions.Add($customRole)
$web.Update()

Advertisements

February 10, 2015

Further Effects of Running Code in Elevated Privileges Block

Filed under: Permissions, PowerShell, SP 2010, SP 2013 — Tags: , , , — Peter Holpar @ 23:29

A few days ago I already published a blog post about the effects of running your PowerShell code in an elevated privileges block. In the past days I made some further tests to check what kind of effect the permission elevation might have if the code runs out of the SharePoint web application context, for example, in the case of server side console applications, windows services or PowerShell scripts, just to name a few typical cases.

To test the effects, I’ve created a simple console application in C#, and a PowerShell script.

I’ve tested the application / script with two different permissions. In both cases, the user running the application was neither site owner (a.k.a. primary administrator) nor a secondary administrator. In the first case, the user has the Full Control permission level on the root web of the site collection, in the second case the user has no permissions at all.

In C# I’ve defined a CheckIfCurrentUserIsSiteAdmin method as:

  1. private void CheckIfCurrentUserIsSiteAdmin(string url)
  2. {
  3.     using (SPSite site = new SPSite(url))
  4.     {
  5.         using (SPWeb web = site.OpenWeb())
  6.         {
  7.             SPUser currentUser = web.CurrentUser;
  8.             Console.WriteLine("Current user ({0}) is site admin on '{1}': {2}", currentUser.LoginName, url, currentUser.IsSiteAdmin);
  9.             Console.WriteLine("Current user ({0}) is site auditor on '{1}': {2}", currentUser.LoginName, url, currentUser.IsSiteAuditor);
  10.             Console.WriteLine("Effective permissions on web: '{0}'", web.EffectiveBasePermissions);
  11.             try
  12.             {
  13.                 Console.WriteLine("web.UserIsWebAdmin: '{0}'", web.UserIsWebAdmin);
  14.             }
  15.             catch (Exception ex)
  16.             {
  17.                 Console.WriteLine("'web.UserIsWebAdmin' threw an exception: '{0}'", ex.Message);
  18.             }
  19.             try
  20.             {
  21.                 Console.WriteLine("web.UserIsSiteAdmin: '{0}'", web.UserIsSiteAdmin);
  22.             }
  23.             catch (Exception ex)
  24.             {
  25.                 Console.WriteLine("'web.UserIsSiteAdmin' threw an exception: '{0}'", ex.Message);
  26.             }
  27.         }
  28.     }
  29. }

Then called it without and with elevated permissions:

  1. string url = http://YourServer;
  2. Console.WriteLine("Before elevation of privileges");
  3. CheckIfCurrentUserIsSiteAdmin(url);
  4. Console.WriteLine("After elevation of privileges");
  5. SPSecurity.RunWithElevatedPrivileges(
  6.     () =>
  7.         {
  8.             CheckIfCurrentUserIsSiteAdmin(url);
  9.         });

The summary of the result:

If the user executing the application has full permission on the (root) web (Full Control permission level):

Before elevation:
Current user is site admin: False
Effective perm.: FullMask
web.UserIsWebAdmin: True
web.UserIsSiteAdmin: False

After elevation:
Current user is site admin: True
Effective perm.: FullMask
web.UserIsWebAdmin: True
web.UserIsSiteAdmin: True

If the user has no permission on the (root) web:

Before elevation:
Current user is site admin: False
Effective perm.: EmptyMask
web.UserIsWebAdmin: ‘Access denied’ exception when reading the property
web.UserIsSiteAdmin: ‘Access denied’ exception when reading the property

After elevation:
Current user is site admin: True
Effective perm.: FullMask
web.UserIsWebAdmin: True
web.UserIsSiteAdmin: True

In PowerShell I defined a CheckIfCurrentUserIsSiteAdmin method as well, and invoked that without and with elevated right:

function CheckIfCurrentUserIsSiteAdmin($url) {
  $site = Get-SPSite $url 
  $web = $site.RootWeb
  $currentUser = $web.CurrentUser
  Write-Host Current user $currentUser.LoginName is site admin on $url : $currentUser.IsSiteAdmin
  Write-Host Current user $currentUser.LoginName is site auditor on $url : $currentUser.IsSiteAuditor

  Write-Host Effective permissions on web: $web.EffectiveBasePermissions
  Write-Host web.UserIsWebAdmin: $web.UserIsWebAdmin
  Write-Host web.UserIsSiteAdmin: $web.UserIsSiteAdmin
}

$url = "http://YourServer"

Write-Host Before elevation of privileges
CheckIfCurrentUserIsSiteAdmin $url

Write-Host After elevation of privileges
[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges(
  {
    CheckIfCurrentUserIsSiteAdmin $url
  }
)

The results were the same as in the case of the C# console application, except the web.UserIsWebAdmin and web.UserIsSiteAdmin, when calling with no permissions on the web. In case we don’t receive any exception, simply no value (neither True nor False) was returned.

These results show, that any code, let it be a method in a standard SharePoint API, or a custom component, that depends on the above tested properties, behaves differently when using with elevated privileges, even if it is executed from an application external to the SharePoint web application context, that means, even if the identity of the process does not change.

April 14, 2014

How to Extend the SharePoint Ribbon to Enable Pushing Down Permissions to Child Items

Filed under: JavaScript, Permissions, Ribbon, Security, SP 2010 — Tags: , , , , — Peter Holpar @ 20:03

If you are familiar with the security options of the NTFS file system you should know the dialog below as well (accessible via the Security tab of the folder properties / Advanced button / Change Permissions… button):

image

By selecting the highlighted checkbox you can push down the permissions set on the current folder to all child items (folders and files). Unfortunately, there is no such option in the standard SharePoint toolbox. As you can see on the screenshot below, the Inheritance group of the Permission Tools on the ribbon includes the options Manage Parent and Stop Inheriting Permissions, but no way to force the inheritance of the current permissions to the child items.

image

In my recent posts I discussed how to check from code whether an site / list has child items with unique permissions, and how to to push down permissions from code.

In this post I will illustrate, how to create a Ribbon extension that enables the missing functionality from SharePoint.

In our CustomAction elements we register the loader ScriptBlock of the custom PageComponent (implemented in InheritPermissions.js, it will be loaded only we are on the permissions page, users.aspx). Next, we register our button (Ribbon.Permission.Manage.InheritDown) in the Inheritance button group (Ribbon.Permission.Parent, defined in 14\TEMPLATE\GLOBAL\XML\CMDUI.XML).

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <CustomAction Location="ScriptLink" ScriptBlock="function startScript() { document.write('&lt;script type=&quot;text/javascript&quot; src=&quot;/_layouts/InheritPermissions/InheritPermissions.js&quot;&gt;&lt;/' + 'script&gt;'); } if (document.location.pathname.toLowerCase().indexOf('_layouts/user.aspx') > -1 ) { ExecuteOrDelayUntilScriptLoaded(startScript, 'sp.js'); }" Sequence="1000"/>
  4.   
  5.   <CustomAction Location="CommandUI.Ribbon">
  6.     <CommandUIExtension>
  7.       <CommandUIDefinitions>
  8.         <CommandUIDefinition Location="Ribbon.Permission.Parent.controls._children">
  9.           <Button
  10.               Id="Ribbon.Permission.Manage.InheritDown"
  11.               Command="Ribbon.Permission.Manage.InheritDown"
  12.               Sequence="50"
  13.               Image16by16="/_layouts/images/InheritPermissions/InheritDown16x16.png"
  14.               Image32by32="/_layouts/images/InheritPermissions/InheritDown32x32.png"
  15.               LabelText="Inherit to Child Objects"
  16.               ToolTipTitle="Inherit to Child Objects"
  17.               ToolTipDescription="Reset permissions on child objects to inherit the permissions of this object.&lt;br&gt;Any custom permissions configured on child objects will be lost.&lt;br&gt;This tool is available only to farm administrators."
  18.               TemplateAlias="o1"/>
  19.         </CommandUIDefinition>
  20.       </CommandUIDefinitions>
  21.     </CommandUIExtension>
  22.   </CustomAction>
  23.  
  24. </Elements>

The event handler functionality custom PageComponent is implemented in InheritPermissions.js. If the command ‘Ribbon.Permission.Manage.InheritDown’ is invoked, we make a page postback with the event target ‘inheritDown’:

__doPostBack(‘inheritDown’, ”);

we process the postback later in our WebControl. Similarly, whether the page component should handle the command ‘Ribbon.Permission.Manage.InheritDown’ is determined by the value of the permInheritEnabled variable, the value is injected by our WebControl as well (see the RegisterStartupScript in the OnInit method of the web control later).

  1. Type.registerNamespace('CustomPermission.UI');
  2.  
  3. CustomPermission.UI.PageComponent = function CustomPermissionUI_PageComponent() {
  4.     CustomPermission.UI.PageComponent.initializeBase(this);
  5. }
  6.  
  7. CustomPermission.UI.PageComponent.initialize = function () {
  8.     var ribbonPageManager = SP.Ribbon.PageManager.get_instance();
  9.     if (null !== ribbonPageManager) {
  10.         ribbonPageManager.addPageComponent(this.instance);
  11.         ribbonPageManager.get_focusManager().requestFocusForComponent(this.instance);
  12.     }
  13. }
  14. CustomPermission.UI.PageComponent.refreshRibbonStatus = function () {
  15.     SP.Ribbon.PageManager.get_instance().get_commandDispatcher().executeCommand(Commands.CommandIds.ApplicationStateChanged, null);
  16. }
  17.  
  18. CustomPermission.UI.PageComponent.prototype = {
  19.     init: function () { },
  20.     getFocusedCommands: function () {
  21.         return ['Ribbon.Permission.Manage.InheritDown'];
  22.     },
  23.  
  24.     getGlobalCommands: function () {
  25.         return ['Ribbon.Permission.Manage.InheritDown'];
  26.     },
  27.  
  28.  
  29.     canHandleCommand: function (commandId) {
  30.         if (commandId === 'Ribbon.Permission.Manage.InheritDown') {
  31.             return permInheritEnabled;
  32.         }
  33.     },
  34.  
  35.     handleCommand: function (commandId, properties, sequence) {
  36.         if (commandId === 'Ribbon.Permission.Manage.InheritDown') {
  37.             var doInherit = confirm('You are about to inherit permissions from his object to all of the child objects having custom permissions. Any custom permissions on the child objects will be lost.');
  38.             if (doInherit) {
  39.                 __doPostBack('inheritDown', '');
  40.             }
  41.         }
  42.  
  43.     },
  44.  
  45.     getId: function () {
  46.         return "CustomPermission.UI.PageComponent";
  47.     }
  48. }
  49.  
  50. CustomPermission.UI.PageComponent.registerClass('CustomPermission.UI.PageComponent', CUI.Page.PageComponent);
  51.  
  52. CustomPermission.UI.PageComponent.instance = new CustomPermission.UI.PageComponent();
  53. CustomPermission.UI.PageComponent.initialize();
  54.  
  55. SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs("InheritPermissions.js");

The InheritPermissions WebControl is injected into the pages via an AdditionalPageHead delegate control.

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <Control
  4.     ControlAssembly="$SharePoint.Project.AssemblyFullName$"
  5.     ControlClass="InheritPermissions.InheritPermissions"
  6.     Sequence="50" Id="AdditionalPageHead"/>
  7. </Elements>

We include our control into the Safe Control Entries:

image

We activate the functionality of the control, if the host page is a permission page (e.g. it is derived from the CBaseAclPage class).

  1. class InheritPermissions : WebControl
  2. {
  3.     bool _isInheritButtonEnabled = false;
  4.  
  5.     protected override void OnInit(EventArgs e)
  6.     {
  7.         base.OnInit(e);
  8.  
  9.         CBaseAclPage baseAclPage = this.Page as CBaseAclPage;
  10.         if (baseAclPage != null)
  11.         {
  12.             baseAclPage.ParseAclObjFromRequest();
  13.  
  14.             SPSecurableObject securable = this.Securable;
  15.  
  16.             if (securable != null)
  17.             {
  18.  
  19.                 if ((this.Page.IsPostBack) && (this.Page.Request["__EVENTTARGET"] == "inheritDown"))
  20.                 {
  21.                     InheritPermissionsDown(securable);
  22.                 }
  23.  
  24.                 _isInheritButtonEnabled = ((SPFarm.Local.CurrentUserIsAdministrator(true)) && (securable.HasSubItemWithUniquePermissions()));
  25.  
  26.                 // inject a JavaScript variable into the page to support client side PageComponent
  27.                 // see canHandleCommand method of CustomPermission.UI.PageComponent (InheritPemissions.js)
  28.                 string enabledScript = (_isInheritButtonEnabled) ? "true" : "false";
  29.                 string script = string.Format(@"<script type=""text/javascript"">
  30.                                var permInheritEnabled = {0}
  31.                             </script>", enabledScript);
  32.                 this.Page.ClientScript.RegisterStartupScript(typeof(SPRibbon), "perminherit", script, false);
  33.             }
  34.         }
  35.     }
  36.  
  37.     protected override void OnPreRender(EventArgs e)
  38.     {
  39.         if (!_isInheritButtonEnabled)
  40.         {
  41.             SPRibbon ribbon = SPRibbon.GetCurrent(this.Page);
  42.             ribbon.TrimById("Ribbon.Permission.Manage.InheritDown");
  43.         }
  44.     }
  45.  
  46.     private SPSecurableObject Securable
  47.     {
  48.         get
  49.         {
  50.             SPSecurableObject securable = null;
  51.             UserRoles userRoles = this.Page as UserRoles;
  52.  
  53.             if (userRoles != null)
  54.             {
  55.                 securable = userRoles.m_Securable;
  56.             }
  57.  
  58.             return securable;
  59.         }
  60.     }
  61.  
  62.     // see further details in the code download…
  63.     
  64. }

We reuse the former code samples mentioned above: code details about how to inherit down permissions are discussed in this post, how to detect items with unique permission is shown in this post.

In the ParseAclObjFromRequest method we invoke the protected method with the same name of the parent page of CBaseAclPage type, thus achieve we a reference in this.Securable to the securable object (web site, list, folder, etc.) the permissions were set on.

We push the permissions down to child object if the page was posted back with the ‘inheritDown’ event target.

The value of the _isInheritButtonEnabled determines if the PageComponent implemented in InheritPermissions.js support the ‘Ribbon.Permission.Manage.InheritDown’ command. The value depends on two factors: it is enabled if the current user is a farm administrator and if the securable object has sub items with unique permissions. See my notes in the former post regarding the shortcomings of the check of unique permissions on child items. Even this check is not perfect we apply the same logic to remain consistent with the standard features / messages.

In my first attempt I invoked the InheritPermissionsDown method from the OnPreRender method and not from the OnInit method. However, the standard JavaScript functions of the permissions page were already registered by this time, so the message ‘Some content on this site has unique permissions which are not controlled from this page.’ were displayed even if we just pushed down the permissions right now. I found this behavior pretty disturbing. Including the logic in the OnInit method we push the permissions down before the standard scripts get registered (see the SetExceptionStatus method of the UserRoles class, derived from the CBaseAclPage type, invoked from InitPage invoked from OnLoad), so there is no inconsistent message displayed.

The button should be hidden if the user is no farm admin or if the item has no subitem with unique permissions (in this case the value of the _isInheritButtonEnabled  is false). We should invoke the TrimById method of the current ribbon instance, but in this case from the OnPreRender method.

Let’s see the new button in action. It is displayed on the ribbon if the current item has child items with unique permissions :

image

If the user clicks on the button, a warning is displayed. After confirmation of the action, the permissions are pushed down to child items.

image

You can download the sample solution from here.

March 1, 2014

How to check from code if a SharePoint Site / List has Content with Unique Permissions

Filed under: Permissions, Reflection, Security, SP 2010 — Tags: , , , — Peter Holpar @ 00:25

Assume you have a SharePoint site / list, and you would like to know if it has any content configured with unique permissions, that means any content (list / folder / list item) that does not inherit the permissions configured for the current site / list. It is easy to see this information from the user interface, when you check the permissions configured for the given site / list object through the page _layouts/user.aspx.

image

But how could we get the same information if we need to access it from code? One logical solution would be to iterate through all subsites / lists / folders / items, and see if any of them has its HasUniqueRoleAssignments property (inherited from the SPSecurableObject base class that implements the obsolete ISecurableObject interface) with value true. Although this approach should definitely do the job, it might be not the most effective and simplest solution. Let’s see instead, how it is implemented in the UserRoles class (namespace and assembly Microsoft.SharePoint.ApplicationPages) that is the class behind the user.aspx page.
The base class of the UserRoles is the CBaseAclPage class, that has a public field called m_Securable of type SPSecurableObject. This field contains the related SPWeb / SPList / SPItem object, all of these are derived classes of the SPSecurableObject class. The highlighted message on the above screenshot comes from the SetExceptionStatus() method (declared as private) of the UserRoles class. In this method we check first if we have an SPWeb or an SPList in the m_Securable field.

If the m_Securable is an SPWeb, we call its internal HasListsWithUniquePermissions() method. If the m_Securable is an SPList, we check its internal HasUniqueScopes property. As the objects of type SPItem (the third derived type of SPSecurableObject) may not have any child objects, it has no sense to check for content with unique permissions in this case.

Note: Well, I did not tell the truth in my previous sentence. We might have folders (also SPItem objects, see Folder property of the SPListItem, inherited from SPItem) in our lists, that contain items with unique permissions. In this case this fact should be indicated on the UI, but I found, that the user.aspx page / UserRoles class ignores this fact. We do as well in this post, and restrict our scope to sites and lists.

Note 2: I’ve found that the Permissions page simply ignores subsites with unique permission. Should you have a site with a subsite, and the subsite configured with its own permission set, the Permissions page of the parent site does not indicate this. This seems to be a bug, but we follow this buggy behavior in this post. Instead of this, we could call the public GetWebsAndListsWithUniquePermissions() method of SPWeb and check the count of items returned by the method. If the count is greater than one, the site has content with unique permissions. This approach works, even if not the subsite itself, but one of its list has unique permissions. But wait a minute! A new problem arose… This method returns subsites / lists that had formerly unique permissions, but later this unique permission set was removed by inheriting back the parent permissions. What to do now? The GetWebsAndListsWithUniquePermissions() method returns a collection of SPWebListInfo objects, and this type has a property called HasUniqueRoleAssignments. It means, a viable alternative of the buggy out-of-the-box solution might be:

bool hasUniquePerms = web.GetWebsAndListsWithUniquePermissions().Any(p => p.HasUniqueRoleAssignments);

But back to the original behavior… It’s quite easy to implement the same logic in our own code by accessing the above mentioned members via reflection.

I’ve created a few extension methods to achieve this goal:

  1. internal static class Extensions
  2. {
  3.     internal static bool HasSubItemWithUniquePermissions(this SPSecurableObject securable)
  4.     {
  5.         bool result = false;
  6.  
  7.         if (securable != null)
  8.         {
  9.             if (securable is SPWeb)
  10.             {
  11.                 result = (((SPWeb)securable).HasListsWithUniquePermissions());
  12.             }
  13.             else if (securable is SPList)
  14.             {
  15.                 result = ((SPList)securable).HasUniqueScopes();
  16.             }
  17.         }
  18.  
  19.         return result;
  20.     }
  21.  
  22.     internal static bool HasListsWithUniquePermissions(this SPWeb web)
  23.     {
  24.         bool result = false;
  25.  
  26.         if (web != null)
  27.         {
  28.             Type webType = typeof(SPWeb);
  29.             MethodInfo mi_HasListsWithUniquePermissions = webType.GetMethod("HasListsWithUniquePermissions", BindingFlags.NonPublic | BindingFlags.Instance);
  30.             result = (bool)mi_HasListsWithUniquePermissions.Invoke(web, new object[] { });
  31.         }
  32.  
  33.         return result;
  34.     }
  35.  
  36.     internal static bool HasUniqueScopes(this SPList list)
  37.     {
  38.         bool result = false;
  39.  
  40.         if (list != null)
  41.         {
  42.             Type listType = typeof(SPList);
  43.             PropertyInfo pi_HasUniqueScopes = listType.GetProperty("HasUniqueScopes", BindingFlags.NonPublic | BindingFlags.Instance);
  44.             result = (bool)pi_HasUniqueScopes.GetValue(list, new object[] { });
  45.         }
  46.  
  47.         return result;
  48.     }     
  49. }

Having this methods you can call the HasSubItemWithUniquePermissions method on your SPWeb / SPList instances (or even in case of SPItem as well, although in this case you will always receive a value of false), and check, if these object have any content with unique permissions.

Note: In my case I declared the helper methods as internal. However, if you wish to access the functionality from external assemblies as well, you might want to declare the HasSubItemWithUniquePermissions method (and the Extensions class itself) as public.

I will demonstrate the usage of these methods in a sample application in an upcoming post (see here).

Create a free website or blog at WordPress.com.