Wednesday, February 11, 2009

Create or update an SPListitem in an SPList from event handlers where current user has read (or no) permission

Scenario: User is adding/updating/deleting an item in a list (listX), where he has adequate permission. But as soon as he does it, I have to change the content of an item which belongs to a list (listY), where he has read permission only (also works if he has no permission at all) . This is how I did it:

In the event handler of listX, add this:

public override void ItemAdded(SPItemEventProperties properties)
{
if (properties.ListTitle != "listX")
{
return;
}
breakListPermission(properties, "listY");
//add new item, or update, or delete an existing item in "listY"
restoreListPermission(properties, "listY");
}

Here is the method for changing list permission to give current user adequate permission:

private void breakListPermission(SPItemEventProperties properties, string strTargetList)
{
base.ItemAdded(properties);
this.DisableEventFiring();
SPSecurity.RunWithElevatedPrivileges(delegate()
{

using (SPSite targetSite = new SPSite(properties.OpenWeb().Site.ID))
{

SPWeb targetWeb = targetSite.OpenWeb(properties.RelativeWebUrl);
targetWeb.AllowUnsafeUpdates = true;
targetWeb.Update();
SPList targetList = targetWeb.Lists[strTargetList];
SPRoleDefinition adminRoleDefinition = targetWeb.RoleDefinitions.GetByType(SPRoleType.Administrator);

if (!targetList.HasUniqueRoleAssignments)
{
SPRoleAssignmentCollection targetWebRoleCollections = targetWeb.RoleAssignments;
SPRoleAssignment targetWebRole = targetWebRoleCollections[2];
if(!targetWebRole.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
targetWebRole.RoleDefinitionBindings.Add(adminRoleDefinition);
targetWebRole.Update();
}
}

else{
SPRoleAssignmentCollection myRoleCollection = targetList.RoleAssignments;
SPRoleAssignment currentRoleAssignment = myRoleCollection[2];

if (!currentRoleAssignment.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
currentRoleAssignment.RoleDefinitionBindings.Add(adminRoleDefinition);
currentRoleAssignment.Update();
}
}
}
}
);
}

First of all, It is really important to declare new SPSite, SPWeb... objects inside the RunWithElevatedPrivileges block. If you use the objects that you declared earlier, simply wont work.
Then you need to update the website to allow unsafe updates by this code:

targetWeb.AllowUnsafeUpdates = true;
targetWeb.Update();

otherwise, you will get exception like this:
"The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again."
Next you need to check whether "listY: inherit permission that has been assigned to the website or not. If it inherits the website's permission, then you have to change the permission for the website, otherwise you have to change the permission for the list. Use this line to check it:

