Workaround: PnP Sites Core not provisioning Sub Web Security!

Introduction

Let’s start by reminding ourselves how SharePoint groups work: when you add a SP group to a sub web, it is actually added to the Site Collection SharePoint groups. And when you assign some permissions (e.g. ‘Contribute’) to that group in the sub web, two things may happen, depending on whether the sub web inherits parent permissions or not:

  • permissions are inherited – the group is given ‘Contribute’ permissions in the Site Collection and all sub webs receive it too, implicitly.
  • permissions inheritance is broken – the group is given ‘Contribute’ permissions only on the Sub Web! In the Site Collection root you will still see the group under ‘People & Groups’ page (because as we said, the groups are provisioned on Site Collection level), but you will not see in the ‘Permissions’ page, because it is given permissions only in the sub web.

The issue

Ok, now consider the following scenario: you are provisioning a PnP Template against a sub web. e.g.:

<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2015/12/ProvisioningSchema">
  <pnp:Preferences Generator="OfficeDevPnP.Core, Version=2.3.1604.0, Culture=neutral, PublicKeyToken=3751622786b357c2" />
  <pnp:Templates ID="CONTAINER-TEMPLATE-XXX">
    <pnp:ProvisioningTemplate ID="TEMPLATE-XXX" Version="1">
      <pnp:Security>
        <pnp:SiteGroups>
          <pnp:SiteGroup Title="My Group" Owner="SHAREPOINT\system" AllowMembersEditMembership="false" AllowRequestToJoinLeave="false" AutoAcceptRequestToJoinLeave="false" OnlyAllowMembersViewMembership="true" />
        </pnp:SiteGroups>
        <pnp:Permissions>
          <pnp:RoleAssignments>
            <pnp:RoleAssignment Principal="My Group" RoleDefinition="Full Control" />
          </pnp:RoleAssignments>
        </pnp:Permissions>
      </pnp:Security>
    </pnp:ProvisioningTemplate>
  </pnp:Templates>
</pnp:Provisioning>

what would you expect to happen when the PnP engine runs this? Personally, I would expect that the engine sees the ‘My Group’, creates it under the Site Collection and assigns ‘Full Control’ permission level to it on the Sub Web level. Now, as we explained, if the Sub Web permission inheritance is not broken, that would actually mean giving the ‘My Group’ ‘Full Control’ rights within the entire site collection. But this is the exact reason why the PnP provisioning engine ignores the Security element for sub webs! Because the PnP team has a principle of ‘provisioning a site can never change settings in other sites‘. Yes, PnP provisioning engine will simply ignore your Security instructions, when executed against a sub web.

However, in the case when the sub web has broken permission inheritance, I disagree! When the inheritance is broken, the Group will be created on the Site Collection level, but it will only be given rights on the sub web, hence – not actually changing anything in the other (parent) site!

Solution

To overcome this issue, I have created a PnP extension, which will perform the Security element provisioning on the sub web exactly the way I think it should be – if the web is a sub web and the permission inheritance is broken – go ahead with the Security provisioning, instead of ignoring it.

Template

First you have to change your template to:

<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2015/12/ProvisioningSchema">
  <pnp:Preferences Generator="OfficeDevPnP.Core, Version=2.3.1604.0, Culture=neutral, PublicKeyToken=3751622786b357c2" />
  <pnp:Templates ID="CONTAINER-TEMPLATE-XXX">
    <pnp:ProvisioningTemplate ID="TEMPLATE-XXX" Version="1">
      <pnp:Security>
        <pnp:SiteGroups>
          <pnp:SiteGroup Title="My Group" Owner="SHAREPOINT\system" AllowMembersEditMembership="false" AllowRequestToJoinLeave="false" AutoAcceptRequestToJoinLeave="false" OnlyAllowMembersViewMembership="true" />
        </pnp:SiteGroups>
        <pnp:Permissions>
          <pnp:RoleAssignments>
            <pnp:RoleAssignment Principal="My Group" RoleDefinition="Full Control" />
          </pnp:RoleAssignments>
        </pnp:Permissions>
      </pnp:Security>
      <pnp:Providers>
        <pnp:Provider Enabled="true" HandlerType="My.Assembly.NameSpace.MyClassName, My.Assembly" />
      </pnp:Providers>
    </pnp:ProvisioningTemplate>
  </pnp:Templates>
</pnp:Provisioning>

Which basically tells the PnP engine to search for the My.Assembly.dll and within it, it shall find My.Assembly.NameSpace.MyClassName class, which implements IProvisioningExtensibilityHandler interface. Search Google for more information on PnP extension engine.

Extension

Here is the C# code for your extension class, which is basically the original PnP method, but with modifications to provision Security against a sub web, when the sub web has broken inheritance.

Conclusion & Download

With this extension in place, you can now provision Security elements against a sub web, when the sub web has broken inheritance. It overrides the default PnP provisioning engine, which ignores the Security element, as soon as it sees that it’s a sub web.

Again, my opinion is that sub web Security should not be ignored, when the sub web has broken inheritance, because it doesn’t change in any way any other site, except adding the Group container on the Site Collection level (but without giving it any permissions!).

You can find the code samples as a here.

Cheers!

Advertisements

When using [TaxonomyField].SetFieldValue you get ‘set AllowUnsafeUpdates to true’ exception

In some situations (such as ajax requests and other non-postback methodologies), you might encounter the following error:

Updates are currently disallowed on GET requests.¬† To allow updates on a GET, set the ‘AllowUnsafeUpdates’ property on SPWeb

This might happen if you are trying to set a Term type of object in an SPList item’s field. e.g.:

SPListItem myItem = … ;

Term myTerm = … ;

var taxonomyField = myItem[“MyTaxonomyField”] as TaxonomyField;

taxonomyField.SetFieldValue(item, term);

myItem.Update();

If you execute this in the backend during an ajax request (or maybe some other use cases too) this will fail with the exception mentioned above.

If you are reading this article it means that¬† you have already set the SPWeb’s AllowUnsafeUpdates to true and the problem still persists. For some reason this is the default SharePoint behavior and it doesn’t matter how many times you set the AllowUnsafeUpdates to true, it will still be ignored. So, we need another solution.

You might be tempted to use a solution (1, 2) such as setting a TaxonomyFieldValue to the field, instead of Term directly. Such as:

string termString = String.Concat(thisYear.GetDefaultLabel(1033), TaxonomyField.TaxonomyGuidLabelDelimiter, thisYear.Id);

TaxonomyFieldValue tagValue = new TaxonomyFieldValue(string.Empty);
tagValue.PopulateFromLabelGuidPair(termString);
oField.SetFieldValue(i, tagValue);

 

That would be a mistake because such approach breaks the Search Crawl (i.e. the crawler won’t crawl your field properly). So what is the correct solution? As crazy as it might sound, it is to set the HttpContext.Current to null just prior to setting the term field and then restoring it back.

So, solution:

var httpContext = HttpContext.Current;

HttpContext.Current = null;

SPListItem myItem = … ;

Term myTerm = … ;

var taxonomyField = myItem[“MyTaxonomyField”] as TaxonomyField;

taxonomyField.SetFieldValue(item, term);

myItem.Update();

HttpContext.Current = httpContext;

 

What does this achieve? It lies to SharePoint that you don’t have a HttpContext (i.e. you are not triggering this code from the browser), so it won’t validate your context and won’t throw an exception about AllowUnsafeUpdates.

 

Best Regards,

Hristo