Fixes up a bunch of TODOs moves user manager to the back office project so we have acess to necessary services, splits apart files, removes old code, starts implementing the 2fa stuff

This commit is contained in:
Shannon
2020-12-01 17:24:23 +11:00
parent 927335149d
commit 20b4f55664
25 changed files with 307 additions and 594 deletions

View File

@@ -156,7 +156,7 @@ namespace Umbraco.Core.BackOffice
/// <param name="user"/>
/// <param name="cancellationToken"></param>
/// <returns/>
public async Task<IdentityResult> UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
public Task<IdentityResult> 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);
}
/// <summary>

View File

@@ -311,13 +311,14 @@ namespace Umbraco.Core.BackOffice
/// </remarks>
Task<string> 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; }
}
}

View File

@@ -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

View File

@@ -111,7 +111,6 @@ namespace Umbraco.Tests.Testing.TestingTests
var memberService = Mock.Of<IMemberService>();
var memberTypeService = Mock.Of<IMemberTypeService>();
var membershipProvider = new MembersMembershipProvider(memberService, memberTypeService, Mock.Of<IUmbracoVersion>(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver());
var membershipHelper = new MembershipHelper(Mock.Of<IHttpContextAccessor>(), Mock.Of<IPublishedMemberCache>(), membershipProvider, Mock.Of<RoleProvider>(), memberService, memberTypeService, Mock.Of<IPublicAccessService>(), AppCaches.Disabled, NullLoggerFactory.Instance, ShortStringHelper, Mock.Of<IEntityService>());
var umbracoMapper = new UmbracoMapper(new MapDefinitionCollection(new[] { Mock.Of<IMapDefinition>() }));
var umbracoApiController = new FakeUmbracoApiController(new GlobalSettings(), Mock.Of<IUmbracoContextAccessor>(), Mock.Of<IBackOfficeSecurityAccessor>(), Mock.Of<ISqlContext>(), ServiceContext.CreatePartial(), AppCaches.NoCache, profilingLogger , Mock.Of<IRuntimeState>(), umbracoMapper, Mock.Of<IPublishedUrlProvider>());

View File

@@ -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;
}
/// <summary>
@@ -303,7 +307,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// <returns></returns>
[SetAngularAntiForgeryTokens]
[DenyLocalLoginAuthorization]
public async Task<UserDetail> PostLogin(LoginModel loginModel)
public async Task<ActionResult<UserDetail>> 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;
}

View File

