Tuesday, July 20, 2010

Create associated Sharepoint groups (AssociatedOwnerGroup, AssociatedMemberGroup and AssociatedVisitorGroup) programmatically

If you create web site in Sharepoint and choose “Use unique permissions” option then after site template will be applied to the newly created site you will be redirected to the standard page “Set Up Groups for this Site” (_layouts/permsetup.aspx page):

image

Using this page you are able to create 3 standard groups for new site: Visitors, Members and Owners. But how these groups can be created programmatically? Suppose that we have customized process of site creation (e.g. use custom site creation page) with custom site template and want to create mentioned groups automatically without additional manual steps required from administrator.

In object model mentioned groups correspond to the following SPWeb properties: AssociatedOwnerGroup, AssociatedMemberGroup and AssociatedVisitorGroup. First of all we need to know what permissions have these groups on associated site. Here they are:

Group Permissions
AssociatedOwnerGroup SPRoleType.Administrator
AssociatedMemberGroup SPRoleType.Contributor
AssociatedVisitorGroup SPRoleType.Reader

Now we can create associated groups programmatically using the following code:

   1: private void setupSecurity(SPWeb web)
   2: {
   3:     // create groups
   4:     string ownersName = SPResource.GetString("DefaultOwnerGroupName",
   5: new object[] {web.Title});
   6:     var owners = SecurityHelper.CreateSiteGroup(web, ownersName);
   7:     owners.Owner = owners;
   8:     owners.Update();
   9:  
  10:     string visitorsName = SPResource.GetString("DefaultVisitorGroupName",
  11: new object[] {web.Title});
  12:     var visitors = SecurityHelper.CreateSiteGroup(web, visitorsName);
  13:     visitors.Owner = owners;
  14:     visitors.Update();
  15:  
  16:     string membersName = SPResource.GetString("DefaultMemberGroupName",
  17: new object[] {web.Title});
  18:     var members = SecurityHelper.CreateSiteGroup(web, membersName);
  19:     members.Owner = owners;
  20:     members.Update();
  21:  
  22:     // assign permissions
  23:     SecurityHelper.AssignGroupRoleToSecurableObject(web, web,
  24: SPRoleType.Reader, visitors);
  25:     SecurityHelper.AssignGroupRoleToSecurableObject(web, web,
  26: SPRoleType.Contributor, members);
  27:     SecurityHelper.AssignGroupRoleToSecurableObject(web, web,
  28: SPRoleType.Administrator, owners);
  29:  
  30:     // associate
  31:     web.AssociatedOwnerGroup = web.SiteGroups[ownersName];
  32:     web.Update();
  33:  
  34:     web.AssociatedMemberGroup = web.SiteGroups[membersName];
  35:     web.Update();
  36:  
  37:     web.AssociatedVisitorGroup = web.SiteGroups[visitorsName];
  38:     web.Update();
  39: }

First of all we need get names of associated groups. In order to get them we use the same approach which is used in OTB permsetup.aspx page, e.g.:

   1: string ownersName = SPResource.GetString("DefaultOwnerGroupName",
   2: new object[] {web.Title});

Also if we want to do things like they done in OTB page, we need to set SPGroup.Owner property to AssociatedOwnerGroup (you can check it after creation of site using OTB create page). AssociatedOwnerGroup will be owner of itself.

After that we need to assign permissions to created site groups using table mentioned above. We do it using the following code:

   1: // assign permissions
   2: SecurityHelper.AssignGroupRoleToSecurableObject(web, web,
   3: SPRoleType.Reader, visitors);
   4: SecurityHelper.AssignGroupRoleToSecurableObject(web, web,
   5: SPRoleType.Contributor, members);
   6: SecurityHelper.AssignGroupRoleToSecurableObject(web, web,
   7: SPRoleType.Administrator, owners);

Here is the code of SecurityHelper utility class:

   1: public static class SecurityHelper
   2: {
   3:     public static SPGroup CreateSiteGroup(SPWeb web, string groupName)
   4:     {
   5:         if (isGroupExist(web, groupName))
   6:         {
   7:             throw new Exception(string.Format("Group '{0}' already exists",
   8:                 groupName));
   9:         }
  10:         web.SiteGroups.Add(groupName, web.SiteAdministrators[0], null,
  11:             string.Empty);
  12:  
  13:         return web.SiteGroups[groupName];
  14:     }
  15:  
  16:     private static bool isGroupExist(SPWeb web, string groupName)
  17:     {
  18:         return web.SiteGroups.Cast<SPGroup>().Any(g =>
  19:             string.Compare(g.Name, groupName, true) == 0);
  20:     }
  21:  
  22:     public static void AssignGroupRoleToSecurableObject(SPWeb web,
  23:         ISecurableObject securableObject, SPRoleType roleType, SPGroup group)
  24:     {
  25:         SPRoleAssignment roleAssignment = new SPRoleAssignment(group);
  26:         SPRoleDefinition roleDefinition = web.RoleDefinitions.GetByType(roleType);
  27:         assignRoleToSecurableObject(securableObject, roleDefinition, roleAssignment, true);
  28:     }
  29:  
  30:     public static void AssignGroupRoleToSecurableObject(SPWeb web,
  31:         ISecurableObject securableObject, SPRoleType roleType, SPGroup group,
  32:         bool copyRoleAssignment)
  33:     {
  34:         SPRoleAssignment roleAssignment = new SPRoleAssignment(group);
  35:         SPRoleDefinition roleDefinition = web.RoleDefinitions.GetByType(roleType);
  36:         assignRoleToSecurableObject(securableObject, roleDefinition, roleAssignment,
  37:             copyRoleAssignment);
  38:     }
  39:  
  40:     private static void assignRoleToSecurableObject(ISecurableObject securableObject,
  41:         SPRoleDefinition roleDefinition, SPRoleAssignment roleAssignment,
  42:         bool copyRoleAssignment)
  43:     {
  44:         roleAssignment.RoleDefinitionBindings.Add(roleDefinition);
  45:         if (!securableObject.HasUniqueRoleAssignments)
  46:         {
  47:             securableObject.BreakRoleInheritance(copyRoleAssignment);
  48:         }
  49:         securableObject.RoleAssignments.Add(roleAssignment);
  50:     }
  51: }

