Adding users to SharePoint dynamically at first request after authentication

That title says it all! Well I was working on case where SharePoint was configured to use external authentication. This external authentication was hosted outside SharePoint and it could be used by many other services too. End users of SharePoint would notice redirection to this external service when they click Login in the UI. And after succesful login they would be redirected back to SharePoint. All of that stuff would be done in secure way of course. From SharePoint configuration point of view this external authentication was just authentication provider. So it was configured just like any other provider (like LDAP, AD etc.). But challenge in this case was the amount of potential users for this SharePoint application... which is millions of users... and none of them cannot be known in advance! There just isn't way to know who is using the service before the user is actually using it. So the result could be only what I've described in the title... adding users dynamically to the SharePoint at first request after authentication :-)

Since this case was proof of concept we just wanted to test this functionality in action before the real project would begin. So of course my first solution was quick (and dirty) hack for this one. I just edited AccessDenied.aspx page to add user to SharePoint if it wasn't there already. And of course redirected the users back to the page before the Access Denied. This was done with notepad in working environment pretty fast. But of course that was just way to see if that kind of "Adding users dynamically" would even work (Note: Don't ever fiddle around with SharePoint aspx files like I did. I just did quick test and verified my theory before doing real solution). And after that I started to make the actually solution... and this time with real programming environment :-). 

So my solution was new HttpModule that would do all the magic. Downside of this approach is of course that this code is run on every request. (Unlike the super hack on AccessDenied.aspx :-). So it needs to be as light as it only can be. The code itself is pretty easy and straight forward... so let's take a look at it:

 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
 using System;using System.Collections.Generic;using System.Text;using System.Web;using Microsoft.SharePoint;using System.Security.Principal;using Microsoft.SharePoint.WebControls;using System.Configuration;namespace Microsoft.MCS.Authorization{  public class AddUserToSystem : IHttpModule  {    private void AddUser()    {      if (SPContext.Current.Web.CurrentUser == null)      {        // This user isn't SharePoint User => Let's add it         SPSecurity.RunWithElevatedPrivileges(delegate()        {          using (SPSite site = new SPSite(SPControl.GetContextSite(HttpContext.Current).ID))          {            using (SPWeb web = site.OpenWeb("/"))            {              Boolean allowUnsafeUpdate = web.AllowUnsafeUpdates;              try              {                web.AllowUnsafeUpdates = true;                // Get user identity name                String login = HttpContext.Current.User.Identity.Name;                String loginName = "ext:" + login;                String name = login.Substring(3, login.IndexOf(',') - 3);                String email = "";                String notes = "Automatically added user";                // Or take from ConfigurationManager.AppSettings[...];                String groupName = "Visitors";                web.SiteUsers.Add(loginName, email, name, notes);                web.Groups[groupName].AddUser(loginName, email, name, notes);                HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Url.ToString());              }              finally              {                web.AllowUnsafeUpdates = allowUnsafeUpdate;              }            }          }        });      }    }    public void Dispose()    {          }    public void Init(HttpApplication context)    {      context.PostAuthenticateRequest += new EventHandler(context_PostAuthenticateRequest);    }    void context_PostAuthenticateRequest(object sender, EventArgs e)    {      if (HttpContext.Current.User.Identity.IsAuthenticated == true)      {        this.AddUser();      }    }  }}

In lines 59-70 there are some basic stuff to be done so that our HttpModule would be called after authentication request. And of course I'm interested knowing that is this current user already authenticated (on line 66). If this user is authenticated then we need to check if it has SPUser (=is user already in SharePoint). If that's not the case then we'll just add it to SharePoint and make it member of some group ("Visitors" in this example). Code in lines 14-52 is for adding the actual user to SharePoint. On line 33 there is some weird string stuff going on... the reason for that is the user identity name is in this case in format: "cn=John Doe, ou=... ". So I just wanted to rip of the John Doe to be name of the newly created user.

If you wonder the AllowUnsafeUpdates change... welll the security model of SharePoint doesn't allow certain changes in GET (=HTTP Method... opposite to POST). You can bypass it by setting AllowUnsafeUpdates = true but of course you don't want it to leave it like that.. so let's clean up our tracks when we're done on line 46.

Again my code example is meant to be just example. In real life you probably want to add more checks (to line 66 or to line 16) so that the overall performance would be as good as possible.I don't know the performance cost of SPContent.Current.Web.CurrentUser but this kind of stuff needs to be well tested before taking into production.

Anyways... Happy hacking!

J