diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs index 7ac3701c5c..cdd81a3fe3 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs @@ -156,7 +156,7 @@ namespace Umbraco.Core.BackOffice /// /// /// - public async Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); @@ -192,7 +192,7 @@ namespace Umbraco.Core.BackOffice } } - return IdentityResult.Success; + return Task.FromResult(IdentityResult.Success); } /// diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs index ee61359a70..664c957f57 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs +++ b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs @@ -311,13 +311,14 @@ namespace Umbraco.Core.BackOffice /// Task GetPhoneNumberAsync(TUser user); + // TODO: These are raised from outside the signinmanager and usermanager in the auth and user controllers, + // let's see if there's a way to avoid that and only have these called within signinmanager and usermanager + // which means we can remove these from the interface (things like invite seems like they cannot be moved) void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId); void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId); SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId); UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser); - void RaiseLoginSuccessEvent(TUser currentUser, int userId); - bool HasSendingUserInviteEventHandler { get; } } } diff --git a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs index 5f258bcb87..78d5d5554c 100644 --- a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs +++ b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs @@ -18,6 +18,7 @@ using Umbraco.Tests.Common.Builders; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Routing; using Umbraco.Web.Common.Install; +using Umbraco.Web.Common.Security; using Umbraco.Web.WebApi; namespace Umbraco.Tests.UnitTests.AutoFixture diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 2d9c66806b..0eaa141250 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -111,7 +111,6 @@ namespace Umbraco.Tests.Testing.TestingTests var memberService = Mock.Of(); var memberTypeService = Mock.Of(); var membershipProvider = new MembersMembershipProvider(memberService, memberTypeService, Mock.Of(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver()); - var membershipHelper = new MembershipHelper(Mock.Of(), Mock.Of(), membershipProvider, Mock.Of(), memberService, memberTypeService, Mock.Of(), AppCaches.Disabled, NullLoggerFactory.Instance, ShortStringHelper, Mock.Of()); var umbracoMapper = new UmbracoMapper(new MapDefinitionCollection(new[] { Mock.Of() })); var umbracoApiController = new FakeUmbracoApiController(new GlobalSettings(), Mock.Of(), Mock.Of(), Mock.Of(), ServiceContext.CreatePartial(), AppCaches.NoCache, profilingLogger , Mock.Of(), umbracoMapper, Mock.Of()); diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 6aa6c46eee..5c95be2afc 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -5,6 +5,7 @@ using System.Net; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; @@ -66,6 +67,7 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly IRequestAccessor _requestAccessor; private readonly LinkGenerator _linkGenerator; private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions; + private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; // TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController // TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here @@ -87,7 +89,8 @@ namespace Umbraco.Web.BackOffice.Controllers Core.Hosting.IHostingEnvironment hostingEnvironment, IRequestAccessor requestAccessor, LinkGenerator linkGenerator, - IBackOfficeExternalLoginProviders externalAuthenticationOptions) + IBackOfficeExternalLoginProviders externalAuthenticationOptions, + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions) { _backofficeSecurityAccessor = backofficeSecurityAccessor; _userManager = backOfficeUserManager; @@ -106,6 +109,7 @@ namespace Umbraco.Web.BackOffice.Controllers _requestAccessor = requestAccessor; _linkGenerator = linkGenerator; _externalAuthenticationOptions = externalAuthenticationOptions; + _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; } /// @@ -303,7 +307,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [SetAngularAntiForgeryTokens] [DenyLocalLoginAuthorization] - public async Task PostLogin(LoginModel loginModel) + public async Task> PostLogin(LoginModel loginModel) { // Sign the user in with username/password, this also gives a chance for developers to // custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker @@ -318,40 +322,25 @@ namespace Umbraco.Web.BackOffice.Controllers if (result.RequiresTwoFactor) { - throw new NotImplementedException("Implement MFA/2FA, we need to have some IOptions or similar to configure this"); + var twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(loginModel.Username); + if (twofactorView.IsNullOrWhiteSpace()) + { + return new ValidationErrorResult($"The registered {typeof(IBackOfficeTwoFactorOptions)} of type {_backOfficeTwoFactorOptions.GetType()} did not return a view for two factor auth "); + } - //var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions; - //if (twofactorOptions == null) - //{ - // throw new HttpResponseException( - // Request.CreateErrorResponse( - // HttpStatusCode.BadRequest, - // "UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions))); - //} + var attemptedUser = _userService.GetByUsername(loginModel.Username); - //var twofactorView = twofactorOptions.GetTwoFactorView( - // owinContext, - // UmbracoContext, - // loginModel.Username); + // create a with information to display a custom two factor send code view + var verifyResponse = new ObjectResult(new + { + twoFactorView = twofactorView, + userId = attemptedUser.Id + }) + { + StatusCode = StatusCodes.Status402PaymentRequired + }; - //if (twofactorView.IsNullOrWhiteSpace()) - //{ - // throw new HttpResponseException( - // Request.CreateErrorResponse( - // HttpStatusCode.BadRequest, - // typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string")); - //} - - //var attemptedUser = Services.UserService.GetByUsername(loginModel.Username); - - //// create a with information to display a custom two factor send code view - //var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new - //{ - // twoFactorView = twofactorView, - // userId = attemptedUser.Id - //}); - - //_userManager.RaiseLoginRequiresVerificationEvent(User, attemptedUser.Id); + //return verifyResponse; } diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 1b8e6fb80a..8d3223a79f 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -63,6 +63,7 @@ namespace Umbraco.Extensions services.TryAddScoped(); services.TryAddScoped(); services.TryAddSingleton(); + services.TryAddSingleton(); /* * IdentityBuilderExtensions.AddUserManager adds UserManager to service collection diff --git a/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs new file mode 100644 index 0000000000..e2a7aeccaf --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/AuthenticationBuilderExtensions.cs @@ -0,0 +1,15 @@ +using System; +using Umbraco.Core.Builder; + +namespace Umbraco.Web.BackOffice.Security +{ + public static class AuthenticationBuilderExtensions + { + public static IUmbracoBuilder AddBackOfficeExternalLogins(this IUmbracoBuilder umbracoBuilder, Action builder) + { + builder(new BackOfficeExternalLoginsBuilder(umbracoBuilder.Services)); + return umbracoBuilder; + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs new file mode 100644 index 0000000000..b3418697e2 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAuthenticationBuilder.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using System; +using Umbraco.Core; + +namespace Umbraco.Web.BackOffice.Security +{ + /// + /// Custom used to associate external logins with umbraco external login options + /// + public class BackOfficeAuthenticationBuilder : AuthenticationBuilder + { + private readonly BackOfficeExternalLoginProviderOptions _loginProviderOptions; + + public BackOfficeAuthenticationBuilder(IServiceCollection services, BackOfficeExternalLoginProviderOptions loginProviderOptions) + : base(services) + { + _loginProviderOptions = loginProviderOptions; + } + + public string SchemeForBackOffice(string scheme) + { + return Constants.Security.BackOfficeExternalAuthenticationTypePrefix + scheme; + } + + /// + /// Overridden to track the final authenticationScheme being registered for the external login + /// + /// + /// + /// + /// + /// + /// + public override AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, Action configureOptions) + { + // Validate that the prefix is set + if (!authenticationScheme.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix)) + { + throw new InvalidOperationException($"The {nameof(authenticationScheme)} is not prefixed with {Constants.Security.BackOfficeExternalAuthenticationTypePrefix}. The scheme must be created with a call to the method {nameof(SchemeForBackOffice)}"); + } + + // add our login provider to the container along with a custom options configuration + Services.AddSingleton(x => new BackOfficeExternalLoginProvider(displayName, authenticationScheme, _loginProviderOptions)); + Services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureBackOfficeScheme>()); + + return base.AddRemoteScheme(authenticationScheme, displayName, configureOptions); + } + + // TODO: We could override and throw NotImplementedException for other methods? + + // Ensures that the sign in scheme is always the Umbraco back office external type + private class EnsureBackOfficeScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions + { + public void PostConfigure(string name, TOptions options) + { + options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType; + } + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs new file mode 100644 index 0000000000..18e5b066dc --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProvider.cs @@ -0,0 +1,39 @@ +using System; + +namespace Umbraco.Web.BackOffice.Security +{ + /// + /// An external login (OAuth) provider for the back office + /// + public class BackOfficeExternalLoginProvider : IEquatable + { + public BackOfficeExternalLoginProvider(string name, string authenticationType, BackOfficeExternalLoginProviderOptions properties) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + AuthenticationType = authenticationType ?? throw new ArgumentNullException(nameof(authenticationType)); + Options = properties ?? throw new ArgumentNullException(nameof(properties)); + } + + public string Name { get; } + public string AuthenticationType { get; } + public BackOfficeExternalLoginProviderOptions Options { get; } + + public override bool Equals(object obj) + { + return Equals(obj as BackOfficeExternalLoginProvider); + } + + public bool Equals(BackOfficeExternalLoginProvider other) + { + return other != null && + Name == other.Name && + AuthenticationType == other.AuthenticationType; + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, AuthenticationType); + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs new file mode 100644 index 0000000000..08ef32dc57 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviders.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Web.BackOffice.Security +{ + public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders + { + public BackOfficeExternalLoginProviders(IEnumerable externalLogins) + { + _externalLogins = externalLogins; + } + + private readonly IEnumerable _externalLogins; + + public BackOfficeExternalLoginProvider Get(string authenticationType) + { + return _externalLogins.FirstOrDefault(x => x.AuthenticationType == authenticationType); + } + + public string GetAutoLoginProvider() + { + var found = _externalLogins.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList(); + return found.Count > 0 ? found[0].AuthenticationType : null; + } + + public IEnumerable GetBackOfficeProviders() + { + return _externalLogins; + } + + public bool HasDenyLocalLogin() + { + var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList(); + return found.Count > 0; + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs new file mode 100644 index 0000000000..402ad8b948 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginsBuilder.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Umbraco.Web.BackOffice.Security +{ + /// + /// Used to add back office login providers + /// + public class BackOfficeExternalLoginsBuilder + { + public BackOfficeExternalLoginsBuilder(IServiceCollection services) + { + _services = services; + } + + private readonly IServiceCollection _services; + + /// + /// Add a back office login provider with options + /// + /// + /// + /// + public BackOfficeExternalLoginsBuilder AddBackOfficeLogin( + BackOfficeExternalLoginProviderOptions loginProviderOptions, + Action build) + { + build(new BackOfficeAuthenticationBuilder(_services, loginProviderOptions)); + return this; + } + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index f08be0bd99..d2050e214f 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -10,8 +10,11 @@ using System.Security.Claims; using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.BackOffice; +using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Security; using Umbraco.Extensions; +using Umbraco.Net; using Umbraco.Web.BackOffice.Security; namespace Umbraco.Web.Common.Security @@ -436,16 +439,17 @@ namespace Umbraco.Web.Common.Security Logger.LogInformation("User: {UserName} logged in from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress); if (user != null) { - _userManager.RaiseLoginSuccessEvent(user, user.Id); + _userManager.RaiseLoginSuccessEvent(Context.User, user.Id); } } else if (result.IsLockedOut) { - _userManager.RaiseAccountLockedEvent(user, user.Id); + _userManager.RaiseAccountLockedEvent(Context.User, user.Id); Logger.LogInformation("Login attempt failed for username {UserName} from IP address {IpAddress}, the user is locked", username, Context.Connection.RemoteIpAddress); } else if (result.RequiresTwoFactor) { + _userManager.RaiseLoginRequiresVerificationEvent(Context.User, user.Id); Logger.LogInformation("Login attempt requires verification for username {UserName} from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress); } else if (!result.Succeeded || result.IsNotAllowed) diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs similarity index 86% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs rename to src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs index 1e5cd8436e..3501242eb5 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManager.cs @@ -1,11 +1,14 @@ -using System; +using System; using System.Collections.Generic; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Umbraco.Core; +using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.Membership; @@ -14,7 +17,8 @@ using Umbraco.Extensions; using Umbraco.Net; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.Core.BackOffice + +namespace Umbraco.Web.Common.Security { public class BackOfficeUserManager : BackOfficeUserManager, IBackOfficeUserManager { @@ -28,9 +32,10 @@ namespace Umbraco.Core.BackOffice BackOfficeLookupNormalizer keyNormalizer, BackOfficeIdentityErrorDescriber errors, IServiceProvider services, + IHttpContextAccessor httpContextAccessor, ILogger> logger, IOptions passwordConfiguration) - : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger, passwordConfiguration) + : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, httpContextAccessor, logger, passwordConfiguration) { } } @@ -39,6 +44,7 @@ namespace Umbraco.Core.BackOffice where T : BackOfficeIdentityUser { private PasswordGenerator _passwordGenerator; + private readonly IHttpContextAccessor _httpContextAccessor; public BackOfficeUserManager( IIpResolver ipResolver, @@ -50,11 +56,13 @@ namespace Umbraco.Core.BackOffice BackOfficeLookupNormalizer keyNormalizer, BackOfficeIdentityErrorDescriber errors, IServiceProvider services, + IHttpContextAccessor httpContextAccessor, ILogger> logger, IOptions passwordConfiguration) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) { IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver)); + _httpContextAccessor = httpContextAccessor; PasswordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration)); } @@ -95,7 +103,7 @@ namespace Umbraco.Core.BackOffice { var userSessionStore = Store as IUserSessionStore; //if this is not set, for backwards compat (which would be super rare), we'll just approve it - if (userSessionStore == null) return true; + if (userSessionStore == null) return true; return await userSessionStore.ValidateSessionIdAsync(userId, sessionId); } @@ -140,7 +148,7 @@ namespace Umbraco.Core.BackOffice { if (user == null) throw new ArgumentNullException(nameof(user)); - if (user.IsApproved == false) return true; + if (user.IsApproved == false) return true; return await base.IsLockedOutAsync(user); } @@ -211,7 +219,9 @@ namespace Umbraco.Core.BackOffice var result = await base.ResetPasswordAsync(user, token, newPassword); if (result.Succeeded) - RaisePasswordChangedEvent(null, userId); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + { + RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, userId); + } return result; } @@ -219,7 +229,9 @@ namespace Umbraco.Core.BackOffice { var result = await base.ChangePasswordAsync(user, currentPassword, newPassword); if (result.Succeeded) - RaisePasswordChangedEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + { + RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + } return result; } @@ -297,11 +309,11 @@ namespace Umbraco.Core.BackOffice // The way we unlock is by setting the lockoutEnd date to the current datetime if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow) { - RaiseAccountLockedEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + RaiseAccountLockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); } else { - RaiseAccountUnlockedEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + RaiseAccountUnlockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); //Resets the login attempt fails back to 0 when unlock is clicked await ResetAccessFailedCountAsync(user); } @@ -321,7 +333,7 @@ namespace Umbraco.Core.BackOffice await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); //raise the event now that it's reset - RaiseResetAccessFailedCountEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + RaiseResetAccessFailedCountEvent(_httpContextAccessor.HttpContext?.User, user.Id); return await UpdateAsync(user); } @@ -357,8 +369,7 @@ namespace Umbraco.Core.BackOffice //Slightly confusing: this will return a Success if we successfully update the AccessFailed count if (result.Succeeded) { - // TODO: This may no longer be the case in netcore, we'll need to see about that - RaiseLoginFailedEvent(null, user.Id); // TODO: How can we get the current user? we have not HttpContext (netstandard), we can make our own IPrincipalAccessor? + RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id); } return result; @@ -367,7 +378,7 @@ namespace Umbraco.Core.BackOffice private int GetCurrentUserId(IPrincipal currentUser) { var umbIdentity = currentUser?.GetUmbracoIdentity(); - var currentUserId = umbIdentity?.GetUserId() ?? Constants.Security.SuperUserId; + var currentUserId = umbIdentity?.GetUserId() ?? Core.Constants.Security.SuperUserId; return currentUserId; } private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, int affectedUserId, string affectedUsername) @@ -383,9 +394,9 @@ namespace Umbraco.Core.BackOffice return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); } - // TODO: Review where these are raised and see if they can be simplified and either done in the this usermanager or the signin manager, lastly we'll resort to the authentication controller - // In some cases it will be nicer/easier to not pass in IPrincipal - public void RaiseAccountLockedEvent(BackOfficeIdentityUser currentUser, int userId) => OnAccountLocked(CreateArgs(AuditEvent.AccountLocked, currentUser, userId, string.Empty)); + // TODO: Review where these are raised and see if they can be simplified and either done in the this usermanager or the signin manager, + // lastly we'll resort to the authentication controller but we should try to remove all instances of that occuring + public void RaiseAccountLockedEvent(IPrincipal currentUser, int userId) => OnAccountLocked(CreateArgs(AuditEvent.AccountLocked, currentUser, userId, string.Empty)); public void RaiseAccountUnlockedEvent(IPrincipal currentUser, int userId) => OnAccountUnlocked(CreateArgs(AuditEvent.AccountUnlocked, currentUser, userId, string.Empty)); @@ -395,11 +406,11 @@ namespace Umbraco.Core.BackOffice public void RaiseLoginFailedEvent(IPrincipal currentUser, int userId) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, userId, string.Empty)); - public void RaiseInvalidLoginAttemptEvent(IPrincipal currentUser, string username) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, Constants.Security.SuperUserId, username)); + //public void RaiseInvalidLoginAttemptEvent(IPrincipal currentUser, string username) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, Constants.Security.SuperUserId, username)); public void RaiseLoginRequiresVerificationEvent(IPrincipal currentUser, int userId) => OnLoginRequiresVerification(CreateArgs(AuditEvent.LoginRequiresVerification, currentUser, userId, string.Empty)); - public void RaiseLoginSuccessEvent(BackOfficeIdentityUser currentUser, int userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); + public void RaiseLoginSuccessEvent(IPrincipal currentUser, int userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId) { @@ -408,7 +419,7 @@ namespace Umbraco.Core.BackOffice OnLogoutSuccess(args); return args; } - + public void RaisePasswordChangedEvent(IPrincipal currentUser, int userId) => OnPasswordChanged(CreateArgs(AuditEvent.LogoutSuccess, currentUser, userId, string.Empty)); public void RaiseResetAccessFailedCountEvent(IPrincipal currentUser, int userId) => OnResetAccessFailedCount(CreateArgs(AuditEvent.ResetAccessFailedCount, currentUser, userId, string.Empty)); @@ -424,17 +435,17 @@ namespace Umbraco.Core.BackOffice public bool HasSendingUserInviteEventHandler => SendingUserInvite != null; - public static event EventHandler AccountLocked; - public static event EventHandler AccountUnlocked; - public static event EventHandler ForgotPasswordRequested; - public static event EventHandler ForgotPasswordChangedSuccess; - public static event EventHandler LoginFailed; - public static event EventHandler LoginRequiresVerification; - public static event EventHandler LoginSuccess; - public static event EventHandler LogoutSuccess; - public static event EventHandler PasswordChanged; - public static event EventHandler PasswordReset; - public static event EventHandler ResetAccessFailedCount; + public event EventHandler AccountLocked; + public event EventHandler AccountUnlocked; + public event EventHandler ForgotPasswordRequested; + public event EventHandler ForgotPasswordChangedSuccess; + public event EventHandler LoginFailed; + public event EventHandler LoginRequiresVerification; + public event EventHandler LoginSuccess; + public event EventHandler LogoutSuccess; + public event EventHandler PasswordChanged; + public event EventHandler PasswordReset; + public event EventHandler ResetAccessFailedCount; /// /// Raised when a user is invited diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs index 6b78e58ead..2274df14a3 100644 --- a/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeExternalLoginProviders.cs @@ -1,111 +1,14 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.OAuth; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; -using System; +using Microsoft.AspNetCore.Authentication.OAuth; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Text; -using Umbraco.Core; -using Umbraco.Core.Builder; namespace Umbraco.Web.BackOffice.Security { - /// - /// Custom used to associate external logins with umbraco external login options - /// - public class BackOfficeAuthenticationBuilder : AuthenticationBuilder - { - private readonly BackOfficeExternalLoginProviderOptions _loginProviderOptions; - - public BackOfficeAuthenticationBuilder(IServiceCollection services, BackOfficeExternalLoginProviderOptions loginProviderOptions) - : base(services) - { - _loginProviderOptions = loginProviderOptions; - } - - public string SchemeForBackOffice(string scheme) - { - return Constants.Security.BackOfficeExternalAuthenticationTypePrefix + scheme; - } - - /// - /// Overridden to track the final authenticationScheme being registered for the external login - /// - /// - /// - /// - /// - /// - /// - public override AuthenticationBuilder AddRemoteScheme(string authenticationScheme, string displayName, Action configureOptions) - { - // Validate that the prefix is set - if (!authenticationScheme.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix)) - { - throw new InvalidOperationException($"The {nameof(authenticationScheme)} is not prefixed with {Constants.Security.BackOfficeExternalAuthenticationTypePrefix}. The scheme must be created with a call to the method {nameof(SchemeForBackOffice)}"); - } - - // add our login provider to the container along with a custom options configuration - Services.AddSingleton(x => new BackOfficeExternalLoginProvider(displayName, authenticationScheme, _loginProviderOptions)); - Services.TryAddEnumerable(ServiceDescriptor.Singleton, EnsureBackOfficeScheme>()); - - return base.AddRemoteScheme(authenticationScheme, displayName, configureOptions); - } - - // TODO: We could override and throw NotImplementedException for other methods? - - // Ensures that the sign in scheme is always the Umbraco back office external type - private class EnsureBackOfficeScheme : IPostConfigureOptions where TOptions : RemoteAuthenticationOptions - { - public void PostConfigure(string name, TOptions options) - { - options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType; - } - } - } /// - /// Used to add back office login providers + /// Service to return instances /// - public class BackOfficeExternalLoginsBuilder - { - public BackOfficeExternalLoginsBuilder(IServiceCollection services) - { - _services = services; - } - - private readonly IServiceCollection _services; - - /// - /// Add a back office login provider with options - /// - /// - /// - /// - public BackOfficeExternalLoginsBuilder AddBackOfficeLogin( - BackOfficeExternalLoginProviderOptions loginProviderOptions, - Action build) - { - build(new BackOfficeAuthenticationBuilder(_services, loginProviderOptions)); - return this; - } - } - - public static class AuthenticationBuilderExtensions - { - public static IUmbracoBuilder AddBackOfficeExternalLogins(this IUmbracoBuilder umbracoBuilder, Action builder) - { - builder(new BackOfficeExternalLoginsBuilder(umbracoBuilder.Services)); - return umbracoBuilder; - } - } - - // TODO: We need to implement this and extend it to support the back office external login options - // basically migrate things from AuthenticationManagerExtensions & AuthenticationOptionsExtensions - // and use this to get the back office external login infos public interface IBackOfficeExternalLoginProviders { BackOfficeExternalLoginProvider Get(string authenticationType); @@ -126,67 +29,4 @@ namespace Umbraco.Web.BackOffice.Security bool HasDenyLocalLogin(); } - public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders - { - public BackOfficeExternalLoginProviders(IEnumerable externalLogins) - { - _externalLogins = externalLogins; - } - - private readonly IEnumerable _externalLogins; - - public BackOfficeExternalLoginProvider Get(string authenticationType) - { - return _externalLogins.FirstOrDefault(x => x.AuthenticationType == authenticationType); - } - - public string GetAutoLoginProvider() - { - var found = _externalLogins.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList(); - return found.Count > 0 ? found[0].AuthenticationType : null; - } - - public IEnumerable GetBackOfficeProviders() - { - return _externalLogins; - } - - public bool HasDenyLocalLogin() - { - var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList(); - return found.Count > 0; - } - } - - public class BackOfficeExternalLoginProvider : IEquatable - { - public BackOfficeExternalLoginProvider(string name, string authenticationType, BackOfficeExternalLoginProviderOptions properties) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - AuthenticationType = authenticationType ?? throw new ArgumentNullException(nameof(authenticationType)); - Options = properties ?? throw new ArgumentNullException(nameof(properties)); - } - - public string Name { get; } - public string AuthenticationType { get; } - public BackOfficeExternalLoginProviderOptions Options { get; } - - public override bool Equals(object obj) - { - return Equals(obj as BackOfficeExternalLoginProvider); - } - - public bool Equals(BackOfficeExternalLoginProvider other) - { - return other != null && - Name == other.Name && - AuthenticationType == other.AuthenticationType; - } - - public override int GetHashCode() - { - return HashCode.Combine(Name, AuthenticationType); - } - } - } diff --git a/src/Umbraco.Web.BackOffice/Security/IBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web.BackOffice/Security/IBackOfficeTwoFactorOptions.cs new file mode 100644 index 0000000000..a05d71f3cb --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/IBackOfficeTwoFactorOptions.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Web.BackOffice.Security +{ + /// + /// Options used to control 2FA for the Umbraco back office + /// + public interface IBackOfficeTwoFactorOptions + { + /// + /// Returns the angular view for handling 2FA interaction + /// + /// + /// + string GetTwoFactorView(string username); + } + +} diff --git a/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs b/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs new file mode 100644 index 0000000000..bbc0b3e049 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/NoopBackOfficeTwoFactorOptions.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Web.BackOffice.Security +{ + public class NoopBackOfficeTwoFactorOptions : IBackOfficeTwoFactorOptions + { + public string GetTwoFactorView(string username) => null; + } + +} diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs index bfb80924d1..d8210556fd 100644 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs +++ b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs @@ -10,6 +10,7 @@ using Umbraco.Web.Security; namespace Umbraco.Web.Compose { + // TODO: Move to netcore public sealed class BackOfficeUserAuditEventsComponent : IComponent { private readonly IAuditService _auditService; @@ -27,14 +28,14 @@ namespace Umbraco.Web.Compose { //BackOfficeUserManager.AccountLocked += ; //BackOfficeUserManager.AccountUnlocked += ; - BackOfficeOwinUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; - BackOfficeOwinUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; - BackOfficeOwinUserManager.LoginFailed += OnLoginFailed; + //BackOfficeOwinUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; + //BackOfficeOwinUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; + //BackOfficeOwinUserManager.LoginFailed += OnLoginFailed; //BackOfficeUserManager.LoginRequiresVerification += ; - BackOfficeOwinUserManager.LoginSuccess += OnLoginSuccess; - BackOfficeOwinUserManager.LogoutSuccess += OnLogoutSuccess; - BackOfficeOwinUserManager.PasswordChanged += OnPasswordChanged; - BackOfficeOwinUserManager.PasswordReset += OnPasswordReset; + //BackOfficeOwinUserManager.LoginSuccess += OnLoginSuccess; + //BackOfficeOwinUserManager.LogoutSuccess += OnLogoutSuccess; + //BackOfficeOwinUserManager.PasswordChanged += OnPasswordChanged; + //BackOfficeOwinUserManager.PasswordReset += OnPasswordReset; //BackOfficeUserManager.ResetAccessFailedCount += ; } @@ -42,14 +43,14 @@ namespace Umbraco.Web.Compose { //BackOfficeUserManager.AccountLocked -= ; //BackOfficeUserManager.AccountUnlocked -= ; - BackOfficeOwinUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; - BackOfficeOwinUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; - BackOfficeOwinUserManager.LoginFailed -= OnLoginFailed; + //BackOfficeOwinUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; + //BackOfficeOwinUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; + //BackOfficeOwinUserManager.LoginFailed -= OnLoginFailed; //BackOfficeUserManager.LoginRequiresVerification -= ; - BackOfficeOwinUserManager.LoginSuccess -= OnLoginSuccess; - BackOfficeOwinUserManager.LogoutSuccess -= OnLogoutSuccess; - BackOfficeOwinUserManager.PasswordChanged -= OnPasswordChanged; - BackOfficeOwinUserManager.PasswordReset -= OnPasswordReset; + //BackOfficeOwinUserManager.LoginSuccess -= OnLoginSuccess; + //BackOfficeOwinUserManager.LogoutSuccess -= OnLogoutSuccess; + //BackOfficeOwinUserManager.PasswordChanged -= OnPasswordChanged; + //BackOfficeOwinUserManager.PasswordReset -= OnPasswordReset; //BackOfficeUserManager.ResetAccessFailedCount -= ; } diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs deleted file mode 100644 index be6a20e7cc..0000000000 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Extensions; -using Umbraco.Web.Models; -using Umbraco.Web.Security; -using IUser = Umbraco.Core.Models.Membership.IUser; - -//Migrated to .NET CORE -namespace Umbraco.Web.Editors -{ - internal class PasswordChanger - { - private readonly ILogger _logger; - - public PasswordChanger(ILogger logger) - { - _logger = logger; - } - - /// - /// Changes the password for a user based on the many different rules and config options - /// - /// The user performing the password save action - /// The user who's password is being changed - /// - /// - /// - public async Task> ChangePasswordWithIdentityAsync( - IUser currentUser, - IUser savingUser, - ChangingPasswordModel passwordModel, - BackOfficeOwinUserManager userMgr) - { - if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel)); - if (userMgr == null) throw new ArgumentNullException(nameof(userMgr)); - - if (passwordModel.NewPassword.IsNullOrWhiteSpace()) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); - } - - var backOfficeIdentityUser = await userMgr.FindByIdAsync(savingUser.Id.ToString()); - if (backOfficeIdentityUser == null) - { - //this really shouldn't ever happen... but just in case - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password could not be verified", new[] { "oldPassword" }) }); - } - - //Are we just changing another user's password? - if (passwordModel.OldPassword.IsNullOrWhiteSpace()) - { - //if it's the current user, the current user cannot reset their own password - if (currentUser.Username == savingUser.Username) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not allowed", new[] { "value" }) }); - } - - //if the current user has access to reset/manually change the password - if (currentUser.HasSectionAccess(Umbraco.Core.Constants.Applications.Users) == false) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("The current user is not authorized", new[] { "value" }) }); - } - - //ok, we should be able to reset it - var resetToken = await userMgr.GeneratePasswordResetTokenAsync(backOfficeIdentityUser); - - var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, passwordModel.NewPassword); - - if (resetResult.Succeeded == false) - { - var errors = resetResult.Errors.ToErrorMessage(); - _logger.LogWarning("Could not reset user password {PasswordErrors}", errors); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "value" }) }); - } - - return Attempt.Succeed(new PasswordChangedModel()); - } - - //is the old password correct? - var validateResult = await userMgr.CheckPasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword); - if (validateResult == false) - { - //no, fail with an error message for "oldPassword" - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Incorrect password", new[] { "oldPassword" }) }); - } - //can we change to the new password? - var changeResult = await userMgr.ChangePasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword, passwordModel.NewPassword); - if (changeResult.Succeeded == false) - { - //no, fail with error messages for "password" - var errors = changeResult.Errors.ToErrorMessage(); - _logger.LogWarning("Could not change user password {PasswordErrors}", errors); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "password" }) }); - } - return Attempt.Succeed(new PasswordChangedModel()); - } - - } -} diff --git a/src/Umbraco.Web/OwinExtensions.cs b/src/Umbraco.Web/OwinExtensions.cs index 62f1643074..801ceae191 100644 --- a/src/Umbraco.Web/OwinExtensions.cs +++ b/src/Umbraco.Web/OwinExtensions.cs @@ -52,25 +52,6 @@ namespace Umbraco.Web return ctx == null ? Attempt.Fail() : Attempt.Succeed(ctx); } - - /// - /// Gets the back office user manager out of OWIN - /// - /// - /// - /// - /// This is required because to extract the user manager we need to user a custom service since owin only deals in generics and - /// developers could register their own user manager types - /// - public static BackOfficeOwinUserManager GetBackOfficeUserManager(this IOwinContext owinContext) - { - var marker = owinContext.Get(BackOfficeOwinUserManager.OwinMarkerKey) - ?? throw new NullReferenceException($"No {typeof (IBackOfficeUserManagerMarker)}, i.e. no Umbraco back-office, has been registered with Owin."); - - return marker.GetManager(owinContext) - ?? throw new NullReferenceException($"Could not resolve an instance of {typeof (BackOfficeOwinUserManager)} from the {typeof (IOwinContext)}."); - } - /// /// Adapted from Microsoft.AspNet.Identity.Owin.OwinContextExtensions /// diff --git a/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs b/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs deleted file mode 100644 index 2f5e858687..0000000000 --- a/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Claims; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Owin.Security.DataProtection; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Mapping; -using Umbraco.Core.Services; -using Umbraco.Net; - -namespace Umbraco.Web.Security -{ - // TODO: Most of this is already migrated to netcore, there's probably not much more to go and then we can complete remove it - public class BackOfficeOwinUserManager : BackOfficeUserManager - { - public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker"; - - public BackOfficeOwinUserManager( - IOptions passwordConfiguration, - IIpResolver ipResolver, - IUserStore store, - IOptions optionsAccessor, - IEnumerable> userValidators, - IEnumerable> passwordValidators, - BackOfficeLookupNormalizer keyNormalizer, - BackOfficeIdentityErrorDescriber errors, - IDataProtectionProvider dataProtectionProvider, - ILogger> logger) - : base(ipResolver, store, optionsAccessor, null, userValidators, passwordValidators, keyNormalizer, errors, null, logger, passwordConfiguration) - { - PasswordConfiguration = passwordConfiguration.Value; - InitUserManager(this, dataProtectionProvider); - } - - #region Static Create methods - - /// - /// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager - /// - public static BackOfficeOwinUserManager Create( - IUserService userService, - IEntityService entityService, - IExternalLoginService externalLoginService, - IOptions globalSettings, - UmbracoMapper mapper, - IOptions passwordConfiguration, - IIpResolver ipResolver, - BackOfficeIdentityErrorDescriber errors, - IDataProtectionProvider dataProtectionProvider, - ILogger> logger) - { - var store = new BackOfficeUserStore(userService, entityService, externalLoginService, globalSettings, mapper); - - return Create( - passwordConfiguration, - ipResolver, - store, - errors, - dataProtectionProvider, - logger); - } - - /// - /// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance - /// - public static BackOfficeOwinUserManager Create( - IOptions passwordConfiguration, - IIpResolver ipResolver, - IUserStore customUserStore, - BackOfficeIdentityErrorDescriber errors, - IDataProtectionProvider dataProtectionProvider, - ILogger> logger) - { - var options = new BackOfficeIdentityOptions(); - - // Configure validation logic for usernames - var userValidators = new List> { new BackOfficeUserValidator() }; - options.User.RequireUniqueEmail = true; - - // Configure validation logic for passwords - var passwordValidators = new List> { new PasswordValidator() }; - options.Password.RequiredLength = passwordConfiguration.Value.RequiredLength; - options.Password.RequireNonAlphanumeric = passwordConfiguration.Value.RequireNonLetterOrDigit; - options.Password.RequireDigit = passwordConfiguration.Value.RequireDigit; - options.Password.RequireLowercase = passwordConfiguration.Value.RequireLowercase; - options.Password.RequireUppercase = passwordConfiguration.Value.RequireUppercase; - - // Ensure Umbraco security stamp claim type is used - options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier; - options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name; - options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role; - options.ClaimsIdentity.SecurityStampClaimType = Constants.Security.SecurityStampClaimType; - - options.Lockout.AllowedForNewUsers = true; - options.Lockout.MaxFailedAccessAttempts = passwordConfiguration.Value.MaxFailedAccessAttemptsBeforeLockout; - //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked - // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are - // locked out or not. - options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(30); - - return new BackOfficeOwinUserManager( - passwordConfiguration, - ipResolver, - customUserStore, - new OptionsWrapper(options), - userValidators, - passwordValidators, - new BackOfficeLookupNormalizer(), - errors, - dataProtectionProvider, - logger); - } - - #endregion - - protected void InitUserManager(BackOfficeOwinUserManager manager, IDataProtectionProvider dataProtectionProvider) - { - // use a custom hasher based on our membership provider - PasswordHasher = GetDefaultPasswordHasher(PasswordConfiguration); - - // set OWIN data protection token provider as default - if (dataProtectionProvider != null) - { - manager.RegisterTokenProvider( - TokenOptions.DefaultProvider, - new OwinDataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")) - { - TokenLifespan = TimeSpan.FromDays(3) - }); - } - - // register ASP.NET Core Identity token providers - manager.RegisterTokenProvider(TokenOptions.DefaultEmailProvider, new EmailTokenProvider()); - manager.RegisterTokenProvider(TokenOptions.DefaultPhoneProvider, new PhoneNumberTokenProvider()); - manager.RegisterTokenProvider(TokenOptions.DefaultAuthenticatorProvider, new AuthenticatorTokenProvider()); - } - } -} diff --git a/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs b/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs deleted file mode 100644 index dd657b48bf..0000000000 --- a/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using Microsoft.Owin; -using Umbraco.Core.BackOffice; - -namespace Umbraco.Web.Security -{ - /// - /// This class is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked - /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this - /// class to resolve the 'real' type of the registered user manager - /// - /// - /// - internal class BackOfficeUserManagerMarker : IBackOfficeUserManagerMarker - where TManager : BackOfficeUserManager - where TUser : BackOfficeIdentityUser - { - public BackOfficeOwinUserManager GetManager(IOwinContext owin) - { - var mgr = owin.Get() as BackOfficeOwinUserManager; - if (mgr == null) throw new InvalidOperationException("Could not cast the registered back office user of type " + typeof(TManager) + " to " + typeof(BackOfficeUserManager)); - return mgr; - } - } -} diff --git a/src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs b/src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs deleted file mode 100644 index 16c0666c9c..0000000000 --- a/src/Umbraco.Web/Security/IBackOfficeUserManagerMarker.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Owin; - -namespace Umbraco.Web.Security -{ - /// - /// This interface is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked - /// with regular 'object' then we wouldn't have to use this work around but because of that we have to use this - /// class to resolve the 'real' type of the registered user manager - /// - internal interface IBackOfficeUserManagerMarker - { - BackOfficeOwinUserManager GetManager(IOwinContext owin); - } -} diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 1e26782d4a..65251320ca 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -18,9 +18,7 @@ using Umbraco.Web.Security.Providers; namespace Umbraco.Web.Security { - /// - /// A helper class for handling Members - /// + // MIGRATED TO NETCORE public class MembershipHelper { private readonly MembersMembershipProvider _membershipProvider; @@ -680,37 +678,6 @@ namespace Umbraco.Web.Security return allowAction; } - /// - /// Changes password for a member/user given the membership provider name and the password change model - /// - /// - /// - /// - /// - public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, string membershipProviderName) - { - var provider = Membership.Providers[membershipProviderName]; - if (provider == null) - { - throw new InvalidOperationException("Could not find provider with name " + membershipProviderName); - } - - return ChangePassword(username, passwordModel, provider); - } - - /// - /// Changes password for a member/user given the membership provider and the password change model - /// - /// - /// - /// - /// - public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) - { - var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger()); - return ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider); - } - /// /// Updates a membership user with all of it's writable properties /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1b4ec244d8..7a2ec6e0f7 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -137,7 +137,6 @@ - @@ -164,8 +163,6 @@ - - @@ -181,7 +178,6 @@ - diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index f744213276..37e552d818 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -31,8 +31,6 @@ namespace Umbraco.Web.WebApi [EnableDetailedErrors] public abstract class UmbracoAuthorizedApiController : UmbracoApiController { - private BackOfficeOwinUserManager _userManager; - protected UmbracoAuthorizedApiController() { } @@ -42,10 +40,5 @@ namespace Umbraco.Web.WebApi { } - /// - /// Gets the user manager. - /// - protected BackOfficeOwinUserManager UserManager - => _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager()); } }