Forked from AdamAndersonFalafelSoftware/CredentialsAuthProviderWithAspnetMigration.cs
Created
February 25, 2014 21:45
-
-
Save falafelsoftware/9218533 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using ServiceStack.Common; | |
using ServiceStack.OrmLite; | |
using ServiceStack.ServiceInterface; | |
using ServiceStack.ServiceInterface.Auth; | |
using ServiceStack.WebHost.Endpoints; | |
using System; | |
using System.Data; | |
using System.Globalization; | |
using System.Security.Cryptography; | |
using System.Text; | |
namespace Blog.Falafel.AdamAnderson { | |
public class CredentialsAuthProviderWithAspnetMigration : CredentialsAuthProvider { | |
/// <summary> | |
/// This is the simplest extension point for implementing a custom credentails provider. | |
/// The majority of this code was copied from a disassembler and then modified to add | |
/// the test for whether the user already exists in the ServiceStack repository and if | |
/// not to migrate it before attempting to authenticate against the repository. | |
/// </summary> | |
public override bool TryAuthenticate(IServiceBase authService, string userName, string password) { | |
// Disassembled code from the parent | |
var userAuthRepository = authService.TryResolve<IUserAuthRepository>(); | |
if (userAuthRepository == null) { | |
AuthProvider.Log.WarnFormat("Tried to authenticate without a registered IUserAuthRepository"); | |
return false; | |
} | |
// Migration condition test and migration call | |
if (userAuthRepository.GetUserAuthByUserName(userName) == null) | |
MigrateAspnetMember(userAuthRepository, userName, password); | |
// Disassembled code from the parent | |
UserAuth userAuth = null; | |
if (!userAuthRepository.TryAuthenticate(userName, password, out userAuth)) | |
return false; | |
IAuthSession session = authService.GetSession(false); | |
session.PopulateWith(userAuth); | |
session.IsAuthenticated = true; | |
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture); | |
session.ProviderOAuthAccess = userAuthRepository.GetUserOAuthProviders( | |
session.UserAuthId).ConvertAll<IOAuthTokens>((UserOAuthProvider x) => x); | |
return true; | |
} | |
/// <summary> | |
/// Input your legacy application name here | |
/// </summary> | |
private const string OLD_APPLICATION_NAME = "/OldAppName"; | |
/// <summary> | |
/// POCO class used to read ASP.NET Membership data | |
/// </summary> | |
private class AspnetMember { | |
public Guid UserId { get; set; } | |
public string Email { get; set; } | |
public string Password { get; set; } | |
public string PasswordSalt { get; set; } | |
} | |
/// <summary> | |
/// This procedure migrates ASP.NET Membership Users and Roles | |
/// </summary> | |
private bool MigrateAspnetMember(IUserAuthRepository userAuthRepository, string userName, string password) { | |
// The following line assumes that an IDbConnectionFactory has been registered with the IOC container | |
using (var db = AppHostBase.Resolve<IDbConnectionFactory>().Open()) { | |
// The following queries could also be implemented as stored procedures | |
// They're implemented as SQL strings for simplicity | |
var queryMember = @" | |
select m.UserId, m.Email, m.Password, m.PasswordSalt | |
from aspnet_Membership m | |
join aspnet_Users u on u.UserId = m.UserId | |
join aspnet_Applications a on a.ApplicationId = m.ApplicationId | |
where u.UserName = @UserName | |
and a.ApplicationName = @ApplicationName"; | |
var queryRoles = @" | |
select r.RoleName | |
from aspnet_Roles r | |
join aspnet_UsersInRoles ur on ur.RoleId = r.RoleId | |
join aspnet_Applications a on a.ApplicationId = r.ApplicationId | |
where ur.UserId = @UserId | |
and a.ApplicationName = @ApplicationName"; | |
var member = db.QuerySingle<AspnetMember>( | |
queryMember, new { UserName = userName, ApplicationName = OLD_APPLICATION_NAME }); | |
if (member == null) | |
return false; | |
var hash = ComputeSaltedHash(password, member.PasswordSalt); | |
if (hash != member.Password) | |
return false; | |
var roles = db.Query<string>( | |
queryRoles, new { UserId = member.UserId, ApplicationName = OLD_APPLICATION_NAME }); | |
var userAuth = userAuthRepository.CreateUserAuth(new UserAuth() { | |
UserName = userName, | |
Email = member.Email, | |
Roles = roles | |
}, password); | |
} | |
return true; | |
} | |
/// <summary> | |
/// Computes the salted hash used by ASP.NET Membership | |
/// </summary> | |
/// <remarks> | |
/// Credit to Malcolm Swaine for the algorithm and code | |
/// http://www.codeproject.com/Articles/32600/Manually-validating-an-ASP-NET-user-account-with-a | |
/// </remarks> | |
private string ComputeSaltedHash(string clearPassword, string passwordSalt) { | |
byte[] bIn = Encoding.Unicode.GetBytes(clearPassword); | |
byte[] bSalt = Convert.FromBase64String(passwordSalt); | |
byte[] bAll = new byte[bSalt.Length + bIn.Length]; | |
byte[] bRet = null; | |
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length); | |
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length); | |
HashAlgorithm s = HashAlgorithm.Create("SHA1"); | |
bRet = s.ComputeHash(bAll); | |
string newHash = Convert.ToBase64String(bRet); | |
return newHash; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment