Fixed merge conflicts, adds required methods to auth controllers.

This commit is contained in:
Shannon
2017-02-02 22:11:34 +11:00
parent fa56f1a59d
commit 5060e709d1
5 changed files with 242 additions and 52 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = "")
{