Second Life of a Hungarian SharePoint Geek

December 16, 2010

How we can’t set rating in the name of other users from code?

Filed under: Rating, Reflector, Social computing, SP 2010 — Tags: , , , — Peter Holpar @ 01:32

Most of the time I blog about how one can do something, but today I will post about how you can’t.

In my last post I illustrated how to set rating in SharePoint 2010 from code. In a former post you can see how to get ratings of other users from code. I’ve assumed that if I have enough permissions in the system, I can use the same (or similar code) to set ratings for other users as well, but it turned out to be a bit more complex.

The goal was to create a method that enable me to act as other user from code to rate, tag and comment items, like this:

  1. SPList list = web.Lists["Rated Items"];
  2. SPListItem listItem = list.Items[0];
  3. SPUser user = web.EnsureUser(@"domain\user");
  4. SetRatingForListItem(listItem, 5, "Very good!", user);

My first attempt was to use standard SharePoint impersonation as described in this post:

How to Programmatically Impersonate Users in SharePoint

This is the result for this approach:

  1. private void SetRatingForListItem(SPListItem listItem, int ratingValue, String ratingTitle, SPUser user)
  2. {
  3.     SetRatingForUrl(String.Format("{0}/{1}", listItem.Web.Url, listItem.Url), ratingValue, ratingTitle, user);
  4. }
  5.  
  6. private void SetRatingForUrl(String url, int ratingValue, String ratingTitle, SPUser user)
  7. {
  8.     if (user == null)
  9.     {
  10.         using (SPSite site = new SPSite(url))
  11.         {
  12.             SetRatingForUrl(url, site, ratingValue, ratingTitle);
  13.         }
  14.     }
  15.     else
  16.     {
  17.         // we assume the process has impersonation privilages
  18.         // otherwise you need an extra round of RunWithElevatedPrivileges
  19.         Guid siteId = user.ParentWeb.Site.ID;
  20.         SPUserToken userToken = user.UserToken;
  21.  
  22.         SPSecurity.RunWithElevatedPrivileges(delegate()
  23.         {
  24.             using (SPSite site = new SPSite(siteId, userToken))
  25.             {
  26.                 SetRatingForUrl(url, site, ratingValue, ratingTitle);
  27.             }
  28.         });
  29.     }
  30.  
  31. }
  32.  
  33. private void SetRatingForUrl(String url, SPSite site, int ratingValue, String ratingTitle)
  34. {
  35.     SPServiceContext serviceContext = SPServiceContext.GetContext(site);
  36.     Uri uri = new Uri(url);
  37.  
  38.     SocialRatingManager ratingManager = new SocialRatingManager(serviceContext);
  39.     ratingManager.SetRating(uri, ratingValue, ratingTitle);
  40.     ratingManager.PropagateRating(uri);
  41. }

Based on my experiments this method works, but the rating is made in the name of the interactive user. The impersonation seems to have no effect at all when it used for social activity in SharePoint, like rating, tagging and commenting.

What’s the reason behind this?

You can get the answer easily using Reflector. As discussed in the first post, the “social’ classes, like SocialRatingManager  are implemented in the Microsoft.Office.Server.SocialData namespace in the Microsoft.Office.Server.UserProfiles.dll assembly and are derived from a common abstract base class called SocialDataManager. SocialDataManager contains a method called GetCurrentUserProfile.

This method is called in the derived classes before doing the actual work of social activity, like rating a URL (see for example SetRating method in SocialRatingManager), and its role is to return the UserProfile of the current user using the ProfileLoader.GetUserProfile method. The UserProfile calls the EnsureUserProfile method to get UserProfile instance through the GetUserProfile method of the UserProfileManager class.

This method uses the strCurrentAccountName property of the class to get the name of the current user and retrieve or to create the UserProfile for the user. This property gets the user name from the GetCurrentUserName method of the UserProfileGlobal class.

This method tries to get the user name from the actual context, like:

HttpContext.Current.User.Identity.Name

or

WindowsIdentity.GetCurrent().Name

that means standard SharePoint impersonation has no effect on it.

My second approach was to impersonating the user through the WindowsImpersonationContext as described in this article:

Using WindowsIdentity.Impersonate in SharePoint applicatons

