First, I would like to credit a lady by the name of Mirjam for her piece at http://www.sharepointblogs.com/mirjam/archive/2007/11/11/setting-item-level-security-in-an-eventhandler.aspx; it was the starting point that I so desperately needed to build this – thanks Mirjam!
What I have here is piece of code that takes the information of two columns, ‘WriteSecurity’ and ‘ReadSecurity’ and sets Contribute and Read permissions respectively on a document library item. This allows the user to choose security for the document that they create/upload at the time of their create/upload.
It is a feature that overrides the ItemUpdating and ItemUpdated events in a site collection. The feature first looks for the columns, WriteSecurity and ReadSecurity; if it does not find both of them, it ignores the rest of the process and continues as normal. If it does find them both, it will check for data within those columns; if no data is present, the method will have the item inherit permissions from its containing library. If it finds that only the ReadSecurity column is filled, it will throw an error saying that Read without Write permissions are not allowed. Otherwise the process will cycle through the users/groups specified and assign the appropriate permissions. All of this is done with elevated permissions, but the ‘Modified By’ column will be that of the calling user.
I’ve spent a lot of time on this code, but I was not a C# programmer when I started and I knew nothing of SharePoint event receivers and code-based modification. That said, this has worked in two environments. I would imagine, however, that an experienced programmer/SharePoint Guru, would identify my messy bits and suggest better approaches (please do!) especially with my method of extracting user IDs from the security columns, and the way I repeat the code for the Write and Read bits.
This has been designed to work with Document Libraries. As said before, the feature looks for the columns ‘WriteSecurity’ and ‘ReadSecurity’ with no spaces – at least, the columns in the library should be created that way; you can rename after. And I believe that this may not work if your user’s domain starts with a number…
For those who are like I was when I first created this, he’s the summarised approach to take:
- Using Visual Studio, create a C# Class Library Project
- Add a reference to Windows SharePoint Services
- Rename the class to SetPermissionsEventhandler
- Copy the C# code into the project
- Sign the project with a strong key
- Build it
- Drag the resulting DLL from the project’s bin folder into the c:\windows\assembly folder on the SharePoint server.
- Copy the Public Key Token from the DLL now in the assembly
- Go to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\ on the SharePoint server
- Create a folder called SetPermissionsEventhandler
- Within that, create two files, Feature.xml and Elements.xml
- Copy the respective XML codes (specified below) into the files
- Open a command prompt
- Go to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN
- Run this: stsadm -o installfeature -filename SetPermissionsEventhandler\Feature.xml
- Run this: stsadm -o activatefeature -filename SetPermissionsEventhandler\Feature.xml -url http://YOURSITECOLLECTION
- Run this: iisreset
- If you haven’t already, create a Document Library with ‘People or Group’ columns called ‘WriteSecurity’ and ‘ReadSecurity’
- Enjoy.
Class Code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Security;
using System.Security.Principal;
using System.Security.Permissions;
using System.Text.RegularExpressions;
namespace SetPermissionsEventhandler
{
public class SetPermissions : SPItemEventReceiver
{
public override void ItemUpdating(SPItemEventProperties properties)
{
base.ItemUpdating(properties);
//make sure security fields exist
if (properties.ListItem.Fields.ContainsField("WriteSecurity") == true &&
properties.ListItem.Fields.ContainsField("ReadSecurity") == true)
{
// check to see if the user has set Read Security without setting
// Write Security. If so, show error page and cancel the process.
if (properties.AfterProperties["WriteSecurity"].ToString() == ""
&& properties.AfterProperties["ReadSecurity"].ToString() != "")
{
properties.Cancel = true;
properties.ErrorMessage = "You cannot specify Read Security without also specifying Write Security. " +
"\r\n\r\nPlease click your browser's BACK button to try again.";
}
}
}
//this will fire if the ItemUpdating event was not cancelled
public override void ItemUpdated(SPItemEventProperties properties)
{
//make sure security fields exist again
if (properties.ListItem.Fields.ContainsField("WriteSecurity") == true &&
properties.ListItem.Fields.ContainsField("ReadSecurity") == true)
{
//make sure that file is not checked-out before setting any permissions
if (properties.ListItem.File.CheckOutStatus == SPFile.SPCheckOutStatus.None)
{
//if no security data has been specified, inherit the permission of the
//containing library otherwise run the method to set the permissions
if (properties.AfterProperties["WriteSecurity"].ToString() == ""
&& properties.AfterProperties["ReadSecurity"].ToString() == "")
{
InheritLibraryPermission(properties);
}
else
{
SetNewPermissions(properties);
}
}
}
}
private void InheritLibraryPermission(SPItemEventProperties properties)
{
Guid siteID,webID,listID;
int itemID;
listID = properties.ListId;
itemID = properties.ListItem.ID;
using (SPWeb web = properties.OpenWeb())
{
siteID = web.Site.ID;
webID = web.ID;
}
//get the calling user id for later use
String callingUserID = properties.CurrentUserId.ToString();
//run this block as System Account
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(siteID))
{
using (SPWeb web = site.OpenWeb(webID))
{
SPList list = web.Lists[listID];
SPRoleAssignmentCollection roles = list.RoleAssignments;
SPListItem updatedItem = list.GetItemById(itemID);
updatedItem.ResetRoleInheritance();
this.DisableEventFiring();
//sets the 'Modified By' column as the calling user, not the System Account
updatedItem["Editor"] = callingUserID;
updatedItem.Update();
this.EnableEventFiring();
}
}
});
}
private void SetNewPermissions(SPItemEventProperties properties)
{
Guid siteID,webID,listID;
int itemID;
listID = properties.ListId;
itemID = properties.ListItem.ID;
//get the calling userid for later use
String callingUserID = properties.CurrentUserId.ToString();
using (SPWeb web = properties.OpenWeb())
{
siteID = web.Site.ID;
webID = web.ID;
}
//run this block as System Account
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(siteID))
{
using (SPWeb web = site.OpenWeb(webID))
{
SPList list = web.Lists[listID];
SPRoleAssignmentCollection roles = list.RoleAssignments;
SPListItem updatedItem = list.GetItemById(itemID);
updatedItem.BreakRoleInheritance(false);
SPRoleDefinitionCollection roleDefinitions = web.RoleDefinitions;
SPRoleAssignmentCollection roleAssignments = updatedItem.RoleAssignments;
SPUserCollection users = web.AllUsers;
//SPGroupCollection groups = web.Groups;
SPGroupCollection groups = web.SiteGroups;
//Remove all Permissions before applying chosen permissions
for (int x = updatedItem.RoleAssignments.Count - 1; x >= 0; --x)
{
updatedItem.RoleAssignments.Remove(x);
}
//set write permissions
foreach (String eachUserID in getUserIDsFromPeopleColumn(updatedItem.File.Properties["WriteSecurity"].ToString()))
{
int intID = Int32.Parse(eachUserID);
//determine if ID is user or group
Boolean isGroup = false;
foreach (SPGroup testGroup in web.SiteGroups)
{
if (testGroup.ID == intID)
{
isGroup = true;
}
}
if (isGroup == false) //user
{
SPUser userToAdd = users.GetByID(intID);
SPRoleAssignment roleAssignment = new SPRoleAssignment(userToAdd);
SPRoleDefinitionBindingCollection roleDefBindings = roleAssignment.RoleDefinitionBindings;
roleDefBindings.Add(roleDefinitions["Contribute"]);
roleAssignments.Add(roleAssignment);
}
else //group
{
SPGroup groupToAdd = groups.GetByID(intID);
SPRoleAssignment groupRoleAssignment = new SPRoleAssignment(groupToAdd);
SPRoleDefinitionBindingCollection groupRoleDefBindings = groupRoleAssignment.RoleDefinitionBindings;
groupRoleDefBindings.Add(roleDefinitions["Contribute"]);
roleAssignments.Add(groupRoleAssignment);
}
}
//repeat the above for Read permissions, if specifed
if (updatedItem.File.Properties["ReadSecurity"].ToString() != "")
{
//set permissions
foreach (String eachUserID in getUserIDsFromPeopleColumn(updatedItem.File.Properties["ReadSecurity"].ToString()))
{
int intID = Int32.Parse(eachUserID);
//determine if ID is user or group
Boolean isGroup = false;
foreach (SPGroup testGroup in web.SiteGroups)
{
if (testGroup.ID == intID)
{
isGroup = true;
}
}
if (isGroup == false) //user
{
SPUser userToAdd = users.GetByID(intID);
SPRoleAssignment roleAssignment = new SPRoleAssignment(userToAdd);
SPRoleDefinitionBindingCollection roleDefBindings = roleAssignment.RoleDefinitionBindings;
roleDefBindings.Add(roleDefinitions["Read"]);
roleAssignments.Add(roleAssignment);
}
else //group
{
SPGroup groupToAdd = groups.GetByID(intID);
SPRoleAssignment groupRoleAssignment = new SPRoleAssignment(groupToAdd);
SPRoleDefinitionBindingCollection groupRoleDefBindings = groupRoleAssignment.RoleDefinitionBindings;
groupRoleDefBindings.Add(roleDefinitions["Read"]);
roleAssignments.Add(groupRoleAssignment);
}
}
}
//update the item preventing further events
this.DisableEventFiring();
updatedItem["Editor"] = callingUserID; //sets the 'Modified By' column as the calling user, not the System Account
updatedItem.Update();
this.EnableEventFiring();
}
}
});
}
public string[] getUserIDsFromPeopleColumn(String rawUsers)
{
rawUsers = "#" + rawUsers;
string[] rawUsersArray = rawUsers.Split(';');
string Users = "";
foreach (string eachUser in rawUsersArray)
{
if (System.Text.RegularExpressions.Regex.IsMatch(eachUser.Substring(1, 1), "^[A-Za-z]$") == false)
{
Users = Users + eachUser.Substring(1) + ";";
}
}
Users = Users.Substring(0, Users.Length - 1);
string[] UsersArray = Users.Split(';');
return UsersArray;
}
}
}
Feature.xml
<Feature Scope="Web"
Title="Set Permissions Event Handler"
Description="This feature takes the People and Groups identified in the 'Write Security' and 'Read Security' columns in Document Libraries and applies the relevant permissions to each item when it is edited. If the item is checked-out, the permissions will not be applied until the item is checked back in. To enable this feature's functionality in a Document Library, 'Person or Group' columns must be created with the names, WriteSecurity and ReadSecurity (with no spaces); they can be renamed thereafter."
Id="CREATEANEWGUIDANDPUTITHERE"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="Elements.xml"/>
</ElementManifests>
</Feature>
Elements.xml
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Receivers ListTemplateId="101">
<Receiver>
<Name>SetPermissionsEventhandler</Name>
<Type>ItemUpdating</Type>
<SequenceNumber>10000</SequenceNumber>
<Assembly>SetPermissionsEventhandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=PUBLICKEYTOKENFROMYOURDLL</Assembly>
<Class>SetPermissionsEventhandler.SetPermissions</Class>
<Data></Data>
<Filter></Filter>
</Receiver>
<Receiver>
<Name>SetPermissionsEventhandler</Name>
<Type>ItemUpdated</Type>
<SequenceNumber>10000</SequenceNumber>
<Assembly>SetPermissionsEventhandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=PUBLICKEYTOKENFROMYOURDLL</Assembly>
<Class>SetPermissionsEventhandler.SetPermissions</Class>
<Data></Data>
<Filter></Filter>
</Receiver>
</Receivers>
</Elements>