blog.ekegren - HOWTO - Impersonation and SharePoint
Impersonation and SharePoint
This is a topic of continuous debate and confusion amongst many developers. There have been written a lot about this and I have found many posts about this. What I have thought about reading all these posts are – are one actually doing the right thing? Impersonation is necessary in order to e.g. list the roles for which the current user is a part of; doing advanced SharePoint stuff you really need to fully understand how to navigate and use the SharePoint API. What I can't underline enough is: context is everything! (Looks like even Microsoft doesn't fully understand when this works or not by the famous post: http://support.microsoft.com/?kbid=892866, I hope this post can help someone to understand the scenario). Always take notice how you get hold of a SPWeb or SPSite object as the identity used in the construction of the objects carries the credentials and what is allowed.
I decided to do a structured investigation of this matter to be able to provide some conclusions into the problem sphere. Furthermore I have over some time had some suspicion that the various service packs for SharePoint actually change the rules for how to use impersonation with the API, therefore I decided to cover how the same piece of code response with clean SPS, SP1 and SP2. Furthermore impersonated use of webservices within webparts has also tricked me a bit so this post was also intended to try to solve this issue.
I developed a webpart NOT deployed in the GAC, but to the bin-directory in wwwroot. Used minimal trust settings for SharePoint, but enabled full trust to my webpart using a codegroup in a modified wss_minimaltrust.config file, for how to do this – look at the great article by Jan Tielens. I developed the webpart on my local machine and set the output directory to the bin-directory on the SharePoint server (which was an all-in-one box installation containing AD, SQL and Exchange). So this is the prerequisites for my investigation (I had nothing else to use at the time of the investigation).
Context is everything. I usually impersonate using the application pool which I configure with domain user which I also grant the SharePoint administrator role. Therefore I switch to the application pool for doing impersonation. This strategy seems to work fine when just working with the OM. See comments #1-#7 in code block. This works for all service packs (tested up until SP2) and shoots down my theory that it behaves differently under the various service packs.
For webservice calls I wanted to use the impersonation branch to assign the credentials of this section of the code (DefaultCredentials) to the webservice credentials for doing impersonation. This however does not work, only way to "impersonate" the webservice is to apply a NetworkCredential object which contains the username, password and domain information:
This collides with that I generally do not want to apply username etc. in configuration anywhere, so I decided to move into enabling NTLM and Kerberos, trusting the server and account for delegation as I thought this was a AD double hop issue. This did not work either – the System.Net.CredentialCache.DefaultCredentials in the impersonation branch, never had the desired effect (providing the credentials of the impersonated user). Instead I learned that if you DO enable this, you need to apply the right authtype string when constructing the CredentialCache to assign to the webservice credentials property:
cc.Add(new Uri(ws.Url), authtype, (NetworkCredential)nc);
ws.Credentials = cc;
The value of the authtype string depends on which NTAuthenticationProvider you enable for the website hosting SharePoint, for ALL other than the "Negotiate,NTLM" (Kerberos) the authtype should be blank, and you can just assign the NetworkCredential/DefaultCredentials - but for Kerberos you need to set authtype to "NTLM" (also look in code block comments in the end of this post).
Using COM+ to impersonate might be a way to go for impersonating webservices not using the NetworkCredential method, might be a topic which should be investigated :-) ... or even better - if anyone has the solution for how to impersonate the webservice calls in another way than illustrated here, please post a solution!
Resources:
http://weblogs.asp.net/jan/archive/2005/06/23/414699.aspx (Common pitfalls for webpart developers)
http://blogs.microsoft.fr/rlondner/articles/7089.aspx (Doing impersonation using COM+)
http://blog.austinwheats.net/archive/2004/04/12/166.aspx (Frustrations about using methods which requires impersonation and how to check for it)
http://support.microsoft.com/?id=832769 (How to enable Kerberos and NTLM)
Code block (webpart code):
protected override void RenderWebPart(HtmlTextWriter output)
{
string originalUser = this.Context.User.Identity.Name;
string currentVersion = String.Empty;
// #1: thisWeb object have current user credentials
SPWeb thisWeb = SPControl.GetContextWeb(this.Context);
SPWeb web;
SiteData sd2;
ICredentials me = System.Net.CredentialCache.DefaultCredentials;
output.Write("username:" + me.GetCredential(this.Context.Request.Url, "Negotiate").UserName);
#region impersonation branch
// #2: relookup the web object as admin
using(RunAsAppPool admin = new RunAsAppPool(true))
{
// #3: this line have no effect as thisWeb is in context of current user
// and current user do not have access to set this variable
thisWeb.Site.CatchAccessDeniedException = false;
// #4: web initialised as admin user..
web = new SPSite(thisWeb.Url).OpenWeb(thisWeb.ID);
// #5: set CatchAccessDeniedException to false to avoid loginboxes to appear
web.Site.CatchAccessDeniedException = false;
SPGlobalAdmin global = new SPGlobalAdmin();
currentVersion = global.Version.ToString();
// #8: connecting to webservice inside impersonation branch have not the expected effect
// assigning the app pool credentials to the credentials cache of the webservice
sd2 = new SiteData();
sd2.Url = thisWeb.Url + "/_vti_bin/SiteData.asmx";
switch(currentVersion)
{
case "6.0.2.5530" :
output.Write(" (" + currentVersion + " Unservice packed)");
break;
case "6.0.2.6361" :
output.Write(" (" + currentVersion + " WSS SP1)");
break;
case "6.0.2.6568" :
output.Write(" (" + currentVersion + " WSS SP2)");
break;
}
System.Net.CredentialCache cc;
ICredentials nc;
if(UseDefaultCredentials)
{
// #9: eventhough we inside an impersonation loop - default credentials is the current
// httpcontext and not the impersonated user
nc = System.Net.CredentialCache.DefaultCredentials;
}
else
{
// only works with a clean meta-base for IIS (NTLM and Kerberos not enabled)
nc = new System.Net.NetworkCredential("administrator","netcompany","demo");
}
if(UseNTML && UseKerberos)
{
cc = new System.Net.CredentialCache();
// #10: I actually thought that this line worked - but it didn't, only NTLM as authtype
// string works
// cc.Add(new Uri(sd2.Url), "Negotiate", (NetworkCredential)nc);
cc.Add(new Uri(sd2.Url), "NTLM", (NetworkCredential)nc);
sd2.Credentials = cc;
}
else if (UseNTML && !UseKerberos)
{
// #11: if Kerberos is not enabled and only NTLM is enabled there is no need to set
// the authtype to anything - just use the clean Credential object
sd2.Credentials = nc;
}
else if (!UseNTML && !UseKerberos)
{
sd2.Credentials = nc;
}
}
#endregion
try
{
// no impersonation
output.Write("<p/><b>No impersonation</b><ul>");
output.Write("<li>User:" + originalUser);
// #6: this will cause login boxes to appear if not the web at #5 was done
// instead UnauthorizedAccessException is thrown
string noImpRoles = Utility.GetRoles(originalUser, thisWeb);
output.Write("<li>Roles: " + noImpRoles);
output.Write("</ul>");
}
catch(UnauthorizedAccessException e)
{
output.Write("</li><li>No impersonation failed: " + e.ToString() + "</ul>");
}
try {
// impersonated web object
output.Write("</ul><b>Impersonated fetch of SPWeb object</b><ul>");
output.Write("<li>User:" + originalUser);
// #7: using the impersonated web object - it is possible to enumerate roles for the user
string impRoles = Utility.GetRoles(originalUser, web);
output.Write("<li>Roles: " + impRoles);
output.Write("</ul>");
}
catch(Exception e)
{
output.Write("</li><li>Impersonated SPWeb object failed: " + e.ToString() + "</ul>");
}
try
{
// use of webservices
output.Write("<b>Use of webservice</b><ul>");
SiteData sd = new SiteData();
sd.Credentials = System.Net.CredentialCache.DefaultCredentials;
sd.Url = thisWeb.Url + "/_vti_bin/SiteData.asmx";
_sList[] lists;
sd.GetListCollection(out lists);
foreach(_sList list in lists)
output.Write("<li>" + list.Title);
output.Write("</ul>");
sd.Dispose();
}
catch(Exception e)
{
output.Write("</li><li>Use of webservice failed: " + e.ToString() + "</ul>");
}
try
{
// use of webservices
output.Write("<b>Use of impersonated webservice</b>");
_sList[] lists2;
sd2.GetListCollection(out lists2);
output.Write("<ul>");
foreach(_sList list in lists2)
output.Write("<li>" + list.Title);
output.Write("</ul>");
}
catch(Exception e)
{
output.Write("</li><li>Use of webservice failed: " + e.ToString() + "</ul>");
}
}
}
0 Comments:
Post a Comment
<< Home