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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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

Comments (13)

  1. Amin Pirzadeh says:

    If you put your httpmodule under stress you will find that a lot of your requests will fail as setting the allowunsafeupdate flag can cause race condition.

    In other words, you have requests or threads that set the allowunsafeupdate to true and requests that are setting the flag to false running on some other threads

    🙁

  2. Hi Amin!

    That's interesting and good point... I haven't tested that scenario because it's not easy to setup. You would need to create script that would create http request for 10000 different users at the same time. And all of those users would need to be first timers, since that code is run only when user isn't already at the SharePoint. And it would be really easy to add locking to avoid that scenario you're referring in single box scenario (performance hit for locking wouldn't be that bad). It would be a bit harder to do it in medium-farm (or larger farms) but I think it would still achieaveable.

    But still you have made valid point and that's why I always try to mention that my code samples are only meant for examples and you need to make real test scripts and add more logic to make it usable in real life 🙂

    Have you tested that kind of situation? And how did you solve the problem?

    Anyways... Happy hacking!

    J

  3. I am curious where you embedded the final code and how you embedded it.  Did you need visual studio on the actual environment to do it?  I have never attempted to embed code in Sharepoint, so I am sorry if this is a really simple question.

  4. Hi Becky!

    That code was compiled to DLL that was then deployed to the target SharePoint installation. Only "special" thing that was needed to do was to add new module under <httpModules> at the web.config. But of course this doesn't mean that you need to have Visual Studio at the server, just at your development machine.

    I hope I answered your question.

    Anyways.... Happy hacking!

    J

  5. I added the code above in a module, but when Access is requested it does not grant access automatically.  Also, "Microsoft.MCS.Authorization" would not work when I tried adding a line of code for the module.  What does your web.config entry look like?  

    Also, how did you get it to work on the Access Denied Page?

  6. Hi Becky!

    Have you debugged you code? I mean willl that code ever get executed? If not then you know why it won't give access rights automatically.

    My web.config entry looks like this:

    <add name="MCSAuthorization" type="Microsoft.MCS.Authorization.AddUserToSystem,Microsoft.MCS.Authorization, Version=1.0.0.0, Culture=neutral,PublicKeyToken=2d99e7422c714ab7" />

    And let's forget about the hack with Access Denied Page since it not something you want to do.

    Anyways... Happy hacking!

    J

  7. jupiterdaisy says:

    Is there any way I can get a copy of the original AccessDenied.aspx page?  My edits to that page completely messed it up. (I did backup the apge before I edited, but for some reason it is still messed up).

  8. Emiliano says:

    Hi!

    how i can run the solution? what's the procedure to  test the code.

    thanks 🙂

  9. Burt says:

    I developed something similar that includes information to help you to setup the sites.

    http://icodehead.blogspot.com/

    Burt

  10. Jaffa says:

    Did u try by linking AD group to SP Group and adding user dynamically to AD Group

  11. brenda says:

    how do u unlock facebook or use facebook in school? i need a website to go on it

Skip to main content