Fixed merge conflicts, adds required methods to auth controllers.
This commit is contained in:
@@ -35,7 +35,7 @@ namespace Umbraco.Core.Security
|
||||
public static BackOfficeSignInManager Create(IdentityFactoryOptions<BackOfficeSignInManager> options, IOwinContext context, ILogger logger)
|
||||
{
|
||||
return new BackOfficeSignInManager(
|
||||
context.GetBackOfficeUserManager(),
|
||||
context.GetBackOfficeUserManager(),
|
||||
context.Authentication,
|
||||
logger,
|
||||
context.Request);
|
||||
@@ -48,8 +48,8 @@ namespace Umbraco.Core.Security
|
||||
/// <returns/>
|
||||
public override async Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
|
||||
{
|
||||
var result = await base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout);
|
||||
|
||||
var result = await PasswordSignInAsyncImpl(userName, password, isPersistent, shouldLockout);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case SignInStatus.Success:
|
||||
@@ -69,7 +69,7 @@ namespace Umbraco.Core.Security
|
||||
case SignInStatus.RequiresVerification:
|
||||
_logger.WriteCore(TraceEventType.Information, 0,
|
||||
string.Format(
|
||||
"Login attempt failed for username {0} from IP address {1}, the user requires verification",
|
||||
"Login attempt requires verification for username {0} from IP address {1}",
|
||||
userName,
|
||||
_request.RemoteIpAddress), null, null);
|
||||
break;
|
||||
@@ -87,6 +87,68 @@ namespace Umbraco.Core.Security
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Borrowed from Micorosoft'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 (user == null)
|
||||
{
|
||||
return SignInStatus.Failure;
|
||||
}
|
||||
if (await UserManager.IsLockedOutAsync(user.Id))
|
||||
{
|
||||
return SignInStatus.LockedOut;
|
||||
}
|
||||
if (await UserManager.CheckPasswordAsync(user, password))
|
||||
{
|
||||
await UserManager.ResetAccessFailedCountAsync(user.Id);
|
||||
return await SignInOrTwoFactor(user, isPersistent);
|
||||
}
|
||||
if (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))
|
||||
{
|
||||
return SignInStatus.LockedOut;
|
||||
}
|
||||
}
|
||||
return SignInStatus.Failure;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Borrowed from Micorosoft'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
|
||||
&& await AuthenticationManager.TwoFactorBrowserRememberedAsync(id) == false)
|
||||
{
|
||||
var identity = new ClaimsIdentity(Constants.Security.BackOfficeTwoFactorAuthenticationType);
|
||||
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id));
|
||||
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>
|
||||
@@ -100,11 +162,11 @@ namespace Umbraco.Core.Security
|
||||
|
||||
// Clear any partial cookies from external or two factor partial sign ins
|
||||
AuthenticationManager.SignOut(
|
||||
Constants.Security.BackOfficeExternalAuthenticationType,
|
||||
Constants.Security.BackOfficeExternalAuthenticationType,
|
||||
Constants.Security.BackOfficeTwoFactorAuthenticationType);
|
||||
|
||||
var nowUtc = DateTime.Now.ToUniversalTime();
|
||||
|
||||
|
||||
if (rememberBrowser)
|
||||
{
|
||||
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
|
||||
@@ -133,5 +195,22 @@ namespace Umbraco.Core.Security
|
||||
user.UserName,
|
||||
_request.RemoteIpAddress), null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user id that has been verified already or null.
|
||||
/// </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 -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,42 @@ function authResource($q, $http, umbRequestHelper, angularHelper) {
|
||||
|
||||
return {
|
||||
|
||||
get2FAProviders: function () {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"authenticationApiBaseUrl",
|
||||
"Get2FAProviders")),
|
||||
'Could not retrive two factor provider info');
|
||||
},
|
||||
|
||||
send2FACode: function (provider) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"authenticationApiBaseUrl",
|
||||
"PostSend2FACode"),
|
||||
{
|
||||
provider: provider
|
||||
}),
|
||||
'Could not send code');
|
||||
},
|
||||
|
||||
verify2FACode: function (code) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"authenticationApiBaseUrl",
|
||||
"PostVerify2FACode"),
|
||||
{
|
||||
code: code
|
||||
}),
|
||||
'Could not verify code');
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.authResource#performLogin
|
||||
|
||||
@@ -4,6 +4,7 @@ angular.module('umbraco.services')
|
||||
var currentUser = null;
|
||||
var lastUserId = null;
|
||||
var loginDialog = null;
|
||||
|
||||
//this tracks the last date/time that the user's remainingAuthSeconds was updated from the server
|
||||
// this is used so that we know when to go and get the user's remaining seconds directly.
|
||||
var lastServerTimeoutSet = null;
|
||||
@@ -25,7 +26,7 @@ angular.module('umbraco.services')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onLoginDialogClose(success) {
|
||||
loginDialog = null;
|
||||
@@ -182,8 +183,7 @@ angular.module('umbraco.services')
|
||||
/** Internal method to display the login dialog */
|
||||
_showLoginDialog: function () {
|
||||
openLoginDialog();
|
||||
},
|
||||
|
||||
},
|
||||
/** Returns a promise, sends a request to the server to check if the current cookie is authorized */
|
||||
isAuthenticated: function () {
|
||||
//if we've got a current user then just return true
|
||||
@@ -199,17 +199,18 @@ angular.module('umbraco.services')
|
||||
authenticate: function (login, password) {
|
||||
|
||||
return authResource.performLogin(login, password)
|
||||
.then(function (data) {
|
||||
.then(this.setAuthenticationSuccessful);
|
||||
},
|
||||
setAuthenticationSuccessful:function (data) {
|
||||
|
||||
//when it's successful, return the user data
|
||||
setCurrentUser(data);
|
||||
//when it's successful, return the user data
|
||||
setCurrentUser(data);
|
||||
|
||||
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" };
|
||||
|
||||
//broadcast a global event
|
||||
eventsService.emit("app.authenticated", result);
|
||||
return result;
|
||||
});
|
||||
//broadcast a global event
|
||||
eventsService.emit("app.authenticated", result);
|
||||
return result;
|
||||
},
|
||||
|
||||
/** Logs the user out
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
angular.module("umbraco").controller("Umbraco.Dialogs.LoginController",
|
||||
function ($scope, $cookies, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource) {
|
||||
function ($scope, $cookies, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService) {
|
||||
|
||||
var setFieldFocus = function(form, field) {
|
||||
$timeout(function() {
|
||||
@@ -7,6 +7,23 @@
|
||||
});
|
||||
}
|
||||
|
||||
var twoFactorloginDialog = null;
|
||||
function show2FALoginDialog(view, callback) {
|
||||
if (!twoFactorloginDialog) {
|
||||
twoFactorloginDialog = dialogService.open({
|
||||
|
||||
//very special flag which means that global events cannot close this dialog
|
||||
manualClose: true,
|
||||
template: view,
|
||||
modalClass: "login-overlay",
|
||||
animation: "slide",
|
||||
show: true,
|
||||
callback: callback,
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetInputValidation() {
|
||||
$scope.confirmPassword = "";
|
||||
$scope.password = "";
|
||||
@@ -94,15 +111,24 @@
|
||||
}
|
||||
|
||||
userService.authenticate(login, password)
|
||||
.then(function (data) {
|
||||
$scope.submit(true);
|
||||
}, function (reason) {
|
||||
$scope.errorMsg = reason.errorMsg;
|
||||
.then(function(data) {
|
||||
$scope.submit(true);
|
||||
},
|
||||
function(reason) {
|
||||
|
||||
//set the form inputs to invalid
|
||||
$scope.loginForm.username.$setValidity("auth", false);
|
||||
$scope.loginForm.password.$setValidity("auth", false);
|
||||
});
|
||||
//is Two Factor required?
|
||||
if (reason.status === 402) {
|
||||
$scope.errorMsg = "Additional authentication required";
|
||||
show2FALoginDialog(reason.data.twoFactorView, $scope.submit);
|
||||
}
|
||||
else {
|
||||
$scope.errorMsg = reason.errorMsg;
|
||||
|
||||
//set the form inputs to invalid
|
||||
$scope.loginForm.username.$setValidity("auth", false);
|
||||
$scope.loginForm.password.$setValidity("auth", false);
|
||||
}
|
||||
});
|
||||
|
||||
//setup a watch for both of the model values changing, if they change
|
||||
// while the form is invalid, then revalidate them so that the form can
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace Umbraco.Web.Editors
|
||||
HttpStatusCode.BadRequest,
|
||||
"UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions)));
|
||||
}
|
||||
|
||||
|
||||
var twofactorView = twofactorOptions.GetTwoFactorView(
|
||||
TryGetOwinContext().Result,
|
||||
UmbracoContext,
|
||||
@@ -175,10 +175,13 @@ namespace Umbraco.Web.Editors
|
||||
typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string"));
|
||||
}
|
||||
|
||||
var attemptedUser = Security.GetBackOfficeUser(loginModel.Username);
|
||||
|
||||
//create a with information to display a custom two factor send code view
|
||||
var verifyResponse = Request.CreateResponse(HttpStatusCode.OK, new
|
||||
var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new
|
||||
{
|
||||
twoFactorView = twofactorView
|
||||
twoFactorView = twofactorView,
|
||||
userId = attemptedUser.Id
|
||||
});
|
||||
|
||||
return verifyResponse;
|
||||
@@ -233,25 +236,48 @@ namespace Umbraco.Web.Editors
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private string ConstructCallbackUrl(int userId, string code)
|
||||
/// <summary>
|
||||
/// Used to retrived the 2FA providers for code submission
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[SetAngularAntiForgeryTokens]
|
||||
public async Task<IEnumerable<string>> Get2FAProviders()
|
||||
{
|
||||
// Get an mvc helper to get the url
|
||||
var http = EnsureHttpContext();
|
||||
var urlHelper = new UrlHelper(http.Request.RequestContext);
|
||||
var action = urlHelper.Action("ValidatePasswordResetCode", "BackOffice",
|
||||
new
|
||||
{
|
||||
area = GlobalSettings.UmbracoMvcArea,
|
||||
u = userId,
|
||||
r = code
|
||||
});
|
||||
var userId = await SignInManager.GetVerifiedUserIdAsync();
|
||||
if (userId < 0)
|
||||
{
|
||||
//TODO: Or just return not found?
|
||||
throw new HttpResponseException(
|
||||
Request.CreateValidationErrorResponse("No verified user found"));
|
||||
}
|
||||
var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId);
|
||||
return userFactors;
|
||||
}
|
||||
|
||||
[SetAngularAntiForgeryTokens]
|
||||
public async Task<IHttpActionResult> PostSend2FACode([FromBody]string provider)
|
||||
{
|
||||
// Generate the token and send it
|
||||
if (await SignInManager.SendTwoFactorCodeAsync(provider) == false)
|
||||
{
|
||||
throw new HttpResponseException(
|
||||
Request.CreateValidationErrorResponse("Invalid code"));
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[SetAngularAntiForgeryTokens]
|
||||
public async Task<IHttpActionResult> PostVerify2FACode([FromBody]string code)
|
||||
{
|
||||
// Generate the token and send it
|
||||
if (await SignInManager.SendTwoFactorCodeAsync(code) == false)
|
||||
{
|
||||
throw new HttpResponseException(
|
||||
Request.CreateValidationErrorResponse("Invalid code"));
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// Construct full URL using configured application URL (which will fall back to request)
|
||||
var applicationUri = new Uri(ApplicationContext.UmbracoApplicationUrl);
|
||||
var callbackUri = new Uri(applicationUri, action);
|
||||
return callbackUri.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a set password request. Validates the request and sets a new password.
|
||||
/// </summary>
|
||||
@@ -269,13 +295,6 @@ namespace Umbraco.Web.Editors
|
||||
result.Errors.Any() ? result.Errors.First() : "Set password failed");
|
||||
}
|
||||
|
||||
private HttpContextBase EnsureHttpContext()
|
||||
{
|
||||
var attempt = this.TryGetHttpContext();
|
||||
if (attempt.Success == false)
|
||||
throw new InvalidOperationException("This method requires that an HttpContext be active");
|
||||
return attempt.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the current user out
|
||||
@@ -295,6 +314,35 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
private string ConstructCallbackUrl(int userId, string code)
|
||||
{
|
||||
// Get an mvc helper to get the url
|
||||
var http = EnsureHttpContext();
|
||||
var urlHelper = new UrlHelper(http.Request.RequestContext);
|
||||
var action = urlHelper.Action("ValidatePasswordResetCode", "BackOffice",
|
||||
new
|
||||
{
|
||||
area = GlobalSettings.UmbracoMvcArea,
|
||||
u = userId,
|
||||
r = code
|
||||
});
|
||||
|
||||
// Construct full URL using configured application URL (which will fall back to request)
|
||||
var applicationUri = new Uri(ApplicationContext.UmbracoApplicationUrl);
|
||||
var callbackUri = new Uri(applicationUri, action);
|
||||
return callbackUri.ToString();
|
||||
}
|
||||
|
||||
|
||||
private HttpContextBase EnsureHttpContext()
|
||||
{
|
||||
var attempt = this.TryGetHttpContext();
|
||||
if (attempt.Success == false)
|
||||
throw new InvalidOperationException("This method requires that an HttpContext be active");
|
||||
return attempt.Result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void AddModelErrors(IdentityResult result, string prefix = "")
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user