Showing posts with label site definitions. Show all posts
Showing posts with label site definitions. 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!

Sunday, 12 August 2007

Site definitions - custom code in the site creation process

This is the second article in a series of three, where I aim to show how to customize the site creation process (known as site provisioning) with your own API code. The full introduction and series contents can be found at http://sharepointnutsandbolts.blogspot.com/2007/07/article-series-custom-permissions-with.html. The example customization I'm using is as follows: any sites created with the definition should use a specific set of permissions, and not simply follow the default behavior of inheriting the parent site's permissions. Since this can't be done with a standard site definition (like many other things you might want to do), use of the API is required.

However, today the focus is less on the permission specifics of my example, and more on how generally to add your own code which runs in the site provisioning process. And the best thing is, it's actually very simple if you understand SharePoint Features.

There are many reasons why you might have cause to use the API in the site provisioning process. Essentially, if you can't find a way to do what you want using CAML schema in the onet.xml file, chances are you'll have to write code. Hence, it's almost easier to think of what you can do in the onet.xml file and reverse the list in order to work out scenarios which require code, but some examples which spring to mind anyhow are:

  • changing the custom master page of a site
  • creating a site column which gets it's data from a list (see my post on my Feature receiver which does this at Feature to create lookup fields on Codeplex)
  • adding custom unique permissions to a site (the example in this article series)
  • set a site property from any kind of dynamic lookup

In short, there are many scenarios.


Creating site definitions with VSeWSS

If you've ever created a site definition with Visual Studio Extensions for Windows SharePoint Services, you'll notice that the VS project it gives you contains a file called SiteProvisioning.cs. Inside is an event-handler method, where you can add your custom code which will execute when a site is created from the definition. The class looks like this:

namespace COB.Demos.SiteDefinition

{

    public partial class ProjectXSiteDefinition

    {

        /// <summary>

        ///  Define your own feature activation action code here

        /// </summary>

        public void OnActivated(SPFeatureReceiverProperties properties)

        {

            // my code here..

        }

    }

}

 

