Created
March 8, 2012 12:50
-
-
Save joshilewis/2000863 to your computer and use it in GitHub Desktop.
NHibernate-based implementation of ServiceStack's IUserAuthRepository
This file contains 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
This is an NHibernate-based implementation of ServiceStack's IUserAuthRepository | |
Out-of-the-box, ServiceStack's UserAuth entity cannot be persisted by NHibernate because one of its properties is a List<> (and not an ILis<>). | |
The work-around for this is to wrap the UserAuth class in another class, UserAuthPersistenceDTO, and map between the two classes in the | |
repository implementation. | |
Note that the UserAuthMap and UserOAuthProviderMap classes are FluentNhibernate mapping classes. |
This file contains 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 System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.Globalization; | |
using ServiceStack.ServiceInterface.Auth; | |
using ServiceStack.Common; | |
using NHibernate; | |
using Ismoos.Infrastructure.Persistence; | |
namespace Ismoos.Infrastructure.ServiceStack | |
{ | |
public class NHibernateUserAuthRepository : IUserAuthRepository | |
{ | |
//http://stackoverflow.com/questions/3588623/c-sharp-regex-for-a-username-with-a-few-restrictions | |
public Regex ValidUserNameRegEx = new Regex(@"^(?=.{3,15}$)([A-Za-z0-9][._-]?)*$", RegexOptions.Compiled); | |
private readonly INHibernateSessionProvider sessionProvider; | |
public NHibernateUserAuthRepository(INHibernateSessionProvider sessionProvider) | |
{ | |
this.sessionProvider = sessionProvider; | |
} | |
private ISession session | |
{ | |
get { return sessionProvider.CurrentSession; } | |
} | |
public void LoadUserAuth(IAuthSession session, IOAuthTokens tokens) | |
{ | |
session.ThrowIfNull("session"); | |
var userAuth = GetUserAuth(session, tokens); | |
LoadUserAuth(session, userAuth); | |
} | |
public UserAuth GetUserAuth(IAuthSession authSession, IOAuthTokens tokens) | |
{ | |
if (!authSession.UserAuthId.IsNullOrEmpty()) | |
{ | |
var userAuth = GetUserAuth(authSession.UserAuthId); | |
if (userAuth != null) return userAuth; | |
} | |
if (!authSession.UserAuthName.IsNullOrEmpty()) | |
{ | |
var userAuth = GetUserAuthByUserName(authSession.UserAuthName); | |
if (userAuth != null) return userAuth; | |
} | |
if (tokens == null || tokens.Provider.IsNullOrEmpty() || tokens.UserId.IsNullOrEmpty()) | |
return null; | |
var oAuthProvider = session.QueryOver<UserOAuthProvider>() | |
.Where(x => x.Provider == tokens.Provider) | |
.And(x => x.UserId == tokens.UserId) | |
.SingleOrDefault(); | |
if (oAuthProvider != null) | |
{ | |
return session.QueryOver<UserAuthPersistenceDTO>() | |
.Where(x => x.Id == oAuthProvider.UserAuthId) | |
.SingleOrDefault(); | |
} | |
return null; | |
} | |
public UserAuth GetUserAuth(string userAuthId) | |
{ | |
int authId = int.Parse(userAuthId); | |
return session.QueryOver<UserAuthPersistenceDTO>() | |
.Where(x => x.Id == authId) | |
.SingleOrDefault() | |
; | |
} | |
private void LoadUserAuth(IAuthSession session, UserAuth userAuth) | |
{ | |
if (userAuth == null) return; | |
session.PopulateWith(userAuth); | |
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture); | |
session.ProviderOAuthAccess = GetUserOAuthProviders(session.UserAuthId) | |
.ConvertAll(x => (IOAuthTokens)x); | |
} | |
public bool TryAuthenticate(string userName, string password, out string userId) | |
{ | |
userId = null; | |
var userAuth = GetUserAuthByUserName(userName); | |
if (userAuth == null) return false; | |
var saltedHash = new SaltedHash(); | |
if (saltedHash.VerifyHashString(password, userAuth.PasswordHash, userAuth.Salt)) | |
{ | |
userId = userAuth.Id.ToString(CultureInfo.InvariantCulture); | |
return true; | |
} | |
return false; | |
} | |
public UserAuth GetUserAuthByUserName(string userNameOrEmail) | |
{ | |
UserAuthPersistenceDTO user = null; | |
if (userNameOrEmail.Contains("@")) | |
{ | |
user = session.QueryOver<UserAuthPersistenceDTO>() | |
.Where(x => x.Email == userNameOrEmail) | |
.SingleOrDefault(); | |
} | |
else | |
{ | |
user = session.QueryOver<UserAuthPersistenceDTO>() | |
.Where(x => x.UserName == userNameOrEmail) | |
.SingleOrDefault(); | |
} | |
return user; | |
} | |
public string CreateOrMergeAuthSession(IAuthSession authSession, IOAuthTokens tokens) | |
{ | |
var userAuth = GetUserAuth(authSession, tokens) ?? new UserAuth(); | |
var oAuthProvider = session.QueryOver<UserOAuthProvider>() | |
.Where(x => x.Provider == tokens.Provider) | |
.And(x => x.UserId == tokens.UserId) | |
.SingleOrDefault(); | |
if (oAuthProvider == null) | |
{ | |
oAuthProvider = new UserOAuthProvider | |
{ | |
Provider = tokens.Provider, | |
UserId = tokens.UserId, | |
}; | |
} | |
oAuthProvider.PopulateMissing(tokens); | |
userAuth.PopulateMissing(oAuthProvider); | |
userAuth.ModifiedDate = DateTime.UtcNow; | |
if (userAuth.CreatedDate == default(DateTime)) | |
userAuth.CreatedDate = userAuth.ModifiedDate; | |
session.Save(new UserAuthPersistenceDTO(userAuth)); | |
oAuthProvider.UserAuthId = userAuth.Id; | |
if (oAuthProvider.CreatedDate == default(DateTime)) | |
oAuthProvider.CreatedDate = userAuth.ModifiedDate; | |
oAuthProvider.ModifiedDate = userAuth.ModifiedDate; | |
session.Save(oAuthProvider); | |
return oAuthProvider.UserAuthId.ToString(CultureInfo.InvariantCulture); | |
} | |
public List<UserOAuthProvider> GetUserOAuthProviders(string userAuthId) | |
{ | |
int authId = int.Parse(userAuthId); | |
var value = session.QueryOver<UserOAuthProvider>() | |
.Where(x => x.UserAuthId == authId) | |
.OrderBy(x => x.ModifiedDate).Asc | |
.List(); | |
var providerList = new List<UserOAuthProvider>(); | |
foreach (var item in value) | |
{ | |
providerList.Add(item); | |
} | |
return providerList; | |
//return new List<UserOAuthProvider>(value); | |
} | |
public UserAuth CreateUserAuth(UserAuth newUser, string password) | |
{ | |
ValidateNewUser(newUser, password); | |
AssertNoExistingUser(newUser); | |
var saltedHash = new SaltedHash(); | |
string salt; | |
string hash; | |
saltedHash.GetHashAndSaltString(password, out hash, out salt); | |
newUser.PasswordHash = hash; | |
newUser.Salt = salt; | |
newUser.CreatedDate = DateTime.UtcNow; | |
newUser.ModifiedDate = newUser.CreatedDate; | |
session.Save(new UserAuthPersistenceDTO(newUser)); | |
return newUser; | |
} | |
private void ValidateNewUser(UserAuth newUser, string password) | |
{ | |
newUser.ThrowIfNull("newUser"); | |
password.ThrowIfNullOrEmpty("password"); | |
if (newUser.UserName.IsNullOrEmpty() && newUser.Email.IsNullOrEmpty()) | |
throw new ArgumentNullException("UserName or Email is required"); | |
if (!newUser.UserName.IsNullOrEmpty()) | |
{ | |
if (!ValidUserNameRegEx.IsMatch(newUser.UserName)) | |
throw new ArgumentException("UserName contains invalid characters", "UserName"); | |
} | |
} | |
private void AssertNoExistingUser(UserAuth newUser, UserAuth exceptForExistingUser = null) | |
{ | |
if (newUser.UserName != null) | |
{ | |
var existingUser = GetUserAuthByUserName(newUser.UserName); | |
if (existingUser != null | |
&& (exceptForExistingUser == null || existingUser.Id != exceptForExistingUser.Id)) | |
throw new ArgumentException(string.Format("User {0} already exists", newUser.UserName)); | |
} | |
if (newUser.Email != null) | |
{ | |
var existingUser = GetUserAuthByUserName(newUser.Email); | |
if (existingUser != null | |
&& (exceptForExistingUser == null || existingUser.Id != exceptForExistingUser.Id)) | |
throw new ArgumentException(string.Format("Email {0} already exists", newUser.Email)); | |
} | |
} | |
public void SaveUserAuth(UserAuth userAuth) | |
{ | |
userAuth.ModifiedDate = DateTime.UtcNow; | |
if (userAuth.CreatedDate == default(DateTime)) | |
userAuth.CreatedDate = userAuth.ModifiedDate; | |
session.Save(new UserAuthPersistenceDTO(userAuth)); | |
} | |
public void SaveUserAuth(IAuthSession authSession) | |
{ | |
var userAuth = !authSession.UserAuthId.IsNullOrEmpty() | |
? session.Load<UserAuthPersistenceDTO>(int.Parse(authSession.UserAuthId)) | |
: authSession.TranslateTo<UserAuth>(); | |
if (userAuth.Id == default(int) && !authSession.UserAuthId.IsNullOrEmpty()) | |
userAuth.Id = int.Parse(authSession.UserAuthId); | |
userAuth.ModifiedDate = userAuth.ModifiedDate; | |
if (userAuth.CreatedDate == default(DateTime)) | |
userAuth.CreatedDate = userAuth.ModifiedDate; | |
session.Save(new UserAuthPersistenceDTO(userAuth)); | |
} | |
public UserAuth UpdateUserAuth(UserAuth existingUser, UserAuth newUser, string password) | |
{ | |
ValidateNewUser(newUser, password); | |
AssertNoExistingUser(newUser, existingUser); | |
var hash = existingUser.PasswordHash; | |
var salt = existingUser.Salt; | |
if (password != null) | |
{ | |
var saltedHash = new SaltedHash(); | |
saltedHash.GetHashAndSaltString(password, out hash, out salt); | |
} | |
newUser.Id = existingUser.Id; | |
newUser.PasswordHash = hash; | |
newUser.Salt = salt; | |
newUser.CreatedDate = existingUser.CreatedDate; | |
newUser.ModifiedDate = DateTime.UtcNow; | |
session.Save(new UserAuthPersistenceDTO(newUser)); | |
return newUser; | |
} | |
} | |
} |
This file contains 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 System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using FluentNHibernate.Mapping; | |
using ServiceStack.ServiceInterface.Auth; | |
namespace Ismoos.Infrastructure.ServiceStack | |
{ | |
public class UserAuthMap : ClassMap<UserAuthPersistenceDTO> | |
{ | |
public UserAuthMap() | |
{ | |
Table("UserAuth"); | |
Id(x => x.Id) | |
.GeneratedBy.Native(); | |
Map(x => x.CreatedDate); | |
Map(x => x.DisplayName); | |
Map(x => x.Email); | |
Map(x => x.FirstName); | |
Map(x => x.LastName); | |
Map(x => x.ModifiedDate); | |
Map(x => x.PasswordHash); | |
Map(x => x.PrimaryEmail); | |
Map(x => x.Salt); | |
Map(x => x.UserName); | |
HasManyToMany(x => x.Permissions1) | |
.Table("UserAuth_Permissions") | |
.ParentKeyColumn("UserAuthID") | |
.Element("Permission"); | |
HasManyToMany(x => x.Roles1) | |
.Table("UserAuth_Roles") | |
.ParentKeyColumn("UserAuthID") | |
.Element("Role"); | |
} | |
} | |
public class UserAuthPersistenceDTO : UserAuth | |
{ | |
public UserAuthPersistenceDTO() | |
: base() | |
{ } | |
public UserAuthPersistenceDTO(UserAuth userAuth) | |
{ | |
Id = userAuth.Id; | |
UserName = userAuth.UserName; | |
Email = userAuth.Email; | |
PrimaryEmail = userAuth.PrimaryEmail; | |
FirstName = userAuth.FirstName; | |
LastName = userAuth.LastName; | |
DisplayName = userAuth.DisplayName; | |
Salt = userAuth.Salt; | |
PasswordHash = userAuth.PasswordHash; | |
Roles = userAuth.Roles; | |
Permissions = userAuth.Permissions; | |
CreatedDate = userAuth.CreatedDate; | |
ModifiedDate = userAuth.ModifiedDate; | |
} | |
public virtual IList<string> Permissions1 | |
{ | |
get { return Permissions; } | |
set { Permissions = new List<string>(value); } | |
} | |
public virtual IList<string> Roles1 | |
{ | |
get { return Roles; } | |
set { Roles = new List<string>(value); } | |
} | |
} | |
} |
This file contains 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 System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using FluentNHibernate.Mapping; | |
using ServiceStack.ServiceInterface.Auth; | |
namespace Ismoos.Infrastructure.ServiceStack | |
{ | |
public class UserOAuthProviderMap : ClassMap<UserOAuthProvider> | |
{ | |
public UserOAuthProviderMap() | |
{ | |
Table("UserOAuthProvider"); | |
Id(x => x.Id) | |
.GeneratedBy.Native(); | |
Map(x => x.AccessToken); | |
Map(x => x.AccessTokenSecret); | |
Map(x => x.CreatedDate); | |
Map(x => x.DisplayName); | |
Map(x => x.Email); | |
Map(x => x.FirstName); | |
Map(x => x.LastName); | |
Map(x => x.ModifiedDate); | |
Map(x => x.Provider); | |
Map(x => x.RequestToken); | |
Map(x => x.RequestTokenSecret); | |
Map(x => x.UserAuthId); | |
Map(x => x.UserId); | |
Map(x => x.UserName); | |
HasMany(x => x.Items) | |
.AsMap<string>( | |
index => index.Column("`Key`").Type<string>(), | |
element => element.Column("Value").Type<string>()) | |
.KeyColumn("UserOAuthProviderID") | |
.Table("UserOAuthProvider_Items") | |
.Not.LazyLoad() | |
.Cascade.All(); | |
} | |
} | |
} |
Awesome, thanks :)
My first author credit!
It was a good one too!
Looking forward to you next one ;)
On Fri, Jun 8, 2012 at 5:38 AM, Joshua Lewis < ***@***.*** > wrote:
Awesome, thanks :)
My first author credit!
---
Reply to this email directly or view it on GitHub:
https://gist.github.com/2000863
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Cool, As I was working on creating NuGets for Contrib on the weekend I ended up adding it to https://github.com/ServiceStack/ServiceStack.Contrib and have already published a NuGet package at: https://nuget.org/packages/ServiceStack.Authentication.NHibernate
I changed it to use an
ISessionFactory
but apart from that, it was left as-is.Feel free to make any changes you want to https://github.com/ServiceStack/ServiceStack.Contrib/tree/master/src/ServiceStack.Authentication.NHibernate - and I'll publish them on NuGet :)