Share-n-dipity

SharePoint serendipity is the effect by which one accidentally discovers something fortunate, especially while looking for something else entirely. In this case, it is the occassional musings, observations, and Ouija board readings about the phabulously

Migrating User Accounts from Windows Claims to SAML Claims

Migrating User Accounts from Windows Claims to SAML Claims

  • Comments 3
  • Likes

In the work I’ve been busy with lately I’ve had a lot of interest from folks that are interested in starting out as Windows claims users, and then at some point switching over and start using SAML claims.  Sounds reasonable enough, but the problem is that we don’t have an out of the box way to migrate accounts from Windows claims to SAML claims.  The good news is the SharePoint product group added in the hooks in the August 2010 CU to let you run your own custom code in the MigrateUsers method.  We have a whole document on the API coming out soon along with a code sample that was a result of some great work by Bryan P. and Raju S., and my sample is based on that.  They’ve done a very good job of documenting the new API (actually an interface – IMigrateUserCallback) so I won’t try and do that in great detail here.  As soon as I have a link to the newly published info on this I will update this posting with it.

So, as usual, I’m going to start out just by pasting in the code of my custom migration class, and then I’ll walk through the parts that I think are interesting.

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Diagnostics;

using System.Security;

using System.Security.Principal;

//add references to Microsoft.SharePoint and Microsoft.IdentityModel for these

using Microsoft.SharePoint;

using Microsoft.SharePoint.Administration;

using Microsoft.SharePoint.Administration.Claims;

using Microsoft.IdentityModel.Claims;

 

 

namespace MigrateUserSample

{

   public class MigrateTest : IMigrateUserCallback

   {

 

       public string SPTrustedIdentityTokenIssuerName { get; set; }

 

 

       public MigrateTest(string TrustedIdentityTokenIssuerName)

       {

          SPTrustedIdentityTokenIssuerName = TrustedIdentityTokenIssuerName;

       }

 

 

       public string ConvertFromOldUser(string oldUser,

              SPWebApplication.AuthenticationMethod authType, bool isGroup)

       {

          string value = string.Empty;

 

          try

          {

              switch (authType)

              {

                 case SPWebApplication.AuthenticationMethod.Windows:

                     //code for converting from classic Windows would be here

                     Debug.WriteLine(oldUser);

                     break;

                 case SPWebApplication.AuthenticationMethod.Claims:

                     //this is the only scenario this sample will cover

                     //migrating from Windows claims to SAML claims

                     Debug.WriteLine(oldUser);

 

                     //get the claim provider manager

                     SPClaimProviderManager cpm = SPClaimProviderManager.Local;

 

                     //create a claim from the identifier so we can see if the

                     //original issuer came from Windows

                     SPClaim idClaim = cpm.ConvertIdentifierToClaim(oldUser,

                           SPIdentifierTypes.EncodedClaim);

 

                     //this is a Windows claims user, and we are going to

                     //convert to a SAML claims user

                     if (idClaim.OriginalIssuer == "Windows")

                     {

                        //windows claims users will be in the format domain\user;

                        //windows claims groups will be in the SID format

                        if (idClaim.Value.Contains("\\"))

                        {

                           //migrating a user

 

                           //you will want to check the identity of the user here

                           //there may be some Windows claims accounts you don't want to

                           //convert yet, and there will also be service accounts that

                           //are passed in that you may not want to convert either; 

                           //ideally you would just read from a data source to determine

                           //which users you should convert, and then check the identity

                           //here to see if it's one of the users that should be

                           //converted

 

                           //in this case, I'm only converting one user - darrins

                           if (idClaim.Value == "contoso\\darrins")

                           {

                               //I’m getting an identity claim here, grabbing the

                               //part after the "domain\", and appending the email

                               //suffix to it, so it becomes darrins@contoso.com

                               SPClaim migratedUserClaim =

                                         SPClaimProviderManager.CreateUserClaim(

                                         idClaim.Value.Split('\\')[1] + "@contoso.com",

                                         SPOriginalIssuerType.TrustedProvider,

                                         SPTrustedIdentityTokenIssuerName);

 

                               //get the encoded value of what the new identity

                               //claim will be

                               value = migratedUserClaim.ToEncodedString();

                           }

                        }

                        else

                        {

                           //migrating a group

 

                           //get the plain name of the group

                           SecurityIdentifier sid =

                               new SecurityIdentifier(idClaim.Value);

                           NTAccount groupAccount =

                               (NTAccount)sid.Translate(typeof(NTAccount));

 

                           string groupName = groupAccount.ToString();

 

                           //only interested in migrating the Portal People group

                           if (groupName.ToLower() == "contoso\\portal people")

                           {

                               //create a new role claim

                               SPClaim migratedGroupClaim =

                                  new SPClaim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role",

                                  groupName.Split('\\')[1],

                                  Microsoft.IdentityModel.Claims.ClaimValueTypes.String,

                                  SPOriginalIssuers.Format(

                                  SPOriginalIssuerType.TrustedProvider,

                                  SPTrustedIdentityTokenIssuerName));

 

                               //get the encoded value of what the new role claim will be

                               value = migratedGroupClaim.ToEncodedString();

                           }

                        }

                     }

 

                     break;

                 case SPWebApplication.AuthenticationMethod.Forms:

                     //code for converting from Forms would be here

                     Debug.WriteLine(oldUser);

                     break;

              }

          }

          catch (Exception ex)

          {

              Debug.WriteLine(ex.Message);

          }

 

          return value;

       }

   }

}

 

 

