Thursday, January 14, 2010

Anonymous access to folders with unique permissions in Sharepoint lists

Folders (SPFolder objects) in Sharepoint lists and document libraries can be used to restrict access to its content. I.e. you can assign different permissions to folders so users which have access to one folder will not have access to another. This is quite convenient solution. When you change folder permission settings you have to break role inheritance for this folder so it will not anymore inherit permissions from parent list:

   1: SPFolder folder = …;
   2: AssignRoleToSecurableObject(web, folder.Item, SPRoleType.Contributor, “Contributors”);
   3:  
   4: public static void AssignRoleToSecurableObject(SPWeb web, ISecurableObject securableObject,
   5:     SPRoleType roleType, string group)
   6: {
   7:     var siteGroup = web.SiteGroups[group];
   8:     var roleAssignment = new SPRoleAssignment(siteGroup);
   9:     var roleDefinition = web.RoleDefinitions.GetByType(roleType);
  10:     roleAssignment.RoleDefinitionBindings.Add(roleDefinition);
  11:     if (!securableObject.HasUniqueRoleAssignments)
  12:     {
  13:         securableObject.BreakRoleInheritance(true);
  14:     }
  15:     securableObject.RoleAssignments.Add(roleAssignment);
  16: }

Unfortunately there is an upsetting side effect - Sharepoint doesn’t allow anonymous users to access content inside folders with unique permissions (see http://yvonneharryman.wordpress.com/2007/11/23/follow-up-on-anonymous-access-and-item-level-permissions-from-sharepoint-connections-07). So what should we do if we have requirement that anonymous access has to be allowed to these folders?

In order to avoid this limitation of Sharepoint I made the following workaround: add special account into AD (lets call it anonymous@example.com). Then I force Sharepoint to use this account when non-authenticated request goes inside the folder with unique permissions. The main points of solution are the following:

  1. Add predefined account anonymous@example.com into AD
  2. All folders with unique permissions which should be available for anonymous are configured so anonymous@example.com account has Reader permission set on them
  3. Implement custom HttpModule which analyzes the target URL of http request. If request goes to the folder with unique permissions HttpModule temporary authenticates request with anonymous@example.com. As folders are accessible for this account (see step 2) Sharepoint successfully authorizes this “fake” anonymous request
  4. At the end of request we “rollback” temporary authentication – so other surface of the site is not touched and works by standard way

The simplified code of HttpModule is the following:

   1: namespace CustomHttpModules
   2: {
   3:     // This module authenticates anonymous user to predefined account
   4:     // when anonymous user requests for folder with unique permissions
   5:     public class AnonymAuthenticationModule : IHttpModule
   6:     {
   7:         private const string ANONYMOUS = "anonymous@example.com"
   8:  
   9:         public void Init(HttpApplication application)
  10:         {
  11:             application.PostAuthenticateRequest += application_PostAuthenticateRequest;
  12:             application.EndRequest += application_EndRequest;
  13:         }
  14:  
  15:         void application_EndRequest(object sender, EventArgs e)
  16:         {
  17:             if (!HttpContext.Current.Request.IsAuthenticated)
  18:                 return;
  19:  
  20:             // if current request was temporary authenticated
  21:             // by predefined anonymous account, call SignOut()
  22:             // in order to continue to work with portal as real
  23:             // anonymous
  24:             if (HttpContext.Current.User.Identity.Name == ANONYMOUS)
  25:             {
  26:                 FormsAuthentication.SignOut();
  27:             }
  28:         }
  29:  
  30:         void application_PostAuthenticateRequest(object sender, EventArgs e)
  31:         {
  32:             if (HttpContext.Current.Request.IsAuthenticated)
  33:                 return;
  34:  
  35:             if (this.shouldAnonymousBeAuthenticated(HttpContext.Current.Request.Url))
  36:             {
  37:                 // authenticate anonym user with special account.
  38:                 // From this point anonymous users will be authenticated
  39:                 FormsAuthentication.SetAuthCookie(ANONYMOUS, false);
  40:  
  41:                 // set auth cookies is not enough. Current request
  42:                 // is still not authenticated. To authenticate request
  43:                 // just redirect response to the same url. As we have
  44:                 // cookies already, this second request will be
  45:                 // authenticated
  46:                 HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.OriginalString);
  47:             }
  48:         }
  49:  
  50:  
  51:         // Returns true if request goes to folder with unique
  52:         // permissions
  53:         private bool shouldAnonymousBeAuthenticated(Uri uri)
  54:         {
  55:            // implement your own logic here
  56:             ...
  57:         }
  58:     }
  59: }

It analyzes request URL (method shouldAnonymousBeAuthenticated()) and based on it authenticates request or leaves it untouched.

Notice that described technique works only for http get verbs. I.e. it wont be work if postbacks should be supported. For last case instead of simple Response.Redirect() you can try to construct HttpWebRequest inside http module and reexecute it with authentication cookies.

2 comments:

  1. Hello Alex,

    We want to give anonymous read permission on the folder.

    Can you advise what to add in this section:
    // implement your own logic here

    Kind regards,
    Mario

    ReplyDelete
  2. Hi Mario,
    did you miss some code in your question? In any case this post contains workaround for the problem, so it should give the right direction in your scenario.

    ReplyDelete