And last step – associate created groups with SPWeb:

   1: // associate
   2: web.AssociatedOwnerGroup = web.SiteGroups[ownersName];
   3: web.Update();
   4:  
   5: web.AssociatedMemberGroup = web.SiteGroups[membersName];
   6: web.Update();
   7:  
   8: web.AssociatedVisitorGroup = web.SiteGroups[visitorsName];
   9: web.Update();

Remaining question is where should we call this setupSecurity(…) method in our customized site creation process? One obvious place – feature receiver in some feature which is activated in our site automatically (this feature should be added into onet.xml of our custom site template). Unfortunately if you create custom portal based on OTB publishing site template, it is not suitable place – because Sharepoint will override associated groups somewhere in later execution steps (you will have null in SPWeb properties which correspond to associated groups, except Visitors group). One suitable place I found for this – is custom portal provisioning provider:

   1: public class CustomPortalProvisioningProvider : SPWebProvisioningProvider
   2: {
   3:     public override void Provision(SPWebProvisioningProperties props)
   4:     {
   5:         // PortalProvisioningProvider is sealed so aggregate it instead of inheritance
   6:         var publishingPortalProvisioningProvider =
   7:             new PortalProvisioningProvider();
   8:         publishingPortalProvisioningProvider.Provision(props);
   9:  
  10:         this.setupSecurity(props.Web);
  11:     }
  12:     
  13:     ...
  14: }

But this approach has own problem: code will be executed using language of site in context of which site creation process was started, i.e. not in context of newly created site. It means that if for example you opened custom site creation page under English site and create Swedish subsite, associated groups names will still have English titles (Owners, Members, Visitors). In order to make group titles on the language of created site we need temporary change locale of current thread. In one of my previous posts I already showed how to make it:

   1: var currentCulture = Thread.CurrentThread.CurrentCulture;
   2: var currentUICulture = Thread.CurrentThread.CurrentUICulture;
   3: try
   4: {
   5:     SPUtility.SetThreadCulture(web);
   6:  
   7:     setupSecurity(web);
   8: }
   9: finally
  10: {
  11:     Thread.CurrentThread.CurrentCulture = currentCulture;
  12:     Thread.CurrentThread.CurrentUICulture = currentUICulture;
  13: }

Now if you will create Swedish site, associated groups will have Swedish titles: Ägare av test, Medlemmar på test, Besökare på test (test is web site title).

10 comments:

  1. Hey!

    We are in the process of doing exactly the above - creating subsites via C# and assigning unique permissions on them - all in code.

    You shouldn't happen to have a full code listing for something like that?

    That would save us some time.

    Thanks for a great blog!
    /Jesper

    ReplyDelete
  2. Hi Jesper,
    I think that code examples in the post is enough to implement it by yourself, although it is not very convenient to copy it from here because code snippet I used for blog post adds lines numbers and you have to remove them manually. But this is not a big problem I think )

    ReplyDelete
  3. kewl - will try that, just if you had some code, there wouldn't be any point in inventing the wheel twice (we are almost done, so it was just to se if you had another way of doing it).

    Another other question:
    Why do we need to Associate???
    We do it because we have seen it on the net, but can't seem to find any documentation as to why we need to do it?

    When we create groups outside the owner,member,visitor spektrum, we don't "associate"... (or should we?? :-)

    Thanks
    /Jesper

    ReplyDelete
  4. Jesper,
    these 3 groups have special meaning, i.e. Sharepoint "knows" about them and uses it internally (using mentioned properties of SPWeb class). That's the only reason you need to specify them for web site. You can also create independent groups and assign permissions levels for them as well - result will be the same, but Sharepoint will know nothing about these groups.
    I would call these 3 associated groups intrusive groups.

    ReplyDelete
  5. Worked like a charm out of the box. Many thanks for saving hours of work

    ReplyDelete
  6. Kalyan,
    glad that is was helpful for you

    ReplyDelete
  7. Hi, I am in the process of creating site programatically.Owners, members and visitors groups are not created by default. could any one of you know the reason

    ReplyDelete
  8. lovesharepoint,
    if you create site programmatically (not from OTB create site page), then you need to create these groups by yourself as well. As you mentioned, they are not created by default. In order to do this you may use example shown in this post.

    ReplyDelete
  9. Is it Possible to do the same useing Web Services?

    I am creating the Site Collection using Web services, using the Team site Template, upon creation the Site Collection is apsent of the 3 Default Sharepoint groups.

    ReplyDelete