|
|
|
Title:
|
Sample Event Handler to set Permissions (not using RunWithElevatedPrivilages)
|
Language:
|
C#
|
|
Description:
|
A request is often made to have an event handler that automagically sets the permissions on a list item so that only the author of the item can edit it, while other people can only read it.
(http://www.sharepoint-tips.com/2007_03_01_archive.html)
|
Views:
|
1696
|
|
Author:
|
T D
|
Date Added:
|
2/6/2009
|
Copy
|
Code
|
|
1using System;
2
3using System.Globalization;
4
5using System.ComponentModel;
6
7using System.IO;
8
9using System.Data;
10
11using System.Text;
12
13using System.Xml;
14
15using System.Collections;
16
17using System.Configuration;
18
19using System.Diagnostics;
20
21using System.Web;
22
23using System.Security;
24
25using System.Security.Policy;
26
27using System.Security.Principal;
28
29using System.Security.Permissions;
30
31using System.Runtime.InteropServices;
32
33using Microsoft.SharePoint;
34
35namespace SharePointTips.SharePoint.Samples.EventHandlers
36
37{
38
39 /// <summary>
40
41 /// This is the event receiver that traps the item added event of the sharepoint list it is attached to.
42
43 /// </summary>
44
45 class ListItemSecuritySetter:SPItemEventReceiver
46
47 {
48 #region constants ...
49
50
51 /// <summary>
52
53 /// defines the permission set for editors.
54
55 /// </summary>
56
57 const SPBasePermissions c_EditorPermissions = SPBasePermissions.EditListItems | SPBasePermissions.ViewListItems;
58
59 /// <summary>
60
61 /// defines the permission set for readers
62
63 /// </summary>
64
65 const SPBasePermissions c_ReaderPermissions = SPBasePermissions.ViewListItems;
66
67 /// <summary>
68
69 /// The name of the role that will be created for the author
70
71 /// </summary>
72
73 const string c_AuthorRoleName = "Item Author and Editor";
74
75 /// <summary>
76
77 /// The name of the role that will be created for the reader
78
79 /// </summary>
80
81 const string c_ReaderRoleName = "Item Reader";
82
83 /// <summary>
84
85 /// Debug mode writes almost every action to the file log. In production switch this to false. Recommend changing that to read from a configuration file
86
87 /// </summary>
88
89 const bool DEBUG_MODE = true;
90
91 /// <summary>
92
93 /// The page where the log file will be created. Recommend changing that to read from a configuration file
94
95 /// </summary>
96
97 const string c_logFileFolder = @"c:\temp";
98 #endregion
99 #region class properties ...
100
101
102 /// <summary>
103
104 /// Returns the name of the log file to be used (file name only - not path)
105
106 /// The string returned will contain {0} and {1} that should be replaced with list name and site name.
107
108 /// </summary>
109
110 private string LogFileName
111
112 {
113
114 get
115
116 {
117
118 return "ListItemSecuritySetter-{0}-{1}-" + DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() + ".htm";
119
120 }
121
122 }
123 #endregion
124 #region local variables ...
125
126
127
128
129 HTMLFileLogging log;
130 #endregion
131 #region event handler event trapping ...
132
133
134 /// <summary>
135
136 /// The sharepoint event for ItemAdded
137
138 /// </summary>
139
140 /// <param name="properties"></param>
141
142 public override void ItemAdded(SPItemEventProperties properties)
143
144 {
145
146 //run the default event handlers on the item
147
148 base.ItemAdded(properties);
149
150
151
152
153
154 //create the log handler object
155
156 log = new HTMLFileLogging(c_logFileFolder, string.Format(LogFileName,properties.ListTitle,properties.OpenWeb().Title) , true, false);
157
158 this.WriteToLog("ItemAdded was triggered",true);
159
160 //if in debug mode, write all the properties in the item to the log
161
162 if(DEBUG_MODE)
163
164 WriteItemPropertiesToLog(properties.ListItem);
165
166
167
168 this.WriteToLog("Setting permissions", true);
169
170 try
171
172 {
173
174 //impersonate an administrator who can change permissions on list items in the list
175
176 ImpersonationUtility imp = ImpersonationUtility.ImpersonateAdmin();
177
178 //open the spweb object to get the token for the user
179
180 using (SPWeb webOrigUser = properties.OpenWeb())
181
182 {
183
184 //get the token for the impersonation user (this will get the user that the ImpersonationUtility is using)
185
186 SPUserToken token = webOrigUser.AllUsers[WindowsIdentity.GetCurrent().Name].UserToken;
187
188 //reopen the spweb object with the new token - full impersonation!
189
190 using (SPSite site = new SPSite(properties.SiteId, token))
191
192 {
193
194 using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
195
196 {
197
198
199
200 //call the function that changes the permissions on the list item.
201
202 //Do not use properties.ListItem since that will break impersonation!
203
204 SetAuthorAsOnlyEditor(web.Lists[properties.ListId].GetItemById(properties.ListItemId));
205
206 }
207
208 }
209
210 }
211
212
213
214
215
216 }
217
218 catch (Exception ex)
219
220 {
221
222 this.WriteToLog("Setting permissions encountered an error: " + ex.Message + Environment.NewLine + ex.ToString(), true);
223
224 }
225
226 }
227 #endregion
228 #region custom functions ...
229
230
231 /// <summary>
232
233 /// Loops over the item properties and prints them to the log file. should only be called in debug mode!
234
235 /// </summary>
236
237 /// <param name="item">the list item that is currently handled</param>
238
239 private void WriteItemPropertiesToLog(SPListItem item)
240
241 {
242
243 this.WriteToLog("Item Properties:",false);
244
245 foreach(SPField field in item.Fields)
246
247 {
248
249 try
250
251 {
252
253 this.WriteToLog(field.Title + " : " + item[field.InternalName].ToString(), false);
254
255 }
256
257 catch { }
258
259 }
260
261 }
262
263 /// <summary>
264
265 /// Function sets the permission on the list item so that the author has edit permission on the item,
266
267 /// and all other people with access to the document library can only read.
268
269 /// Relies on the values in the constants c_EditorPermissions and c_ReaderPermissions.
270
271 /// </summary>
272
273 /// <param name="item">the list item that is currently handled</param>
274
275 private void SetAuthorAsOnlyEditor(SPListItem item)
276
277 {
278
279
280
281 using (SPWeb currentWeb = item.Web)
282
283 {
284
285 this.WriteToLog("Getting author from item", true);
286
287 //get the author from the item. 'Author' is a built-in property, so it should be in all lists.
288
289 string authorValue = item["Author"].ToString();
290
291 SPFieldUserValue authorUserValue = new SPFieldUserValue(currentWeb, authorValue);
292
293 SPUser authorUser = authorUserValue.User;
294
295 this.WriteToLog("Got author name:'" + authorUser.Name + "', email: '" + authorUser.Email + "'", true);
296
297
298
299 this.WriteToLog("Breaking role inheritance for the item", true);
300
301 //break the security of the item from the list, but keep the permissions
302
303 item.BreakRoleInheritance(true);
304
305 //change the permissions of everyone with access to this item so they are readers only (c_ReaderPermissions)
306
307 ChangeItemExistingRoles(item);
308
309
310
311 this.WriteToLog("Creating role '" + c_AuthorRoleName + "' in the site if needed", true);
312
313 //create a security definition in the web for an author-editor
314
315 SPRoleDefinition def = CreateRoleInSite(currentWeb,c_AuthorRoleName,c_EditorPermissions);
316
317 this.WriteToLog("Assigning role to the user", true);
318
319 //Set the author user with the permissions defined
320
321 SPRoleAssignment authorRole = new SPRoleAssignment(authorUser.LoginName, authorUser.Email, authorUser.Name, authorUser.Notes);
322
323 this.WriteToLog("Binding the role assignment of the user to the definition", true);
324
325 authorRole.RoleDefinitionBindings.Add(def);
326
327 this.WriteToLog("Adding the role to the item", true);
328
329 item.RoleAssignments.Add(authorRole);
330
331 this.WriteToLog("Updating the item", true);
332
333 item.Update();
334
335 this.WriteToLog("Success!", true);
336
337 }
338
339 }
340
341 /// <summary>
342
343 /// This function will make everyone with access to the item a reader.
344
345 /// Loops over all the roles that exist in the item, removes the bindings and adds the reader permission definition to them
346
347 /// </summary>
348
349 /// <param name="item">the list item currently handled</param>
350
351 private void ChangeItemExistingRoles(SPListItem item)
352
353 {
354
355 //get, and if necessary create, the reader role
356
357 SPRoleDefinition readerDef = CreateRoleInSite(item.Web, c_ReaderRoleName, c_ReaderPermissions);
358
359
360
361
362
363 foreach (SPRoleAssignment roleAssignment in item.RoleAssignments)
364
365 {
366
367 //delete the existing permissions
368
369 roleAssignment.RoleDefinitionBindings.RemoveAll();
370
371 //add the reader permission
372
373 roleAssignment.RoleDefinitionBindings.Add(readerDef);
374
375 roleAssignment.Update();
376
377 item.Update();
378
379
380
381 }
382
383 }
384
385 /// <summary>
386
387 /// Gets and if necessary creates the role in the site.
388
389 /// </summary>
390
391 /// <param name="web">The site where the role should be created</param>
392
393 /// <param name="roleName">The name of the role to create</param>
394
395 /// <param name="permissions">The permission set to give the role. Example: SPBasePermissions.EditListItems | SPBasePermissions.ViewListItems</param>
396
397 /// <returns></returns>
398
399 private SPRoleDefinition CreateRoleInSite(SPWeb web,string roleName,SPBasePermissions permissions)
400
401 {
402
403 this.WriteToLog("Checking if role '"+roleName+"' exists in the web", true);
404
405 //check that the role exists
406
407 if (RoleExists(web, roleName))
408
409 {
410
411 this.WriteToLog("Role exists in the web", true);
412
413 //role exists - return it
414
415 return web.RoleDefinitions[roleName];
416
417 }
418
419 else
420
421 {
422
423 //role does not exist in the site- create it and return.
424
425 this.WriteToLog("Role does not exist in the web. creating a new role", true);
426
427 //Create the role definition in the web by the name specified in c_AuthorRoleName
428
429 SPRoleDefinition def = new SPRoleDefinition();
430
431 def.BasePermissions = permissions;
432
433 def.Name = roleName;
434
435 this.WriteToLog("Adding the role to the FirstUniqueRoleDefinitionWeb", true);
436
437 web.FirstUniqueRoleDefinitionWeb.RoleDefinitions.Add(def);
438
439 this.WriteToLog("Updating the web", true);
440
441 web.FirstUniqueRoleDefinitionWeb.Update();
442
443 web.FirstUniqueRoleDefinitionWeb.Dispose();
444
445 this.WriteToLog("Reopening the current web object", true);
446
447 web = web.Site.OpenWeb();
448
449 this.WriteToLog("Verifying role is in current web", true);
450
451 if (RoleExists(web, roleName))
452
453 return web.RoleDefinitions[roleName];
454
455 else
456
457 {
458
459 throw new Exception("Role does not exist?");
460
461 }
462
463
464
465 }
466
467 }
468
469 /// <summary>
470
471 /// This function checks the spweb objec to see if a specific role exists (by name)
472
473 /// </summary>
474
475 /// <param name="web">the spweb object for the site to contain the role.</param>
476
477 /// <param name="roleName">the name of the role searched for</param>
478
479 /// <returns></returns>
480
481 private bool RoleExists(SPWeb web, string roleName)
482
483 {
484
485 this.WriteToLog("Loading the RoleDefinitions xml string:", true);
486
487 this.WriteToLog(web.RoleDefinitions.Xml, true);
488
489 //read the xml of the roledefinitions
490
491 XmlDocument doc = new XmlDocument();
492
493 doc.LoadXml(web.RoleDefinitions.Xml);
494
495 this.WriteToLog("Searching for the role in the xml", true);
496
497 //search for the role with the name in the xml
498
499 XmlNode node = doc.SelectSingleNode("//Role[@Name='"+roleName+"']");
500
501 //if the search returned null, the role does not exist
502
503 if (node == null)
504
505 return false;
506
507 else
508
509 return true;
510
511 }
512
513 /// <summary>
514
515 /// writes a message to the log file
516
517 /// </summary>
518
519 /// <param name="message">The message to write</param>
520
521 /// <param name="debugOnly">If true, the message will only get written when the code runs in debug mode.</param>
522
523 private void WriteToLog(string message,bool debugOnly)
524
525 {
526
527 if (DEBUG_MODE || !debugOnly)
528
529 log.WriteToLogFile(message);
530
531 }
532 #endregion
533
534 }
535
536 /// <summary>
537
538 /// Handles simple file logging. Recommend switching to trace log.
539
540 /// </summary>
541
542 class HTMLFileLogging
543
544 {
545 #region class properties ...
546
547
548 private string logFolderPath = @"c:\logs";
549
550 public string LogFolderPath
551
552 {
553
554 get
555
556 {
557
558 return logFolderPath;
559
560 }
561
562 set
563
564 {
565
566 if (Directory.Exists(value))
567
568 {
569
570 logFolderPath = value;
571
572 if (logFolderPath.EndsWith("\\"))
573
574 {
575
576 logFolderPath = logFolderPath.Remove(logFolderPath.Length);
577
578 }
579
580 }
581
582 else
583
584 {
585
586 throw new DirectoryNotFoundException();
587
588 }
589
590 }
591
592 }
593
594 private string logFileName = "";
595
596 public string LogFileName
597
598 {
599
600 get
601
602 {
603
604 return logFileName;
605
606 }
607
608 set
609
610 {
611
612 logFileName = value;
613
614 }
615
616 }
617
618 public string LogFilePath
619
620 {
621
622 get
623
624 {
625
626 return this.LogFolderPath + "\\" + this.LogFileName;
627
628 }
629
630 }
631 #endregion
632 #region CTOR ...
633
634
635 /// <summary>
636
637 /// Create a HTMLFileLogging object
638
639 /// </summary>
640
641 /// <param name="folderPath">The path of the folder that will hold the log file (no file name)</param>
642
643 /// <param name="fileName">The name of the file to create</param>
644
645 /// <param name="createPath">When this is set to true and the folder does not exist, the code will create the folder.</param>
646
647 /// <param name="deleteFile">When this is set to true and the file exists, the code will delete the file and create a new one</param>
648
649 public HTMLFileLogging(string folderPath, string fileName, bool createPath, bool deleteFile)
650
651 {
652
653
654
655 this.LogFileName = fileName;
656
657
658
659 if (createPath && !Directory.Exists(folderPath))
660
661 {
662
663 Directory.CreateDirectory(folderPath);
664
665 }
666
667 this.LogFolderPath = folderPath;
668
669 if (File.Exists(this.LogFilePath) && deleteFile)
670
671 {
672
673 File.Delete(this.LogFilePath);
674
675 }
676
677
678
679 }
680 #endregion
681 #region custom code ...
682
683
684 /// <summary>
685
686 /// Writes a string to the log file.
687
688 /// </summary>
689
690 /// <param name="message">a string to write. supports html tags.</param>
691
692 public void WriteToLogFile(string message)
693
694 {
695
696 try
697
698 {
699
700 StreamWriter sw = new StreamWriter(this.LogFilePath,true);
701
702 sw.WriteLine("<p>");
703
704 sw.WriteLine("<date>" + DateTime.Now.ToShortDateString()+ "</date> <time>" + DateTime.Now.ToLongTimeString() + "</time> <br /> <message>" + message + "</message>");
705
706 sw.WriteLine("</p>");
707
708 sw.Flush();
709
710 sw.Close();
711
712 }
713
714 catch (Exception ex)
715
716 {
717
718 }
719
720 }
721 #endregion
722
723
724
725 }
726
727 /// <summary>
728
729 /// Thanks to impersonation example of Victor Vogelpoel [Macaw]
730
731 /// http://dotnetjunkies.com/WebLog/victorv/archive/category/2032.aspx
732
733 /// </summary>
734
735 public sealed class ImpersonationUtility
736
737 {
738
739 private static string ADMINDOMAINACCOUNT = @"domain\user";//CHANGE THIS!
740
741 private static string ADMINDOMAIN = "domain";//CHANGE THIS!
742
743 private static string ADMINACCOUNT = "user";//CHANGE THIS!
744
745 private static string ADMINPASSWORD = "password";//CHANGE THIS!
746
747 private WindowsImpersonationContext _wiContext;
748
749 public IntPtr token;
750
751 /// <summary>
752
753 /// Private ctor.
754
755 /// </summary>
756
757 private ImpersonationUtility()
758
759 { }
760
761 /// <summary>
762
763 /// Start impersonating the administrator.
764
765 /// </summary>
766
767 /// <returns>an ImpersonationUtility instance.</returns>
768
769 public static ImpersonationUtility ImpersonateAdmin()
770
771 {
772
773 ImpersonationUtility imp = new ImpersonationUtility();
774
775 imp._ImpersonateAdmin();
776
777 return imp;
778
779 }
780
781 /// <summary>
782
783 /// Undo the impersonation.
784
785 /// </summary>
786
787 public void Undo()
788
789 {
790
791 if (this._wiContext != null)
792
793 {
794
795 this._wiContext.Undo();
796
797 this._wiContext = null;
798
799 }
800
801 }
802
803 private void _ImpersonateAdmin()
804
805 {
806
807 token = IntPtr.Zero;
808
809 IntPtr tokenDuplicate = IntPtr.Zero;
810
811 try
812
813 {
814
815 // Only start admin impersonation if we're not the admin...
816
817 if (String.Compare(ADMINDOMAINACCOUNT, WindowsIdentity.GetCurrent().Name, true, CultureInfo.InvariantCulture) != 0)
818
819 {
820
821 // Temporarily stop the impersonation started by Web.Config.
822
823 // WindowsIdentity.Impersonate() will store the current identity (the
824
825 // account of the requestor) and return to the account of the ApplicationPool
826
827 // which is "DOMAIN\SPAdmin".
828
829 this._wiContext = WindowsIdentity.Impersonate(IntPtr.Zero);
830
831 // But somehow the reverted account "DOMAIN\SPAdmin" still does
832
833 // not have enough privileges to access SharePoint objects, so
834
835 // we're logging in DOMAIN\SPAdmin again...
836
837 if (NativeMethods.LogonUserA(ADMINACCOUNT, ADMINDOMAIN, ADMINPASSWORD, NativeMethods.LOGON32_LOGON_INTERACTIVE,
838
839 NativeMethods.LOGON32_PROVIDER_DEFAULT, ref token) != 0)
840
841 {
842
843 if (NativeMethods.DuplicateToken(token, 2, ref tokenDuplicate) != 0)
844
845 {
846
847 WindowsIdentity wi = new WindowsIdentity(tokenDuplicate);
848
849 // NOTE: Impersonate may fail if account that tries to impersonate does
850
851 // not hold the "Impersonate after Authentication" privilege
852
853 // See local security policy - user rights assignment.
854
855 // Note that the ImpersonationContext from the Impersonate() call
856
857 // is ignored. Upon the Undo() call, the original account
858
859 // will be reinstated.
860
861 wi.Impersonate();
862
863 }
864
865 else
866
867 {
868
869 throw new Win32Exception(Marshal.GetLastWin32Error(),
870
871 "Impersonation: Error duplicating token after logon for user \"DOMAIN\\SPAdmin\"");
872
873 }
874
875 }
876
877 else
878
879 {
880
881 throw new Win32Exception(Marshal.GetLastWin32Error(),
882
883 "Impersonation: Error logging on user \"DOMAIN\\SPAdmin\"");
884
885 }
886
887 }
888
889 }
890
891 finally
892
893 {
894
895 if (token != IntPtr.Zero)
896
897 NativeMethods.CloseHandle(token);
898
899 if (tokenDuplicate != IntPtr.Zero)
900
901 NativeMethods.CloseHandle(tokenDuplicate);
902
903 }
904
905 }
906
907 }
908
909 /// <summary>
910
911 /// Thanks to impersonation example of Victor Vogelpoel [Macaw]
912
913 /// http://dotnetjunkies.com/WebLog/victorv/archive/category/2032.aspx
914
915 /// </summary>
916
917 internal sealed class NativeMethods
918
919 {
920
921 private NativeMethods() { }
922
923
924
925 [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
926
927 public static extern bool CloseHandle(IntPtr handle);
928
929
930
931 public const int LOGON32_PROVIDER_DEFAULT = 0;
932
933 public const int LOGON32_LOGON_INTERACTIVE = 2;
934
935 public const int LOGON32_LOGON_NETWORK = 3;
936
937
938
939 [DllImport("advapi32.dll")]
940
941 public static extern int LogonUserA(String lpszUserName,
942
943 String lpszDomain,
944
945 String lpszPassword,
946
947 int dwLogonType,
948
949 int dwLogonProvider,
950
951 ref IntPtr phToken);
952
953
954
955 [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
956
957 public static extern int DuplicateToken(IntPtr hToken,
958
959 int impersonationLevel,
960
961 ref IntPtr hNewToken);
962
963
964
965
966
967 [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
968
969 public static extern bool RevertToSelf();
970
971 }
972
973}
|
|
Notes
|
|
A request is often made to have an event handler that automagically sets the permissions on a list item so that only the author of the item can edit it, while other people with contributor permissions on the library can only read it.
Please note that it is a sample code and should not be used in production environment without some major changes. For example - getting the user name and password of a user with elevated permissions on the list should be from an encrypted config file. Another example for a change is changing the logging to use a trace log.
All that aside, this is still a good working example that shows some interesting things:
•How to get a list item in the event handler
•How to impersonate another user totaly (not using RunWithElevatedPrivilages)
•How to change an item's permissions
•How to create a new permission role in a web site
•How to check if a role exists or not (nice trick there - can you spot it?)
|
|
|