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>
8public 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>
47internal 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>
80private 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>
107private 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 if (f.Folder.ServerRelativeUrl.Substring(targetList.RootFolder.ServerRelativeUrl.Length) ==
115 sourceFolder.Folder.ServerRelativeUrl.Substring(sourceList.RootFolder.ServerRelativeUrl.Length))
116 {
117 targetFolder = f;
118 break;
119 }
120 }
121 if (targetFolder == null)
122 continue;
123
124 SetObjectSecurity(targetWeb, sourceFolder, targetFolder, quiet, targetFolder.Folder.ServerRelativeUrl);
125 }
126}
127
128/// <summary>
129/// Sets the object security.
130/// </summary>
131/// <param name="targetWeb">The target web.</param>
132/// <param name="sourceObject">The source object.</param>
133/// <param name="targetObject">The target object.</param>
134/// <param name="quiet">if set to <c>true</c> [quiet].</param>
135/// <param name="itemName">Name of the item.</param>
136private static void SetObjectSecurity(SPWeb targetWeb, ISecurableObject sourceObject, ISecurableObject targetObject, bool quiet, string itemName)
137{
138 if (!sourceObject.HasUniqueRoleAssignments && targetObject.HasUniqueRoleAssignments)
139 {
140 if (!quiet)
141 Console.WriteLine("Progress: Setting target object to inherit permissions from parent for \"{0}\".", itemName);
142 targetObject.ResetRoleInheritance();
143 return;
144 }
145 else if (sourceObject.HasUniqueRoleAssignments && !targetObject.HasUniqueRoleAssignments)
146 {
147 if (!quiet)
148 Console.WriteLine("Progress: Breaking target object inheritance from parent for \"{0}\".", itemName);
149 targetObject.BreakRoleInheritance(false);
150 }
151 else if (!sourceObject.HasUniqueRoleAssignments && !targetObject.HasUniqueRoleAssignments)
152 {
153 if (!quiet)
154 Console.WriteLine("Progress: Ignoring \"{0}\". Target object and source object both inheritance from parent.", itemName);
155 return; // Both are inheriting so don't change.
156 }
157
158 foreach (SPRoleAssignment ra in sourceObject.RoleAssignments)
159 {
160 SPRoleAssignment existingRoleAssignment = GetRoleAssignement(targetObject, ra.Member);
161 if (existingRoleAssignment == null)
162 {
163 targetObject.RoleAssignments.Add(ra);
164
165 existingRoleAssignment = GetRoleAssignement(targetObject, ra.Member);
166 if (existingRoleAssignment != null)
167 {
168 if (!quiet)
169 Console.WriteLine("Progress: Added \"{0}\" to target object \"{1}\".", ra.Member, itemName);
170 continue;
171 }
172
173 SPRoleAssignment newRA = new SPRoleAssignment(ra.Member);
174 foreach (SPRoleDefinition rd in ra.RoleDefinitionBindings)
175 {
176 if (rd.Name == "Limited Access")
177 continue;
178
179 SPRoleDefinition existingRoleDef = targetWeb.RoleDefinitions[rd.Name];
180 if (existingRoleDef == null)
181 {
182 existingRoleDef = new SPRoleDefinition();
183 existingRoleDef.BasePermissions = rd.BasePermissions;
184 existingRoleDef.Description = rd.Description;
185 existingRoleDef.Update();
186 targetWeb.RoleDefinitions.Add(existingRoleDef);
187 }
188 newRA.RoleDefinitionBindings.Add(existingRoleDef);
189 }
190 if (newRA.RoleDefinitionBindings.Count == 0)
191 {
192 if (!quiet)
193 Console.WriteLine("Progress: Unable to add \"{0}\" to target object \"{1}\" (principals with only \"Limited Access\" cannot be added).", ra.Member, itemName);
194 continue;
195 }
196
197 targetObject.RoleAssignments.Add(newRA);
198
199 existingRoleAssignment = GetRoleAssignement(targetObject, ra.Member);
200 if (existingRoleAssignment == null)
201 {
202 Console.WriteLine("Progress: Unable to add \"{0}\" to target object \"{1}\".", ra.Member, itemName);
203 }
204 else
205 {
206 if (!quiet)
207 Console.WriteLine("Progress: Added \"{0}\" to target object \"{1}\".", ra.Member, itemName);
208 }
209 }
210 }
211}
212
213/// <summary>
214/// Gets the role assignement.
215/// </summary>
216/// <param name="securableObject">The securable object.</param>
217/// <param name="principal">The principal.</param>
218/// <returns></returns>
219private static SPRoleAssignment GetRoleAssignement(ISecurableObject securableObject, SPPrincipal principal)
220{
221 SPRoleAssignment ra = null;
222 try
223 {
224 ra = securableObject.RoleAssignments.GetAssignmentByPrincipal(principal);
225 }
226 catch (ArgumentException)
227 { }
228 return ra;
229}
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.