if (!targetList.HasUniqueRoleAssignments){

You can also break the inheritence, and create new roleassignments for current list:

targetList.BreakRoleInheritance(false);

For my work, it was good enough just to change the permission of the web. Since, the list inherits permission from the web.
I have used this block to retrieve the SPRoleAssignment relevant to current user:

SPRoleAssignmentCollection myRoleCollection = targetList.RoleAssignments;
SPRoleAssignment currentRoleAssignment = myRoleCollection[2];

I have used hardcode here, because I knew that the 3rd roleassignment of both site and "listY" would be the roleassignment that contains current user. If you dont know where the current user is, then you have to do this:
(You must run this following code block before the RunWithElevatedPrivileges starts. When the process enters RunWithElevatedPrivileges code block, the current user will change into "System Account".)

SPGroupCollection lmsGroups = targetWeb.Groups; //this targetWeb was initiated before RunWithElevatedPrivileges

SPGroup lmsMember = lmsGroups[0]; //just initiate

foreach (SPGroup spgroup in lmsGroups)
{
if (spgroup.ContainsCurrentUser)
{
lmsMember = spgroup;
}
}

(Run this following block inside the RunWithElevatedPrivileges:)

SPRoleAssignmentCollection myRoleCollection = targetList.RoleAssignments;
SPRoleAssignment currentRoleAssignment = myRoleCollection.GetAssignmentByPrincipal((SPPrincipal)lmsMember);

If the current user/ group doesn't belong to that list, then you have to create, and add it with the list. then you have to give them permission by using SPRoleDefinition. If you try to give them permission before you add it to the list you will get exception (at least, I got the exception):
"Cannot update a permission level assignment that is not part of a permission level assignment collection."
Here's how (they should belong to the web, at least.. or this wont work):

get the group where the user belongs before the RunWithElevatedPrivileges block, or create a single user SPRoleAssignment:
(for user:)

SPRoleAssignment currentRoleAssignment = new SPRoleAssignment(targetWeb.CurrentUser.LoginName, targetWeb.CurrentUser.Email, targetWeb.CurrentUser.Name, targetWeb.CurrentUser.Notes);

(for group, after getting the group:)

SPRoleAssignment currentRoleAssignment = new SPRoleAssignment((SPPrincipal)lmsMember);

Now inside the RunWithElevatedPrivileges block do this:
you should check the value for currentRoleAssignment.Parent . If it is null, and you add a roledefinition with it, and update the roleassignment, then surely you will heat the preceeding exception when executing: currentRoleAssignment.Update(); line. But if currentRoleAssignment.Parent is not null, and its value is equal to either the list name, or the web name, then its ok to add a roledefinition with it. Else, you need to associate it with the list first:

targetList.RoleAssignments.Add(currentRoleAssignment);
targetList.Update();

If the adding is successful, then retrieve this new roleassignment from role assignment collection:

currentRoleAssignment= targetList.RoleAssignments[targetList.RoleAssignments.Count - 1];

And give roledefinition (permission) according to this way:

SPRoleDefinition adminRoleDefinition = targetWeb.RoleDefinitions.GetByType(SPRoleType.Administrator);
if (!currentRoleAssignment.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
currentRoleAssignment.RoleDefinitionBindings.Add(adminRoleDefinition);
currentRoleAssignment.Update();
}

That should do the trick. And when removing permission, just do the opposite:

private void restoreListPermission(SPItemEventProperties properties, string strTargetList)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite targetSite = new SPSite(properties.OpenWeb().Site.ID))
{

SPWeb targetWeb = targetSite.OpenWeb(properties.RelativeWebUrl);
SPList targetList = targetWeb.Lists[strTargetList];

SPRoleDefinition adminRoleDefinition = targetWeb.RoleDefinitions.GetByType(SPRoleType.Administrator);

if (!targetList.HasUniqueRoleAssignments)
{

SPRoleAssignmentCollection targetWebRoleCollections = targetWeb.RoleAssignments;
SPRoleAssignment targetWebRole = targetWebRoleCollections[2];
if (targetWebRole.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
targetWebRole.RoleDefinitionBindings.Remove(adminRoleDefinition);
targetWebRole.Update();
}
}
else{
SPRoleAssignmentCollection myRoleCollection = targetList.RoleAssignments;
SPRoleAssignment currentRoleAssignment = myRoleCollection[2];

if (currentRoleAssignment.RoleDefinitionBindings.Contains(adminRoleDefinition))
{
currentRoleAssignment.RoleDefinitionBindings.Remove(adminRoleDefinition);
currentRoleAssignment.Update();
}
}

targetWeb.AllowUnsafeUpdates = false;
targetWeb.Update();

}
}
);
this.EnableEventFiring();
}

There you go. It did the job for me. Hope it might help you too. But Sharepoint is so unpredictable sometimes... I don't promise anything.. :)

2 comments:

Anonymous said...

Hello Andalib, Thanks for the excellent post. I have a problem, when i login as an administrator, everything works, but when I login as a user with add only [no edit] permission, the following code doesnt work:

currentRoleAssignment.RoleDefinitionBindings.Add(adminRoleDefinition);
currentRoleAssignment.Update();

because it says the logged in user doesnt have the privilege to update. Have you gone this issue before. Please respond

cosmicice said...

Helo Azeem, yes I had the same problem. When logged in as administrator, roleassignments work. But when logged in as a member, it doesnt work. Thats why, you need to change roleassignment within the following block:

SPSecurity.RunWithElevatedPrivileges(delegate()
{

using (SPSite targetSite = new SPSite(properties.OpenWeb().Site.ID))
{
SPWeb targetWeb = targetSite.OpenWeb(properties.RelativeWebUrl);
//change the roleassignment here. use the targetWeb object to access list, roleassignments, etc....
//do not use SPSite, or SPWeb objects that you created earlier. They wont work.
//In this block, all the objects change, and the currentuser changes into Administrator. So it should work
}
}

Thnx...