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.

Saturday, April 12, 2008

Import List Security

If you saw my last post, Exporting List Security Settings, then you know what this post is all about.  I needed a way to be able to copy the security settings from one variation to another or from one farm to another.  I already had my gl-copylistsecurity command which could handle the first piece of this but I needed another set of commands to handle going from one farm to another.  The first was gl-exportlistsecurity which handles the exporting - the second command is, obviously enough, gl-importlistsecurity.

Like the export code this was fairly easy for me to do because I already had the code for the copy command - it was just a matter of refactoring it so that the source could be an XML file rather than another list:

   1: /// <summary>
   2: /// Imports the security.
   3: /// </summary>
   4: /// <param name="inputFile">The input file.</param>
   5: /// <param name="url">The URL.</param>
   6: /// <param name="includeItemSecurity">if set to <c>true</c> [include item security].</param>
   7: public static void ImportSecurity(string inputFile, string url, bool includeItemSecurity)
   8: {
   9:     using (SPSite site = new SPSite(url))
  10:     using (SPWeb web = site.OpenWeb())
  11:     {
  12:         XmlDocument xmlDoc = new XmlDocument();
  13:         xmlDoc.Load(inputFile);
  14:  
  15:         Log("Start Time: {0}.", DateTime.Now.ToString());
  16:  
  17:         foreach (XmlElement listElement in xmlDoc.SelectNodes("//List"))
  18:         {
  19:             SPList list = null;
  20:  
  21:             try
  22:             {
  23:                 list = web.GetList(web.ServerRelativeUrl.TrimEnd('/') + "/" + listElement.GetAttribute("Url"));
  24:             }
  25:             catch (ArgumentException) { }
  26:             catch (FileNotFoundException) { }
  27:  
  28:             if (list == null)
  29:             {
  30:                 Console.WriteLine("WARNING: List was not found - skipping.");
  31:                 continue;
  32:             }
  33:  
  34:             ImportSecurity(list, web, includeItemSecurity, listElement);
  35:  
  36:         }
  37:         Log("Finish Time: {0}.\r\n", DateTime.Now.ToString());
  38:     }
  39: }
  40:  
  41: /// <summary>
  42: /// Imports the security.
  43: /// </summary>
  44: /// <param name="targetList">The target list.</param>
  45: /// <param name="web">The web.</param>
  46: /// <param name="includeItemSecurity">if set to <c>true</c> [include item security].</param>
  47: /// <param name="listElement">The list element.</param>
  48: internal static void ImportSecurity(SPList targetList, SPWeb web, bool includeItemSecurity, XmlElement listElement)
  49: {
  50:     Log("Progress: Processing list \"{0}\".", targetList.RootFolder.ServerRelativeUrl);
  51:  
  52:     try
  53:     {
  54:         int writeSecurity = int.Parse(listElement.GetAttribute("WriteSecurity"));
  55:         int readSecurity = int.Parse(listElement.GetAttribute("ReadSecurity"));
  56:  
  57:         if (writeSecurity != targetList.WriteSecurity)
  58:             targetList.WriteSecurity = writeSecurity;
  59:  
  60:         if (readSecurity != targetList.ReadSecurity)
  61:             targetList.ReadSecurity = readSecurity;
  62:  
  63:         // Set the security on the list itself.
  64:         SetObjectSecurity(web, targetList, targetList.RootFolder.ServerRelativeUrl, listElement);
  65:  
  66:         // Set the security on any folders in the list.
  67:         SetFolderSecurity(web, targetList, listElement);
  68:  
  69:         // Set the security on any items in the list.
  70:         if (includeItemSecurity)
  71:             SetItemSecurity(web, targetList, listElement);
  72:  
  73:         targetList.Update();
  74:  
  75:     }
  76:     finally
  77:     {
  78:         Log("Progress: Finished processing list \"{0}\".", targetList.RootFolder.ServerRelativeUrl);
  79:     }
  80: }
  81:  
  82: /// <summary>
  83: /// Sets the folder security.
  84: /// </summary>
  85: /// <param name="web">The web.</param>
  86: /// <param name="list">The list.</param>
  87: /// <param name="listElement">The list element.</param>
  88: private static void SetFolderSecurity(SPWeb web, SPList list, XmlElement listElement)
  89: {
  90:     foreach (XmlElement folderElement in listElement.SelectNodes("Folder"))
  91:     {
  92:         string folderUrl = folderElement.GetAttribute("Url");
  93:         SPListItem folder = null;
  94:         foreach (SPListItem tempFolder in list.Folders)
  95:         {
  96:             if (tempFolder.Folder.Url.ToLowerInvariant() == folderUrl.ToLowerInvariant())
  97:             {
  98:                 folder = tempFolder;
  99:                 break;
 100:             }
 101:         }
 102:         if (folder == null)
 103:         {
 104:             Log("Progress: Unable to locate folder '{0}'.", EventLogEntryType.Warning, folderUrl);
 105:             continue;
 106:         }
 107:         SetObjectSecurity(web, folder, folderUrl, folderElement);
 108:     }
 109: }
 110:  
 111: /// <summary>
 112: /// Sets the item security.
 113: /// </summary>
 114: /// <param name="web">The web.</param>
 115: /// <param name="list">The list.</param>
 116: /// <param name="listElement">The list element.</param>
 117: private static void SetItemSecurity(SPWeb web, SPList list, XmlElement listElement)
 118: {
 119:     foreach (XmlElement itemElement in listElement.SelectNodes("Item"))
 120:     {
 121:         int itemId = int.Parse(itemElement.GetAttribute("Id"));
 122:         SPListItem item = null;
 123:         try
 124:         {
 125:             item = list.GetItemById(itemId);
 126:         }
 127:         catch (ArgumentException)
 128:         {
 129:             // no-op
 130:         }
 131:         if (item == null)
 132:         {
 133:             Log("Progress: Unable to locate item '{0}'.", EventLogEntryType.Warning, itemId.ToString());
 134:             continue;
 135:         }
 136:         SetObjectSecurity(web, item, "Item " + itemId, itemElement);
 137:     }
 138: }
 139:  
 140: /// <summary>
 141: /// Sets the object security.
 142: /// </summary>
 143: /// <param name="web">The web.</param>
 144: /// <param name="targetObject">The target object.</param>
 145: /// <param name="itemName">Name of the item.</param>
 146: /// <param name="sourceElement">The source element.</param>
 147: private static void SetObjectSecurity(SPWeb web, ISecurableObject targetObject, string itemName, XmlElement sourceElement)
 148: {
 149:  
 150:     bool hasUniqueRoleAssignments = bool.Parse(sourceElement.GetAttribute("HasUniqueRoleAssignments"));
 151:  
 152:     if (!hasUniqueRoleAssignments && targetObject.HasUniqueRoleAssignments)
 153:     {
 154:         Log("Progress: Setting target object to inherit permissions from parent for \"{0}\".", itemName);
 155:         targetObject.ResetRoleInheritance();
 156:         return;
 157:     }
 158:     else if (hasUniqueRoleAssignments && !targetObject.HasUniqueRoleAssignments)
 159:     {
 160:         Log("Progress: Breaking target object inheritance from parent for \"{0}\".", itemName);
 161:         targetObject.BreakRoleInheritance(false);
 162:     }
 163:     else if (!hasUniqueRoleAssignments && !targetObject.HasUniqueRoleAssignments)
 164:     {
 165:         Log("Progress: Ignoring \"{0}\".  Target object and source object both inherit from parent.", itemName);
 166:         return; // Both are inheriting so don't change.
 167:     }
 168:     if (hasUniqueRoleAssignments && targetObject.HasUniqueRoleAssignments)
 169:     {
 170:         while (targetObject.RoleAssignments.Count > 0)
 171:             targetObject.RoleAssignments.Remove(0); // Clear out any existing permissions
 172:     }
 173:  
 174:     foreach (XmlElement roleAssignmentElement in sourceElement.SelectNodes("RoleAssignments/RoleAssignment"))
 175:     {
 176:         string memberName = roleAssignmentElement.GetAttribute("Member");
 177:         SPRoleAssignment existingRoleAssignment = GetRoleAssignement(web, targetObject, memberName);
 178:  
 179:         if (existingRoleAssignment != null)
 180:         {
 181:             if (AddRoleDefinitions(web, existingRoleAssignment, roleAssignmentElement))
 182:             {
 183:                 existingRoleAssignment.Update();
 184:  
 185:                 Log("Progress: Updated \"{0}\" at target object \"{1}\".", memberName, itemName);
 186:             }
 187:         }
 188:         else
 189:         {
 190:             SPPrincipal principal = GetPrincipal(web, memberName);
 191:             if (principal == null)
 192:             {
 193:                 Log("Progress: Unable to add Role Assignment for \"{0}\" - Member \"{1}\" not found.", EventLogEntryType.Warning, itemName, memberName);
 194:                 continue;
 195:             }
 196:  
 197:             SPRoleAssignment newRA = new SPRoleAssignment(principal);
 198:             AddRoleDefinitions(web, newRA, roleAssignmentElement);
 199:  
 200:             if (newRA.RoleDefinitionBindings.Count == 0)
 201:             {
 202:                 Log("Progress: Unable to add \"{0}\" to target object \"{1}\" (principals with only \"Limited Access\" cannot be added).", EventLogEntryType.Warning, memberName, itemName);
 203:                 continue;
 204:             }
 205:  
 206:             Log("Progress: Adding new Role Assignment \"{0}\".", newRA.Member.Name);
 207:             
 208:             targetObject.RoleAssignments.Add(newRA);
 209:  
 210:             existingRoleAssignment = GetRoleAssignement(targetObject, principal);
 211:             if (existingRoleAssignment == null)
 212:             {
 213:                 Log("Progress: Unable to add \"{0}\" to target object \"{1}\".", EventLogEntryType.Warning, memberName, itemName);
 214:             }
 215:             else
 216:             {
 217:                 Log("Progress: Added \"{0}\" to target object \"{1}\".", memberName, itemName);
 218:             }
 219:         }
 220:     }
 221: }
 222:  
 223: /// <summary>
 224: /// Adds the role definitions.
 225: /// </summary>
 226: /// <param name="web">The web.</param>
 227: /// <param name="roleAssignment">The role assignment.</param>
 228: /// <param name="roleAssignmentElement">The role assignment element.</param>
 229: /// <returns></returns>
 230: private static bool AddRoleDefinitions(SPWeb web, SPRoleAssignment roleAssignment, XmlElement roleAssignmentElement)
 231: {
 232:     bool modified = false;
 233:     foreach (XmlElement roleDefinitionElement in roleAssignmentElement.SelectNodes("RoleDefinitionBindings/RoleDefinition"))
 234:     {
 235:         string name = roleDefinitionElement.GetAttribute("Name");
 236:         if (name == "Limited Access")
 237:             continue;
 238:  
 239:         SPRoleDefinition existingRoleDef = web.RoleDefinitions[name];
 240:         if (existingRoleDef == null)
 241:         {
 242:             Log("Progress: Adding new Role Definition \"{0}\".", name);
 243:  
 244:             SPBasePermissions perms = SPBasePermissions.EmptyMask;
 245:             foreach (string perm in roleDefinitionElement.GetAttribute("BasePermissions").Split(','))
 246:             {
 247:                 perms = perms | (SPBasePermissions)Enum.Parse(typeof(SPBasePermissions), perm, true);
 248:             }
 249:             existingRoleDef = new SPRoleDefinition();
 250:             existingRoleDef.Name = name;
 251:             existingRoleDef.BasePermissions = perms;
 252:             existingRoleDef.Description = roleDefinitionElement.GetAttribute("Description");
 253:             existingRoleDef.Order = int.Parse(roleDefinitionElement.GetAttribute("Description"));
 254:             existingRoleDef.Update();
 255:  
 256:             SPWeb tempWeb = web;
 257:             while (!tempWeb.HasUniqueRoleDefinitions)
 258:                 tempWeb = tempWeb.ParentWeb;
 259:  
 260:             tempWeb.RoleDefinitions.Add(existingRoleDef);
 261:         }
 262:         if (!roleAssignment.RoleDefinitionBindings.Contains(existingRoleDef))
 263:         {
 264:             roleAssignment.RoleDefinitionBindings.Add(existingRoleDef);
 265:             modified = true;
 266:         }
 267:     }
 268:     List<SPRoleDefinition> roleDefsToRemove = new List<SPRoleDefinition>();
 269:     foreach (SPRoleDefinition roleDef in roleAssignment.RoleDefinitionBindings)
 270:     {
 271:         if (roleDef.Name == "Limited Access")
 272:             continue;
 273:  
 274:         bool found = false;
 275:         foreach (XmlElement roleDefinitionElement in roleAssignmentElement.SelectNodes("RoleDefinitionBindings/RoleDefinition"))
 276:         {
 277:             if (roleDef.Name == roleDefinitionElement.GetAttribute("Name"))
 278:             {
 279:                 found = true;
 280:                 break;
 281:             }
 282:         }
 283:         if (!found)
 284:         {
 285:             roleDefsToRemove.Add(roleDef);
 286:             modified = true;
 287:         }
 288:     }
 289:     foreach (SPRoleDefinition roleDef in roleDefsToRemove)
 290:     {
 291:         Log("Progress: Removing '{0}' from '{1}'", roleDef.Name, roleAssignment.Member.Name);
 292:         roleAssignment.RoleDefinitionBindings.Remove(roleDef);
 293:     }
 294:     return modified;
 295: }
 296:  
 297: /// <summary>
 298: /// Gets the role assignement.
 299: /// </summary>
 300: /// <param name="web">The web.</param>
 301: /// <param name="securableObject">The securable object.</param>
 302: /// <param name="memberName">Name of the member.</param>
 303: /// <returns></returns>
 304: private static SPRoleAssignment GetRoleAssignement(SPWeb web, ISecurableObject securableObject, string memberName)
 305: {
 306:     SPPrincipal principal = GetPrincipal(web, memberName);
 307:     return GetRoleAssignement(securableObject, principal);
 308: }
 309:  
 310: /// <summary>
 311: /// Gets the role assignement.
 312: /// </summary>
 313: /// <param name="securableObject">The securable object.</param>
 314: /// <param name="principal">The principal.</param>
 315: /// <returns></returns>
 316: private static SPRoleAssignment GetRoleAssignement(ISecurableObject securableObject, SPPrincipal principal)
 317: {
 318:     SPRoleAssignment ra = null;
 319:     try
 320:     {
 321:         ra = securableObject.RoleAssignments.GetAssignmentByPrincipal(principal);
 322:     }
 323:     catch (ArgumentException)
 324:     { }
 325:     return ra;
 326: }
 327:  
 328: /// <summary>
 329: /// Gets the principal.
 330: /// </summary>
 331: /// <param name="web">The web.</param>
 332: /// <param name="memberName">Name of the member.</param>
 333: /// <returns></returns>
 334: private static SPPrincipal GetPrincipal(SPWeb web, string memberName)
 335: {
 336:     foreach (SPPrincipal p in web.SiteUsers)
 337:     {
 338:         if (p.Name == memberName)
 339:             return p;
 340:     }
 341:     foreach (SPPrincipal p in web.SiteGroups)
 342:     {
 343:         if (p.Name == memberName)
 344:             return p;
 345:     }
 346:     return null;
 347: }

