Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save falafelsoftware/9218533 to your computer and use it in GitHub Desktop.
Save falafelsoftware/9218533 to your computer and use it in GitHub Desktop.
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