@@ -63,6 +63,7 @@ namespace Umbraco.Extensions
services.TryAddScoped<BackOfficeIdentityErrorDescriber>();
services.TryAddScoped<IIpResolver, AspNetCoreIpResolver>();
services.TryAddSingleton<IBackOfficeExternalLoginProviders, BackOfficeExternalLoginProviders>();
services.TryAddSingleton<IBackOfficeTwoFactorOptions, NoopBackOfficeTwoFactorOptions>();
/*
* IdentityBuilderExtensions.AddUserManager adds UserManager<BackOfficeIdentityUser> to service collection

View File

@@ -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<BackOfficeExternalLoginsBuilder> builder)
{
builder(new BackOfficeExternalLoginsBuilder(umbracoBuilder.Services));
return umbracoBuilder;
}
}
}

View File

@@ -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
{
/// <summary>
/// Custom <see cref="AuthenticationBuilder"/> used to associate external logins with umbraco external login options
/// </summary>
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;
}
/// <summary>
/// Overridden to track the final authenticationScheme being registered for the external login
/// </summary>
/// <typeparam name="TOptions"></typeparam>
/// <typeparam name="THandler"></typeparam>
/// <param name="authenticationScheme"></param>
/// <param name="displayName"></param>
/// <param name="configureOptions"></param>
/// <returns></returns>
public override AuthenticationBuilder AddRemoteScheme<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> 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<IPostConfigureOptions<TOptions>, EnsureBackOfficeScheme<TOptions>>());
return base.AddRemoteScheme<TOptions, THandler>(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<TOptions> : IPostConfigureOptions<TOptions> where TOptions : RemoteAuthenticationOptions
{
public void PostConfigure(string name, TOptions options)
{
options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType;
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
namespace Umbraco.Web.BackOffice.Security
{
/// <summary>
/// An external login (OAuth) provider for the back office
/// </summary>
public class BackOfficeExternalLoginProvider : IEquatable<BackOfficeExternalLoginProvider>
{
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);
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Web.BackOffice.Security
{
public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders
{
public BackOfficeExternalLoginProviders(IEnumerable<BackOfficeExternalLoginProvider> externalLogins)
{
_externalLogins = externalLogins;
}
private readonly IEnumerable<BackOfficeExternalLoginProvider> _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<BackOfficeExternalLoginProvider> GetBackOfficeProviders()
{
return _externalLogins;
}
public bool HasDenyLocalLogin()
{
var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList();
return found.Count > 0;
}
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Umbraco.Web.BackOffice.Security
{
/// <summary>
/// Used to add back office login providers
/// </summary>
public class BackOfficeExternalLoginsBuilder
{
public BackOfficeExternalLoginsBuilder(IServiceCollection services)
{
_services = services;
}
private readonly IServiceCollection _services;
/// <summary>
/// Add a back office login provider with options
/// </summary>
/// <param name="loginProviderOptions"></param>
/// <param name="build"></param>
/// <returns></returns>
public BackOfficeExternalLoginsBuilder AddBackOfficeLogin(
BackOfficeExternalLoginProviderOptions loginProviderOptions,
Action<BackOfficeAuthenticationBuilder> build)
{
build(new BackOfficeAuthenticationBuilder(_services, loginProviderOptions));
return this;
}
}
}

View File

@@ -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)

View File

@@ -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<BackOfficeIdentityUser>, IBackOfficeUserManager
{
@@ -28,9 +32,10 @@ namespace Umbraco.Core.BackOffice
BackOfficeLookupNormalizer keyNormalizer,
BackOfficeIdentityErrorDescriber errors,
IServiceProvider services,
IHttpContextAccessor httpContextAccessor,
ILogger<UserManager<BackOfficeIdentityUser>> logger,
IOptions<UserPasswordConfigurationSettings> 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<UserManager<T>> logger,
IOptions<UserPasswordConfigurationSettings> 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<T>;
//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<int?>() ?? Constants.Security.SuperUserId;
var currentUserId = umbIdentity?.GetUserId<int?>() ?? 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<IdentityAuditEventArgs> AccountLocked;
public static event EventHandler<IdentityAuditEventArgs> AccountUnlocked;
public static event EventHandler<IdentityAuditEventArgs> ForgotPasswordRequested;
public static event EventHandler<IdentityAuditEventArgs> ForgotPasswordChangedSuccess;
public static event EventHandler<IdentityAuditEventArgs> LoginFailed;
public static event EventHandler<IdentityAuditEventArgs> LoginRequiresVerification;
public static event EventHandler<IdentityAuditEventArgs> LoginSuccess;
public static event EventHandler<SignOutAuditEventArgs> LogoutSuccess;
public static event EventHandler<IdentityAuditEventArgs> PasswordChanged;
public static event EventHandler<IdentityAuditEventArgs> PasswordReset;
public static event EventHandler<IdentityAuditEventArgs> ResetAccessFailedCount;
public event EventHandler<IdentityAuditEventArgs> AccountLocked;
public event EventHandler<IdentityAuditEventArgs> AccountUnlocked;
public event EventHandler<IdentityAuditEventArgs> ForgotPasswordRequested;
public event EventHandler<IdentityAuditEventArgs> ForgotPasswordChangedSuccess;
public event EventHandler<IdentityAuditEventArgs> LoginFailed;
public event EventHandler<IdentityAuditEventArgs> LoginRequiresVerification;
public event EventHandler<IdentityAuditEventArgs> LoginSuccess;
public event EventHandler<SignOutAuditEventArgs> LogoutSuccess;
public event EventHandler<IdentityAuditEventArgs> PasswordChanged;
public event EventHandler<IdentityAuditEventArgs> PasswordReset;
public event EventHandler<IdentityAuditEventArgs> ResetAccessFailedCount;
/// <summary>
/// Raised when a user is invited

View File

@@ -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
{
/// <summary>
/// Custom <see cref="AuthenticationBuilder"/> used to associate external logins with umbraco external login options
/// </summary>
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;
}
/// <summary>
/// Overridden to track the final authenticationScheme being registered for the external login
/// </summary>
/// <typeparam name="TOptions"></typeparam>
/// <typeparam name="THandler"></typeparam>
/// <param name="authenticationScheme"></param>
/// <param name="displayName"></param>
/// <param name="configureOptions"></param>
/// <returns></returns>
public override AuthenticationBuilder AddRemoteScheme<TOptions, THandler>(string authenticationScheme, string displayName, Action<TOptions> 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<IPostConfigureOptions<TOptions>, EnsureBackOfficeScheme<TOptions>>());
return base.AddRemoteScheme<TOptions, THandler>(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<TOptions> : IPostConfigureOptions<TOptions> where TOptions : RemoteAuthenticationOptions
{
public void PostConfigure(string name, TOptions options)
{
options.SignInScheme = Constants.Security.BackOfficeExternalAuthenticationType;
}
}
}
/// <summary>
/// Used to add back office login providers
/// Service to return <see cref="BackOfficeExternalLoginProvider"/> instances
/// </summary>
public class BackOfficeExternalLoginsBuilder
{
public BackOfficeExternalLoginsBuilder(IServiceCollection services)
{
_services = services;
}
private readonly IServiceCollection _services;
/// <summary>
/// Add a back office login provider with options
/// </summary>
/// <param name="loginProviderOptions"></param>
/// <param name="build"></param>
/// <returns></returns>
public BackOfficeExternalLoginsBuilder AddBackOfficeLogin(
BackOfficeExternalLoginProviderOptions loginProviderOptions,
Action<BackOfficeAuthenticationBuilder> build)
{
build(new BackOfficeAuthenticationBuilder(_services, loginProviderOptions));
return this;
}
}
public static class AuthenticationBuilderExtensions
{
public static IUmbracoBuilder AddBackOfficeExternalLogins(this IUmbracoBuilder umbracoBuilder, Action<BackOfficeExternalLoginsBuilder> 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<BackOfficeExternalLoginProvider> externalLogins)
{
_externalLogins = externalLogins;
}
private readonly IEnumerable<BackOfficeExternalLoginProvider> _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<BackOfficeExternalLoginProvider> GetBackOfficeProviders()
{
return _externalLogins;
}
public bool HasDenyLocalLogin()
{
var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList();
return found.Count > 0;
}
}
public class BackOfficeExternalLoginProvider : IEquatable<BackOfficeExternalLoginProvider>
{
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);
}
}
}

View File

@@ -0,0 +1,16 @@
namespace Umbraco.Web.BackOffice.Security
{
/// <summary>
/// Options used to control 2FA for the Umbraco back office
/// </summary>
public interface IBackOfficeTwoFactorOptions
{
/// <summary>
/// Returns the angular view for handling 2FA interaction
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
string GetTwoFactorView(string username);
}
}

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Web.BackOffice.Security
{
public class NoopBackOfficeTwoFactorOptions : IBackOfficeTwoFactorOptions
{
public string GetTwoFactorView(string username) => null;
}
}

View File

@@ -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 -= ;
}

View File

@@ -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<PasswordChanger> _logger;
public PasswordChanger(ILogger<PasswordChanger> logger)
{
_logger = logger;
}
/// <summary>
/// Changes the password for a user based on the many different rules and config options
/// </summary>
/// <param name="currentUser">The user performing the password save action</param>
/// <param name="savingUser">The user who's password is being changed</param>
/// <param name="passwordModel"></param>
/// <param name="userMgr"></param>
/// <returns></returns>
public async Task<Attempt<PasswordChangedModel>> 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());
}
}
}

View File

@@ -52,25 +52,6 @@ namespace Umbraco.Web
return ctx == null ? Attempt<HttpContextBase>.Fail() : Attempt.Succeed(ctx);
}
/// <summary>
/// Gets the back office user manager out of OWIN
/// </summary>
/// <param name="owinContext"></param>
/// <returns></returns>
/// <remarks>
/// 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
/// </remarks>
public static BackOfficeOwinUserManager GetBackOfficeUserManager(this IOwinContext owinContext)
{
var marker = owinContext.Get<IBackOfficeUserManagerMarker>(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)}.");
}
/// <summary>
/// Adapted from Microsoft.AspNet.Identity.Owin.OwinContextExtensions
/// </summary>

View File

@@ -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<UserPasswordConfigurationSettings> passwordConfiguration,
IIpResolver ipResolver,
IUserStore<BackOfficeIdentityUser> store,
IOptions<BackOfficeIdentityOptions> optionsAccessor,
IEnumerable<IUserValidator<BackOfficeIdentityUser>> userValidators,
IEnumerable<IPasswordValidator<BackOfficeIdentityUser>> passwordValidators,
BackOfficeLookupNormalizer keyNormalizer,
BackOfficeIdentityErrorDescriber errors,
IDataProtectionProvider dataProtectionProvider,
ILogger<UserManager<BackOfficeIdentityUser>> logger)
: base(ipResolver, store, optionsAccessor, null, userValidators, passwordValidators, keyNormalizer, errors, null, logger, passwordConfiguration)
{
PasswordConfiguration = passwordConfiguration.Value;
InitUserManager(this, dataProtectionProvider);
}
#region Static Create methods
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
/// </summary>
public static BackOfficeOwinUserManager Create(
IUserService userService,
IEntityService entityService,
IExternalLoginService externalLoginService,
IOptions<GlobalSettings> globalSettings,
UmbracoMapper mapper,
IOptions<UserPasswordConfigurationSettings> passwordConfiguration,
IIpResolver ipResolver,
BackOfficeIdentityErrorDescriber errors,
IDataProtectionProvider dataProtectionProvider,
ILogger<UserManager<BackOfficeIdentityUser>> logger)
{
var store = new BackOfficeUserStore(userService, entityService, externalLoginService, globalSettings, mapper);
return Create(
passwordConfiguration,
ipResolver,
store,
errors,
dataProtectionProvider,
logger);
}
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance
/// </summary>
public static BackOfficeOwinUserManager Create(
IOptions<UserPasswordConfigurationSettings> passwordConfiguration,
IIpResolver ipResolver,
IUserStore<BackOfficeIdentityUser> customUserStore,
BackOfficeIdentityErrorDescriber errors,
IDataProtectionProvider dataProtectionProvider,
ILogger<UserManager<BackOfficeIdentityUser>> logger)
{
var options = new BackOfficeIdentityOptions();
// Configure validation logic for usernames
var userValidators = new List<UserValidator<BackOfficeIdentityUser>> { new BackOfficeUserValidator<BackOfficeIdentityUser>() };
options.User.RequireUniqueEmail = true;
// Configure validation logic for passwords
var passwordValidators = new List<IPasswordValidator<BackOfficeIdentityUser>> { new PasswordValidator<BackOfficeIdentityUser>() };
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<BackOfficeIdentityOptions>(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<BackOfficeIdentityUser>(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = TimeSpan.FromDays(3)
});
}
// register ASP.NET Core Identity token providers
manager.RegisterTokenProvider(TokenOptions.DefaultEmailProvider, new EmailTokenProvider<BackOfficeIdentityUser>());
manager.RegisterTokenProvider(TokenOptions.DefaultPhoneProvider, new PhoneNumberTokenProvider<BackOfficeIdentityUser>());
manager.RegisterTokenProvider(TokenOptions.DefaultAuthenticatorProvider, new AuthenticatorTokenProvider<BackOfficeIdentityUser>());
}
}
}

View File

@@ -1,25 +0,0 @@
using System;
using Microsoft.Owin;
using Umbraco.Core.BackOffice;
namespace Umbraco.Web.Security
{
/// <summary>
/// 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
/// </summary>
/// <typeparam name="TManager"></typeparam>
/// <typeparam name="TUser"></typeparam>
internal class BackOfficeUserManagerMarker<TManager, TUser> : IBackOfficeUserManagerMarker
where TManager : BackOfficeUserManager<TUser>
where TUser : BackOfficeIdentityUser
{
public BackOfficeOwinUserManager GetManager(IOwinContext owin)
{
var mgr = owin.Get<TManager>() as BackOfficeOwinUserManager;
if (mgr == null) throw new InvalidOperationException("Could not cast the registered back office user of type " + typeof(TManager) + " to " + typeof(BackOfficeUserManager<BackOfficeIdentityUser>));
return mgr;
}
}
}

View File

@@ -1,14 +0,0 @@
using Microsoft.Owin;
namespace Umbraco.Web.Security
{
/// <summary>
/// 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
/// </summary>
internal interface IBackOfficeUserManagerMarker
{
BackOfficeOwinUserManager GetManager(IOwinContext owin);
}
}

View File

@@ -18,9 +18,7 @@ using Umbraco.Web.Security.Providers;
namespace Umbraco.Web.Security
{
/// <summary>
/// A helper class for handling Members
/// </summary>
// MIGRATED TO NETCORE
public class MembershipHelper
{
private readonly MembersMembershipProvider _membershipProvider;
@@ -680,37 +678,6 @@ namespace Umbraco.Web.Security
return allowAction;
}
/// <summary>
/// Changes password for a member/user given the membership provider name and the password change model
/// </summary>
/// <param name="username"></param>
/// <param name="passwordModel"></param>
/// <param name="membershipProviderName"></param>
/// <returns></returns>
public virtual Attempt<PasswordChangedModel> 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);
}
/// <summary>
/// Changes password for a member/user given the membership provider and the password change model
/// </summary>
/// <param name="username"></param>
/// <param name="passwordModel"></param>
/// <param name="membershipProvider"></param>
/// <returns></returns>
public virtual Attempt<PasswordChangedModel> ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider)
{
var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger<PasswordChanger>());
return ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider);
}
/// <summary>
/// Updates a membership user with all of it's writable properties
/// </summary>

View File

@@ -137,7 +137,6 @@
<Compile Include="AspNet\AspNetUmbracoApplicationLifetime.cs" />
<Compile Include="Compose\BackOfficeUserAuditEventsComponent.cs" />
<Compile Include="Compose\BackOfficeUserAuditEventsComposer.cs" />
<Compile Include="Editors\PasswordChanger.cs" />
<Compile Include="HttpContextExtensions.cs" />
<Compile Include="Macros\MemberUserKeyProvider.cs" />
<Compile Include="Mvc\IRenderController.cs" />
@@ -164,8 +163,6 @@
<Compile Include="RoutableDocumentFilter.cs" />
<Compile Include="Runtime\AspNetUmbracoBootPermissionChecker.cs" />
<Compile Include="Security\BackOfficeSignInManager.cs" />
<Compile Include="Security\BackOfficeOwinUserManager.cs" />
<Compile Include="Security\BackOfficeUserManagerMarker.cs" />
<Compile Include="WebAssets\CDF\ClientDependencyComponent.cs" />
<Compile Include="WebAssets\CDF\ClientDependencyComposer.cs" />
<Compile Include="Security\MembershipProviderBase.cs" />
@@ -181,7 +178,6 @@
<Compile Include="Runtime\WebInitialComposer.cs" />
<Compile Include="Security\ActiveDirectoryBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\BackOfficeUserPasswordCheckerResult.cs" />
<Compile Include="Security\IBackOfficeUserManagerMarker.cs" />
<Compile Include="Security\IBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\IdentityAuditEventArgs.cs" />
<Compile Include="UmbracoBuilderExtensions.cs" />

View File

@@ -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
{
}
/// <summary>
/// Gets the user manager.
/// </summary>
protected BackOfficeOwinUserManager UserManager
=> _userManager ?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager());
}
}