It builds!

This commit is contained in:
Shannon
2020-10-23 14:18:53 +11:00
parent 1400a02798
commit 64d8b56eca
80 changed files with 601 additions and 2356 deletions

View File

@@ -31,6 +31,7 @@ using Umbraco.Web.WebAssets;
using Constants = Umbraco.Core.Constants;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
using Umbraco.Web.Security;
namespace Umbraco.Web.BackOffice.Controllers
{
@@ -50,7 +51,7 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly ILogger<BackOfficeController> _logger;
private readonly IJsonSerializer _jsonSerializer;
private readonly IExternalAuthenticationOptions _externalAuthenticationOptions;
private readonly IBackOfficeExternalLoginProviders _externalLogins;
public BackOfficeController(
IBackOfficeUserManager userManager,
@@ -65,7 +66,7 @@ namespace Umbraco.Web.BackOffice.Controllers
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
ILogger<BackOfficeController> logger,
IJsonSerializer jsonSerializer,
IExternalAuthenticationOptions externalAuthenticationOptions)
IBackOfficeExternalLoginProviders externalLogins)
{
_userManager = userManager;
_runtimeMinifier = runtimeMinifier;
@@ -79,7 +80,7 @@ namespace Umbraco.Web.BackOffice.Controllers
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_logger = logger;
_jsonSerializer = jsonSerializer;
_externalAuthenticationOptions = externalAuthenticationOptions;
_externalLogins = externalLogins;
}
[HttpGet]
@@ -260,6 +261,9 @@ namespace Umbraco.Web.BackOffice.Controllers
}
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
// TODO: I believe we will have to fill in our own XsrfKey like we use to do since I think
// we validate against that key?
// see https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/src/Umbraco.Web/Editors/ChallengeResult.cs#L48
return Challenge(properties, provider);
}
@@ -275,6 +279,9 @@ namespace Umbraco.Web.BackOffice.Controllers
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Action(nameof(ExternalLinkLoginCallback), this.GetControllerName());
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, User.Identity.GetUserId());
// TODO: I believe we will have to fill in our own XsrfKey like we use to do since I think
// we validate against that key?
// see https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/src/Umbraco.Web/Editors/ChallengeResult.cs#L48
return Challenge(properties, provider);
}
@@ -305,6 +312,11 @@ namespace Umbraco.Web.BackOffice.Controllers
[HttpGet]
public async Task<IActionResult> ExternalLinkLoginCallback()
{
// TODO: Do we need/want to tell it an expected xsrf.
// In v8 the xsrf used to be set to the user id which was verified manually, in this case I think we don't specify
// the key and that is up to the underlying sign in manager to set so we'd just tell it to expect the user id,
// the XSRF value used to be set in our ChallengeResult but now we don't have that so this needs to be set in the
// BackOfficeController when we issue a Challenge, see TODO notes there.
var loginInfo = await _signInManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
@@ -322,8 +334,8 @@ namespace Umbraco.Web.BackOffice.Controllers
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
var result2 = await _userManager.AddLoginAsync(user, loginInfo);
if (result2.Succeeded)
var addLoginResult = await _userManager.AddLoginAsync(user, loginInfo);
if (addLoginResult.Succeeded)
{
// Update any authentication tokens if login succeeded
// TODO: This is a new thing that we need to implement and because we can store data with the external login now, this is exactly
@@ -334,7 +346,7 @@ namespace Umbraco.Web.BackOffice.Controllers
}
//Add errors and redirect for it to be displayed
TempData[ViewDataExtensions.TokenExternalSignInError] = result2.Errors;
TempData[ViewDataExtensions.TokenExternalSignInError] = addLoginResult.Errors;
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
@@ -352,15 +364,27 @@ namespace Umbraco.Web.BackOffice.Controllers
ViewData.SetUmbracoPath(_globalSettings.GetUmbracoMvcArea(_hostingEnvironment));
//check if there is the TempData with the any token name specified, if so, assign to view bag and render the view
if (ViewData.FromTempData(TempData, ViewDataExtensions.TokenExternalSignInError) ||
//check if there is the TempData or cookies with the any token name specified, if so, assign to view bag and render the view
if (ViewData.FromBase64CookieData<BackOfficeExternalLoginProviderErrors>(HttpContext, ViewDataExtensions.TokenExternalSignInError, _jsonSerializer) ||
ViewData.FromTempData(TempData, ViewDataExtensions.TokenExternalSignInError) ||
ViewData.FromTempData(TempData, ViewDataExtensions.TokenPasswordResetCode))
return defaultResponse();
//First check if there's external login info, if there's not proceed as normal
var loginInfo = await _signInManager.GetExternalLoginInfoAsync();
if (loginInfo == null || loginInfo.Principal == null)
{
// if the user is not logged in, check if there's any auto login redirects specified
if (!_backofficeSecurityAccessor.BackOfficeSecurity.ValidateCurrentUser())
{
var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider();
if (!oauthRedirectAuthProvider.IsNullOrWhiteSpace())
{
return ExternalLogin(oauthRedirectAuthProvider);
}
}
return defaultResponse();
}
@@ -383,7 +407,7 @@ namespace Umbraco.Web.BackOffice.Controllers
}
else
{
autoLinkOptions = _externalAuthenticationOptions.Get(authType.Name);
autoLinkOptions = _externalLogins.Get(authType.Name);
}
// Sign in the user with this external login provider if the user already has a login
@@ -391,12 +415,6 @@ namespace Umbraco.Web.BackOffice.Controllers
var user = await _userManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
if (user != null)
{
// TODO: It might be worth keeping some of the claims associated with the ExternalLoginInfo, in which case we
// wouldn't necessarily sign the user in here with the standard login, instead we'd update the
// UseUmbracoBackOfficeExternalCookieAuthentication extension method to have the correct provider and claims factory,
// ticket format, etc.. to create our back office user including the claims assigned and in this method we'd just ensure
// that the ticket is created and stored and that the user is logged in.
var shouldSignIn = true;
if (autoLinkOptions != null && autoLinkOptions.OnExternalLogin != null)
{
@@ -417,7 +435,10 @@ namespace Umbraco.Web.BackOffice.Controllers
{
if (await AutoLinkAndSignInExternalAccount(loginInfo, autoLinkOptions) == false)
{
ViewData.SetExternalSignInError(new[] { "The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account" });
ViewData.SetExternalSignInProviderErrors(
new BackOfficeExternalLoginProviderErrors(
loginInfo.LoginProvider,
new[] { "The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account" }));
}
//Remove the cookie otherwise this message will keep appearing
@@ -440,7 +461,10 @@ namespace Umbraco.Web.BackOffice.Controllers
//we are allowing auto-linking/creating of local accounts
if (email.IsNullOrWhiteSpace())
{
ViewData.SetExternalSignInError(new[] { $"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked." });
ViewData.SetExternalSignInProviderErrors(
new BackOfficeExternalLoginProviderErrors(
loginInfo.LoginProvider,
new[] { $"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked." }));
}
else
{
@@ -448,13 +472,26 @@ namespace Umbraco.Web.BackOffice.Controllers
var autoLinkUser = await _userManager.FindByEmailAsync(email);
if (autoLinkUser != null)
{
// TODO This will be filled out with 8.9 changes
throw new NotImplementedException("Merge 8.9 changes in!");
try
{
//call the callback if one is assigned
autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider);
ViewData.SetExternalSignInProviderErrors(
new BackOfficeExternalLoginProviderErrors(
loginInfo.LoginProvider,
new[] { "Could not link login provider " + loginInfo.LoginProvider + ". " + ex.Message }));
return true;
}
await LinkUser(autoLinkUser, loginInfo);
}
else
{
var name = loginInfo.Principal?.Identity?.Name;
if (name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null");
autoLinkUser = BackOfficeIdentityUser.CreateNew(_globalSettings, email, email, autoLinkOptions.GetUserAutoLinkCulture(_globalSettings), name);
@@ -465,40 +502,76 @@ namespace Umbraco.Web.BackOffice.Controllers
}
//call the callback if one is assigned
autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo);
try
{
autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider);
ViewData.SetExternalSignInProviderErrors(
new BackOfficeExternalLoginProviderErrors(
loginInfo.LoginProvider,
new[] { "Could not link login provider " + loginInfo.LoginProvider + ". " + ex.Message }));
return true;
}
var userCreationResult = await _userManager.CreateAsync(autoLinkUser);
if (userCreationResult.Succeeded == false)
{
ViewData.SetExternalSignInError(userCreationResult.Errors.Select(x => x.Description).ToList());
ViewData.SetExternalSignInProviderErrors(
new BackOfficeExternalLoginProviderErrors(
loginInfo.LoginProvider,
userCreationResult.Errors.Select(x => x.Description).ToList()));
}
else
{
var linkResult = await _userManager.AddLoginAsync(autoLinkUser, loginInfo);
if (linkResult.Succeeded == false)
{
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).Select(x => x.Description).ToList());
}
}
else
{
//sign in
await _signInManager.SignInAsync(autoLinkUser, isPersistent: false);
}
await LinkUser(autoLinkUser, loginInfo);
}
}
}
return true;
}
private async Task LinkUser(BackOfficeIdentityUser autoLinkUser, ExternalLoginInfo loginInfo)
{
var existingLogins = await _userManager.GetLoginsAsync(autoLinkUser);
var exists = existingLogins.FirstOrDefault(x => x.LoginProvider == loginInfo.LoginProvider && x.ProviderKey == loginInfo.ProviderKey);
// if it already exists (perhaps it was added in the AutoLink callbak) then we just continue
if (exists != null)
{
//sign in
await _signInManager.SignInAsync(autoLinkUser, isPersistent: false);
return;
}
var linkResult = await _userManager.AddLoginAsync(autoLinkUser, loginInfo);
if (linkResult.Succeeded)
{
//we're good! sign in
await _signInManager.SignInAsync(autoLinkUser, isPersistent: false);
return;
}
ViewData.SetExternalSignInProviderErrors(
new BackOfficeExternalLoginProviderErrors(
loginInfo.LoginProvider,
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)
{
//DOH! ... this isn't good, combine all errors to be shown
ViewData.SetExternalSignInProviderErrors(
new BackOfficeExternalLoginProviderErrors(
loginInfo.LoginProvider,
linkResult.Errors.Concat(deleteResult.Errors).Select(x => x.Description).ToList()));
}
}
// Used for XSRF protection when adding external logins
// TODO: This is duplicated in BackOfficeSignInManager
private const string XsrfKey = "XsrfId";