MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Friday, November 16, 2007

Copy List Security (permissions) Settings

I got a comment the other day on my blog about an issue with the copying/importing of lists. The problem was that the security settings on the list were not being preserved during the import. The natural assumption is that if I specify "-includeusersecurity" that any permissions on the list would be preserved. In other words, if my source list was not inheriting permissions on the list itself, a folder, or an individual item, then those custom permissions should carry over when I import the list to a new web. Unfortunately this is not the case - includeusersecurity only copies over the users and groups, not the permissions (at least when importing a list).

I tore through the code to see if there was some setting that I was missing that would make this work but alas, I found nothing. For me this problem was a huge concern because it would be too easy for someone to want to copy a list and just expect that the security remained the same (as I unfortunately did). Because of the potential security issues I decided it was prudent to find a workaround. What I ended up creating was a new command which could be used to copy the permissions from one list to another: gl-copylistsecurity.

The code is also written so that it can be used independently as its own command or via the existing gl-importlist and gl-copylist commands, which have now been updated to utilize this new feature. By default the command, when used independently, will only copy the permissions for the list itself and all folders - it will not copy permissions for individual list items unless you pass in the "-includeitemsecurity" parameter. When used by the gl-copylist command and the gl-importlist command this option is set to true. This way you can set the security on one list the way you want it and then propagate those settings to other, similarly structured lists without having to do it by hand. The code can be seen below:

   1: /// <summary>
   2: /// Runs the specified command.
   3: /// </summary>
   4: /// <param name="command">The command.</param>
   5: /// <param name="keyValues">The key values.</param>
   6: /// <param name="output">The output.</param>
   7: /// <returns></returns>
   8: public override int Run(string command, System.Collections.Specialized.StringDictionary keyValues, out string output)
   9: {
  10:  output = string.Empty;
  11:  
  12:  InitParameters(keyValues);
  13:  
  14:  string sourceUrl = Params["sourceurl"].Value;
  15:  string targetUrl = Params["targeturl"].Value;
  16:  bool quiet = Params["quiet"].UserTypedIn;
  17:  bool includeItemSecurity = Params["includeitemsecurity"].UserTypedIn;
  18:  
  19:  using (SPSite sourceSite = new SPSite(sourceUrl))
  20:  using (SPSite targetSite = new SPSite(targetUrl))
  21:  using (SPWeb sourceWeb = sourceSite.OpenWeb())
  22:  using (SPWeb targetWeb = targetSite.OpenWeb())
  23:  {
  24:   SPList sourceList = Utilities.GetListFromViewUrl(sourceWeb, sourceUrl);
  25:   SPList targetList = Utilities.GetListFromViewUrl(targetWeb, targetUrl);
  26:  
  27:   if (sourceList == null)
  28:    throw new SPException("Source list was not found.");
  29:   if (targetList == null)
  30:    throw new SPException("Target list was not found.");
  31:  
  32:   CopySecurity(sourceList, targetList, targetWeb, includeItemSecurity, quiet);
  33:  }
  34:  return 1;
  35: }
  36:  
  37:  
  38:  
  39: /// <summary>
  40: /// Copies the security.
  41: /// </summary>
  42: /// <param name="sourceList">The source list.</param>
  43: /// <param name="targetList">The target list.</param>
  44: /// <param name="targetWeb">The target web.</param>
  45: /// <param name="includeItemSecurity">if set to <c>true</c> [include item security].</param>
  46: /// <param name="quiet">if set to <c>true</c> [quiet].</param>
  47: internal static void CopySecurity(SPList sourceList, SPList targetList, SPWeb targetWeb, bool includeItemSecurity, bool quiet)
  48: {
  49:  if (!quiet)
  50:   Console.WriteLine("Start Time: {0}.", DateTime.Now);
  51:  
  52:  try
  53:  {
  54:   // Set the security on the list itself.
  55:   SetObjectSecurity(targetWeb, sourceList, targetList, quiet, targetList.RootFolder.ServerRelativeUrl);
  56:  
  57:   // Set the security on any folders in the list.
  58:   SetFolderSecurity(targetWeb, sourceList, targetList, quiet);
  59:  
  60:   if (includeItemSecurity)
  61:   {
  62:    // Set the security on list items.
  63:    SetListItemSecurity(targetWeb, sourceList, targetList, quiet);
  64:   }
  65:  }
  66:  finally
  67:  {
  68:   if (!quiet)
  69:    Console.WriteLine("Finish Time: {0}.\r\n", DateTime.Now);
  70:  }
  71: }
  72:  
  73: /// <summary>
  74: /// Sets the list item security.
  75: /// </summary>
  76: /// <param name="targetWeb">The target web.</param>
  77: /// <param name="sourceList">The source list.</param>
  78: /// <param name="targetList">The target list.</param>
  79: /// <param name="quiet">if set to <c>true</c> [quiet].</param>
  80: private static void SetListItemSecurity(SPWeb targetWeb, SPList sourceList, SPList targetList, bool quiet)
  81: {
  82:  foreach (SPListItem sourceItem in sourceList.Items)
  83:  {
  84:   SPListItem targetItem = null;
  85:   foreach (SPListItem i in targetList.Items)
  86:   {
  87:    if (sourceItem.Name == i.Name)
  88:    {
  89:     targetItem = i;
  90:     break;
  91:    }
  92:   }
  93:   if (targetItem == null)
  94:    continue;
  95:  
  96:   SetObjectSecurity(targetWeb, sourceItem, targetItem, quiet, sourceItem.Name);
  97:  }
  98: }
  99:  
 100: /// <summary>
 101: /// Sets the folder security.
 102: /// </summary>
 103: /// <param name="targetWeb">The target web.</param>
 104: /// <param name="sourceList">The source list.</param>
 105: /// <param name="targetList">The target list.</param>
 106: /// <param name="quiet">if set to <c>true</c> [quiet].</param>
 107: private static void SetFolderSecurity(SPWeb targetWeb, SPList sourceList, SPList targetList, bool quiet)
 108: {
 109:  foreach (SPListItem sourceFolder in sourceList.Folders)
 110:  {
 111:   SPListItem targetFolder = null;
 112:   foreach (SPListItem f in targetList.Folders)
 113:   {
 114:     
 115:    if (f.Folder.ServerRelativeUrl.Substring(targetList.RootFolder.ServerRelativeUrl.Length) ==
 116:     sourceFolder.Folder.ServerRelativeUrl.Substring(sourceList.RootFolder.ServerRelativeUrl.Length))
 117:    {
 118:     targetFolder = f;
 119:     break;
 120:    }
 121:   }
 122:   if (targetFolder == null)
 123:    continue;
 124:  
 125:   SetObjectSecurity(targetWeb, sourceFolder, targetFolder, quiet, targetFolder.Folder.ServerRelativeUrl);
 126:  }
 127: }
 128:  
 129: /// <summary>
 130: /// Sets the object security.
 131: /// </summary>
 132: /// <param name="targetWeb">The target web.</param>
 133: /// <param name="sourceObject">The source object.</param>
 134: /// <param name="targetObject">The target object.</param>
 135: /// <param name="quiet">if set to <c>true</c> [quiet].</param>
 136: /// <param name="itemName">Name of the item.</param>
 137: private static void SetObjectSecurity(SPWeb targetWeb, ISecurableObject sourceObject, ISecurableObject targetObject, bool quiet, string itemName)
 138: {
 139:  if (!sourceObject.HasUniqueRoleAssignments && targetObject.HasUniqueRoleAssignments)
 140:  {
 141:   if (!quiet)
 142:    Console.WriteLine("Progress: Setting target object to inherit permissions from parent for \"{0}\".", itemName);
 143:   targetObject.ResetRoleInheritance();
 144:   return;
 145:  }
 146:  else if (sourceObject.HasUniqueRoleAssignments && !targetObject.HasUniqueRoleAssignments)
 147:  {
 148:   if (!quiet)
 149:    Console.WriteLine("Progress: Breaking target object inheritance from parent for \"{0}\".", itemName);
 150:   targetObject.BreakRoleInheritance(false);
 151:  }
 152:  else if (!sourceObject.HasUniqueRoleAssignments && !targetObject.HasUniqueRoleAssignments)
 153:  {
 154:   if (!quiet)
 155:    Console.WriteLine("Progress: Ignoring \"{0}\".  Target object and source object both inheritance from parent.", itemName);
 156:   return; // Both are inheriting so don't change.
 157:  }
 158:  
 159:  foreach (SPRoleAssignment ra in sourceObject.RoleAssignments)
 160:  {
 161:   SPRoleAssignment existingRoleAssignment = GetRoleAssignement(targetObject, ra.Member);
 162:  
 163:   if (existingRoleAssignment == null)
 164:   {
 165:  
 166:    targetObject.RoleAssignments.Add(ra);
 167:     
 168:    existingRoleAssignment = GetRoleAssignement(targetObject, ra.Member);
 169:    if (existingRoleAssignment != null)
 170:    {
 171:     if (!quiet)
 172:      Console.WriteLine("Progress: Added \"{0}\" to target object \"{1}\".", ra.Member, itemName);
 173:     continue;
 174:    }
 175:  
 176:    SPRoleAssignment newRA = new SPRoleAssignment(ra.Member);
 177:    foreach (SPRoleDefinition rd in ra.RoleDefinitionBindings)
 178:    {
 179:     if (rd.Name == "Limited Access")
 180:      continue;
 181:  
 182:     SPRoleDefinition existingRoleDef = targetWeb.RoleDefinitions[rd.Name];
 183:     if (existingRoleDef == null)
 184:     {
 185:      existingRoleDef = new SPRoleDefinition();
 186:      existingRoleDef.BasePermissions = rd.BasePermissions;
 187:      existingRoleDef.Description = rd.Description;
 188:      existingRoleDef.Update();
 189:      targetWeb.RoleDefinitions.Add(existingRoleDef);
 190:     }
 191:     newRA.RoleDefinitionBindings.Add(existingRoleDef);
 192:    }
 193:    if (newRA.RoleDefinitionBindings.Count == 0)
 194:    {
 195:     if (!quiet)
 196:      Console.WriteLine("Progress: Unable to add \"{0}\" to target object \"{1}\" (principals with only \"Limited Access\" cannot be added).", ra.Member, itemName);
 197:     continue;
 198:    }
 199:  
 200:    targetObject.RoleAssignments.Add(newRA);
 201:  
 202:    existingRoleAssignment = GetRoleAssignement(targetObject, ra.Member);
 203:    if (existingRoleAssignment == null)
 204:    {
 205:     Console.WriteLine("Progress: Unable to add \"{0}\" to target object \"{1}\".", ra.Member, itemName);
 206:    }
 207:    else
 208:    {
 209:     if (!quiet)
 210:      Console.WriteLine("Progress: Added \"{0}\" to target object \"{1}\".", ra.Member, itemName);
 211:    }
 212:   }
 213:  }
 214: }
 215:  
 216: /// <summary>
 217: /// Gets the role assignement.
 218: /// </summary>
 219: /// <param name="securableObject">The securable object.</param>
 220: /// <param name="principal">The principal.</param>
 221: /// <returns></returns>
 222: private static SPRoleAssignment GetRoleAssignement(ISecurableObject securableObject, SPPrincipal principal)
 223: {
 224:  SPRoleAssignment ra = null;
 225:  try
 226:  {
 227:   ra = securableObject.RoleAssignments.GetAssignmentByPrincipal(principal);
 228:  }
 229:  catch (ArgumentException)
 230:  { }
 231:  return ra;
 232: }
