Monday, 31 January 2011

Reverting Claim Based Authentication to Classic Mode Authentication

Let me start with my story first: I have 3 different SharePoint environments:

  • Development with Classic Mode (Windows)
  • Testing with Claim Based
  • Production with Classic Mode (Windows)

Sometimes I will need to move a site from development to the testing environment, and since they are using different types of authentication, I will need to execute the $webApp.MigrateUsers($true) to convert the stored username in the content database from the “Windows” format (DOMAIN/username) to Claim based format (i:0#.w|DOMAIN/username). 

And now here’s a very stupid things that I have done last week.  I tried to move a site from Testing to the Production environment.  I went to the site permission and noticed the most of the username are stored in the Claim Based format, and the user is getting access denied error.  Immediately I think of the MigrateUsers($true) command that I mentioned above and I executed it (Yes, a real stupidity as a SharePoint guy).  Ops, other sites sit under the same Web Application starts to bound users off with an “Access Denied” error.  I can’t even login to the site with the farm administrator account. 

I immediately search for any information about MigrateUsers and found the Microsoft Official description here.  It says “true to move users to claims authentication; false to move users from claims authentication”.  Cool, this is a life saver and all I need to do is execute this command passing in false.  Run this and I am getting “Operation is not valid due to the current state of the object”. 

image

No good again.  My colleague opened up reflector and tried to look at the MigrateUsers method, and what we have noticed is the method intentionally throws an exception when we pass in false as the parameter.  Why??? I did more searching and found out that Claims to Classic mode is not currently supported by Microsoft. 

I went into the content database and checked the UserInfo table and have noticed that all logins had been converted to claim based format.  I tried to change one of the login back to Windows format (by the way, modification directly to SQL is not supported), but still not success. 

I did another test by creating a new site collection in a new content database under the same web application, and setting my Windows account as the site collection administrator.  In theory, this should work as everything is clean, however I am still getting “Access Denied”. 

Based on the above testing and investigation, we can prove that:

  • MigrateUsers($true) convert all username to “Claim-Based” format and this change applies to every content database under the same web application. 
  • Since the site is still using Windows Classic mode, when a user access the site, it will take the username in Windows format.  However, when it compares the “Windows” username with the converted claim based format username in the content database.  Since they are different, it returns an access denied error. 
  • Based on the result of the new site collection testing mentioned above, the MigratedUsers command will also do something to the web application, making it no longer accept “Windows” username.  Hence the web application is corrupted. 

After spending whole night in the office, finally I found a solution to roll back the changes: A command already existed in SharePoint 2007 fixed the problem, and it is MigrateUser.  Basically this command will convert one login to a new login. However this only works on single user every time. 

So here is my strategy for the problem:

  1. As mentioned, the web application is corrupted.  We will need to delete the corrupted web application and recreate a new one.  Make sure you keep an backup of the database
  2. After you recreate the web application, mount the content database(s). 
  3. Reset the Site Collection Administrator in Central Administration. 

Up to this point, you should be able to login to the site using the site collection administrator account that you defined in 3.).  And the next/final step that we need to do is to run the MigrateUser command to every users in the site collection.  To do that, I have implemented the following console application:

Code Snippet
  1. public Program(string url)
  2. {
  3.     using (SPSite site = new SPSite(url))
  4.     {
  5.         using (SPWeb web = site.RootWeb)
  6.         {
  7.             foreach (SPUser user in web.AllUsers)
  8.             {
  9.                 string username = GetClaimBasedUserName(user);
  10.                 if (!username.Equals(string.Empty))
  11.                 {
  12.                     Console.Write("Migrating {0} to {1}...", user.LoginName, username);
  13.                     try
  14.                     {
  15.                         SPFarm Farm = SPFarm.Local;
  16.                         Farm.MigrateUserAccount(user.LoginName, username, false);
  17.                         Console.WriteLine("Done");
  18.                     }
  19.                     catch (Exception ex)
  20.                     {
  21.                         Console.WriteLine(ex.Message);
  22.                     }
  23.                 }
  24.             }
  25.         }
  26.     }
  27. }
  28.  
  29. private string GetClaimBasedUserName(SPUser user)
  30. {
  31.     string username = string.Empty;
  32.     try
  33.     {
  34.         if (user.IsDomainGroup)
  35.         {
  36.             if (user.LoginName.StartsWith("c:0+.w|"))
  37.             {
  38.                 username = user.Name;
  39.             }
  40.         }
  41.         else
  42.         {
  43.             if (user.LoginName.StartsWith("i:0#.w|"))
  44.             {
  45.                 username = user.LoginName.Substring(7);
  46.             }
  47.         }
  48.     }
  49.     catch
  50.     {
  51.  
  52.     }
  53.     return username;
  54. }
  1. Basically the above code does the following:
  2. Grab all the users in the site collections,
  3. Get the current username which is in the claim based format
  4. Extract the “Windows” user name from the claim based username (Just trim the first 7 characters)
  5. Run the MigrateUser method

And after running this console application, your site is back up and running with Classic mode.

5 comments:

Harit said...

Thanks Wilson,

Your post saved lot of time for me.
BTW, I created a Powershell Script to migrate the users.

Thanks Again
Harit

Harit said...

Wilson,

Thank you for the post it helps me to solve my issue of changing Claimbased to Classic authentication.

BTW, I created a powershell script to migrate the users.

Thanks
Harit

daniel said...

great post. thanks for sharing.

Wilson Leung said...

Hi Harit,

That's good. I was thinking to create a script but I was in an emergency situation at that time, so I created a console which is easy to debug from my view. I may create a PowerShell version when I have time. Thanks for your info.

cace said...

Please provide the Powershell script or the console app.

thanks.