Compiling with minimal Microsoft.AspNet.Identity references

This commit is contained in:
Scott Brady
2020-03-16 13:53:03 +00:00
parent 2e7e6bc4a9
commit db87a9dd40
28 changed files with 283 additions and 2171 deletions
@@ -24,17 +24,17 @@ namespace Umbraco.Web.Compose
public void Initialize()
{
//BackOfficeUserManager.AccountLocked += ;
//BackOfficeUserManager.AccountUnlocked += ;
BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest;
BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange;
BackOfficeUserManager.LoginFailed += OnLoginFailed;
//BackOfficeUserManager.LoginRequiresVerification += ;
BackOfficeUserManager.LoginSuccess += OnLoginSuccess;
BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess;
BackOfficeUserManager.PasswordChanged += OnPasswordChanged;
BackOfficeUserManager.PasswordReset += OnPasswordReset;
//BackOfficeUserManager.ResetAccessFailedCount += ;
//BackOfficeUserManager2.AccountLocked += ;
//BackOfficeUserManager2.AccountUnlocked += ;
BackOfficeUserManager2.ForgotPasswordRequested += OnForgotPasswordRequest;
BackOfficeUserManager2.ForgotPasswordChangedSuccess += OnForgotPasswordChange;
BackOfficeUserManager2.LoginFailed += OnLoginFailed;
//BackOfficeUserManager2.LoginRequiresVerification += ;
BackOfficeUserManager2.LoginSuccess += OnLoginSuccess;
BackOfficeUserManager2.LogoutSuccess += OnLogoutSuccess;
BackOfficeUserManager2.PasswordChanged += OnPasswordChanged;
BackOfficeUserManager2.PasswordReset += OnPasswordReset;
//BackOfficeUserManager2.ResetAccessFailedCount += ;
}
public void Terminate()
@@ -8,8 +8,7 @@ using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.AspNetCore.Identity;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Models;
@@ -41,8 +40,8 @@ namespace Umbraco.Web.Editors
[IsBackOffice]
public class AuthenticationController : UmbracoApiController
{
private BackOfficeUserManager<BackOfficeIdentityUser> _userManager;
private BackOfficeSignInManager _signInManager;
private BackOfficeUserManager2<BackOfficeIdentityUser> _userManager;
private BackOfficeSignInManager2 _signInManager;
private readonly IUserPasswordConfiguration _passwordConfiguration;
private readonly IRuntimeState _runtimeState;
private readonly IUmbracoSettingsSection _umbracoSettingsSection;
@@ -70,11 +69,11 @@ namespace Umbraco.Web.Editors
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
}
protected BackOfficeUserManager<BackOfficeIdentityUser> UserManager => _userManager
?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager());
protected BackOfficeUserManager2<BackOfficeIdentityUser> UserManager => _userManager
?? (_userManager = TryGetOwinContext().Result.GetBackOfficeUserManager2());
protected BackOfficeSignInManager SignInManager => _signInManager
?? (_signInManager = TryGetOwinContext().Result.GetBackOfficeSignInManager());
protected BackOfficeSignInManager2 SignInManager => _signInManager
?? (_signInManager = TryGetOwinContext().Result.GetBackOfficeSignInManager2());
/// <summary>
/// Returns the configuration for the backoffice user membership provider - used to configure the change password dialog
@@ -105,11 +104,11 @@ namespace Umbraco.Web.Editors
if (decoded.IsNullOrWhiteSpace())
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
var identityUser = await UserManager.FindByIdAsync(id);
var identityUser = await UserManager.FindByIdAsync(id.ToString());
if (identityUser == null)
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
var result = await UserManager.ConfirmEmailAsync(id, decoded);
var result = await UserManager.ConfirmEmailAsync(identityUser, decoded);
if (result.Succeeded == false)
{
@@ -131,13 +130,15 @@ namespace Umbraco.Web.Editors
[ValidateAngularAntiForgeryToken]
public async Task<HttpResponseMessage> PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var result = await UserManager.RemoveLoginAsync(
User.Identity.GetUserId<int>(),
new UserLoginInfo(unlinkLoginModel.LoginProvider, unlinkLoginModel.ProviderKey));
user,
unlinkLoginModel.LoginProvider,
unlinkLoginModel.ProviderKey);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId<int>());
await SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false);
return Request.CreateResponse(HttpStatusCode.OK);
}
@@ -225,7 +226,7 @@ namespace Umbraco.Web.Editors
[ValidateAngularAntiForgeryToken]
public async Task<Dictionary<string, string>> GetCurrentUserLinkedLogins()
{
var identityUser = await UserManager.FindByIdAsync(UmbracoContext.Security.GetUserId().ResultOr(0));
var identityUser = await UserManager.FindByIdAsync(UmbracoContext.Security.GetUserId().ResultOr(0).ToString());
return identityUser.Logins.ToDictionary(x => x.LoginProvider, x => x.ProviderKey);
}
@@ -244,61 +245,58 @@ namespace Umbraco.Web.Editors
var result = await SignInManager.PasswordSignInAsync(
loginModel.Username, loginModel.Password, isPersistent: true, shouldLockout: true);
switch (result)
if (result.Succeeded)
{
case SignInStatus.Success:
// get the user
var user = Services.UserService.GetByUsername(loginModel.Username);
UserManager.RaiseLoginSuccessEvent(user.Id);
// get the user
var user = Services.UserService.GetByUsername(loginModel.Username);
UserManager.RaiseLoginSuccessEvent(user.Id);
return SetPrincipalAndReturnUserDetail(user, owinContext.Request.User);
case SignInStatus.RequiresVerification:
var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions;
if (twofactorOptions == null)
{
throw new HttpResponseException(
Request.CreateErrorResponse(
HttpStatusCode.BadRequest,
"UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions)));
}
var twofactorView = twofactorOptions.GetTwoFactorView(
owinContext,
UmbracoContext,
loginModel.Username);
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(attemptedUser.Id);
return verifyResponse;
case SignInStatus.LockedOut:
case SignInStatus.Failure:
default:
// return BadRequest (400), we don't want to return a 401 because that get's intercepted
// by our angular helper because it thinks that we need to re-perform the request once we are
// authorized and we don't want to return a 403 because angular will show a warning message indicating
// that the user doesn't have access to perform this function, we just want to return a normal invalid message.
throw new HttpResponseException(HttpStatusCode.BadRequest);
return SetPrincipalAndReturnUserDetail(user, owinContext.Request.User);
}
if (result.RequiresTwoFactor)
{
var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions;
if (twofactorOptions == null)
{
throw new HttpResponseException(
Request.CreateErrorResponse(
HttpStatusCode.BadRequest,
"UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions)));
}
var twofactorView = twofactorOptions.GetTwoFactorView(
owinContext,
UmbracoContext,
loginModel.Username);
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(attemptedUser.Id);
return verifyResponse;
}
// return BadRequest (400), we don't want to return a 401 because that get's intercepted
// by our angular helper because it thinks that we need to re-perform the request once we are
// authorized and we don't want to return a 403 because angular will show a warning message indicating
// that the user doesn't have access to perform this function, we just want to return a normal invalid message.
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
/// <summary>
@@ -315,13 +313,13 @@ namespace Umbraco.Web.Editors
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
var identityUser = await SignInManager.UserManager.FindByEmailAsync(model.Email);
var identityUser = await UserManager.FindByEmailAsync(model.Email);
if (identityUser != null)
{
var user = Services.UserService.GetByEmail(model.Email);
if (user != null)
{
var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id);
var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser);
var callbackUrl = ConstructCallbackUrl(identityUser.Id, code);
var message = Services.TextService.Localize("resetPasswordEmailCopyFormat",
@@ -329,11 +327,12 @@ namespace Umbraco.Web.Editors
UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings),
new[] { identityUser.UserName, callbackUrl });
await UserManager.SendEmailAsync(identityUser.Id,
// TODO: SB: SendEmailAsync
/*await UserManager.SendEmailAsync(identityUser.Id,
Services.TextService.Localize("login/resetPasswordEmailCopySubject",
// Ensure the culture of the found user is used for the email!
UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)),
message);
message);*/
UserManager.RaiseForgotPasswordRequestedEvent(user.Id);
}
@@ -355,7 +354,10 @@ namespace Umbraco.Web.Editors
Logger.Warn<AuthenticationController>("Get2FAProviders :: No verified user found, returning 404");
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId);
var user = await UserManager.FindByIdAsync(userId.ToString());
var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(user);
return userFactors;
}
@@ -399,18 +401,19 @@ namespace Umbraco.Web.Editors
var owinContext = TryGetOwinContext().Result;
var user = Services.UserService.GetByUsername(userName);
switch (result)
if (result.Succeeded)
{
case SignInStatus.Success:
UserManager.RaiseLoginSuccessEvent(user.Id);
return SetPrincipalAndReturnUserDetail(user, owinContext.Request.User);
case SignInStatus.LockedOut:
UserManager.RaiseAccountLockedEvent(user.Id);
return Request.CreateValidationErrorResponse("User is locked out");
case SignInStatus.Failure:
default:
return Request.CreateValidationErrorResponse("Invalid code");
UserManager.RaiseLoginSuccessEvent(user.Id);
return SetPrincipalAndReturnUserDetail(user, owinContext.Request.User);
}
if (result.IsLockedOut)
{
UserManager.RaiseAccountLockedEvent(user.Id);
return Request.CreateValidationErrorResponse("User is locked out");
}
return Request.CreateValidationErrorResponse("Invalid code");
}
/// <summary>
@@ -420,22 +423,24 @@ namespace Umbraco.Web.Editors
[SetAngularAntiForgeryTokens]
public async Task<HttpResponseMessage> PostSetPassword(SetPasswordModel model)
{
var result = await UserManager.ResetPasswordAsync(model.UserId, model.ResetCode, model.Password);
var identityUser = await UserManager.FindByIdAsync(model.UserId.ToString());
var result = await UserManager.ResetPasswordAsync(identityUser, model.ResetCode, model.Password);
if (result.Succeeded)
{
var lockedOut = await UserManager.IsLockedOutAsync(model.UserId);
var lockedOut = await UserManager.IsLockedOutAsync(identityUser);
if (lockedOut)
{
Logger.Info<AuthenticationController>("User {UserId} is currently locked out, unlocking and resetting AccessFailedCount", model.UserId);
//// var user = await UserManager.FindByIdAsync(model.UserId);
var unlockResult = await UserManager.SetLockoutEndDateAsync(model.UserId, DateTimeOffset.Now);
var unlockResult = await UserManager.SetLockoutEndDateAsync(identityUser, DateTimeOffset.Now);
if (unlockResult.Succeeded == false)
{
Logger.Warn<AuthenticationController>("Could not unlock for user {UserId} - error {UnlockError}", model.UserId, unlockResult.Errors.First());
}
var resetAccessFailedCountResult = await UserManager.ResetAccessFailedCountAsync(model.UserId);
var resetAccessFailedCountResult = await UserManager.ResetAccessFailedCountAsync(identityUser);
if (resetAccessFailedCountResult.Succeeded == false)
{
Logger.Warn<AuthenticationController>("Could not reset access failed count {UserId} - error {UnlockError}", model.UserId, unlockResult.Errors.First());
@@ -446,13 +451,13 @@ namespace Umbraco.Web.Editors
// if user was only invited, then they have not been approved
// but a successful forgot password flow (e.g. if their token had expired and they did a forgot password instead of request new invite)
// means we have verified their email
if (!UserManager.IsEmailConfirmed(model.UserId))
// TODO: SB: ConfirmEmailAsync
if (!await UserManager.IsEmailConfirmedAsync(identityUser))
{
await UserManager.ConfirmEmailAsync(model.UserId, model.ResetCode);
await UserManager.ConfirmEmailAsync(identityUser, model.ResetCode);
}
// if the user is invited, enable their account on forgot password
var identityUser = await UserManager.FindByIdAsync(model.UserId);
// invited is not approved, never logged in, invited date present
/*
if (LastLoginDate == default && IsApproved == false && InvitedDate != null)
@@ -474,7 +479,7 @@ namespace Umbraco.Web.Editors
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateValidationErrorResponse(
result.Errors.Any() ? result.Errors.First() : "Set password failed");
result.Errors.Any() ? result.Errors.First().Description : "Set password failed");
}
@@ -561,9 +566,8 @@ namespace Umbraco.Web.Editors
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(prefix, error);
ModelState.AddModelError(prefix, error.Description);
}
}
}
}
+21 -24
View File
@@ -7,8 +7,6 @@ using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Newtonsoft.Json;
using Umbraco.Core;
@@ -30,6 +28,7 @@ using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Web.Trees;
using UserLoginInfo = Microsoft.AspNetCore.Identity.UserLoginInfo;
namespace Umbraco.Web.Editors
{
@@ -44,8 +43,8 @@ namespace Umbraco.Web.Editors
private readonly IManifestParser _manifestParser;
private readonly UmbracoFeatures _features;
private readonly IRuntimeState _runtimeState;
private BackOfficeUserManager<BackOfficeIdentityUser> _userManager;
private BackOfficeSignInManager _signInManager;
private BackOfficeUserManager2<BackOfficeIdentityUser> _userManager;
private BackOfficeSignInManager2 _signInManager;
private readonly IUmbracoVersion _umbracoVersion;
private readonly IGridConfig _gridConfig;
private readonly IUmbracoSettingsSection _umbracoSettingsSection;
@@ -85,9 +84,9 @@ namespace Umbraco.Web.Editors
_httpContextAccessor = httpContextAccessor;
}
protected BackOfficeSignInManager SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager());
protected BackOfficeSignInManager2 SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager2());
protected BackOfficeUserManager<BackOfficeIdentityUser> UserManager => _userManager ?? (_userManager = OwinContext.GetBackOfficeUserManager());
protected BackOfficeUserManager2<BackOfficeIdentityUser> UserManager => _userManager ?? (_userManager = OwinContext.GetBackOfficeUserManager2());
protected IAuthenticationManager AuthenticationManager => OwinContext.Authentication;
@@ -139,21 +138,15 @@ namespace Umbraco.Web.Editors
}
var id = parts[0];
int intId;
if (int.TryParse(id, out intId) == false)
{
Logger.Warn<BackOfficeController>("VerifyUser endpoint reached with invalid token: {Invite}", invite);
return RedirectToAction("Default");
}
var identityUser = await UserManager.FindByIdAsync(intId);
var identityUser = await UserManager.FindByIdAsync(id);
if (identityUser == null)
{
Logger.Warn<BackOfficeController>("VerifyUser endpoint reached with non existing user: {UserId}", id);
return RedirectToAction("Default");
}
var result = await UserManager.ConfirmEmailAsync(intId, decoded);
var result = await UserManager.ConfirmEmailAsync(identityUser, decoded);
if (result.Succeeded == false)
{
@@ -329,10 +322,10 @@ namespace Umbraco.Web.Editors
[HttpGet]
public async Task<ActionResult> ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode)
{
var user = UserManager.FindById(userId);
var user = await UserManager.FindByIdAsync(userId.ToString());
if (user != null)
{
var result = await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", resetCode, UserManager, user);
var result = await UserManager.VerifyUserTokenAsync(user, "ResetPassword", "ResetPassword", resetCode); // TODO: SB: password reset token provider
if (result)
{
//Add a flag and redirect for it to be displayed
@@ -360,7 +353,10 @@ namespace Umbraco.Web.Editors
return RedirectToLocal(Url.Action("Default", "BackOffice"));
}
var result = await UserManager.AddLoginAsync(User.Identity.GetUserId<int>(), loginInfo.Login);
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var result = await UserManager.AddLoginAsync(user,
new UserLoginInfo(loginInfo.Login.LoginProvider, loginInfo.Login.ProviderKey, loginInfo.Login.LoginProvider));
if (result.Succeeded)
{
return RedirectToLocal(Url.Action("Default", "BackOffice"));
@@ -403,7 +399,7 @@ namespace Umbraco.Web.Editors
return await ExternalSignInAsync(loginInfo, externalSignInResponse);
}
private async Task<ActionResult> ExternalSignInAsync(ExternalLoginInfo loginInfo, Func<ActionResult> response)
private async Task<ActionResult> ExternalSignInAsync(ExternalLoginInfo2 loginInfo, Func<ActionResult> response)
{
if (loginInfo == null) throw new ArgumentNullException("loginInfo");
if (response == null) throw new ArgumentNullException("response");
@@ -424,7 +420,7 @@ namespace Umbraco.Web.Editors
}
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindAsync(loginInfo.Login);
var user = await UserManager.FindByLoginAsync(loginInfo.Login.LoginProvider, loginInfo.Login.ProviderKey);
if (user != null)
{
// TODO: It might be worth keeping some of the claims associated with the ExternalLoginInfo, in which case we
@@ -466,7 +462,7 @@ namespace Umbraco.Web.Editors
return response();
}
private async Task<bool> AutoLinkAndSignInExternalAccount(ExternalLoginInfo loginInfo, ExternalSignInAutoLinkOptions autoLinkOptions)
private async Task<bool> AutoLinkAndSignInExternalAccount(ExternalLoginInfo2 loginInfo, ExternalSignInAutoLinkOptions autoLinkOptions)
{
if (autoLinkOptions == null)
return false;
@@ -515,21 +511,22 @@ namespace Umbraco.Web.Editors
if (userCreationResult.Succeeded == false)
{
ViewData.SetExternalSignInError(userCreationResult.Errors);
ViewData.SetExternalSignInError(userCreationResult.Errors.Select(x => x.Description).ToList());
}
else
{
var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login);
var linkResult = await UserManager.AddLoginAsync(autoLinkUser,
new UserLoginInfo(loginInfo.Login.LoginProvider, loginInfo.Login.ProviderKey, loginInfo.Login.LoginProvider));
if (linkResult.Succeeded == false)
{
ViewData.SetExternalSignInError(linkResult.Errors);
ViewData.SetExternalSignInError(linkResult.Errors.Select(x => x.Description).ToList());
//If this fails, we should really delete the user since it will be in an inconsistent state!
var deleteResult = await UserManager.DeleteAsync(autoLinkUser);
if (deleteResult.Succeeded == false)
{
//DOH! ... this isn't good, combine all errors to be shown
ViewData.SetExternalSignInError(linkResult.Errors.Concat(deleteResult.Errors));
ViewData.SetExternalSignInError(linkResult.Errors.Concat(deleteResult.Errors).Select(x => x.Description).ToList());
}
}
else
@@ -158,7 +158,8 @@ namespace Umbraco.Web.Editors
[OverrideAuthorization]
public async Task<UserDetail> PostSetInvitedUserPassword([FromBody]string newPassword)
{
var result = await UserManager.AddPasswordAsync(Security.GetUserId().ResultOr(0), newPassword);
var user = await UserManager.FindByIdAsync(Security.GetUserId().ResultOr(0).ToString());
var result = await UserManager.AddPasswordAsync(user, newPassword);
if (result.Succeeded == false)
{
+10 -12
View File
@@ -34,7 +34,7 @@ namespace Umbraco.Web.Editors
IUser currentUser,
IUser savingUser,
ChangingPasswordModel passwordModel,
BackOfficeUserManager<BackOfficeIdentityUser> userMgr)
BackOfficeUserManager2<BackOfficeIdentityUser> userMgr)
{
if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel));
if (userMgr == null) throw new ArgumentNullException(nameof(userMgr));
@@ -44,6 +44,13 @@ namespace Umbraco.Web.Editors
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())
{
@@ -60,7 +67,7 @@ namespace Umbraco.Web.Editors
}
//ok, we should be able to reset it
var resetToken = await userMgr.GeneratePasswordResetTokenAsync(savingUser.Id);
var resetToken = await userMgr.GeneratePasswordResetTokenAsync(backOfficeIdentityUser);
var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, passwordModel.NewPassword);
@@ -74,15 +81,6 @@ namespace Umbraco.Web.Editors
return Attempt.Succeed(new PasswordChangedModel());
}
//we're changing our own password...
//get the user
var backOfficeIdentityUser = await userMgr.FindByIdAsync(savingUser.Id);
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" }) });
}
//is the old password correct?
var validateResult = await userMgr.CheckPasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword);
if (validateResult == false)
@@ -91,7 +89,7 @@ namespace Umbraco.Web.Editors
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Incorrect password", new[] { "oldPassword" }) });
}
//can we change to the new password?
var changeResult = await userMgr.ChangePasswordAsync(savingUser.Id, passwordModel.OldPassword, passwordModel.NewPassword);
var changeResult = await userMgr.ChangePasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword, passwordModel.NewPassword);
if (changeResult.Succeeded == false)
{
//no, fail with error messages for "password"
+21 -22
View File
@@ -10,7 +10,6 @@ using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
@@ -20,7 +19,6 @@ using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Web.Editors.Filters;
@@ -321,23 +319,18 @@ namespace Umbraco.Web.Editors
Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors)));
}
//we need to generate a password, however we can only do that if the user manager has a password validator that
//we can read values from
var passwordValidator = UserManager.PasswordValidator as PasswordValidator;
var resetPassword = string.Empty;
if (passwordValidator != null)
{
var password = UserManager.GeneratePassword();
string resetPassword;
var password = UserManager.GeneratePassword();
var result = await UserManager.AddPasswordAsync(identityUser.Id, password);
if (result.Succeeded == false)
{
throw new HttpResponseException(
Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors)));
}
resetPassword = password;
var result = await UserManager.AddPasswordAsync(identityUser, password);
if (result.Succeeded == false)
{
throw new HttpResponseException(
Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors)));
}
resetPassword = password;
//now re-look the user back up which will now exist
var user = Services.UserService.GetByEmail(userSave.Email);
@@ -472,7 +465,8 @@ namespace Umbraco.Web.Editors
private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, string fromEmail, IUser to, string message)
{
var token = await UserManager.GenerateEmailConfirmationTokenAsync((int)userDisplay.Id);
var user = await UserManager.FindByIdAsync(((int) userDisplay.Id).ToString());
var token = await UserManager.GenerateEmailConfirmationTokenAsync(user);
var inviteToken = string.Format("{0}{1}{2}",
(int)userDisplay.Id,
@@ -501,7 +495,8 @@ namespace Umbraco.Web.Editors
UmbracoUserExtensions.GetUserCulture(to.Language, Services.TextService, GlobalSettings),
new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail });
await UserManager.EmailService.SendAsync(
// TODO: SB: EmailService.SendAsync
/*await UserManager.EmailService.SendAsync(
//send the special UmbracoEmailMessage which configures it's own sender
//to allow for events to handle sending the message if no smtp is configured
new UmbracoEmailMessage(new EmailSender(GlobalSettings, true))
@@ -509,7 +504,7 @@ namespace Umbraco.Web.Editors
Body = emailBody,
Destination = userDisplay.Email,
Subject = emailSubject
});
});*/
}
@@ -705,22 +700,26 @@ namespace Umbraco.Web.Editors
if (userIds.Length <= 0)
return Request.CreateResponse(HttpStatusCode.OK);
var user = await UserManager.FindByIdAsync(userIds[0].ToString());
if (userIds.Length == 1)
{
var unlockResult = await UserManager.SetLockoutEndDateAsync(userIds[0], DateTimeOffset.Now);
var unlockResult = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
if (unlockResult.Succeeded == false)
{
return Request.CreateValidationErrorResponse(
string.Format("Could not unlock for user {0} - error {1}", userIds[0], unlockResult.Errors.First()));
}
var user = await UserManager.FindByIdAsync(userIds[0]);
return Request.CreateNotificationSuccessResponse(
Services.TextService.Localize("speechBubbles/unlockUserSuccess", new[] { user.Name }));
}
foreach (var u in userIds)
{
var unlockResult = await UserManager.SetLockoutEndDateAsync(u, DateTimeOffset.Now);
var unlockResult = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
if (unlockResult.Succeeded == false)
{
return Request.CreateValidationErrorResponse(
@@ -56,15 +56,15 @@ namespace Umbraco.Web.Install.InstallSteps
throw new InvalidOperationException("Could not find the super user!");
}
var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager();
var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId);
var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager2();
var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId.ToString());
if (membershipUser == null)
{
throw new InvalidOperationException($"No user found in membership provider with id of {Constants.Security.SuperUserId}.");
}
//To change the password here we actually need to reset it since we don't have an old one to use to change
var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id);
var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser);
var resetResult = await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, user.Password.Trim());
if (!resetResult.Succeeded)
{
@@ -1,32 +1,7 @@
using Microsoft.AspNet.Identity;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Identity;
namespace Umbraco.Web.Models.Identity
{
internal class UserLoginInfoWrapper : IUserLoginInfo
{
private readonly UserLoginInfo _info;
public static IUserLoginInfo Wrap(UserLoginInfo info) => new UserLoginInfoWrapper(info);
private UserLoginInfoWrapper(UserLoginInfo info)
{
_info = info;
}
public string LoginProvider
{
get => _info.LoginProvider;
set => _info.LoginProvider = value;
}
public string ProviderKey
{
get => _info.ProviderKey;
set => _info.ProviderKey = value;
}
}
internal class UserLoginInfoWrapper2 : IUserLoginInfo
{
private readonly Microsoft.AspNetCore.Identity.UserLoginInfo _info;
+7 -7
View File
@@ -57,10 +57,10 @@ namespace Umbraco.Web
/// </summary>
/// <param name="owinContext"></param>
/// <returns></returns>
public static BackOfficeSignInManager GetBackOfficeSignInManager(this IOwinContext owinContext)
public static BackOfficeSignInManager2 GetBackOfficeSignInManager(this IOwinContext owinContext)
{
return owinContext.Get<BackOfficeSignInManager>()
?? throw new NullReferenceException($"Could not resolve an instance of {typeof (BackOfficeSignInManager)} from the {typeof(IOwinContext)}.");
return owinContext.Get<BackOfficeSignInManager2>()
?? throw new NullReferenceException($"Could not resolve an instance of {typeof (BackOfficeSignInManager2)} from the {typeof(IOwinContext)}.");
}
/// <summary>
@@ -83,13 +83,13 @@ namespace Umbraco.Web
/// 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 BackOfficeUserManager<BackOfficeIdentityUser> GetBackOfficeUserManager(this IOwinContext owinContext)
public static BackOfficeUserManager2<BackOfficeIdentityUser> GetBackOfficeUserManager(this IOwinContext owinContext)
{
var marker = owinContext.Get<IBackOfficeUserManagerMarker>(BackOfficeUserManager.OwinMarkerKey)
var marker = owinContext.Get<IBackOfficeUserManagerMarker>(BackOfficeUserManager2.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 (BackOfficeUserManager<BackOfficeIdentityUser>)} from the {typeof (IOwinContext)}.");
?? throw new NullReferenceException($"Could not resolve an instance of {typeof (BackOfficeUserManager2<BackOfficeIdentityUser>)} from the {typeof (IOwinContext)}.");
}
/// <summary>
@@ -107,7 +107,7 @@ namespace Umbraco.Web
?? throw new NullReferenceException($"No {typeof (IBackOfficeUserManagerMarker2)}, 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 (BackOfficeUserManager<BackOfficeIdentityUser>)} from the {typeof (IOwinContext)}.");
?? throw new NullReferenceException($"Could not resolve an instance of {typeof (BackOfficeUserManager2<BackOfficeIdentityUser>)} from the {typeof (IOwinContext)}.");
}
// TODO: SB: OWIN DI
@@ -1,8 +1,8 @@
using System;
using System.Threading;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Owin.Extensions;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
@@ -16,7 +16,6 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Net;
@@ -34,16 +33,10 @@ namespace Umbraco.Web.Security
/// <summary>
/// Configure Default Identity User Manager for Umbraco
/// </summary>
/// <param name="app"></param>
/// <param name="services"></param>
/// <param name="contentSettings"></param>
/// <param name="globalSettings"></param>
/// <param name="userMembershipProvider"></param>
public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
ServiceContext services,
UmbracoMapper mapper,
IContentSection contentSettings,
IGlobalSettings globalSettings,
UmbracoMapper mapper,
// TODO: This could probably be optional?
IPasswordConfiguration passwordConfiguration,
IIpResolver ipResolver)
@@ -51,39 +44,37 @@ namespace Umbraco.Web.Security
if (services == null) throw new ArgumentNullException(nameof(services));
//Configure Umbraco user manager to be created per request
app.CreatePerOwinContext<BackOfficeUserManager>(
(options, owinContext) => BackOfficeUserManager.Create(
options,
app.CreatePerOwinContext<BackOfficeUserManager2>(
(options, owinContext) => BackOfficeUserManager2.Create(
services.UserService,
services.EntityService,
services.ExternalLoginService,
mapper,
contentSettings,
globalSettings,
mapper,
passwordConfiguration,
ipResolver));
ipResolver,
new OptionsWrapper<IdentityOptions>(new IdentityOptions()),
new UserAwarePasswordHasher2<BackOfficeIdentityUser>(new PasswordSecurity(passwordConfiguration)),
new[] {new UserValidator<BackOfficeIdentityUser>(),},
new[] {new PasswordValidator<BackOfficeIdentityUser>()},
new UpperInvariantLookupNormalizer(),
new IdentityErrorDescriber(),
null,
new NullLogger<BackOfficeUserManager2<BackOfficeIdentityUser>>()));
app.SetBackOfficeUserManagerType<BackOfficeUserManager, BackOfficeIdentityUser>();
app.SetBackOfficeUserManagerType<BackOfficeUserManager2, BackOfficeIdentityUser>();
//Create a sign in manager per request
app.CreatePerOwinContext<BackOfficeSignInManager>((options, context) => BackOfficeSignInManager.Create(options, context, globalSettings, app.CreateLogger<BackOfficeSignInManager>()));
app.CreatePerOwinContext<BackOfficeSignInManager2>((options, context) => BackOfficeSignInManager2.Create(context, globalSettings, app.CreateLogger<BackOfficeSignInManager2>()));
}
/// <summary>
/// Configure a custom UserStore with the Identity User Manager for Umbraco
/// </summary>
/// <param name="app"></param>
/// <param name="runtimeState"></param>
/// <param name="globalSettings"></param>
/// <param name="userMembershipProvider"></param>
/// <param name="customUserStore"></param>
/// <param name="contentSettings"></param>
/// <param name="ipResolver"></param>
public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
IRuntimeState runtimeState,
IContentSection contentSettings,
IGlobalSettings globalSettings,
BackOfficeUserStore customUserStore,
BackOfficeUserStore2 customUserStore,
// TODO: This could probably be optional?
IPasswordConfiguration passwordConfiguration,
IIpResolver ipResolver)
@@ -92,22 +83,28 @@ namespace Umbraco.Web.Security
if (customUserStore == null) throw new ArgumentNullException(nameof(customUserStore));
//Configure Umbraco user manager to be created per request
app.CreatePerOwinContext<BackOfficeUserManager>(
(options, owinContext) => BackOfficeUserManager.Create(
options,
customUserStore,
contentSettings,
app.CreatePerOwinContext<BackOfficeUserManager2>(
(options, owinContext) => BackOfficeUserManager2.Create(
passwordConfiguration,
ipResolver,
globalSettings));
customUserStore,
new OptionsWrapper<IdentityOptions>(new IdentityOptions()),
new UserAwarePasswordHasher2<BackOfficeIdentityUser>(new PasswordSecurity(passwordConfiguration)),
new[] { new Microsoft.AspNetCore.Identity.UserValidator<BackOfficeIdentityUser>(), },
new[] { new PasswordValidator<BackOfficeIdentityUser>() },
new UpperInvariantLookupNormalizer(),
new IdentityErrorDescriber(),
null,
new NullLogger<BackOfficeUserManager2<BackOfficeIdentityUser>>()));
app.SetBackOfficeUserManagerType<BackOfficeUserManager, BackOfficeIdentityUser>();
app.SetBackOfficeUserManagerType<BackOfficeUserManager2, BackOfficeIdentityUser>();
//Create a sign in manager per request
app.CreatePerOwinContext<BackOfficeSignInManager>((options, context) => BackOfficeSignInManager.Create(options, context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager).FullName)));
app.CreatePerOwinContext<BackOfficeSignInManager2>((options, context) => BackOfficeSignInManager2.Create(context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager2).FullName)));
}
/// <summary>
// TODO: SB: ConfigureUserManagerForUmbracoBackOffice using IdentityFactoryOptions
/*/// <summary>
/// Configure a custom BackOfficeUserManager for Umbraco
/// </summary>
/// <param name="app"></param>
@@ -118,7 +115,7 @@ namespace Umbraco.Web.Security
IRuntimeState runtimeState,
IGlobalSettings globalSettings,
Func<IdentityFactoryOptions<TManager>, IOwinContext, TManager> userManager)
where TManager : BackOfficeUserManager<TUser>
where TManager : BackOfficeUserManager2<TUser>
where TUser : BackOfficeIdentityUser
{
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
@@ -130,9 +127,9 @@ namespace Umbraco.Web.Security
app.SetBackOfficeUserManagerType<TManager, TUser>();
//Create a sign in manager per request
app.CreatePerOwinContext<BackOfficeSignInManager>(
(options, context) => BackOfficeSignInManager.Create(options, context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager).FullName)));
}
app.CreatePerOwinContext<BackOfficeSignInManager2>(
(options, context) => BackOfficeSignInManager2.Create(context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager2).FullName)));
}*/
/// <summary>
/// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline
@@ -190,14 +187,15 @@ namespace Umbraco.Web.Security
authOptions.Provider = new BackOfficeCookieAuthenticationProvider(userService, runtimeState, globalSettings, ioHelper, umbracoSettingsSection)
{
// TODO: SB: SecurityStampValidator
// Enables the application to validate the security stamp when the user
// logs in. This is a security feature which is used when you
// change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator
.OnValidateIdentity<BackOfficeUserManager, BackOfficeIdentityUser, int>(
/*OnValidateIdentity = SecurityStampValidator
.OnValidateIdentity<BackOfficeUserManager2, BackOfficeIdentityUser, int>(
TimeSpan.FromMinutes(30),
(manager, user) => manager.GenerateUserIdentityAsync(user),
identity => identity.GetUserId<int>()),
identity => identity.GetUserId<int>()),*/
};
@@ -268,7 +266,7 @@ namespace Umbraco.Web.Security
/// differently in the owin context
/// </remarks>
private static void SetBackOfficeUserManagerType<TManager, TUser>(this IAppBuilder app)
where TManager : BackOfficeUserManager<TUser>
where TManager : BackOfficeUserManager2<TUser>
where TUser : BackOfficeIdentityUser
{
if (_markerSet) throw new InvalidOperationException("The back office user manager marker has already been set, only one back office user manager can be configured");
@@ -278,7 +276,7 @@ namespace Umbraco.Web.Security
// a generic strongly typed instance
app.Use((context, func) =>
{
context.Set(BackOfficeUserManager.OwinMarkerKey, new BackOfficeUserManagerMarker<TManager, TUser>());
context.Set(BackOfficeUserManager2.OwinMarkerKey, new BackOfficeUserManagerMarker2<TManager, TUser>());
return func();
});
}
@@ -1,15 +1,14 @@
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.AspNetCore.Identity;
using Microsoft.Owin.Security;
namespace Umbraco.Web.Security
{
public static class AuthenticationManagerExtensions
{
private static ExternalLoginInfo GetExternalLoginInfo(AuthenticateResult result)
private static ExternalLoginInfo2 GetExternalLoginInfo(AuthenticateResult result)
{
if (result == null || result.Identity == null)
{
@@ -26,11 +25,12 @@ namespace Umbraco.Web.Security
{
name = name.Replace(" ", "");
}
var email = result.Identity.FindFirstValue(ClaimTypes.Email);
return new ExternalLoginInfo
var email = result.Identity.FindFirst(ClaimTypes.Email)?.Value;
return new ExternalLoginInfo2
{
ExternalIdentity = result.Identity,
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value),
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value, idClaim.Issuer),
DefaultUserName = name,
Email = email
};
@@ -47,7 +47,7 @@ namespace Umbraco.Web.Security
/// dictionary
/// </param>
/// <returns></returns>
public static async Task<ExternalLoginInfo> GetExternalLoginInfoAsync(this IAuthenticationManager manager,
public static async Task<ExternalLoginInfo2> GetExternalLoginInfoAsync(this IAuthenticationManager manager,
string authenticationType,
string xsrfKey, string expectedValue)
{
@@ -74,7 +74,7 @@ namespace Umbraco.Web.Security
/// <param name="manager"></param>
/// <param name="authenticationType"></param>
/// <returns></returns>
public static async Task<ExternalLoginInfo> GetExternalLoginInfoAsync(this IAuthenticationManager manager, string authenticationType)
public static async Task<ExternalLoginInfo2> GetExternalLoginInfoAsync(this IAuthenticationManager manager, string authenticationType)
{
if (manager == null)
{
@@ -83,4 +83,19 @@ namespace Umbraco.Web.Security
return GetExternalLoginInfo(await manager.AuthenticateAsync(authenticationType));
}
}
public class ExternalLoginInfo2
{
/// <summary>Associated login data</summary>
public UserLoginInfo Login { get; set; }
/// <summary>Suggested user name for a user</summary>
public string DefaultUserName { get; set; }
/// <summary>Email claim from the external identity</summary>
public string Email { get; set; }
/// <summary>The external identity</summary>
public ClaimsIdentity ExternalIdentity { get; set; }
}
}
@@ -1,337 +0,0 @@
using System;
using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Security;
using Umbraco.Web.Models.Identity;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Security
{
// TODO: In v8 we need to change this to use an int? nullable TKey instead, see notes against overridden TwoFactorSignInAsync
public class BackOfficeSignInManager : SignInManager<BackOfficeIdentityUser, int>
{
private readonly ILogger _logger;
private readonly IOwinRequest _request;
private readonly IGlobalSettings _globalSettings;
public BackOfficeSignInManager(UserManager<BackOfficeIdentityUser, int> userManager, IAuthenticationManager authenticationManager, ILogger logger, IGlobalSettings globalSettings, IOwinRequest request)
: base(userManager, authenticationManager)
{
if (logger == null) throw new ArgumentNullException("logger");
if (request == null) throw new ArgumentNullException("request");
_logger = logger;
_request = request;
_globalSettings = globalSettings;
AuthenticationType = Constants.Security.BackOfficeAuthenticationType;
}
public override Task<ClaimsIdentity> CreateUserIdentityAsync(BackOfficeIdentityUser user)
{
return ((BackOfficeUserManager<BackOfficeIdentityUser>)UserManager).GenerateUserIdentityAsync(user);
}
public static BackOfficeSignInManager Create(IdentityFactoryOptions<BackOfficeSignInManager> options, IOwinContext context, IGlobalSettings globalSettings, ILogger logger)
{
return new BackOfficeSignInManager(
context.GetBackOfficeUserManager(),
context.Authentication,
logger,
globalSettings,
context.Request);
}
/// <summary>
/// Sign in the user in using the user name and password
/// </summary>
/// <param name="userName"/><param name="password"/><param name="isPersistent"/><param name="shouldLockout"/>
/// <returns/>
public override async Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
{
var result = await PasswordSignInAsyncImpl(userName, password, isPersistent, shouldLockout);
switch (result)
{
case SignInStatus.Success:
_logger.WriteCore(TraceEventType.Information, 0,
$"User: {userName} logged in from IP address {_request.RemoteIpAddress}", null, null);
break;
case SignInStatus.LockedOut:
_logger.WriteCore(TraceEventType.Information, 0,
$"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}, the user is locked", null, null);
break;
case SignInStatus.RequiresVerification:
_logger.WriteCore(TraceEventType.Information, 0,
$"Login attempt requires verification for username {userName} from IP address {_request.RemoteIpAddress}", null, null);
break;
case SignInStatus.Failure:
_logger.WriteCore(TraceEventType.Information, 0,
$"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}", null, null);
break;
default:
throw new ArgumentOutOfRangeException();
}
return result;
}
/// <summary>
/// Borrowed from Microsoft's underlying sign in manager which is not flexible enough to tell it to use a different cookie type
/// </summary>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <param name="isPersistent"></param>
/// <param name="shouldLockout"></param>
/// <returns></returns>
private async Task<SignInStatus> PasswordSignInAsyncImpl(string userName, string password, bool isPersistent, bool shouldLockout)
{
if (UserManager == null)
{
return SignInStatus.Failure;
}
var user = await UserManager.FindByNameAsync(userName);
//if the user is null, create an empty one which can be used for auto-linking
if (user == null)
user = BackOfficeIdentityUser.CreateNew(_globalSettings, userName, null, _globalSettings.DefaultUILanguage);
//check the password for the user, this will allow a developer to auto-link
//an account if they have specified an IBackOfficeUserPasswordChecker
if (await UserManager.CheckPasswordAsync(user, password))
{
//the underlying call to this will query the user by Id which IS cached!
if (await UserManager.IsLockedOutAsync(user.Id))
{
return SignInStatus.LockedOut;
}
// We need to verify that the user belongs to one or more groups that define content and media start nodes.
// To do so we have to create the user claims identity and validate the calculated start nodes.
var userIdentity = await CreateUserIdentityAsync(user);
if (userIdentity is UmbracoBackOfficeIdentity backOfficeIdentity)
{
if (backOfficeIdentity.StartContentNodes.Length == 0 || backOfficeIdentity.StartMediaNodes.Length == 0)
{
_logger.WriteCore(TraceEventType.Information, 0,
$"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}, no content and/or media start nodes could be found for any of the user's groups", null, null);
return SignInStatus.Failure;
}
}
await UserManager.ResetAccessFailedCountAsync(user.Id);
return await SignInOrTwoFactor(user, isPersistent);
}
var requestContext = _request.Context;
if (user.HasIdentity && shouldLockout)
{
// If lockout is requested, increment access failed count which might lock out the user
await UserManager.AccessFailedAsync(user.Id);
if (await UserManager.IsLockedOutAsync(user.Id))
{
//at this point we've just locked the user out after too many failed login attempts
if (requestContext != null)
{
var backofficeUserManager = requestContext.GetBackOfficeUserManager();
if (backofficeUserManager != null)
backofficeUserManager.RaiseAccountLockedEvent(user.Id);
}
return SignInStatus.LockedOut;
}
}
if (requestContext != null)
{
var backofficeUserManager = requestContext.GetBackOfficeUserManager();
if (backofficeUserManager != null)
backofficeUserManager.RaiseInvalidLoginAttemptEvent(userName);
}
return SignInStatus.Failure;
}
/// <summary>
/// Borrowed from Microsoft's underlying sign in manager which is not flexible enough to tell it to use a different cookie type
/// </summary>
/// <param name="user"></param>
/// <param name="isPersistent"></param>
/// <returns></returns>
private async Task<SignInStatus> SignInOrTwoFactor(BackOfficeIdentityUser user, bool isPersistent)
{
var id = Convert.ToString(user.Id);
if (await UserManager.GetTwoFactorEnabledAsync(user.Id)
&& (await UserManager.GetValidTwoFactorProvidersAsync(user.Id)).Count > 0)
{
var identity = new ClaimsIdentity(Constants.Security.BackOfficeTwoFactorAuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id));
identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName));
AuthenticationManager.SignIn(identity);
return SignInStatus.RequiresVerification;
}
await SignInAsync(user, isPersistent, false);
return SignInStatus.Success;
}
/// <summary>
/// Creates a user identity and then signs the identity using the AuthenticationManager
/// </summary>
/// <param name="user"></param>
/// <param name="isPersistent"></param>
/// <param name="rememberBrowser"></param>
/// <returns></returns>
public override async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent, bool rememberBrowser)
{
var userIdentity = await CreateUserIdentityAsync(user);
// Clear any partial cookies from external or two factor partial sign ins
AuthenticationManager.SignOut(
Constants.Security.BackOfficeExternalAuthenticationType,
Constants.Security.BackOfficeTwoFactorAuthenticationType);
var nowUtc = DateTime.Now.ToUniversalTime();
if (rememberBrowser)
{
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
AuthenticationManager.SignIn(new AuthenticationProperties()
{
IsPersistent = isPersistent,
AllowRefresh = true,
IssuedUtc = nowUtc,
ExpiresUtc = nowUtc.AddMinutes(_globalSettings.TimeOutInMinutes)
}, userIdentity, rememberBrowserIdentity);
}
else
{
AuthenticationManager.SignIn(new AuthenticationProperties()
{
IsPersistent = isPersistent,
AllowRefresh = true,
IssuedUtc = nowUtc,
ExpiresUtc = nowUtc.AddMinutes(_globalSettings.TimeOutInMinutes)
}, userIdentity);
}
//track the last login date
user.LastLoginDateUtc = DateTime.UtcNow;
if (user.AccessFailedCount > 0)
//we have successfully logged in, reset the AccessFailedCount
user.AccessFailedCount = 0;
await UserManager.UpdateAsync(user);
//set the current request's principal to the identity just signed in!
_request.User = new ClaimsPrincipal(userIdentity);
_logger.WriteCore(TraceEventType.Information, 0,
string.Format(
"Login attempt succeeded for username {0} from IP address {1}",
user.UserName,
_request.RemoteIpAddress), null, null);
}
/// <summary>
/// Get the user id that has been verified already or int.MinValue if the user has not been verified yet
/// </summary>
/// <returns></returns>
/// <remarks>
/// Replaces the underlying call which is not flexible and doesn't support a custom cookie
/// </remarks>
public new async Task<int> GetVerifiedUserIdAsync()
{
var result = await AuthenticationManager.AuthenticateAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType);
if (result != null && result.Identity != null && string.IsNullOrEmpty(result.Identity.GetUserId()) == false)
{
return ConvertIdFromString(result.Identity.GetUserId());
}
return int.MinValue;
}
/// <summary>
/// Get the username that has been verified already or null.
/// </summary>
/// <returns></returns>
public async Task<string> GetVerifiedUserNameAsync()
{
var result = await AuthenticationManager.AuthenticateAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType);
if (result != null && result.Identity != null && string.IsNullOrEmpty(result.Identity.GetUserName()) == false)
{
return result.Identity.GetUserName();
}
return null;
}
/// <summary>
/// Two factor verification step
/// </summary>
/// <param name="provider"></param>
/// <param name="code"></param>
/// <param name="isPersistent"></param>
/// <param name="rememberBrowser"></param>
/// <returns></returns>
/// <remarks>
/// This is implemented because we cannot override GetVerifiedUserIdAsync and instead we have to shadow it
/// so due to this and because we are using an INT as the TKey and not an object, it can never be null. Adding to that
/// the default(int) value returned by the base class is always a valid user (i.e. the admin) so we just have to duplicate
/// all of this code to check for int.MinValue
/// </remarks>
public override async Task<SignInStatus> TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberBrowser)
{
var userId = await GetVerifiedUserIdAsync();
if (userId == int.MinValue)
{
return SignInStatus.Failure;
}
var user = await UserManager.FindByIdAsync(userId);
if (user == null)
{
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.Id))
{
return SignInStatus.LockedOut;
}
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code))
{
// When token is verified correctly, clear the access failed count used for lockout
await UserManager.ResetAccessFailedCountAsync(user.Id);
await SignInAsync(user, isPersistent, rememberBrowser);
return SignInStatus.Success;
}
// If the token is incorrect, record the failure which also may cause the user to be locked out
await UserManager.AccessFailedAsync(user.Id);
return SignInStatus.Failure;
}
/// <summary>Send a two factor code to a user</summary>
/// <param name="provider"></param>
/// <returns></returns>
/// <remarks>
/// This is implemented because we cannot override GetVerifiedUserIdAsync and instead we have to shadow it
/// so due to this and because we are using an INT as the TKey and not an object, it can never be null. Adding to that
/// the default(int) value returned by the base class is always a valid user (i.e. the admin) so we just have to duplicate
/// all of this code to check for int.MinVale instead.
/// </remarks>
public override async Task<bool> SendTwoFactorCodeAsync(string provider)
{
var userId = await GetVerifiedUserIdAsync();
if (userId == int.MinValue)
return false;
var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider);
var identityResult = await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token);
return identityResult.Succeeded;
}
}
}
@@ -18,7 +18,7 @@ namespace Umbraco.Web.Security
/// Custom sign in manager due to SignInManager not being .NET Standard.
/// Can be removed once the web project moves to .NET Core.
/// </summary>
public class BackOfficeSignInManager2
public class BackOfficeSignInManager2 : IDisposable
{
private readonly BackOfficeUserManager2<BackOfficeIdentityUser> _userManager;
private readonly IAuthenticationManager _authenticationManager;
@@ -350,5 +350,10 @@ namespace Umbraco.Web.Security
{
return id == null ? default(int) : (int) Convert.ChangeType(id, typeof(int), CultureInfo.InvariantCulture);
}
public void Dispose()
{
_userManager?.Dispose();
}
}
}
@@ -1,641 +0,0 @@
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security.DataProtection;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Net;
using Umbraco.Web.Models.Identity;
using IPasswordHasher = Microsoft.AspNet.Identity.IPasswordHasher;
namespace Umbraco.Web.Security
{
/// <summary>
/// Default back office user manager
/// </summary>
public class BackOfficeUserManager : BackOfficeUserManager<BackOfficeIdentityUser>
{
public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker";
public BackOfficeUserManager(
IUserStore<BackOfficeIdentityUser, int> store,
IdentityFactoryOptions<BackOfficeUserManager> options,
IContentSection contentSectionConfig,
IPasswordConfiguration passwordConfiguration,
IIpResolver ipResolver,
IGlobalSettings globalSettings)
: base(store, passwordConfiguration, ipResolver)
{
if (options == null) throw new ArgumentNullException("options");
InitUserManager(this, passwordConfiguration, options.DataProtectionProvider, contentSectionConfig, globalSettings);
}
#region Static Create methods
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
/// </summary>
/// <param name="options"></param>
/// <param name="userService"></param>
/// <param name="entityService"></param>
/// <param name="externalLoginService"></param>
/// <param name="passwordConfiguration"></param>
/// <param name="contentSectionConfig"></param>
/// <param name="globalSettings"></param>
/// <returns></returns>
public static BackOfficeUserManager Create(
IdentityFactoryOptions<BackOfficeUserManager> options,
IUserService userService,
IEntityService entityService,
IExternalLoginService externalLoginService,
UmbracoMapper mapper,
IContentSection contentSectionConfig,
IGlobalSettings globalSettings,
IPasswordConfiguration passwordConfiguration,
IIpResolver ipResolver)
{
if (options == null) throw new ArgumentNullException("options");
if (userService == null) throw new ArgumentNullException("userService");
if (externalLoginService == null) throw new ArgumentNullException("externalLoginService");
var store = new BackOfficeUserStore(userService, entityService, externalLoginService, globalSettings, mapper);
var manager = new BackOfficeUserManager(store, options, contentSectionConfig, passwordConfiguration, ipResolver, globalSettings);
return manager;
}
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance
/// </summary>
/// <param name="options"></param>
/// <param name="customUserStore"></param>
/// <param name="passwordConfiguration"></param>
/// <param name="contentSectionConfig"></param>
/// <returns></returns>
public static BackOfficeUserManager Create(
IdentityFactoryOptions<BackOfficeUserManager> options,
BackOfficeUserStore customUserStore,
IContentSection contentSectionConfig,
IPasswordConfiguration passwordConfiguration,
IIpResolver ipResolver,
IGlobalSettings globalSettings)
{
var manager = new BackOfficeUserManager(customUserStore, options, contentSectionConfig, passwordConfiguration, ipResolver, globalSettings);
return manager;
}
#endregion
}
/// <summary>
/// Generic Back office user manager
/// </summary>
public class BackOfficeUserManager<T> : UserManager<T, int>
where T : BackOfficeIdentityUser
{
private PasswordGenerator _passwordGenerator;
public BackOfficeUserManager(IUserStore<T, int> store,
IPasswordConfiguration passwordConfiguration,
IIpResolver ipResolver)
: base(store)
{
PasswordConfiguration = passwordConfiguration;
IpResolver = ipResolver;
}
#region What we support do not currently
// TODO: We could support this - but a user claims will mostly just be what is in the auth cookie
public override bool SupportsUserClaim
{
get { return false; }
}
// TODO: Support this
public override bool SupportsQueryableUsers
{
get { return false; }
}
/// <summary>
/// Developers will need to override this to support custom 2 factor auth
/// </summary>
public override bool SupportsUserTwoFactor
{
get { return false; }
}
// TODO: Support this
public override bool SupportsUserPhoneNumber
{
get { return false; }
}
#endregion
public virtual async Task<ClaimsIdentity> GenerateUserIdentityAsync(T user)
{
// NOTE the authenticationType must match the umbraco one
// defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await CreateIdentityAsync(user, Core.Constants.Security.BackOfficeAuthenticationType);
return userIdentity;
}
/// <summary>
/// Initializes the user manager with the correct options
/// </summary>
/// <param name="manager"></param>
/// <param name="passwordConfig"></param>
/// <param name="dataProtectionProvider"></param>
/// <param name="contentSectionConfig"></param>
/// <returns></returns>
protected void InitUserManager(
BackOfficeUserManager<T> manager,
IPasswordConfiguration passwordConfig,
IDataProtectionProvider dataProtectionProvider,
IContentSection contentSectionConfig,
IGlobalSettings globalSettings)
{
// Configure validation logic for usernames
manager.UserValidator = new BackOfficeUserValidator<T>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new ConfiguredPasswordValidator(passwordConfig);
//use a custom hasher based on our membership provider
manager.PasswordHasher = GetDefaultPasswordHasher(passwordConfig);
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<T, int>(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = TimeSpan.FromDays(3)
};
}
manager.UserLockoutEnabledByDefault = true;
manager.MaxFailedAccessAttemptsBeforeLockout = passwordConfig.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.
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromDays(30);
//custom identity factory for creating the identity object for which we auth against in the back office
manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory<T>();
manager.EmailService = new EmailService(
contentSectionConfig.NotificationEmailAddress,
new EmailSender(globalSettings));
//NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to support it
//// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
//// You can write your own provider and plug in here.
//manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
//{
// MessageFormat = "Your security code is: {0}"
//});
//manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
//{
// Subject = "Security Code",
// BodyFormat = "Your security code is: {0}"
//});
//manager.SmsService = new SmsService();
}
/// <summary>
/// Used to validate a user's session
/// </summary>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
public virtual async Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
{
var userSessionStore = Store as IUserSessionStore<BackOfficeIdentityUser, int>;
//if this is not set, for backwards compat (which would be super rare), we'll just approve it
if (userSessionStore == null)
return true;
return await userSessionStore.ValidateSessionIdAsync(userId, sessionId);
}
/// <summary>
/// This will determine which password hasher to use based on what is defined in config
/// </summary>
/// <returns></returns>
protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration)
{
//we can use the user aware password hasher (which will be the default and preferred way)
return new UserAwarePasswordHasher(new PasswordSecurity(passwordConfiguration));
}
/// <summary>
/// Gets/sets the default back office user password checker
/// </summary>
public IBackOfficeUserPasswordChecker BackOfficeUserPasswordChecker { get; set; }
public IPasswordConfiguration PasswordConfiguration { get; }
public IIpResolver IpResolver { get; }
/// <summary>
/// Helper method to generate a password for a user based on the current password validator
/// </summary>
/// <returns></returns>
public string GeneratePassword()
{
if (_passwordGenerator == null) _passwordGenerator = new PasswordGenerator(PasswordConfiguration);
var password = _passwordGenerator.GeneratePassword();
return password;
}
/// <summary>
/// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
/// <remarks>
/// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values
/// </remarks>
public override async Task<bool> IsLockedOutAsync(int userId)
{
var user = await FindByIdAsync(userId);
if (user == null)
throw new InvalidOperationException("No user found by id " + userId);
if (user.IsApproved == false)
return true;
return await base.IsLockedOutAsync(userId);
}
#region Overrides for password logic
/// <summary>
/// Logic used to validate a username and password
/// </summary>
/// <param name="user"></param>
/// <param name="password"></param>
/// <returns></returns>
/// <remarks>
/// By default this uses the standard ASP.Net Identity approach which is:
/// * Get password store
/// * Call VerifyPasswordAsync with the password store + user + password
/// * Uses the PasswordHasher.VerifyHashedPassword to compare the stored password
///
/// In some cases people want simple custom control over the username/password check, for simplicity
/// sake, developers would like the users to simply validate against an LDAP directory but the user
/// data remains stored inside of Umbraco.
/// See: http://issues.umbraco.org/issue/U4-7032 for the use cases.
///
/// We've allowed this check to be overridden with a simple callback so that developers don't actually
/// have to implement/override this class.
/// </remarks>
public override async Task<bool> CheckPasswordAsync(T user, string password)
{
if (BackOfficeUserPasswordChecker != null)
{
var result = await BackOfficeUserPasswordChecker.CheckPasswordAsync(user, password);
if (user.HasIdentity == false)
{
return false;
}
//if the result indicates to not fallback to the default, then return true if the credentials are valid
if (result != BackOfficeUserPasswordCheckerResult.FallbackToDefaultChecker)
{
return result == BackOfficeUserPasswordCheckerResult.ValidCredentials;
}
}
//we cannot proceed if the user passed in does not have an identity
if (user.HasIdentity == false)
return false;
//use the default behavior
return await base.CheckPasswordAsync(user, password);
}
public override Task<IdentityResult> ResetPasswordAsync(int userId, string token, string newPassword)
{
var result = base.ResetPasswordAsync(userId, token, newPassword);
return result;
}
/// <summary>
/// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event
/// </summary>
/// <param name="userId"></param>
/// <param name="token"></param>
/// <param name="newPassword"></param>
/// <returns></returns>
/// <remarks>
/// We use this because in the back office the only way an admin can change another user's password without first knowing their password
/// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset
/// </remarks>
public Task<IdentityResult> ChangePasswordWithResetAsync(int userId, string token, string newPassword)
{
var result = base.ResetPasswordAsync(userId, token, newPassword);
if (result.Result.Succeeded)
RaisePasswordChangedEvent(userId);
return result;
}
public override Task<IdentityResult> ChangePasswordAsync(int userId, string currentPassword, string newPassword)
{
var result = base.ChangePasswordAsync(userId, currentPassword, newPassword);
if (result.Result.Succeeded)
RaisePasswordChangedEvent(userId);
return result;
}
/// <summary>
/// Override to determine how to hash the password
/// </summary>
/// <param name="store"></param>
/// <param name="user"></param>
/// <param name="password"></param>
/// <returns></returns>
protected override async Task<bool> VerifyPasswordAsync(IUserPasswordStore<T, int> store, T user, string password)
{
var userAwarePasswordHasher = PasswordHasher as IUserAwarePasswordHasher<BackOfficeIdentityUser, int>;
if (userAwarePasswordHasher == null)
return await base.VerifyPasswordAsync(store, user, password);
var hash = await store.GetPasswordHashAsync(user);
return userAwarePasswordHasher.VerifyHashedPassword(user, hash, password) != PasswordVerificationResult.Failed;
}
/// <summary>
/// Override to determine how to hash the password
/// </summary>
/// <param name="passwordStore"></param>
/// <param name="user"></param>
/// <param name="newPassword"></param>
/// <returns></returns>
/// <remarks>
/// This method is called anytime the password needs to be hashed for storage (i.e. including when reset password is used)
/// </remarks>
protected override async Task<IdentityResult> UpdatePassword(IUserPasswordStore<T, int> passwordStore, T user, string newPassword)
{
user.LastPasswordChangeDateUtc = DateTime.UtcNow;
var userAwarePasswordHasher = PasswordHasher as IUserAwarePasswordHasher<BackOfficeIdentityUser, int>;
if (userAwarePasswordHasher == null)
return await base.UpdatePassword(passwordStore, user, newPassword);
var result = await PasswordValidator.ValidateAsync(newPassword);
if (result.Succeeded == false)
return result;
await passwordStore.SetPasswordHashAsync(user, userAwarePasswordHasher.HashPassword(user, newPassword));
await UpdateSecurityStampInternal(user);
return IdentityResult.Success;
}
/// <summary>
/// This is copied from the underlying .NET base class since they decided to not expose it
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
private async Task UpdateSecurityStampInternal(BackOfficeIdentityUser user)
{
if (SupportsUserSecurityStamp == false)
return;
await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp());
}
/// <summary>
/// This is copied from the underlying .NET base class since they decided to not expose it
/// </summary>
/// <returns></returns>
private IUserSecurityStampStore<BackOfficeIdentityUser, int> GetSecurityStore()
{
var store = Store as IUserSecurityStampStore<BackOfficeIdentityUser, int>;
if (store == null)
throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>));
return store;
}
/// <summary>
/// This is copied from the underlying .NET base class since they decided to not expose it
/// </summary>
/// <returns></returns>
private static string NewSecurityStamp()
{
return Guid.NewGuid().ToString();
}
#endregion
public override async Task<IdentityResult> SetLockoutEndDateAsync(int userId, DateTimeOffset lockoutEnd)
{
var result = await base.SetLockoutEndDateAsync(userId, lockoutEnd);
// The way we unlock is by setting the lockoutEnd date to the current datetime
if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow)
{
RaiseAccountLockedEvent(userId);
}
else
{
RaiseAccountUnlockedEvent(userId);
//Resets the login attempt fails back to 0 when unlock is clicked
await ResetAccessFailedCountAsync(userId);
}
return result;
}
public override async Task<IdentityResult> ResetAccessFailedCountAsync(int userId)
{
var lockoutStore = (IUserLockoutStore<BackOfficeIdentityUser, int>)Store;
var user = await FindByIdAsync(userId);
if (user == null)
throw new InvalidOperationException("No user found by user id " + userId);
var accessFailedCount = await GetAccessFailedCountAsync(user.Id);
if (accessFailedCount == 0)
return IdentityResult.Success;
await lockoutStore.ResetAccessFailedCountAsync(user);
//raise the event now that it's reset
RaiseResetAccessFailedCountEvent(userId);
return await UpdateAsync(user);
}
/// <summary>
/// Overrides the Microsoft ASP.NET user management method
/// </summary>
/// <param name="userId"></param>
/// <returns>
/// returns a Async Task<IdentityResult>
/// </returns>
/// <remarks>
/// Doesn't set fail attempts back to 0
/// </remarks>
public override async Task<IdentityResult> AccessFailedAsync(int userId)
{
var lockoutStore = (IUserLockoutStore<BackOfficeIdentityUser, int>)Store;
var user = await FindByIdAsync(userId);
if (user == null)
throw new InvalidOperationException("No user found by user id " + userId);
var count = await lockoutStore.IncrementAccessFailedCountAsync(user);
if (count >= MaxFailedAccessAttemptsBeforeLockout)
{
await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(DefaultAccountLockoutTimeSpan));
//NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0
//here we are persisting the value for the back office
}
var result = await UpdateAsync(user);
//Slightly confusing: this will return a Success if we successfully update the AccessFailed count
if (result.Succeeded)
RaiseLoginFailedEvent(userId);
return result;
}
internal void RaiseAccountLockedEvent(int userId)
{
OnAccountLocked(new IdentityAuditEventArgs(AuditEvent.AccountLocked, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseAccountUnlockedEvent(int userId)
{
OnAccountUnlocked(new IdentityAuditEventArgs(AuditEvent.AccountUnlocked, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseForgotPasswordRequestedEvent(int userId)
{
OnForgotPasswordRequested(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordRequested, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseForgotPasswordChangedSuccessEvent(int userId)
{
OnForgotPasswordChangedSuccess(new IdentityAuditEventArgs(AuditEvent.ForgotPasswordChangedSuccess, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseLoginFailedEvent(int userId)
{
OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseInvalidLoginAttemptEvent(string username)
{
OnLoginFailed(new IdentityAuditEventArgs(AuditEvent.LoginFailed, IpResolver.GetCurrentRequestIpAddress(), username, string.Format("Attempted login for username '{0}' failed", username)));
}
internal void RaiseLoginRequiresVerificationEvent(int userId)
{
OnLoginRequiresVerification(new IdentityAuditEventArgs(AuditEvent.LoginRequiresVerification, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseLoginSuccessEvent(int userId)
{
OnLoginSuccess(new IdentityAuditEventArgs(AuditEvent.LoginSucces, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseLogoutSuccessEvent(int userId)
{
OnLogoutSuccess(new IdentityAuditEventArgs(AuditEvent.LogoutSuccess, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaisePasswordChangedEvent(int userId)
{
OnPasswordChanged(new IdentityAuditEventArgs(AuditEvent.PasswordChanged, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
internal void RaiseResetAccessFailedCountEvent(int userId)
{
OnResetAccessFailedCount(new IdentityAuditEventArgs(AuditEvent.ResetAccessFailedCount, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId));
}
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;
protected virtual void OnAccountLocked(IdentityAuditEventArgs e)
{
if (AccountLocked != null) AccountLocked(this, e);
}
protected virtual void OnAccountUnlocked(IdentityAuditEventArgs e)
{
if (AccountUnlocked != null) AccountUnlocked(this, e);
}
protected virtual void OnForgotPasswordRequested(IdentityAuditEventArgs e)
{
if (ForgotPasswordRequested != null) ForgotPasswordRequested(this, e);
}
protected virtual void OnForgotPasswordChangedSuccess(IdentityAuditEventArgs e)
{
if (ForgotPasswordChangedSuccess != null) ForgotPasswordChangedSuccess(this, e);
}
protected virtual void OnLoginFailed(IdentityAuditEventArgs e)
{
if (LoginFailed != null) LoginFailed(this, e);
}
protected virtual void OnLoginRequiresVerification(IdentityAuditEventArgs e)
{
if (LoginRequiresVerification != null) LoginRequiresVerification(this, e);
}
protected virtual void OnLoginSuccess(IdentityAuditEventArgs e)
{
if (LoginSuccess != null) LoginSuccess(this, e);
}
protected virtual void OnLogoutSuccess(IdentityAuditEventArgs e)
{
if (LogoutSuccess != null) LogoutSuccess(this, e);
}
protected virtual void OnPasswordChanged(IdentityAuditEventArgs e)
{
if (PasswordChanged != null) PasswordChanged(this, e);
}
protected virtual void OnPasswordReset(IdentityAuditEventArgs e)
{
if (PasswordReset != null) PasswordReset(this, e);
}
protected virtual void OnResetAccessFailedCount(IdentityAuditEventArgs e)
{
if (ResetAccessFailedCount != null) ResetAccessFailedCount(this, e);
}
}
}
@@ -42,14 +42,6 @@ namespace Umbraco.Web.Security
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
/// </summary>
/// <param name="options"></param>
/// <param name="userService"></param>
/// <param name="entityService"></param>
/// <param name="externalLoginService"></param>
/// <param name="passwordConfiguration"></param>
/// <param name="contentSectionConfig"></param>
/// <param name="globalSettings"></param>
/// <returns></returns>
public static BackOfficeUserManager2 Create(
IUserService userService,
IEntityService entityService,
@@ -85,11 +77,10 @@ namespace Umbraco.Web.Security
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance
/// </summary>
/// <returns></returns>
public static BackOfficeUserManager2 Create(
IPasswordConfiguration passwordConfiguration,
IIpResolver ipResolver,
IUserStore<BackOfficeIdentityUser> store,
IUserStore<BackOfficeIdentityUser> customUserStore,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<BackOfficeIdentityUser> passwordHasher,
IEnumerable<IUserValidator<BackOfficeIdentityUser>> userValidators,
@@ -102,7 +93,7 @@ namespace Umbraco.Web.Security
return new BackOfficeUserManager2(
passwordConfiguration,
ipResolver,
store,
customUserStore,
optionsAccessor,
passwordHasher,
userValidators,
@@ -207,9 +198,9 @@ namespace Umbraco.Web.Security
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
public virtual async Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
public virtual async Task<bool> ValidateSessionIdAsync(string userId, string sessionId)
{
var userSessionStore = Store as IUserSessionStore2<T, int>;
var userSessionStore = Store as IUserSessionStore2<T>;
//if this is not set, for backwards compat (which would be super rare), we'll just approve it
if (userSessionStore == null) return true;
@@ -1,8 +1,5 @@
using System;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Security;
using Umbraco.Web.Models.Identity;
namespace Umbraco.Web.Security
@@ -14,14 +11,14 @@ namespace Umbraco.Web.Security
/// </summary>
/// <typeparam name="TManager"></typeparam>
/// <typeparam name="TUser"></typeparam>
internal class BackOfficeUserManagerMarker<TManager, TUser> : IBackOfficeUserManagerMarker
where TManager : BackOfficeUserManager<TUser>
internal class BackOfficeUserManagerMarker2<TManager, TUser> : IBackOfficeUserManagerMarker2
where TManager : BackOfficeUserManager2<TUser>
where TUser : BackOfficeIdentityUser
{
public BackOfficeUserManager<BackOfficeIdentityUser> GetManager(IOwinContext owin)
public BackOfficeUserManager2<BackOfficeIdentityUser> GetManager(IOwinContext owin)
{
var mgr = owin.Get<TManager>() as BackOfficeUserManager<BackOfficeIdentityUser>;
if (mgr == null) throw new InvalidOperationException("Could not cast the registered back office user of type " + typeof(TManager) + " to " + typeof(BackOfficeUserManager<BackOfficeIdentityUser>));
var mgr = owin.Get<TManager>() as BackOfficeUserManager2<BackOfficeIdentityUser>;
if (mgr == null) throw new InvalidOperationException("Could not cast the registered back office user of type " + typeof(TManager) + " to " + typeof(BackOfficeUserManager2<BackOfficeIdentityUser>));
return mgr;
}
}
@@ -1,776 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Configuration;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Web.Models.Identity;
using IUser = Umbraco.Core.Models.Membership.IUser;
using Task = System.Threading.Tasks.Task;
namespace Umbraco.Core.Security
{
public class BackOfficeUserStore : DisposableObjectSlim,
IUserStore<BackOfficeIdentityUser, int>,
IUserPasswordStore<BackOfficeIdentityUser, int>,
IUserEmailStore<BackOfficeIdentityUser, int>,
IUserLoginStore<BackOfficeIdentityUser, int>,
IUserRoleStore<BackOfficeIdentityUser, int>,
IUserSecurityStampStore<BackOfficeIdentityUser, int>,
IUserLockoutStore<BackOfficeIdentityUser, int>,
IUserTwoFactorStore<BackOfficeIdentityUser, int>,
IUserSessionStore<BackOfficeIdentityUser, int>
// TODO: This would require additional columns/tables for now people will need to implement this on their own
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
// TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
//IQueryableUserStore<BackOfficeIdentityUser, int>
{
private readonly IUserService _userService;
private readonly IEntityService _entityService;
private readonly IExternalLoginService _externalLoginService;
private readonly IGlobalSettings _globalSettings;
private readonly UmbracoMapper _mapper;
private bool _disposed = false;
public BackOfficeUserStore(IUserService userService, IEntityService entityService, IExternalLoginService externalLoginService, IGlobalSettings globalSettings, UmbracoMapper mapper)
{
_userService = userService;
_entityService = entityService;
_externalLoginService = externalLoginService;
_globalSettings = globalSettings;
if (userService == null) throw new ArgumentNullException("userService");
if (externalLoginService == null) throw new ArgumentNullException("externalLoginService");
_mapper = mapper;
_userService = userService;
_externalLoginService = externalLoginService;
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObjectSlim"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
{
_disposed = true;
}
/// <summary>
/// Insert a new user
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task CreateAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
//the password must be 'something' it could be empty if authenticating
// with an external provider so we'll just generate one and prefix it, the
// prefix will help us determine if the password hasn't actually been specified yet.
//this will hash the guid with a salt so should be nicely random
var aspHasher = new PasswordHasher();
var emptyPasswordValue = Constants.Security.EmptyPasswordPrefix +
aspHasher.HashPassword(Guid.NewGuid().ToString("N"));
var userEntity = new User(_globalSettings, user.Name, user.Email, user.UserName, emptyPasswordValue)
{
Language = user.Culture ?? _globalSettings.DefaultUILanguage,
StartContentIds = user.StartContentIds ?? new int[] { },
StartMediaIds = user.StartMediaIds ?? new int[] { },
IsLockedOut = user.IsLockedOut,
};
UpdateMemberProperties(userEntity, user);
// TODO: We should deal with Roles --> User Groups here which we currently are not doing
_userService.Save(userEntity);
if (!userEntity.HasIdentity) throw new DataException("Could not create the user, check logs for details");
//re-assign id
user.Id = userEntity.Id;
return Task.FromResult(0);
}
/// <summary>
/// Update a user
/// </summary>
/// <param name="user"/>
/// <returns/>
public async Task UpdateAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
var asInt = user.Id.TryConvertTo<int>();
if (asInt == false)
{
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
}
var found = _userService.GetUserById(asInt.Result);
if (found != null)
{
// we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it.
var isLoginsPropertyDirty = user.IsPropertyDirty("Logins");
if (UpdateMemberProperties(found, user))
{
_userService.Save(found);
}
if (isLoginsPropertyDirty)
{
var logins = await GetLoginsAsync(user);
_externalLoginService.SaveUserLogins(found.Id, logins.Select(UserLoginInfoWrapper.Wrap));
}
}
}
/// <summary>
/// Delete a user
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task DeleteAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
var asInt = user.Id.TryConvertTo<int>();
if (asInt == false)
{
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
}
var found = _userService.GetUserById(asInt.Result);
if (found != null)
{
_userService.Delete(found);
}
_externalLoginService.DeleteUserLogins(asInt.Result);
return Task.FromResult(0);
}
/// <summary>
/// Finds a user
/// </summary>
/// <param name="userId"/>
/// <returns/>
public async Task<BackOfficeIdentityUser> FindByIdAsync(int userId)
{
ThrowIfDisposed();
var user = _userService.GetUserById(userId);
if (user == null)
{
return null;
}
return await Task.FromResult(AssignLoginsCallback(_mapper.Map<BackOfficeIdentityUser>(user)));
}
/// <summary>
/// Find a user by name
/// </summary>
/// <param name="userName"/>
/// <returns/>
public async Task<BackOfficeIdentityUser> FindByNameAsync(string userName)
{
ThrowIfDisposed();
var user = _userService.GetByUsername(userName);
if (user == null)
{
return null;
}
var result = AssignLoginsCallback(_mapper.Map<BackOfficeIdentityUser>(user));
return await Task.FromResult(result);
}
/// <summary>
/// Set the user password hash
/// </summary>
/// <param name="user"/><param name="passwordHash"/>
/// <returns/>
public Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
if (passwordHash == null) throw new ArgumentNullException(nameof(passwordHash));
if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentException("Value can't be empty.", nameof(passwordHash));
user.PasswordHash = passwordHash;
return Task.FromResult(0);
}
/// <summary>
/// Get the user password hash
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<string> GetPasswordHashAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(user.PasswordHash);
}
/// <summary>
/// Returns true if a user has a password set
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<bool> HasPasswordAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(string.IsNullOrEmpty(user.PasswordHash) == false);
}
/// <summary>
/// Set the user email
/// </summary>
/// <param name="user"/><param name="email"/>
/// <returns/>
public Task SetEmailAsync(BackOfficeIdentityUser user, string email)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
if (email.IsNullOrWhiteSpace()) throw new ArgumentNullException("email");
user.Email = email;
return Task.FromResult(0);
}
/// <summary>
/// Get the user email
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<string> GetEmailAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(user.Email);
}
/// <summary>
/// Returns true if the user email is confirmed
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<bool> GetEmailConfirmedAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(user.EmailConfirmed);
}
/// <summary>
/// Sets whether the user email is confirmed
/// </summary>
/// <param name="user"/><param name="confirmed"/>
/// <returns/>
public Task SetEmailConfirmedAsync(BackOfficeIdentityUser user, bool confirmed)
{
ThrowIfDisposed();
user.EmailConfirmed = confirmed;
return Task.FromResult(0);
}
/// <summary>
/// Returns the user associated with this email
/// </summary>
/// <param name="email"/>
/// <returns/>
public Task<BackOfficeIdentityUser> FindByEmailAsync(string email)
{
ThrowIfDisposed();
var user = _userService.GetByEmail(email);
var result = user == null
? null
: _mapper.Map<BackOfficeIdentityUser>(user);
return Task.FromResult(AssignLoginsCallback(result));
}
/// <summary>
/// Adds a user login with the specified provider and key
/// </summary>
/// <param name="user"/><param name="login"/>
/// <returns/>
public Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
if (login == null) throw new ArgumentNullException(nameof(login));
var logins = user.Logins;
var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id);
var userLogin = instance;
logins.Add(userLogin);
return Task.FromResult(0);
}
/// <summary>
/// Removes the user login with the specified combination if it exists
/// </summary>
/// <param name="user"/><param name="login"/>
/// <returns/>
public Task RemoveLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
if (login == null) throw new ArgumentNullException(nameof(login));
var provider = login.LoginProvider;
var key = login.ProviderKey;
var userLogin = user.Logins.SingleOrDefault((l => l.LoginProvider == provider && l.ProviderKey == key));
if (userLogin != null)
user.Logins.Remove(userLogin);
return Task.FromResult(0);
}
/// <summary>
/// Returns the linked accounts for this user
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<IList<UserLoginInfo>> GetLoginsAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult((IList<UserLoginInfo>)
user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey)).ToList());
}
/// <summary>
/// Returns the user associated with this login
/// </summary>
/// <returns/>
public Task<BackOfficeIdentityUser> FindAsync(UserLoginInfo login)
{
ThrowIfDisposed();
if (login == null) throw new ArgumentNullException(nameof(login));
//get all logins associated with the login id
var result = _externalLoginService.Find(UserLoginInfoWrapper.Wrap(login)).ToArray();
if (result.Any())
{
//return the first user that matches the result
BackOfficeIdentityUser output = null;
foreach (var l in result)
{
var user = _userService.GetUserById(l.UserId);
if (user != null)
{
output = _mapper.Map<BackOfficeIdentityUser>(user);
break;
}
}
return Task.FromResult(AssignLoginsCallback(output));
}
return Task.FromResult<BackOfficeIdentityUser>(null);
}
/// <summary>
/// Adds a user to a role (user group)
/// </summary>
/// <param name="user"/><param name="roleName"/>
/// <returns/>
public Task AddToRoleAsync(BackOfficeIdentityUser user, string roleName)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
if (roleName == null) throw new ArgumentNullException(nameof(roleName));
if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(roleName));
var userRole = user.Roles.SingleOrDefault(r => r.RoleId == roleName);
if (userRole == null)
{
user.AddRole(roleName);
}
return Task.FromResult(0);
}
/// <summary>
/// Removes the role (user group) for the user
/// </summary>
/// <param name="user"/><param name="roleName"/>
/// <returns/>
public Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string roleName)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
if (roleName == null) throw new ArgumentNullException(nameof(roleName));
if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(roleName));
var userRole = user.Roles.SingleOrDefault(r => r.RoleId == roleName);
if (userRole != null)
{
user.Roles.Remove(userRole);
}
return Task.FromResult(0);
}
/// <summary>
/// Returns the roles (user groups) for this user
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<IList<string>> GetRolesAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult((IList<string>)user.Roles.Select(x => x.RoleId).ToList());
}
/// <summary>
/// Returns true if a user is in the role
/// </summary>
/// <param name="user"/><param name="roleName"/>
/// <returns/>
public Task<bool> IsInRoleAsync(BackOfficeIdentityUser user, string roleName)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(user.Roles.Select(x => x.RoleId).InvariantContains(roleName));
}
/// <summary>
/// Set the security stamp for the user
/// </summary>
/// <param name="user"/><param name="stamp"/>
/// <returns/>
public Task SetSecurityStampAsync(BackOfficeIdentityUser user, string stamp)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
user.SecurityStamp = stamp;
return Task.FromResult(0);
}
/// <summary>
/// Get the user security stamp
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<string> GetSecurityStampAsync(BackOfficeIdentityUser user)
{
ThrowIfDisposed();
if (user == null) throw new ArgumentNullException(nameof(user));
//the stamp cannot be null, so if it is currently null then we'll just return a hash of the password
return Task.FromResult(user.SecurityStamp.IsNullOrWhiteSpace()
? user.PasswordHash.GenerateHash()
: user.SecurityStamp);
}
private BackOfficeIdentityUser AssignLoginsCallback(BackOfficeIdentityUser user)
{
if (user != null)
{
user.SetLoginsCallback(new Lazy<IEnumerable<IIdentityUserLogin>>(() =>
_externalLoginService.GetAll(user.Id)));
}
return user;
}
/// <summary>
/// Sets whether two factor authentication is enabled for the user
/// </summary>
/// <param name="user"/><param name="enabled"/>
/// <returns/>
public virtual Task SetTwoFactorEnabledAsync(BackOfficeIdentityUser user, bool enabled)
{
user.TwoFactorEnabled = false;
return Task.FromResult(0);
}
/// <summary>
/// Returns whether two factor authentication is enabled for the user
/// </summary>
/// <param name="user"/>
/// <returns/>
public virtual Task<bool> GetTwoFactorEnabledAsync(BackOfficeIdentityUser user)
{
return Task.FromResult(false);
}
#region IUserLockoutStore
/// <summary>
/// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered not locked out.
/// </summary>
/// <param name="user"/>
/// <returns/>
/// <remarks>
/// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status
/// </remarks>
public Task<DateTimeOffset> GetLockoutEndDateAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
return user.LockoutEndDateUtc.HasValue
? Task.FromResult(DateTimeOffset.MaxValue)
: Task.FromResult(DateTimeOffset.MinValue);
}
/// <summary>
/// Locks a user out until the specified end date (set to a past date, to unlock a user)
/// </summary>
/// <param name="user"/><param name="lockoutEnd"/>
/// <returns/>
/// <remarks>
/// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status
/// </remarks>
public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset lockoutEnd)
{
if (user == null) throw new ArgumentNullException(nameof(user));
user.LockoutEndDateUtc = lockoutEnd.UtcDateTime;
return Task.FromResult(0);
}
/// <summary>
/// Used to record when an attempt to access the user has failed
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<int> IncrementAccessFailedCountAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
user.AccessFailedCount++;
return Task.FromResult(user.AccessFailedCount);
}
/// <summary>
/// Used to reset the access failed count, typically after the account is successfully accessed
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
user.AccessFailedCount = 0;
return Task.FromResult(0);
}
/// <summary>
/// Returns the current number of failed access attempts. This number usually will be reset whenever the password is
/// verified or the account is locked out.
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<int> GetAccessFailedCountAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(user.AccessFailedCount);
}
/// <summary>
/// Returns true
/// </summary>
/// <param name="user"/>
/// <returns/>
public Task<bool> GetLockoutEnabledAsync(BackOfficeIdentityUser user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
return Task.FromResult(user.LockoutEnabled);
}
/// <summary>
/// Doesn't actually perform any function, users can always be locked out
/// </summary>
/// <param name="user"/><param name="enabled"/>
/// <returns/>
public Task SetLockoutEnabledAsync(BackOfficeIdentityUser user, bool enabled)
{
if (user == null) throw new ArgumentNullException(nameof(user));
user.LockoutEnabled = enabled;
return Task.FromResult(0);
}
#endregion
private bool UpdateMemberProperties(IUser user, BackOfficeIdentityUser identityUser)
{
var anythingChanged = false;
//don't assign anything if nothing has changed as this will trigger the track changes of the model
if (identityUser.IsPropertyDirty("LastLoginDateUtc")
|| (user.LastLoginDate != default(DateTime) && identityUser.LastLoginDateUtc.HasValue == false)
|| identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)
{
anythingChanged = true;
//if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();
user.LastLoginDate = dt;
}
if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc")
|| (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|| identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value)
{
anythingChanged = true;
user.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc.Value.ToLocalTime();
}
if (identityUser.IsPropertyDirty("EmailConfirmed")
|| (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default(DateTime) && identityUser.EmailConfirmed == false)
|| ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default(DateTime)) && identityUser.EmailConfirmed))
{
anythingChanged = true;
user.EmailConfirmedDate = identityUser.EmailConfirmed ? (DateTime?)DateTime.Now : null;
}
if (identityUser.IsPropertyDirty("Name")
&& user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.Name = identityUser.Name;
}
if (identityUser.IsPropertyDirty("Email")
&& user.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.Email = identityUser.Email;
}
if (identityUser.IsPropertyDirty("AccessFailedCount")
&& user.FailedPasswordAttempts != identityUser.AccessFailedCount)
{
anythingChanged = true;
user.FailedPasswordAttempts = identityUser.AccessFailedCount;
}
if (user.IsLockedOut != identityUser.IsLockedOut)
{
anythingChanged = true;
user.IsLockedOut = identityUser.IsLockedOut;
if (user.IsLockedOut)
{
//need to set the last lockout date
user.LastLockoutDate = DateTime.Now;
}
}
if (identityUser.IsPropertyDirty("UserName")
&& user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.Username = identityUser.UserName;
}
if (identityUser.IsPropertyDirty("PasswordHash")
&& user.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.RawPasswordValue = identityUser.PasswordHash;
}
if (identityUser.IsPropertyDirty("Culture")
&& user.Language != identityUser.Culture && identityUser.Culture.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
user.Language = identityUser.Culture;
}
if (identityUser.IsPropertyDirty("StartMediaIds")
&& user.StartMediaIds.UnsortedSequenceEqual(identityUser.StartMediaIds) == false)
{
anythingChanged = true;
user.StartMediaIds = identityUser.StartMediaIds;
}
if (identityUser.IsPropertyDirty("StartContentIds")
&& user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds) == false)
{
anythingChanged = true;
user.StartContentIds = identityUser.StartContentIds;
}
if (user.SecurityStamp != identityUser.SecurityStamp)
{
anythingChanged = true;
user.SecurityStamp = identityUser.SecurityStamp;
}
// TODO: Fix this for Groups too
if (identityUser.IsPropertyDirty("Roles") || identityUser.IsPropertyDirty("Groups"))
{
var userGroupAliases = user.Groups.Select(x => x.Alias).ToArray();
var identityUserRoles = identityUser.Roles.Select(x => x.RoleId).ToArray();
var identityUserGroups = identityUser.Groups.Select(x => x.Alias).ToArray();
var combinedAliases = identityUserRoles.Union(identityUserGroups).ToArray();
if (userGroupAliases.ContainsAll(combinedAliases) == false
|| combinedAliases.ContainsAll(userGroupAliases) == false)
{
anythingChanged = true;
//clear out the current groups (need to ToArray since we are modifying the iterator)
user.ClearGroups();
//go lookup all these groups
var groups = _userService.GetUserGroupsByAlias(combinedAliases).Select(x => x.ToReadOnlyGroup()).ToArray();
//use all of the ones assigned and add them
foreach (var group in groups)
{
user.AddGroup(group);
}
//re-assign
identityUser.Groups = groups;
}
}
//we should re-set the calculated start nodes
identityUser.CalculatedMediaStartNodeIds = user.CalculateMediaStartNodeIds(_entityService);
identityUser.CalculatedContentStartNodeIds = user.CalculateContentStartNodeIds(_entityService);
//reset all changes
identityUser.ResetDirtyProperties(false);
return anythingChanged;
}
private void ThrowIfDisposed()
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
}
public Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
{
Guid guidSessionId;
if (Guid.TryParse(sessionId, out guidSessionId))
{
return Task.FromResult(_userService.ValidateLoginSession(userId, guidSessionId));
}
return Task.FromResult(false);
}
}
}
@@ -28,7 +28,7 @@ namespace Umbraco.Web.Security
IUserSecurityStampStore<BackOfficeIdentityUser>,
IUserLockoutStore<BackOfficeIdentityUser>,
IUserTwoFactorStore<BackOfficeIdentityUser>,
IUserSessionStore2<BackOfficeIdentityUser, int>
IUserSessionStore2<BackOfficeIdentityUser>
// TODO: This would require additional columns/tables for now people will need to implement this on their own
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
@@ -119,9 +119,9 @@ namespace Umbraco.Web.Security
// with an external provider so we'll just generate one and prefix it, the
// prefix will help us determine if the password hasn't actually been specified yet.
//this will hash the guid with a salt so should be nicely random
var aspHasher = new Microsoft.AspNet.Identity.PasswordHasher();
var aspHasher = new PasswordHasher<BackOfficeIdentityUser>();
var emptyPasswordValue = Constants.Security.EmptyPasswordPrefix +
aspHasher.HashPassword(Guid.NewGuid().ToString("N"));
aspHasher.HashPassword(user, Guid.NewGuid().ToString("N"));
var userEntity = new User(_globalSettings, user.Name, user.Email, user.UserName, emptyPasswordValue)
{
@@ -902,12 +902,13 @@ namespace Umbraco.Web.Security
throw new ObjectDisposedException(GetType().Name);
}
public Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
public Task<bool> ValidateSessionIdAsync(string userId, string sessionId)
{
Guid guidSessionId;
if (Guid.TryParse(sessionId, out guidSessionId))
{
return Task.FromResult(_userService.ValidateLoginSession(userId, guidSessionId));
// TODO: SB: Normalize string to int conversion
return Task.FromResult(_userService.ValidateLoginSession(int.Parse(sessionId), guidSessionId));
}
return Task.FromResult(false);
}
@@ -1,28 +0,0 @@
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Umbraco.Web.Models.Identity;
namespace Umbraco.Core.Security
{
/// <summary>
/// Custom validator to not validate a user's username or email if they haven't changed
/// </summary>
/// <typeparam name="T"></typeparam>
internal class BackOfficeUserValidator<T> : UserValidator<T, int>
where T : BackOfficeIdentityUser
{
public BackOfficeUserValidator(UserManager<T, int> manager) : base(manager)
{
}
public override async Task<IdentityResult> ValidateAsync(T item)
{
//Don't validate if the user's email or username hasn't changed otherwise it's just wasting SQL queries.
if (item.IsPropertyDirty("Email") || item.IsPropertyDirty("UserName"))
{
return await base.ValidateAsync(item);
}
return IdentityResult.Success;
}
}
}
@@ -1,8 +1,6 @@
using System;
using Microsoft.AspNet.Identity.Owin;
using Umbraco.Core;
using Umbraco.Web.Composing;
using Umbraco.Core.Models.Identity;
using Umbraco.Web.Models.Identity;
namespace Umbraco.Web.Security
@@ -33,22 +31,22 @@ namespace Umbraco.Web.Security
/// <summary>
/// A callback executed during account auto-linking and before the user is persisted
/// </summary>
public Action<BackOfficeIdentityUser, ExternalLoginInfo> OnAutoLinking { get; set; }
public Action<BackOfficeIdentityUser, ExternalLoginInfo2> OnAutoLinking { get; set; }
/// <summary>
/// A callback executed during every time a user authenticates using an external login.
/// returns a boolean indicating if sign in should continue or not.
/// </summary>
public Func<BackOfficeIdentityUser, ExternalLoginInfo, bool> OnExternalLogin { get; set; }
public Func<BackOfficeIdentityUser, ExternalLoginInfo2, bool> OnExternalLogin { get; set; }
/// <summary>
/// <summary>B
/// The default User group aliases to use for auto-linking users
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="loginInfo"></param>
/// <returns></returns>
public string[] GetDefaultUserGroups(IUmbracoContext umbracoContext, ExternalLoginInfo loginInfo)
public string[] GetDefaultUserGroups(IUmbracoContext umbracoContext, ExternalLoginInfo2 loginInfo)
{
return _defaultUserGroups;
}
@@ -61,7 +59,7 @@ namespace Umbraco.Web.Security
///
/// For public auth providers this should always be false!!!
/// </summary>
public bool ShouldAutoLinkExternalAccount(IUmbracoContext umbracoContext, ExternalLoginInfo loginInfo)
public bool ShouldAutoLinkExternalAccount(IUmbracoContext umbracoContext, ExternalLoginInfo2 loginInfo)
{
return _autoLinkExternalAccount;
}
@@ -71,7 +69,7 @@ namespace Umbraco.Web.Security
/// <summary>
/// The default Culture to use for auto-linking users
/// </summary>
public string GetDefaultCulture(IUmbracoContext umbracoContext, ExternalLoginInfo loginInfo)
public string GetDefaultCulture(IUmbracoContext umbracoContext, ExternalLoginInfo2 loginInfo)
{
return _defaultCulture;
}
@@ -11,7 +11,7 @@ namespace Umbraco.Web.Security
/// </summary>
internal interface IBackOfficeUserManagerMarker
{
BackOfficeUserManager<BackOfficeIdentityUser> GetManager(IOwinContext owin);
BackOfficeUserManager2<BackOfficeIdentityUser> GetManager(IOwinContext owin);
}
/// <summary>
/// This interface is only here due to the fact that IOwinContext Get / Set only work in generics, if they worked
@@ -1,18 +0,0 @@
using System;
using Microsoft.AspNet.Identity;
namespace Umbraco.Core.Security
{
/// <summary>
/// A password hasher that is User aware so that it can process the hashing based on the user's settings
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TUser"></typeparam>
public interface IUserAwarePasswordHasher<in TUser, TKey> : Microsoft.AspNet.Identity.IPasswordHasher
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
string HashPassword(TUser user, string password);
PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword);
}
}
+3 -11
View File
@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNetCore.Identity;
namespace Umbraco.Core.Security
{
@@ -8,16 +7,9 @@ namespace Umbraco.Core.Security
/// An IUserStore interface part to implement if the store supports validating user session Ids
/// </summary>
/// <typeparam name="TUser"></typeparam>
/// <typeparam name="TKey"></typeparam>
public interface IUserSessionStore<TUser, in TKey> : IUserStore<TUser, TKey>, IDisposable
where TUser : class, IUser<TKey>
{
Task<bool> ValidateSessionIdAsync(int userId, string sessionId);
}
public interface IUserSessionStore2<TUser, in TKey> : Microsoft.AspNetCore.Identity.IUserStore<TUser>, IDisposable
public interface IUserSessionStore2<TUser> : IUserStore<TUser>
where TUser : class
{
Task<bool> ValidateSessionIdAsync(int userId, string sessionId);
Task<bool> ValidateSessionIdAsync(string userId, string sessionId); // TODO: SB: Should take a user???
}
}
@@ -2,15 +2,12 @@ using System;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Security.Cookies;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Security;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.Security
@@ -88,16 +85,16 @@ namespace Umbraco.Web.Security
if (validate == false)
return true;
var manager = owinCtx.GetUserManager<BackOfficeUserManager>();
var manager = owinCtx.Get<BackOfficeUserManager2>();
if (manager == null)
return false;
var userId = currentIdentity.GetUserId<int>();
var userId = currentIdentity.GetUserId();
var user = await manager.FindByIdAsync(userId);
if (user == null)
return false;
var sessionId = currentIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
var sessionId = currentIdentity.FindFirst(Constants.Security.SessionIdClaimType)?.Value;
if (await manager.ValidateSessionIdAsync(userId, sessionId) == false)
return false;
@@ -1,47 +0,0 @@
using System;
using Microsoft.AspNet.Identity;
using Umbraco.Web.Models.Identity;
namespace Umbraco.Core.Security
{
/// <summary>
/// The default password hasher that is User aware so that it can process the hashing based on the user's settings
/// </summary>
public class UserAwarePasswordHasher : IUserAwarePasswordHasher<BackOfficeIdentityUser, int>
{
private readonly PasswordSecurity _passwordSecurity;
public UserAwarePasswordHasher(PasswordSecurity passwordSecurity)
{
_passwordSecurity = passwordSecurity;
}
public string HashPassword(string password)
{
return _passwordSecurity.HashPasswordForStorage(password);
}
public string HashPassword(BackOfficeIdentityUser user, string password)
{
// TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089
//NOTE: For now this just falls back to the hashing we are currently using
return HashPassword(password);
}
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
return _passwordSecurity.VerifyPassword(providedPassword, hashedPassword)
? PasswordVerificationResult.Success
: PasswordVerificationResult.Failed;
}
public PasswordVerificationResult VerifyHashedPassword(BackOfficeIdentityUser user, string hashedPassword, string providedPassword)
{
// TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089
//NOTE: For now this just falls back to the hashing we are currently using
return VerifyHashedPassword(hashedPassword, providedPassword);
}
}
}
+8 -10
View File
@@ -1,11 +1,9 @@
using System;
using System.Linq;
using System.Security;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Services;
using Umbraco.Core.Models.Membership;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
@@ -54,17 +52,17 @@ namespace Umbraco.Web.Security
}
}
private BackOfficeSignInManager _signInManager;
private BackOfficeSignInManager SignInManager
private BackOfficeSignInManager2 _signInManager;
private BackOfficeSignInManager2 SignInManager
{
get
{
if (_signInManager == null)
{
var mgr = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().Get<BackOfficeSignInManager>();
var mgr = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().Get<BackOfficeSignInManager2>();
if (mgr == null)
{
throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager) + " from the " + typeof(IOwinContext));
throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager2) + " from the " + typeof(IOwinContext));
}
_signInManager = mgr;
}
@@ -72,9 +70,9 @@ namespace Umbraco.Web.Security
}
}
private BackOfficeUserManager<BackOfficeIdentityUser> _userManager;
protected BackOfficeUserManager<BackOfficeIdentityUser> UserManager
=> _userManager ?? (_userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager());
private BackOfficeUserManager2<BackOfficeIdentityUser> _userManager;
protected BackOfficeUserManager2<BackOfficeIdentityUser> UserManager
=> _userManager ?? (_userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager2());
[Obsolete("This needs to be removed, ASP.NET Identity should always be used for this operation, this is currently only used in the installer which needs to be updated")]
public double PerformLogin(int userId)
@@ -84,7 +82,7 @@ namespace Umbraco.Web.Security
//ensure it's done for owin too
owinCtx.Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType);
var user = UserManager.FindByIdAsync(userId).Result;
var user = UserManager.FindByIdAsync(userId.ToString()).Result;
SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false).Wait();
+1 -7
View File
@@ -200,13 +200,11 @@
<Compile Include="Scheduling\SchedulerComposer.cs" />
<Compile Include="Security\BackOfficeSignInManager2.cs" />
<Compile Include="Security\BackOfficeUserManager2.cs" />
<Compile Include="Security\BackOfficeUserStore.cs" />
<Compile Include="Security\BackOfficeUserManagerMarker.cs" />
<Compile Include="Security\BackOfficeUserStore2.cs" />
<Compile Include="Security\BackOfficeUserValidator.cs" />
<Compile Include="Security\BackOfficeUserValidator2.cs" />
<Compile Include="Security\ConfiguredPasswordValidator.cs" />
<Compile Include="Security\EmailService.cs" />
<Compile Include="Security\IUserAwarePasswordHasher.cs" />
<Compile Include="Security\IUserSessionStore.cs" />
<Compile Include="Security\MembershipProviderBase.cs" />
<Compile Include="Security\MembershipProviderExtensions.cs" />
@@ -214,7 +212,6 @@
<Compile Include="Security\UmbracoBackOfficeIdentity.cs" />
<Compile Include="Security\UmbracoEmailMessage.cs" />
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
<Compile Include="Security\UserAwarePasswordHasher.cs" />
<Compile Include="Security\UserAwarePasswordHasher2.cs" />
<Compile Include="StringExtensions.cs" />
<Compile Include="Trees\TreeCollectionBuilder.cs" />
@@ -230,7 +227,6 @@
<Compile Include="Runtime\WebInitialComposer.cs" />
<Compile Include="Security\ActiveDirectoryBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\BackOfficeClaimsIdentityFactory.cs" />
<Compile Include="Security\BackOfficeUserManagerMarker.cs" />
<Compile Include="Security\BackOfficeUserPasswordCheckerResult.cs" />
<Compile Include="Security\IBackOfficeUserManagerMarker.cs" />
<Compile Include="Security\IBackOfficeUserPasswordChecker.cs" />
@@ -247,8 +243,6 @@
<Compile Include="Mvc\ContainerControllerFactory.cs" />
<Compile Include="OwinExtensions.cs" />
<Compile Include="Security\BackOfficeCookieAuthenticationProvider.cs" />
<Compile Include="Security\BackOfficeSignInManager.cs" />
<Compile Include="Security\BackOfficeUserManager.cs" />
<Compile Include="Security\SessionIdValidator.cs" />
<Compile Include="SignalR\PreviewHubComposer.cs" />
<Compile Include="Trees\FilesTreeController.cs" />
+1 -2
View File
@@ -88,9 +88,8 @@ namespace Umbraco.Web
// (EXPERT: an overload accepts a custom BackOfficeUserStore implementation)
app.ConfigureUserManagerForUmbracoBackOffice(
Services,
Mapper,
UmbracoSettings.Content,
GlobalSettings,
Mapper,
UserPasswordConfig,
IpResolver);
}