One thing to note - I've not had a lot of time to test every possible scenario with this. It's quite possible you may run into issues if a Role Assignment can't be found due to a missing user or group or something like that (though I believe from my testing that it should simply ignore those users and not add the permission - I don't believe it will try to add the user or group so make sure all your users of interest exist in the target site). It worked quite well for me in my environment but I'm running short on time and was not able to test a lot of scenarios. If you run into any issues please pass your findings along so that others can benefit. The syntax of the command can be seen below:
C:\>stsadm -help gl-copylistsecurity

stsadm -o gl-copylistsecurity

Copies a list's security settings from one list to another.

Parameters:
        -sourceurl <list view url to copy security from>
        -targeturl <list view url to copy security to>
        [-quiet]
        [-includeitemsecurity]
Here's an example of how to copy the permissios from one list to another:
stsadm -o gl-copylistsecurity -sourceurl "http://intranet/documents/forms/allitems.aspx" -targeturl "http://intranet/testweb1/documents/forms/allitems.aspx" -includeitemsecurity
Note that gl-copylist and gl-importlist have been updated to include this functionality.

23 comments:

nsp said...

do you have any scripting can copy the security access right from WSS v2 to WSS v3 ?

i have manually copy all the document and list to new V3 site, but the security right i need to add in one by one. do you have any command can help me to map the v2 user right to v3 sites?

