Showing posts with label security. Show all posts
Showing posts with label security. Show all posts

Sunday, 19 August 2007

Automatically setting custom permissions on new sites

This is the third and final article in a series of three, where I demonstrate how how to perform custom processing in the site creation process. See 'Article series - custom permissions with a site definition' for the full run down on the article series. Specifically, I wanted to show how to use code to modify sites as they are created, in order to do things which aren't normally possible with site definitions/site templates. In the example I'm using, I'm setting custom permissions on the created sites. A scenario where this might be useful is if say, your organization is using SharePoint in a collaboration sense and users are creating sites themselves, but certain sites need to be secured so that access is restricted to specific users. Often end users might not understand the details of the SharePoint security model, so it would be nice if we could take care of this automatically for them.

The solution

In the last article 'Site definitions - custom code in the site creation process', I showed how it's possible to use a Feature receiver in conjunction with the site definition to do pretty much anything you might want to do as sites are created. Based on this approach, my solution is based around the following:



  • Custom list which stores the list of authorized users in the site collection's root web. This list stores a mapping of users to the permissions they should have in the created site.
  • Custom site definition, created by copying an existing definition as described in the SDK.
  • Feature which doesn't have any Feature elements defined, but is attached to a Feature receiver. A property is defined to pass in the name of the permissions list.
  • Feature receiver code which uses the object model to iterate the permission list and grant appropriate permissions to each user listed.

So let's break down each element of the solution. Note that all the code etc. is available for download and is linked to at the end of the article. First of all, we create a list which looks like this (click to enlarge):



Looking at the the edit view for the list (shown below), we see that the two key columns are:

  • 'User' - Person or Group data type
  • 'Permission level' - choice data type, with allowable values 'Owner', 'Contributor' and 'Viewer'




If we are creating sites which are restricted we would probably want to secure the list so that curious users cannot add themselves, and then gain access to any future restricted sites which are created.


So that's the list. The site definition in my example doesn't do anything special - it's just a copy of the 'BLANKINTERNET' definition to keep the example simple. However, 'Creating, deploying and updating custom site definitions' has more information on the kinds of customizations you can make with your site definitions.

The Feature is defined to reference the Feature receiver class we are creating. This ensures our custom code will run when the Feature is activated.

<Feature  Title="SiteProvisioning" Id="7C020FFF-FF42-4fe2-8A9B-9BCA0D5F8001" Description="" Version="1.0.0.0" Scope="Web"

          Hidden="TRUE" DefaultResourceFile="core"

          ReceiverAssembly="COB.Demos.SiteDefinition, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cd9b418c14cff42e"

          ReceiverClass="COB.Demos.ProjectXSiteDefinition.SiteProvisioning" xmlns="http://schemas.microsoft.com/sharepoint/">

  <Properties>

    <!-- could also retrieve list by GUID by passing in as property and amending code slightly -->

    <Property Key="PermissionsListName" Value="Project X Permissions" />

  </Properties>

</Feature>


The 'ReceiverAssembly' and 'ReceiverClass' attributes have the values which point to our Feature receiver class which contains our custom code. Note also we are passing in a value which can be retrieved in the code by using a Feature property. This can be used as a more flexible alternative to hardcoding values in the class - in this case we are using it pass in the name of the 'authorized users' list, meaning that this Feature can be reused across different requirements (which would use different lists). A minor tweak to the code and property will allow you to use the list GUID if you prefer, though note that list GUIDs will be different if the list is recreated in another SharePoint environment.

So that's all great but what ensures the Feature gets activated? Ah you've lost the thread since the last article haven't you?! The Feature is activated automatically when sites are created using the definition courtesy of this line in the WebFeatures section of the onet.xml file which specifies what the site definition consists of:


<Feature ID="7C020FFF-FF42-4fe2-8A9B-9BCA0D5F8001">


And so finally, onto the code which we have written in our Feature receiver class:

using System;

using Microsoft.SharePoint;

 

namespace COB.Demos.ProjectXSiteDefinition

{

    class SiteProvisioning : SPFeatureReceiver

    {

        public override void FeatureActivated(SPFeatureReceiverProperties properties)

        {

            SPWeb currentWeb = null;

            SPSite currentSite = null;

            object oParent = properties.Feature.Parent;

 

            // retrieve the permissions list by name..

            string sPermsListName = properties.Definition.Properties["PermissionsListName"].Value;

 

            // only perform processing if the site definition is being used to create a web within the expected site collection..

            if (properties.Feature.Parent is SPWeb)

            {

                currentWeb = (SPWeb)properties.Feature.Parent;

                currentSite = currentWeb.Site;

 

                SPList permsList = currentSite.RootWeb.Lists[sPermsListName];

 

                // ensure the web is set to use unique permissions, we won't copy existing permissions from parent site..

                if (!currentWeb.HasUniqueRoleAssignments)

                {

                    currentWeb.BreakRoleInheritance(false);

                }

 

                foreach (SPListItem perm in permsList.Items)

                {

                    string sPermLevel = (string)perm["Permission level"];

 

                    SPFieldUserValue userValue = (SPFieldUserValue)perm.Fields["User"].GetFieldValue(perm["User"].ToString());

                    SPUser user = userValue.User;

                    setPermission(currentWeb, user, sPermLevel);

                }

 

                currentWeb.Update();

            }

        }

 

        private void setPermission(SPWeb currentWeb, SPUser user, string sPermLevel)