This command simply takes in the output from the gl-exportlistsecurity command so naturally anything that command doesn't support this one won't either.  The syntax can be seen below:

C:\>stsadm -help gl-importlistsecurity

stsadm -o gl-importlistsecurity


Imports security settings using data output from gl-exportlistsecurity.

Parameters:
        -url <url to import security to>
        -inputfile <file to import settings from>
        [-quiet]
        [-includeitemsecurity]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-importlistsecurity WSS v3, MOSS 2007 Released: 4/12/2008
Updated: 8/28/2008 

Parameter Name Short Form Required Description Example Usage
url   Yes The web URL to import the security settings to. -url http://portal
inputfile file Yes The file with the security information to import. -inputfile "c:\security.xml"

-file "c:\security.xml"
quiet   No If specified then progress information will not be output to the console. -quiet
-includeitemsecurity items No If specified then item level security will be imported. -includeitemsecurity

-items

Here's an example of how to import the security settings:

stsadm -o gl-importlistsecurity -url http://portal/documents/forms/allitems.aspx -inputfile c:\security.xml

Update 8/28/2008: I've updated the code so that it now supports importing item level security.

8 comments:

Anonymous said...

Hi Gary,

I'm having this error when a security group has both read and restricted permission on the original list: you can not give the user a restricted access level permission. (translated)