thanks

Gary Lapointe said...

I've not done anything to go form v2 to v3 like that but it may be possible for you to modify this command to get it to work (I've not done any v2 API coding so I can't say how different it is).

Anonymous said...

Hi I have just been using your copylistsecurity extension for stsadm and it's really great.

What I'd like to know is, do you know of a way to set permissions on a list object directly, the adduser and userrole commands seem to only work when setting security on a web, not on document libraries, etc.

At the moment I am using your tool to copy the permissions from a Document Library in a template site onto the Document Library in the target site on a MOSS 2007 setup.

I need to create 2000 or so course sites with read access to a AD user group, but then on a Document Library within it called "Drop Box" to have contribute perms for that same group. Any help would be appreciated! Your copylistsecurity is the closest I've got to solving it! Thanks, Talwyn.

Gary Lapointe said...

Talwyn - if you look at the SPList object you'll see that it implements the ISecurableObject interface - this means that you can set the security on the list the same way as an item (you can see that I'm doing this by using the SetObjectSecurity method - of course I'm pass in another list as my source but you could just as easily change the code to pass in an xml structure or some other data structure to tell the code what permissions to set on the list).

whall said...

Great work on all these commands, Gary.

I'm looking to set permissions on 500+ document libraries on a site to the same set. I figured if I used gl-copy-listsecurity in a batch file, that would probably do me well.

So the problem I'm having now is - how can I enumerate all the document libaries? I don't see it in normal stsadm or your super commands.

viewlsts.aspx in layouts does basically what I want (given specific parameters) soI may just end up going to the page, view source, and then some vi editing to get my list of URLs to use as -targeturl for your command, but it would seem that enumerating lists or doc libs would be something you could do, and I'm just missing it.

Thanx again! These commands are wonderful.

Gary Lapointe said...

PowerShell is your friend :)

Take a look at this - it should help you get started: http://blogs.flexnetconsult.co.uk/colinbyrne/PermaLink,guid,1665700b-e0de-4b8a-bb1c-014d6fbcf2db.aspx

Write a simple PowerShell script that iterates over all the lists and then call my command using the URL obtained from the list properties.

whall said...

I'll check it out - I've been hearing about PowerShell for some time now. I'm a Perl guy, so I ended up just going to the View All Site Content page, view source, save off the html, then some gawk and grep commands to give me a list of folders, then a perl script to iterate through each.

In general, the script did this
- used gl-exportsitesecurity to capture existing settings
- look through the settings to see if it had unique role assignments and also to see if it had the weird migrated permissions
- if it did, used gl-copylistsecurity of a good doc lib onto this doc lib, and then stored an "after" gl-exportlistsecurity snapshot

I'll probably stick with perl as my scripting choice but I'm sure I'll hit a wall when I start needing .net interaction.

My next challenge is to see how to query sharepoint v2 permissions on a doc lib, parse them, and then set sharepoint v3 permissions, as nsp above had mentioned.

Mauro Masucci said...

Hi Gary,

the information here is excellent, I wonder though if you can point me in the right direction to resolve an issue I have.

I wanted to disable the permission inheritance on a lot of subsites so I can then use STSADM to set the relative owners and members up correctly.

Do you know any way of doing that using STSADM or .Net code?

Gary Lapointe said...

Mauro - I don't have (or know of any) stsadm commands to break permission inheritance for a site but it's pretty easy to do via code - you just call the BreakRoleInheritance method.

Anonymous said...

Thank you for these tools!
I am looking to get the _item_ level security settings exported to a file similar to what you do with gl-exportlistsecurity. I understand gl-copylistsecurity can read item level security from the source list and apply it to the destination list, but there is no exporting of the settings. Any suggestions?

Gary Lapointe said...

Download the latest version. There's now an "-includeitemsecurity" parameter for the gl-exportlistsecurity and gl-importlistsecurity commands.

Anonymous said...

Awesome - Thanks for implementing the "-includeitemsecurity" parameter!

Anonymous said...

Already long time I am looking for possibility to switch on and off anonymous access for list. I need this for giving access to list RSS for couple minutes once a day. May be you have some idea?

Gary Lapointe said...

I've never looked into making lists anonymous but this property may help you: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splist.anonymouspermmask64.aspx - not sure if it will do what you're looking for (I think you have to enable anonymous access to the web).

Anonymous said...

Hint on the site
http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splist.anonymouspermmask64.aspx

looks promising, but I am not programmer and I don't know how to use it...?

Paul A Bedford said...

Hi Gary. Just want to add my thanks along with all the others for your fantastic work and altruistic approach.

One question when copying list permissions; do you know what it is about the limited access permissions that prevent them fro not getting copied?