        {

            SPGroup permissionsGroup = null;

 

            switch (sPermLevel)

            {

                case "Owner":

                    permissionsGroup = currentWeb.AssociatedOwnerGroup;

                    break;

                case "Visitor":

                    permissionsGroup = currentWeb.AssociatedVisitorGroup;

                    break;

                case "Member":

                    permissionsGroup = currentWeb.AssociatedMemberGroup;

                    break;

                default:

                    throw new NotImplementedException(string.Format("Group '{0}' not yet implemented.", sPermLevel));

                    break;

            }

 

            permissionsGroup.AddUser(user);

      }

 

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

        {

        }

 

        public override void FeatureInstalled(SPFeatureReceiverProperties properties)

        {

        }

 

        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)

        {

        }

    }

}


Stepping through the code, we first find the 'authorized users' list, tell SharePoint we don't want to inherit permissions for the web being created, and then iterate through the list adding each user to the appropriate security group for the web as we find them. Note the SPWeb object has properties to allow you to easily reference the 'Owners', 'Visitors' groups etc. - these will be named in the form '[My site name] Owners' so this avoids you having to do any nasty string concatenation here.

In terms of how where this class fits alongside the rest of the files, I just store it in the same VS project. In this example I'm not using VSeWSS to create the Solution, but as I mentioned last time this can make things much simpler. I'm choosing not to here because I wanted to pass the list name using a Feature property, and in the current version VSeWSS does not have the flexibility to support this. In any case, having the class in the same VS project means that when the project is compiled, the receiver assembly is built and is output to the same project's bin directory. My .ddf file which is passed to makecab.exe then adds this dll and all the other files to the Solution package (.wsp) which is built for deployment. You may choose to use a post-build type solution (MSBuild, post-build script etc.) to automatically deploy this to your local environment on every compile, either by straight XCOPY or using the STSADM commands - my 'Building and deploying SharePoint Solution packages' article has more information on this. So my overall project structure looks like this:




So for deployment this means everything is in one package - on deployment the assembly hits the GAC before the Feature activation process runs, meaning the Feature receiver code is in place and will execute successfully. Once deployed, the site definition is available for use and new sites can be created from it. If we go ahead and create a site, if we look at the different security groups we see the appropriate users have been added according the configuration data we stored in the 'authorized users' list.

Owners group:



Visitors group:


So that's it! We now have our solution which enables us to 'package up' custom permissions with a site definition. Clearly we could store the permissions mappings in some other store such as a database table or XML file, but all things being equal I'm a big advocate of using SharePoint lists to store such data. The user interface is provided for you, and security can be applied to ensure standard users are unaware of the list's presence.

All the files I used can be downloaded from http://sharepointchris.googlepages.com/sitedefinitionwithcustompermissions.

In terms of using the files, you can follow this process to use the technique:

  • create the authorized users list from the list template I supply - 'PermissionsListTemplate.stp'
  • add your users and permission mappings to the list
  • add your site definition files to the appropriate places in the VS project, and modify the onet.xml file etc. as necessary
  • if files have been added, amend the .ddf file to include these and rebuild the Solution package
  • deploy the package using STSADM (a .bat file is included in the zip) and create sites from the definition!

Hopefully this series has been some use. While the approach is certainly useful in my scenario of rolling out a site definition used to create automatically secured sites from, in general terms you can use the technique to do any custom processing you want in the site creation process. If there are any queries please leave a comment!

Tuesday, 31 July 2007

Article series - custom permissions with a site definition

This is an introductory article which gives the background to an interesting problem I've seen recently. Over the next 3 articles, I'll be looking at a solution to the problem and will discuss the following:

What I aim to show is how to deploy a site definition which has custom permissions 'attached' to it. By this I mean that any sites created with this template will acquire a set of custom permissions, without a user/admin having to configure them separately. What we're effectively doing is automatically putting a set of users into SharePoint site groups (at the time of site creation) - assuming the site was called 'HR', these would be:

  • HR owners
  • HR members
  • HR visitors

This can be extremely useful in certain scenarios when users are creating their own sites and workspaces in SharePoint, i.e. collaboration scenarios. One example might be a situation where say, the senior management of your organization (who are all fairly familiar with SharePoint of course ;-)) want to create sites for an important initiative they have. They want to do this on an ad-hoc basis without involving IT, but the sites must be restricted access such that only certain people can use them. Additionally, the requirements might be that some people have full control, but others can only read.

Since these security requirements are probably different to those of the parent site collection, the site must implement 'unique permissions'. This is key - if the site can share (and that's share, not inherit - think about the difference) the permissions of the parent site, then custom permissioning is not required. Otherwise if you are happy to set up the custom permissions manually each time a site is created, that can be done simply too without a solution like mine. SharePoint provides dialog screens for this during the site creation process, if you select 'Use unique permissions' in the Permissions section.

However, when it should happen automatically, you need to perform some custom processing during the site provisioning process, which is what I'm illustrating here. So with the background out of the way, the articles will consist of the following:

Article 1 - Site definition basics

  • Process for creating custom site definitions
  • How to deploy using a SharePoint Solution package (.wsp)

Article 2 - Custom code in the site provisioning process

  • When this is necessary
  • How to get your code to run at this time

Article 3 - how to implement unique permissions for sites created with the definition

  • Storing permissions data in a secured SharePoint list
  • Feature Receiver code to apply the permissions

Article 1 in the series should be published in a couple of days. See you then!