The first thing I’m doing is checking the value of the SPWebApplication.AuthenticationMethod parameter that was passed in.  Since I’m only interested in converting claims users (Windows to SAML), that’s the only situation in which I have code to execute.  When the current user is a claims user, I start out by getting a reference to the local SPClaimProviderManager so I can get a claim representation of the user.  I do that so I can determine if the user is a Windows claim user, FBA claims user, or SAML claims user.  In my case I only want to convert users that are Windows claims users. 

 

After I’ve determined I have one of those, the next thing I try to figure out is whether the claim is for a user or group.  Here’s one of the weird things that you may notice.  Even when the current user is a Windows claims group, the isGroup parameter that is passed into the method returns false.  That means that I need to check myself to figure out if the current “entity” is a user or group.  So I just look at the claim value – if it’s a user, it will be in the format domain\user; otherwise it’s a group, which will be in a SID format.

Now that I know which type of entity it is, I can determine which type of claim is needed.  For a user, I need to create an identity claim.  One of the things that requires is for me to know the name of the SPTrustedIdentityTokenIssuer that is being used on the web application.  I could have written code to figure that out, but instead I went the route of “hey this is a sample, sue me for being lazy” and force you to pass in the correct name in the constructor for my class.  So I take the user’s login name (after the domain part) and for my purpose their email address will always be loginname@contoso.com.  If your organization is not that way then you’ll need your own means to determine the correct email address.  I use it with the code above to create an identity claim for that user, and that’s the value that I return – that’s what in this case, the vbtoys\darrins account will be converted to.  (grammar police don’t bug me about ending my sentence in a preposition)

For groups, I take the SID that I’ve been given and I use the NTAccount class to get the friendly name of the group.  I use that to create a new role claim, and then pull the encoded value from that as the value to which the group should be migrated.  (grammar police happy now, yes?!?)

One other thing worth noting there – for both the users and groups, I don’t automatically try and migrate everything.  There are some things you may not want to migrate, like service accounts, built in accounts, etc.; whether you will or not depends on your requirements.  What’s neat about this method of doing the migration though is that you can do it as many times as you want.  If you want migrate just a subset of users, or do it in batches, or over time, or whatever – you can do that.  For example, you might have a database that has all the users you’re supposed to migrate.  You could query that to get the list and then as each user is called in your migration code, you could check to see if it’s in the list of users you got from your database.  Just one example there.

Okay, so I don’t want to get into the SDK documentation that Bryan and Raju worked on too much, but I do feel compelled to at least let you know how your class gets invoked so I don’t leave you hanging here.  What I did was just write a winforms application and add a project reference to my custom assembly I’ve described above.  That made it extremely easy to build and debug together.  The code that I use then to invoke my class and do a migration looks something like this:

 

//get a reference to my web application

SPWebApplication wa = SPWebApplication.Lookup(new Uri("http://foo"));

 

//this is the name of my trusted identity token issuer

string SPTrustedIdentityTokenIssuerName = "ADFSProvider";

 

//create an instance of the custom migrate user callback class

MigrateUserSample.MigrateTest mt =

new MigrateUserSample.MigrateTest(SPTrustedIdentityTokenIssuerName);

 

//create an interface reference to it

IMigrateUserCallback muc = mt as IMigrateUserCallback;

 

//migrate the users with it

wa.MigrateUsers(muc);

 

That’s it.  There will be many other variations of this needed to cover all scenarios, but this is a pretty good start, and Bryan and Raju will be adding significantly more to this body of work.

Attachment: Migrating User Accounts from Windows Claims to SAML Claims.docx
Comments
  • Hi Steve,

    Very brilliant attempt. Thanks for that. The topic you have chosen to discuss is rally good and you described it very optimally. You have used the custom migration class very effectively. Explanation with code is good approach.

    www.gloriatech.com/microsoft-windows-server-2008-setup.aspx

  • Hi Steve,

    Thanks for the confirmation on this. I had figured out the same during my early analysis social.msdn.microsoft.com/.../21e22aef-7b4f-4bfc-b59b-ca0269d42ef5.  But I had used it solve upgrade scenarios happening from 2007 to 2010 in which we moved from FBA to IDP. On the same front I had an open Q w.r.t SSP profiles.

    Is there any similar approach for migrating the user profiles which was created after the upgrade from 2007?

    I would have assumed that deleting those user profiles and freshly creating the user profiles is better but values like mysite urls and other parameters  which sharepoint has stored as part of the user profile needs to migrated than creating new.

    Regards

    Anoob

  • Hi Steve,

    I am investigating an interesting scenario.

    In the scenario there are multiple SharePoint sites.  The sites are accessed by the same people.  Some of the sites use Windows claims.  But others require SSO with applications external to SharePoint which are presented inside of webparts in the SharePoint site.  The external applications are protected with a 3rd party web access management (WAM) solution and are not using native Windows claims.  To achieve SSO between the SharePoint site and the external application claims-based authentication is used for SharePoint.  This enables the 3rd party WAM solution to provide SSO to SharePoint via SAML claims while also establishing the session token used by the WAM to provide access to the external application.

    The problem is that the SharePoint site using Windows claims and the SharePoint site using SAML claims have different profiles for the same user.  So things like MySite and voting scores are not shared between them and the user experience can be confusing.

    I wonder if there is some way to assign a single profile for a user authenticating with different claim types in different SharePoint instances?

    Is Microsoft considering ways now or in the future to address use cases like this?

    Thanks,

    Jason

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment