diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
index 087f5913e6..3ba5b4259a 100644
--- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
+++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
@@ -16,6 +16,8 @@ namespace Umbraco.Core.Models.Identity
public string[] AllowedApplications { get; set; }
public string Culture { get; set; }
+ public string UserTypeAlias { get; set; }
+
///
/// Overridden to make the retrieval lazy
///
diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
new file mode 100644
index 0000000000..8fa2703f39
--- /dev/null
+++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Linq;
+using AutoMapper;
+
+using Umbraco.Core.Models.Mapping;
+using Umbraco.Core.Models.Membership;
+
+namespace Umbraco.Core.Models.Identity
+{
+ public class IdentityModelMappings : MapperConfiguration
+ {
+ public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
+ {
+ config.CreateMap()
+ .ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email))
+ .ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id))
+ .ForMember(user => user.LockoutEnabled, expression => expression.MapFrom(user => user.IsLockedOut))
+ .ForMember(user => user.LockoutEndDateUtc, expression => expression.UseValue(DateTime.MaxValue.ToUniversalTime()))
+ .ForMember(user => user.UserName, expression => expression.MapFrom(user => user.Username))
+ .ForMember(user => user.PasswordHash, expression => expression.MapFrom(user => GetPasswordHash(user.RawPasswordValue)))
+ .ForMember(user => user.Culture, expression => expression.MapFrom(user => user.Language))
+ .ForMember(user => user.Name, expression => expression.MapFrom(user => user.Name))
+ .ForMember(user => user.StartMediaNode, expression => expression.MapFrom(user => user.StartMediaId))
+ .ForMember(user => user.StartContentNode, expression => expression.MapFrom(user => user.StartContentId))
+ .ForMember(user => user.UserTypeAlias, expression => expression.MapFrom(user => user.UserType.Alias))
+ .ForMember(user => user.AllowedApplications, expression => expression.MapFrom(user => user.AllowedSections.ToArray()));
+ }
+
+ private string GetPasswordHash(string storedPass)
+ {
+ return storedPass.StartsWith("___UIDEMPTYPWORD__") ? null : storedPass;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
index f9c77031e9..efed4ce37c 100644
--- a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
+++ b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
@@ -9,6 +9,13 @@ namespace Umbraco.Core.Models.Identity
///
public class IdentityUserLogin : Entity, IIdentityUserLogin
{
+ public IdentityUserLogin(string loginProvider, string providerKey, int userId)
+ {
+ LoginProvider = loginProvider;
+ ProviderKey = providerKey;
+ UserId = userId;
+ }
+
public IdentityUserLogin(int id, string loginProvider, string providerKey, int userId, DateTime createDate)
{
Id = id;
diff --git a/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs b/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs
index 52683231e2..046f4429b2 100644
--- a/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs
+++ b/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs
@@ -12,4 +12,6 @@ namespace Umbraco.Core.Models.Mapping
{
public abstract void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext);
}
+
+
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs
index 9c51f53596..0ffd6cbfe1 100644
--- a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs
+++ b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs
@@ -1,11 +1,41 @@
using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
+using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Mappers
{
+ [MapperFor(typeof(IIdentityUserLogin))]
+ [MapperFor(typeof(IdentityUserLogin))]
+ public sealed class ExternalLoginMapper : BaseMapper
+ {
+ private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary();
+ public ExternalLoginMapper()
+ {
+ BuildMap();
+ }
+
+ #region Overrides of BaseMapper
+
+ internal override ConcurrentDictionary PropertyInfoCache
+ {
+ get { return PropertyInfoCacheInstance; }
+ }
+
+ internal override void BuildMap()
+ {
+ CacheMap(src => src.Id, dto => dto.Id);
+ CacheMap(src => src.CreateDate, dto => dto.CreateDate);
+ CacheMap(src => src.LoginProvider, dto => dto.LoginProvider);
+ CacheMap(src => src.ProviderKey, dto => dto.ProviderKey);
+ CacheMap(src => src.UserId, dto => dto.UserId);
+ }
+
+ #endregion
+ }
+
[MapperFor(typeof(IUser))]
[MapperFor(typeof(User))]
public sealed class UserMapper : BaseMapper
diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs
index 427bc306da..8347bfe55c 100644
--- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs
+++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs
@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Security;
+using AutoMapper;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Models.Identity;
+using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
namespace Umbraco.Core.Security
@@ -13,22 +15,19 @@ namespace Umbraco.Core.Security
{
private readonly IUserService _userService;
private readonly IExternalLoginService _externalLoginService;
- private readonly MembershipProviderBase _usersMembershipProvider;
public BackOfficeUserStore(IUserService userService, IExternalLoginService externalLoginService, MembershipProviderBase usersMembershipProvider)
{
_userService = userService;
_externalLoginService = externalLoginService;
- _usersMembershipProvider = usersMembershipProvider;
if (userService == null) throw new ArgumentNullException("userService");
if (usersMembershipProvider == null) throw new ArgumentNullException("usersMembershipProvider");
if (externalLoginService == null) throw new ArgumentNullException("externalLoginService");
_userService = userService;
- _usersMembershipProvider = usersMembershipProvider;
_externalLoginService = externalLoginService;
- if (_usersMembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed)
+ if (usersMembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed)
{
throw new InvalidOperationException("Cannot use ASP.Net Identity with UmbracoMembersUserStore when the password format is not Hashed");
}
@@ -39,7 +38,6 @@ namespace Umbraco.Core.Security
///
protected override void DisposeResources()
{
- throw new NotImplementedException();
}
///
@@ -49,7 +47,43 @@ namespace Umbraco.Core.Security
///
public Task CreateAsync(BackOfficeIdentityUser user)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+
+ var userType = _userService.GetUserTypeByAlias(
+ user.UserTypeAlias.IsNullOrWhiteSpace() ? _userService.GetDefaultMemberType() : user.UserTypeAlias);
+
+ var member = new User(userType)
+ {
+ DefaultToLiveEditing = false,
+ Email = user.Email,
+ Language = Configuration.GlobalSettings.DefaultUILanguage,
+ Name = user.Name,
+ Username = user.UserName,
+ StartContentId = -1,
+ StartMediaId = -1,
+ IsLockedOut = false,
+ IsApproved = true
+ };
+
+ UpdateMemberProperties(member, user);
+
+ //the password must be 'something' it could be empty if authenticating
+ // with an external provider so we'll just generate one and prefix it, the
+ // prefix will help us determine if the password hasn't actually been specified yet.
+ if (member.RawPasswordValue.IsNullOrWhiteSpace())
+ {
+ //this will hash the guid with a salt so should be nicely random
+ var aspHasher = new PasswordHasher();
+ member.RawPasswordValue = "___UIDEMPTYPWORD__" +
+ aspHasher.HashPassword(Guid.NewGuid().ToString("N"));
+
+ }
+ _userService.Save(member);
+
+ //re-assign id
+ user.Id = member.Id;
+
+ return Task.FromResult(0);
}
///
@@ -57,9 +91,30 @@ namespace Umbraco.Core.Security
///
///
///
- public Task UpdateAsync(BackOfficeIdentityUser user)
+ public async Task UpdateAsync(BackOfficeIdentityUser user)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+
+ var asInt = user.Id.TryConvertTo();
+ if (asInt == false)
+ {
+ throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
+ }
+
+ var found = _userService.GetUserById(asInt.Result);
+ if (found != null)
+ {
+ if (UpdateMemberProperties(found, user))
+ {
+ _userService.Save(found);
+ }
+
+ if (user.LoginsChanged)
+ {
+ var logins = await GetLoginsAsync(user);
+ _externalLoginService.SaveUserLogins(found.Id, logins);
+ }
+ }
}
///
@@ -69,7 +124,22 @@ namespace Umbraco.Core.Security
///
public Task DeleteAsync(BackOfficeIdentityUser user)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+
+ var asInt = user.Id.TryConvertTo();
+ if (asInt == false)
+ {
+ throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
+ }
+
+ var found = _userService.GetUserById(asInt.Result);
+ if (found != null)
+ {
+ _userService.Delete(found);
+ }
+ _externalLoginService.DeleteUserLogins(asInt.Result);
+
+ return Task.FromResult(0);
}
///
@@ -79,7 +149,12 @@ namespace Umbraco.Core.Security
///
public Task FindByIdAsync(int userId)
{
- throw new NotImplementedException();
+ var user = _userService.GetUserById(userId);
+ if (user == null)
+ {
+ return null;
+ }
+ return Task.FromResult(AssignLoginsCallback(Mapper.Map(user)));
}
///
@@ -89,7 +164,15 @@ namespace Umbraco.Core.Security
///
public Task FindByNameAsync(string userName)
{
- throw new NotImplementedException();
+ var user = _userService.GetByUsername(userName);
+ if (user == null)
+ {
+ return null;
+ }
+
+ var result = AssignLoginsCallback(Mapper.Map(user));
+
+ return Task.FromResult(result);
}
///
@@ -99,7 +182,12 @@ namespace Umbraco.Core.Security
///
public Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+ if (passwordHash.IsNullOrWhiteSpace()) throw new ArgumentNullException("passwordHash");
+
+ user.PasswordHash = passwordHash;
+
+ return Task.FromResult(0);
}
///
@@ -109,7 +197,9 @@ namespace Umbraco.Core.Security
///
public Task GetPasswordHashAsync(BackOfficeIdentityUser user)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+
+ return Task.FromResult(user.PasswordHash);
}
///
@@ -119,7 +209,9 @@ namespace Umbraco.Core.Security
///
public Task HasPasswordAsync(BackOfficeIdentityUser user)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+
+ return Task.FromResult(user.PasswordHash.IsNullOrWhiteSpace() == false);
}
///
@@ -129,7 +221,12 @@ namespace Umbraco.Core.Security
///
public Task SetEmailAsync(BackOfficeIdentityUser user, string email)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+ if (email.IsNullOrWhiteSpace()) throw new ArgumentNullException("email");
+
+ user.Email = email;
+
+ return Task.FromResult(0);
}
///
@@ -139,7 +236,9 @@ namespace Umbraco.Core.Security
///
public Task GetEmailAsync(BackOfficeIdentityUser user)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+
+ return Task.FromResult(user.Email);
}
///
@@ -169,7 +268,12 @@ namespace Umbraco.Core.Security
///
public Task FindByEmailAsync(string email)
{
- throw new NotImplementedException();
+ var user = _userService.GetByEmail(email);
+ var result = user == null
+ ? null
+ : Mapper.Map(user);
+
+ return Task.FromResult(AssignLoginsCallback(result));
}
///
@@ -179,7 +283,15 @@ namespace Umbraco.Core.Security
///
public Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+ if (login == null) throw new ArgumentNullException("login");
+
+ var logins = user.Logins;
+ var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id);
+ var userLogin = instance;
+ logins.Add(userLogin);
+
+ return Task.FromResult(0);
}
///
@@ -189,7 +301,16 @@ namespace Umbraco.Core.Security
///
public Task RemoveLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+ if (login == null) throw new ArgumentNullException("login");
+
+ var provider = login.LoginProvider;
+ var key = login.ProviderKey;
+ var userLogin = user.Logins.SingleOrDefault((l => l.LoginProvider == provider && l.ProviderKey == key));
+ if (userLogin != null)
+ user.Logins.Remove(userLogin);
+
+ return Task.FromResult(0);
}
///
@@ -199,7 +320,9 @@ namespace Umbraco.Core.Security
///
public Task> GetLoginsAsync(BackOfficeIdentityUser user)
{
- throw new NotImplementedException();
+ if (user == null) throw new ArgumentNullException("user");
+ return Task.FromResult((IList)
+ user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey)).ToList());
}
///
@@ -213,21 +336,13 @@ namespace Umbraco.Core.Security
if (result.Any())
{
//return the first member that matches the result
- var user = (from l in result
+ var output = (from l in result
select _userService.GetUserById(l.Id)
- into member
- where member != null
- select new BackOfficeIdentityUser
- {
- Email = member.Email,
- Id = member.Id,
- LockoutEnabled = member.IsLockedOut,
- LockoutEndDateUtc = DateTime.MaxValue.ToUniversalTime(),
- UserName = member.Username,
- PasswordHash = GetPasswordHash(member.RawPasswordValue)
- }).FirstOrDefault();
+ into user
+ where user != null
+ select Mapper.Map(user)).FirstOrDefault();
- return Task.FromResult(AssignLoginsCallback(user));
+ return Task.FromResult(AssignLoginsCallback(output));
}
return Task.FromResult(null);
@@ -243,9 +358,73 @@ namespace Umbraco.Core.Security
return user;
}
- private string GetPasswordHash(string storedPass)
+ private bool UpdateMemberProperties(Models.Membership.IUser user, BackOfficeIdentityUser identityUser)
{
- return storedPass.StartsWith("___UIDEMPTYPWORD__") ? null : storedPass;
+ var anythingChanged = false;
+ //don't assign anything if nothing has changed as this will trigger
+ //the track changes of the model
+ if (user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false)
+ {
+ anythingChanged = true;
+ user.Name = identityUser.Name;
+ }
+ if (user.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false)
+ {
+ anythingChanged = true;
+ user.Email = identityUser.Email;
+ }
+ if (user.FailedPasswordAttempts != identityUser.AccessFailedCount)
+ {
+ anythingChanged = true;
+ user.FailedPasswordAttempts = identityUser.AccessFailedCount;
+ }
+ if (user.IsLockedOut != identityUser.LockoutEnabled)
+ {
+ anythingChanged = true;
+ user.IsLockedOut = identityUser.LockoutEnabled;
+ }
+ if (user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false)
+ {
+ anythingChanged = true;
+ user.Username = identityUser.UserName;
+ }
+ if (user.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false)
+ {
+ anythingChanged = true;
+ user.RawPasswordValue = identityUser.PasswordHash;
+ }
+
+ if (user.Language != identityUser.Culture && identityUser.Culture.IsNullOrWhiteSpace() == false)
+ {
+ anythingChanged = true;
+ user.Language = identityUser.Culture;
+ }
+ if (user.StartMediaId != identityUser.StartMediaNode)
+ {
+ anythingChanged = true;
+ user.StartMediaId = identityUser.StartMediaNode;
+ }
+ if (user.StartContentId != identityUser.StartContentNode)
+ {
+ anythingChanged = true;
+ user.StartContentId = identityUser.StartContentNode;
+ }
+ if (user.AllowedSections.ContainsAll(identityUser.AllowedApplications) == false
+ || identityUser.AllowedApplications.ContainsAll(user.AllowedSections) == false)
+ {
+ anythingChanged = true;
+ foreach (var allowedSection in user.AllowedSections)
+ {
+ user.RemoveAllowedSection(allowedSection);
+ }
+ foreach (var allowedApplication in identityUser.AllowedApplications)
+ {
+ user.AddAllowedSection(allowedApplication);
+ }
+ }
+
+ return anythingChanged;
}
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index f76cb2667c..bd32b08677 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -344,6 +344,7 @@
+
diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs
index 27dfe80c6d..2267e30ca7 100644
--- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs
+++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs
@@ -77,6 +77,8 @@ namespace Umbraco.Web.Security.Identity
{
if (app == null) throw new ArgumentNullException("app");
+ //TODO: Figure out why this isn't working and is only working with the default one, must be a reference somewhere
+
//app.UseExternalSignInCookie("UmbracoExternalCookie");
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);