it works when I deleted the restricted access settings line in the exported xml file (Name="" )

Russdog said...

Hi Gary

I have created a script that creates an xml file for gl-importlistsecurity and works fine, however it does not resolve some usernames and returns the following error:

Progress: Unable to add Role Assignment for "reports/Rampton, Izabella Clare" -
Member "Mr R Nelson and Ms Rampton " not found.

I have tried also with the username in the format "Domain\user" as well with the same result.

If, however, I log into sharepoint with the user, then try this again, it recoginses the user and adds the permission. The problem does not occur again. Is there any reason for this? It is possible that the user has never logged into sharepoint before. If this is the cause, is there any way to compensate for this?

Cheers

Gary Lapointe said...

Russ - sorry for the delay in getting to you. I just pushed out a new build that should correct this issue. You will need to add a LoginName attribute to any elements where the PrincipalType is User in your xml (do a test export and you'll see what I mean). Note that AD groups come through as SPUser (thus User) objects so those will need a LoginName attribute as well.

Russdog said...

Hi Gary

I worked around this problem in the meantime as I found that the users had not logged on before and hence were not present in the SQL table: dbo.userinfo.

The workaround I used was to cycle through the specific user accounts using gl-adduser2 to give them read permission to a dummy workspace then remove them with deleteuser. I found that new users are added to the table when they first log on, or site permissions are set.

I set the script to run daily just after new users are scheduled to be created by a 3rd party app.

I will give your new release a go when I get a chance as well

Cheers
Russ

Anonymous said...

This command is great. It does something I have been looking for, it breaks inhereitance and applies permissions groups.

I have 3 questions:

1) Why does it add the System Account role to the site level permission access?

I look forward to your response.

2) Can this be prevented?

3) Can this be applied as an inhereitance breaker at the site level as well as the list level?

Gary Lapointe said...

1. It will add the account of the user running the command - this is actually an SP thing (my code doesn't do it - the act of breaking the inheritance does).
2. Right after running the command just remove the user from the group (I believe there's an stsadm command that will do this but I can't remember at the moment - either way a simple powershell script will work).
3. The code could be modified to handle site level - I've just not gotten around to it (both the SPWeb and SPList objects implement the same interface which my method works against - it would just be a matter of modifying the calling code).

Anonymous said...

Hi there!

We found a code error in the

AddRoleDefinitions(SPWeb web, SPRoleAssignment roleAssignment, XmlElement roleAssignmentElement)

method at line 239.

SPRoleDefinition existingRoleDef = web.RoleDefinitions[name];

The system cannot find a role definition that doesn't exists in the system. The web.RoleDefinitions[name] throws exception, not returns with null value if it cannot find the roledefinition based on the name.

I think you should write this line in a try catch block.

Bye,
Anonymous


Permission level cannot be found.
at Microsoft.SharePoint.SPRoleDefinitionCollection.get_Item(String name)

Gary Lapointe said...

Thanks for the info - I've fixed this in the latest release.