Thursday, March 15, 2007

Event handler impersonation - continued

In his comment to my latest article about impersonation in event handlers, Anders Rask said an interesting thing. He suggested that I use the "RunWithElevatedPrivileges" function of the SPSecurity class to run the piece of code as the account running the application pool.
He was kind enough to leave a code sample, and it is clear he knows what he is talking about (check out his article about impersontion in a web part for the 2003 version. Link requires registration to - a site I don't believe still exists, seeing it's horrible interface).

To answer Mr. Rask, I will explain why I am not using this "simple" impersonation:

  1. Because I don't always want to impersonate that system account. My clients are very security sensitive, and system accounts get as little permissions as possible. If my event handler needs to do things that require access to systems that the account is not supposed to touch, we need to impersonate another account.
  2. Because it doesn't work!
    Ok, maybe I am over reacting, but the fact of the matter is that even though the "SPSecurity.RunWithElevatedPrivileges" seems to switch to the system account, I actually get 'access denied' when I try to access resources that the account should have permissions to access.
    Example: in my event handler I want to write to a log file. Because the data written to the log is very sensitive, I want to put the log file in a secure file system folder on the server. for this example, "c:\temp" is secure enough. I made the application pool account a server administrator (more, much more than you need to write a file in c:\temp) and still I get permission denied when the event handler is trying to open the stream.
    Using the impersonation example of Victor Vogelpoel [Macaw] I had no problem specifying the actuall application pool account and it's password and the log file is writing!
    Even more, I check the "web.CurrentUser" value and that returns "SHAREPOINT\system" and not the user name for the application pool (I guess that is why it fails accessing the file system). It also didn't "see" the document library that I had removed all user access from (except the SHAREPOINT\system and the application pool account).

So my analysis say - the "RunWithElevatedPrivileges" may be useful in webparts (I never tried), but looks to be useless in Event Handlers (at least for my purposes).
I would welcome any feedback!


Anders Rask said...

Hi Ishai,

as you also have found out yourself, the account that is used for impersonation when using RunWithElevatedPrivileges is SHAREPOINT\system which isnt a physical account, hence when going "outside" WSS/MOSS, eg. writing on disk, you will need to impersonate a physical user on the box. I would always prefer RunWithElevatedPrivileges combined with CAS for event handlers where i just need to access parts of the API that a normal user cant access. :-)

You mention your customer is sensitive to security issues. If that is the case i would strongly recommend a try/catch around your code so that your Undo() method will be called no matter what. Else if your code break, the rest of the session will use elevated privileges!

//do impersonation
catch ()
//if impersonation obj != null call Undo()

On a side note *never* call Undo() in "finally", since malicious applications using your code could force an exception to happen and use the VB specific When Filter() in its *own* exception handling to call elevated code before you close your object in finally (read ). -im not saying its gonna happen in an event handler, im just saying its not good practice ;-)

Also i would consider storing the password used for impersonation a security issue. You should consider encrypting password and place it in registry instead of web.config for security reasons

To summarize: when ever possible use RunWithElevatedPrivileges. If access outside WSS API is needed, use impersonation :-)

Anonymous said...

Hi Ishai, Hi all,

About impersonation, the code I posted to Victor a while ago is based on a IDisposable implementation (or if not I then wrote a new version that is), You should prefer
// Do whatever stuff you got to do

This way no messing up the exception stack frame nor writing old try / finally blocks (I didn't test it against your VB attacks however, I'll look into that).

The RunWithElevatedPrivileges did not exist at the time this code was written (MOSS 2007 was only a far idea, we didn't even have any dogfood version to test), I since had chances to use this MOSS2007/WSS3 function, which helped a lot by the way.

Last, using the SPSite given by the event handler, you wuill have to play with obscure impersonation code, I recommend saving the IDs of the elements you want to access (SiteID, WebID, ...), then you impersonate, and finally you can create a new SPSite, a new SPWeb (don't forget to dispose them afterwards otherwise you'll mess up your memory and performances with unused COM objects) and play with them.

About the security issue, which was one of my main concerns when developing this, you don't need to store any password at all, since you impersonate ad the application pool.

I agree, use RunWithElevatedPrivileges as much as possible if you are on a WSS3/MOSS platform, avoir using Impersonation, because I use some P/Invoke and security manipulation wich should better be avoided if possible (changing context, playing with security tokens is never the best option, it was on SPS 2K3 because we basically had no other choices).


Thomas said...

Just to clarify (hopefully) event further:

Julien is right in his comments above. See the following MSDN article: The very last paragraph states:

"You must create a new SPSite object inside the delegate because SPSite objects created outside do not have Full Control even when referenced inside the delegate. Use the using keyword to ensure that the object is disposed in the delegate..."

In short: IF you're trying to impersonate using RunWithElevatedPrivileges, ALWAYS follow the practice of creating a NEW SPSite inside the RunWith... block. You can grab the current site ID from the event properties object, and then instantiate a new SPSite by e.g.:

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

When doing this, I've found that RunWithElevatedPrivileges works as intended, also in Event Handlers.

cosmicice said...

Trust me, SPSecurity.RunWithElevatedPrivileges(delegate(){
//your code
does not work in even handlers. If the user doesnt have enough permission to do sth, he will never be able to do that with that method. It constantly gives "Accessdenied" exception. I wasted 2 days and finally realized that. Now I think I will give a try with impersonation, lets see what happens...

cosmicice said...

ok, creating new spsite objects inside the SPSecurity.RunWithElevatedPrivileges actually did the trick. Everything ran very smoothly. Thanks to biilmann...

cosmicice said...

Helo Ishai, I used RunWithElevatedPrivileges inside event handlers and it worked fine. I used this to create/ update a listitem in a list, where the current user had read permission only. Here is what I did:
Hope it helps who is in need.