Setting item level security in an eventhandler

Last week I was building a solution for a customer that involved setting item level security on a document in a document library the moment it is added to the document library.
I'm not a big fan of item level security, because it can create chaos from a maintenance perspective, but sometimes it's simply the best, or even the only solution.

I started out be creating the feature that will contain the eventhandler.

The Feature.xml is very straightforward:

<Feature Scope="Web"
    Title="Set Security Eventhandler"
    Id="7B2CB0DC-8F27-4252-A4F2-89729DF9331B"
    xmlns="http://schemas.microsoft.com/sharepoint/">
    <ElementManifests>
        <ElementManifest Location="Elements.xml"/>
    </ElementManifests>
</Feature>

The Elements.xml looks like this:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Receivers ListTemplateId="101">
        <Receiver>
            <Name>AddedEventHandler</Name>
            <Type>ItemAdded</Type>
            <SequenceNumber>10000</SequenceNumber>
            <Assembly>Macaw.Custom.Moss2007.Portal.Business.Components, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6bdc41c2016ac3e3</Assembly>
            <Class>Macaw.Custom.Moss2007.Portal.Business.Components.SetSecurityEventHandler</Class>
            <Data></Data>
            <Filter></Filter>
        </Receiver>
    </Receivers>
</Elements>

So you can see that I created an eventhandler that will fire when an item is added to the library. By setting the ListTemplateId I'm linking the eventhandler to document libraries. This means that the eventhandler will be linked to every document library on the web where this feature is activated. It would be better to create a feature with your own custom type of document library and link to that, so you can make sure that the eventhandler will only fire when it's supposed to.

Now that the feature is created it's time to move on to the actual code for the eventhandler.

    // First create a new class that inherits from the SPItemEvenReceiver class
    public class SetSecurityEventhandler : SPItemEventReceiver
    {

        // Override the ItemAdded event and add your own code
        public override void ItemAdded(SPItemEventProperties properties)
        {
            SetSecurityForNewItem(properties.ListItem.File, properties.ListItem.ParentList);
        }

        // The function in which I actually set the item level security on the newly added document
        private void SetSecurityForNewItem(SPFile newFile, SPList docLib)
        {
            SPListItem newItem = newFile.Item;
            newItem.BreakRoleInheritance(false);

            SPRoleDefinitionCollection roleDefinitions = docLib.ParentWeb.RoleDefinitions;
            SPRoleAssignmentCollection roleAssignments = newItem.RoleAssignments;

            SPUserCollection users = docLib.ParentWeb.AllUsers;

            try
            {
                SPUser userToAdd = users.GetByEmail(newFile.Properties["MailTo"].ToString());
                SPRoleAssignment roleAssignment = new SPRoleAssignment(userToAdd);
                SPRoleDefinitionBindingCollection roleDefBindings = roleAssignment.RoleDefinitionBindings;
                roleDefBindings.Add(roleDefinitions["Contribute"]);
                roleAssignments.Add(roleAssignment);
            }
            catch (Exception ex)
            {
                ExceptionPublisher.PublishInternalException(ex);
            }
        }
    }

The interesting bit of code can be found in the SetSecurityForNewItem function.

The function starts of by resolving the SPListItem from the SPFile and by breaking the role inheritance on the item. A necessary step to enable item level security on this item. The false means that the security as it is set on the parent document library is not being copied into the item before breaking the inheritance.

Next step is to get the roledefinitions from the parentweb and the roleassignments from the item. The roledefinitions are only assigned to webs, and not to document libraries, lists or items.

Now I need to get the collection of users from the web, these are all the users that are members of the site, or that have browsed to the site. I need this collection of users in order to resolve the user belonging to the email address that is added in one of the properties of the document. The document is a postal item, and the user belonging to the email address is the addressee. To get the address from the document property I use newFile.Properties["MailTo"].ToString(). Using newItem["MailTo"].ToString() would have produced the same result.

Next I add the user to a new roleassignment and get the collection of roledefinitionbindings from the roleassignment, The roledefinitionbindings are used to bind roledefiitions to a roleassignment. Now add a roledefinition (in this case I add the Contribute definition) to the roledefinitionbindings. The last step is to add the new roleassignment to the collection of roleassignments of the document.

Setting security in WSS 3.0 from code is quite complicated. There are roleassignments, roledefinitions and roledefinitionbindings. Roleassignments are used to add users and to link them to sites, webs, libraries or items. Roledefinitions are used to determine the level of security, it can for instance be Read, or Contribute, or you can create your own definition, I'll write another post about that later on. Roledefinitionbindings are used to bind roledefinitions to roleassignments. I must say that I do not completely understand why we can't just bind roledefinitions directly to roleassignments, but we can't, and there is probably an explanation, I just don't know what it is.
I can however very easy change the above code to add the same user with conbribute roledefinitions to the document library instead of the item. The only line that needs to change in order to achieve this is:

            SPRoleAssignmentCollection roleAssignments = docLib.RoleAssignments;

In order to give the user read instead of contribute rights I would use:

                roleDefBindings.Add(roleDefinitions["Read"]);

Basically all you have to do to use the object model to set security in WSS 3.0 is remember the piece of code. You don't need to understand the "why" to build great solution with it..

Comments -
  1. Gravatar

    Hi Dan,

    You are right, I missed one replacement when adjusting my code for the post. I will change it.

    Thanks!
    Mirjam

      
  2. Gravatar

    Dan, Satheesh,


    I have never used item level security on a scale that caused problems. After hitting the WSS v3 bounderies once I try to stay far away from them.


    You can have 2000 security principles (= users and groups) in a site collection. Also check this link for more MOSS 2007&nbsp;boundaries.


    Apparently when using item level security there are other forces at work that aren't documented as well yet.


    Generally speaking these things can't be solved, except be breaking the solution down over multiple site collections and content databases.


    Regards,


    Mirjam

      
  3. Gravatar

    I think the code from your sample is run in user context, so for all visitors and contributors it should be wrapped in RunWithElevatedPriveleges method.

      
  4. Gravatar

    I wanted to point out that the performance hit of the BreakRoleInheritance(false) call isn't strictly tied to the number of users/groups. You could have only 5 groups/users and create a multi-threaded test harness that calls BreakRoleInheritance(false) once for each item in a document library and your database will peg and you'll receive all kinds of random errors. The problem appears to come from BreakRoleInheritance(false)'s call to the proc_SecRemovePrincipalFromScope() SQL Stored Procedure which is called for each inherited principal that's being removed from the item. In a load scenario such as above, this call eats a tremendous amount of SQL resources and will peg a fairly robust box at 100% ute.

      
  5. Gravatar

      
Comments have been closed on this topic.