The plumbing behind all this is interesting. At first glance, the method signature looks like a Feature receiver, but it's actually not. However, examining the VS project (you'll need to build the project with F5 at least once to generate the files) reveals that VSeWSS has in fact created some Features in the background. These files can be found under the bin\Debug\solution folder in your VS project (hidden by default - you'll need to do a 'Show All Files' in Visual Studio Solution Explorer). If you do some more delving around to see exactly what VSeWSS is doing, you'll find the following:

  • 2 hidden Features have been created - 1 deploys the 'default.aspx' file, the other has no 'elements' file but is hooked up to a Feature receiver - this is a class in an assembly named the same as your VS project. If you check the GAC, you will indeed find this assembly there.
  • a line similar to the following has been added to the onet.xml file under the 'WebFeatures' element:

    <Feature ID="67b2507c-8822-41dc-b939-3d8f34b5ad13" />


    Notably, this is the ID of the Feature which is hooked up to the Feature receiver.
  • Using Reflector on the assembly containing the Feature receiver shows that the main event-handler method performs some processing and then calls into the OnActivated method shown above, i.e. the place where VSeWSS provides for you to add your own code to execute when sites are created. This code is actually contained in the SiteProvisioning.Internal.cs file within the VS project. (If you're curious as to what on earth all the code in here is doing, the answer as far as I can tell is nothing when site definitions are created with the VSeWSS project template. However, this code is also found when Solution Generator is used to extract a site definition - in that case there are some fixups which need to be done, and this is the code which is used.)

So in summary, VSeWSS creates a hidden Feature is added to the 'WebFeatures' section of the onet.xml so that it is automatically activated when the definition is used to create a web*. The Feature is hooked up to a Feature receiver which calls the OnActivated method where your custom code lives.

*(Note that if the definition is used to create a site definition, the root web is also created automatically so the Feature would also be activated then. Also note the feature needs to be already installed in the farm for it to be activated in this way).

What we can derive from this is that there's no 'special place' in the site provisioning process to inject custom code, but it can be accomplished by use of a Feature receiver. So if you don't want to use VSeWSS to create site definitions, this is the technique to use to add your custom code to the site creation process.

In terms of what that code might look like, a 'Hello World' example could be:

public void OnActivated(SPFeatureReceiverProperties properties)

{

     SPWeb currentWeb = null;

     SPSite currentSite = null;

     object oParent = properties.Feature.Parent;

 

     if (properties.Feature.Parent is SPWeb)

     {

         currentWeb = (SPWeb)oParent;

         currentSite = currentWeb.Site;

     }

     else

     {

         currentSite = (SPSite)oParent;

         currentWeb = currentSite.RootWeb;

     }

 

     currentWeb.Title = "Set from provisioning code at " +  DateTime.Now.ToString();

     currentWeb.Update();

}


Hopefully this illustrates that it's quite simple to write code which sets properties on sites created from the definition. Generally the SPWeb object is the entry point, and any property which can be modified can be modified using the API. So, this is a pretty powerful technique which can be used in many scenarios.

If you have this type of requirement, I'd definitely recommend using VSeWSS to simplify the process. It's certainly possible to hook everything up manually and package it into a Solution, but the tool does save a large amount of hassle. However as usual with VSeWSS, the price of this is some flexibility. As my sample code in the final article will show, it's sometimes useful to pass data into Features by using Feature properties, and this unfortunately is not supported by VSeWSS. So in case it's useful, the following link provides a zip file containing a Solution/Feature which uses the above technique, without using VSeWSS:

http://sharepointchris.googlepages.com/customcodewithsitedefinitions

In the next and final article, I'll cover the specifics of using the API to modify site permissions as sites are created. As is hopefully clear, this is in conjunction with the technique detailed here so the net result is that the specific permissions are set 'automatically', courtesy of the Feature which is automatically activated against a site when it is created.

Sunday, 5 August 2007

Creating, deploying and updating custom site definitions

This is the first article in a series of three where we'll discuss custom site definitions, and in particular how to run your own custom code in the site creation process. This technique is useful if you need to make any customizations using the API beyond what can normally be accomplished with a site definition. In my series (for the full series contents, see my introduction at http://sharepointnutsandbolts.blogspot.com/2007/07/article-series-custom-permissions-with.html), I use the example of creating a site definition with specific security permissions 'attached' - so that when any sites are created using the definition, specific permissions are applied which are different to those of the parent site (this is unlike the default, which is for new sites to inherit the parent site's permissions). More background can be found in the introductory article linked to earlier.

In this article we'll start with the site definition basics - I'll also supply the set of files used in this article in a link at the end. Fundamentally, a custom site definition is a template from which new SharePoint sites can be created. Customizations can be packaged into the definition, so that they are present automatically in sites created from the template. Consider the following about site definitions:

  • they are created by copying an existing (e.g. out-of-the-box) site definition and adding customizations
  • XML files (in particular the onet.xml file) specify what a site definition consist of (i.e. .aspx pages, images, web parts, functionality [in the form of SharePoint Features])
  • they provide a similar functionality to site templates (.stp files) - for a discussion of the differences between site definitions and site templates see http://msdn2.microsoft.com/en-us/library/aa979683.aspx
  • site definitions can be deployed using a SharePoint Solution package (.wsp file), so that files do not need to be manually copied to each web server in a SharePoint farm

The process for creating site definitions is well-documented in the WSS 3.0 SDK at http://msdn2.microsoft.com/en-us/library/ms454677.aspx, so I actually won't cover it here but would encourage you to follow the link. However I will quickly run through some of the key elements in the onet.xml file to go over what can be done with site definitions. For an example, let's take an extract of the onet.xml file for a publishing (note for clarity this is an extract only - a full version is available at the link at the end of the article):

<Configuration ID="0" Name="BLANKINTERNET">

  <SiteFeatures>

    <Feature ID="A392DA98-270B-4e85-9769-04C0FDE267AA">

      <!-- PublishingPrerequisites -->

    </Feature>

    <Feature ID="7C637B23-06C4-472d-9A9A-7C175762C5C4">

      <!-- ViewFormPagesLockDown -->

    </Feature>

    <Feature ID="F6924D36-2FA8-4f0b-B16D-06B7250180FA">

      <!-- Office SharePoint Server Publishing -->

    </Feature>

  </SiteFeatures>

  <WebFeatures>

    <Feature ID="00BFEA71-4EA5-48D4-A4AD-305CF7030140" > </Feature>

    <Feature ID="22A9EF51-737B-4ff2-9346-694633FE4416">

      <!-- Publishing -->

      <Properties xmlns="http://schemas.microsoft.com/sharepoint/">

        <Property Key="ChromeMasterUrl" Value="~SiteCollection/_catalogs/masterpage/BlueBand.master"/>

        <Property Key="WelcomePageUrl" Value="$Resources:cmscore,List_Pages_UrlName;/default.aspx"/>

        <Property Key="PagesListUrl" Value=""/>

        <Property Key="AvailableWebTemplates" Value="*-ProjectX#0"/>

        <Property Key="AvailablePageLayouts" Value="ThreeColumnLayout.aspx"/>

        <Property Key="AlternateCssUrl" Value="" />

        <Property Key="SimplePublishing" Value="false" />

      </Properties>

    </Feature>

  </WebFeatures>

  <Modules>

    <Module Name="LoginPage" />

    <Module Name="Images" />

    <Module Name="Home" />

  </Modules>

</Configuration>


  • The Configuration element represents settings to be used with the selected definition, allowing groups of settings to be reused across definitions for flexibility
  • SiteFeatures /WebFeatures - specifies which Features should be automatically activated when the definition is used to create a site collection or child site respectively. This is key to our overall aim in this series of creating a site definition which applies custom unique permissions to sites created from it.
  • AvailableWebTemplates property of publishing feature - can be used to restrict which site definitions can be used to create child sites within sites created from this definition. This can be useful to prevent your content creators adding a team site onto your public-facing .com website for example. Here I'm specifying that only the 'ProjectX' definition with Configuration '0' can be used for child sites.
  • AvailablePageLayouts property of publishing feature - can be used to restrict which page layouts can be used within sites created from this definition. Again, this can be useful in controlling the look and feel of your website.
  • Modules - a module is a set of files to be automatically added when sites are created. Note it's also possible to specify which web parts should be added by default to a web part page by using the AllUsersWebPart element.


Deploying site definitions


What I really want to focus on however, is how site definitions can be packaged into a Solution to simplify deployment. This is typically most useful when deploying to multiple environments (e.g. test, staging, production) and/or deploying to a farm which consists of multiple SharePoint web servers. If you don't have this requirement, you may want to consider the simpler process of copying the XML files around manually, as detailed in the WSS SDK.

Before we start, let me highlight that Visual Studio Extensions for Windows SharePoint Services (VSeWSS) is a useful tool for creating and deploying custom site definitions, and I'd recommend looking into it for this requirement if you haven't already. However, I'm illustrating the 'manual' way here to hopefully provide an understanding of the nuts and bolts.

So, to package these files as a Solution manually, we follow the SDK instructions to get our customized onet.xml and webtemp*.xml files, but then place the files in a similar folder structure to the existing site definition files found under the 12 folder. I recommend creating a Visual Studio project as the best way to group these files (VSeWSS also follows this approach). This means you should end up with something looking like this:



Note we also have a .ddf file which is used with makecab.exe to build the Solution package - see my article on building and deploying Solution packages for details on the full process here. Effectively, we need to build the Solution by passing the .ddf file as a parameter to makecab.exe, and then run the STSADM -o addsolution and STSADM -o deploysolution commands to deploy to our target SharePoint site.

Once this is done, on the 'create site' screen we should see our new template appear:



Users can now use this definition to create sites which automatically have all the functionality and appearance we specified upfront. If we then need to deploy the definition to other environments, it's a simple case of copying the .wsp file there and running the STSADM commands. We are now well on our way to creating a site definition with custom permissions associated with it.

The set of files used in this article are at http://sharepointchris.googlepages.com/creatinganddeployingcustomsitedefinition

Updating existing site definitions


Finally, a quick note on updating definitions. Care needs to be taken here as often you will want to update files which are in use by sites already created with the definition - this can break things! Generally adding to a definition is OK, but modifying/deleting things can cause problems.

So a good technique is to copy the existing definition, add the updates and deploy for new sites to use, but also hide the earlier version so it cannot be used going forward. This is accomplished by removing the webtemp*.xml file from the TEMPLATE\\XML directory. Any sites already provisioned from the earlier version of the definition will continue to run fine, since you're leaving the actual definition (onet.xml etc.) intact over in TEMPLATE\SiteTemplates.

Remember also that new Features can be stapled to existing site definitions (affecting only new sites which are created, not existing ones), and this can be useful in avoiding having to update the site definition itself. See my article on Feature-stapling for more details.

So that's site definition basics. Next in the series - how to go beyond simple site definitions : add custom code which will execute when sites are created!

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!

Monday, 25 June 2007

UK SharePoint user group - Customizing SharePoint the supported way

The UK SharePoint user group is meeting this Thursday (28th June) at the Microsoft campus in Reading. There are two sessions, one of which I'm presenting:

Customizing MOSS the supported way – from end-user to admin interfaces

One of the key concepts SharePoint developers should be aware of is that modifications to core shipped files are typically unsupported. This session looks at ways in which you can implement the kind of customizations your users may ask for, but without modifying the original files. Aspects covered include custom site definitions, modifying the site administration/central administration areas, and changing the user controls SharePoint uses with the DelegateControl architecture. The session is packed with demos, and also contains some quick tips on the best way to make other common customizations not shown in the detailed examples.

Speaker: Chris O'Brien

..and also:

Groove: A powerful tool for collaboration, that is now part of Office 07.

In this session, we review the basic functionality and then show, using the latest Microsoft templates (including issue tracking and document review), how Groove is not just a tool but a platform for delivering functionality. We'll also look at how Groove and SharePoint can both effectively work together.

Speaker: TBA

If you're in the area and would like to register, simply leave your name on the following thread over at the UK user group site:

http://suguk.org/forums/thread/3534.aspx

I'll be posting my slides and sample code after the event. The content on customizing the site admin/central admin areas will probably make it's way into separate posts for clarity.