Compiling with minimal Microsoft.AspNet.Identity references
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user