It resulted the following code:

  1. private void SetRatingForListItem(SPListItem listItem, int ratingValue, String ratingTitle, SPUser user)
  2. {
  3.     SetRatingForUrl(String.Format("{0}/{1}", listItem.Web.Url, listItem.Url), ratingValue, ratingTitle, user);
  4. }
  5.  
  6. private void SetRatingForUrl(String url, int ratingValue, String ratingTitle, SPUser user)
  7. {
  8.     if (user == null)
  9.     {
  10.         using (SPSite site = new SPSite(url))
  11.         {
  12.             SetRatingForUrl(url, site, ratingValue, ratingTitle);
  13.         }
  14.     }
  15.     else
  16.     {
  17.         WindowsImpersonationContext impersonationContext = null;
  18.         try
  19.         {
  20.             WindowsIdentity userIdentity = new WindowsIdentity(GetUpn(user));
  21.             WindowsImpersonationContext context = userIdentity.Impersonate();
  22.  
  23.             using (SPSite site = new SPSite(user.ParentWeb.Site.ID))
  24.             {
  25.                 SetRatingForUrl(url, site, ratingValue, ratingTitle);
  26.             }
  27.         }
  28.         finally
  29.         {
  30.             if (impersonationContext != null)
  31.             {
  32.                 impersonationContext.Undo();
  33.             }
  34.         }
  35.  
  36.     }
  37.  
  38. }
  39.  
  40.  
  41. private static string GetUpn(SPUser spUser)
  42. {
  43.     string[] userName = spUser.LoginName.Split('\\');
  44.     System.DirectoryServices.ActiveDirectory.Domain domain =
  45.         System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
  46.     return userName[1] + "@" + domain.Name;
  47. }
  48.  
  49.  
  50. private void SetRatingForUrl(String url, SPSite site, int ratingValue, String ratingTitle)
  51. {
  52.     SPServiceContext serviceContext = SPServiceContext.GetContext(site);
  53.     Uri uri = new Uri(url);
  54.  
  55.     SocialRatingManager ratingManager = new SocialRatingManager(serviceContext);
  56.     ratingManager.SetRating(uri, ratingValue, ratingTitle);
  57.     ratingManager.PropagateRating(uri);
  58. }

Unfortunately, this approach doesn’t work either.

If the user we would like to impersonate, has no log on locally user right (that is more than typical in the case of a server), or if you grant that right for the user, then you will receive an SqlException "A transport-level error has occurred when sending the request to the server. (provider: Shared Memory Provider, error: 0 – Either a required impersonation level was not provided, or the provided impersonation level is invalid." when trying to get the server or service context from the SPSite object.

To sum up my experience, up to now I found no way to work around the limitation the “social” impersonation. If you have more success in that area, you are welcome to share your results with us.

4 Comments »

  1. Thanks for the post, Peter.

    I have a simpler question that I can’t seem to find an answer to. When a new major version of a document is published, in a library with rating enabled, the old rating sticks to the new document. We expected it to be reset to null with a newly-published version.

    As is, you may end up with a fantastic new document with a lousy rating – but the rating reflects the last version!

    Thanks for any help you can provide….

    Comment by AW — January 27, 2011 @ 20:45

    • Hi,

      Yes, it is a real issue. Probably I would solve it through event handlers. It is not an out-of-the-box solution, and seems to be trivial at the first sight, but there might be difficulties exactly due to behavior discussed in this post. If my time allows I try to do some kind of POC and publish the results on the blog.

      Peter

      Comment by Peter Holpar — January 27, 2011 @ 21:15

  2. Hi
    As you said it uses HttpContext user
    So you can perfectly fake it
    I use this for impersonation in Tags management, and it works fine

    IPrincipal impersonationPrincipal = new WindowsPrincipal(
    new WindowsIdentity(GetUpn(user)));

    HttpRequest request =
    new HttpRequest(string.Empty, site.RootWeb.Url, string.Empty);

    HttpResponse response =
    new HttpResponse(
    new System.IO.StreamWriter(new System.IO.MemoryStream()));

    HttpContext impersonatedContext =
    new HttpContext(request, response);

    impersonatedContext.User = impersonationPrincipal;
    impersonatedContext.Items[“HttpHandlerSPWeb”] = site.RootWeb;

    HttpContext.Current = impersonatedContext;

    Comment by Andres Ciampo — February 7, 2011 @ 17:01

    • Hi Andres,

      Thanks for your tip, it works like a charm! See this post for the solution based on your code.
      Peter

      Comment by Peter Holpar — February 16, 2011 @ 19:59


RSS feed for comments on this post. TrackBack URI

Leave a reply to AW Cancel reply

Blog at WordPress.com.