Thanks once again!

Gary Lapointe said...

Thanks! The limited access permission cannot be set directly using the API. Think of it more as a special marker - it will get assigned automatically if a child element gives the user some explicit permission - so you need this limited access permission in order to get to the child item (and assigning directly doesn't make sense because what is the child element you are assigning?). Think of it this way - I have a web, a list, and an item, each breaking inheritence - an individual user is assigned contribute rights to the single item but they previously had no rights to the list or web - in order to get to the item they need this special limited access permission assigned to the list and web so that they can navigate to the item and see various resource files (style sheets, master page, etc.). If I'm copying security then assigning limited access directly doesn't make sense because it would be automatically assigned as I secure individual child items. I think some confusion comes in when, for instance, the individual item mentioned above changes it's security (re-inherits for example) it will not remove the limited access permission from the parent so you end up with an orphaned permission (it doesn't remove it because it's too expensive to traverse all the children to see if it's still necessary).

Hope this helps.

Montanan Missing her Mountains said...

Gary,

Another great script!! I ran it once with no problems at all and now when I try and run it I get the following errors:

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN>stsa
dm -o gl-copylistsecurity -sourceurl http://connect.company.com/client/ACMEMEC
HANICAL/Lists/Network%20Equipment/AllItems.aspx -targeturl http://connect.company.com/client/PHSERVICES200801/Lists/Network%20Equipment/AllItems.aspx -include
itemsecurity

Start Time: 10/7/2009 10:37:27 PM.
Progress: Breaking target object inheritance from parent for "/client/PHSERVICES
200801/Lists/Network Equipment".
Progress: Added "connect.company.com Owners" to target object "/client/PHSERVI
CES200801/Lists/Network Equipment".
Progress: Added "Approvers" to target object "/client/PHSERVICES200801/Lists/Net
work Equipment".
Progress: Added "Client Information Edit" role definition to target role assignment "Company General".

Error Type: System.InvalidOperationException
Error Message: This operation is not allowed on an object that inherits permissions.
Error Source: Microsoft.SharePoint
Error TargetSite:Void UpdateAssignment(Int32, Microsoft.SharePoint.SPRoleDefinit
ionBindingCollection, Boolean)
Error Stack Trace:
at Microsoft.SharePoint.SPRoleAssignmentCollection.UpdateAssignment(Int32 pri
ncipalId, SPRoleDefinitionBindingCollection bindings, Boolean addOnly)
at Microsoft.SharePoint.SPRoleAssignment.Update()
at Lapointe.SharePoint.STSADM.Commands.Lists.CopyListSecurity.CopyRoleDefinit
ionBindings(SPWeb targetWeb, SPRoleAssignment sourceRoleAssignment, SPRoleAssign
ment targetRoleAssignment, Boolean quiet)
at Lapointe.SharePoint.STSADM.Commands.Lists.CopyListSecurity.SetObjectSecuri
ty(SPWeb targetWeb, SPWeb sourceWeb, ISecurableObject sourceObject, ISecurableOb
ject targetObject, Boolean quiet, String itemName)
at Lapointe.SharePoint.STSADM.Commands.Lists.CopyListSecurity.CopySecurity(SP
List sourceList, SPList targetList, SPWeb targetWeb, Boolean includeItemSecurity
, Boolean quiet)
Finish Time: 10/7/2009 10:37:28 PM.

Operation completed successfully.

So it does break the inheritance and start adding the correct groups but then hits that snag and crashes.

Any hints or suggestions?

Anonymous said...

Hello, and thank you for this great work!
As for copy list, I wonder if you could make the whole thing:i.e. a) copy list b) Create MOSS group and give access right to that list. Optionally, you may make AND the AD Gropus.

ktjags said...

Hi Gary,

I have downloaded Lapointe.SharePoint.STSADM.Commands.wsp and installed as per your instructions.

Now i would like to modify one of your utility.

Which template should i use in VS 2008 while creating a project.

Could you please help me.

Thanks in advance.

kind regards
Ktjags

Gary Lapointe said...

You can download the source code and start with that. Otherwise you'd start with a class library project.

ktjags said...

Hi Gary,

I have downloaded "MyStsAdmCommands" from Codeplex. if this is the code you are talking about.

Thanks & regards,
Jagadish

Gary Lapointe said...

You could use that and merge the code from the download available on my blog or you could start with the download from my blog and remove what you don't need. Either will work.