It builds!
This commit is contained in:
@@ -31,6 +31,7 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Web.Editors.Filters;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
@@ -63,6 +64,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly Core.Hosting.IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IRequestAccessor _requestAccessor;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions;
|
||||
|
||||
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController
|
||||
// TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here
|
||||
@@ -83,7 +85,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
ISmsSender smsSender,
|
||||
Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
IRequestAccessor requestAccessor,
|
||||
LinkGenerator linkGenerator)
|
||||
LinkGenerator linkGenerator,
|
||||
IBackOfficeExternalLoginProviders externalAuthenticationOptions)
|
||||
{
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
_userManager = backOfficeUserManager;
|
||||
@@ -101,6 +104,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_requestAccessor = requestAccessor;
|
||||
_linkGenerator = linkGenerator;
|
||||
_externalAuthenticationOptions = externalAuthenticationOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,6 +127,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// This will also update the security stamp for the user so it can only be used once
|
||||
/// </remarks>
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
[DenyLocalLoginAuthorization]
|
||||
public async Task<ActionResult<UserDisplay>> PostVerifyInvite([FromQuery] int id, [FromQuery] string token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
@@ -154,11 +159,29 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
[UmbracoAuthorize]
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public async Task<ActionResult> PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel)
|
||||
public async Task<IActionResult> PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel)
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(User.Identity.GetUserId());
|
||||
if (user == null) throw new InvalidOperationException("Could not find user");
|
||||
|
||||
ExternalSignInAutoLinkOptions autoLinkOptions = null;
|
||||
var authType = (await _signInManager.GetExternalAuthenticationSchemesAsync())
|
||||
.FirstOrDefault(x => x.Name == unlinkLoginModel.LoginProvider);
|
||||
|
||||
if (authType == null)
|
||||
{
|
||||
_logger.LogWarning("Could not find external authentication provider registered: {LoginProvider}", unlinkLoginModel.LoginProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
autoLinkOptions = _externalAuthenticationOptions.Get(authType.Name);
|
||||
if (!autoLinkOptions.AllowManualLinking)
|
||||
{
|
||||
// If AllowManualLinking is disabled for this provider we cannot unlink
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
var result = await _userManager.RemoveLoginAsync(
|
||||
user,
|
||||
unlinkLoginModel.LoginProvider,
|
||||
@@ -243,6 +266,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// </remarks>
|
||||
[UmbracoAuthorize(redirectToUmbracoLogin: false, requireApproval: false)]
|
||||
[SetAngularAntiForgeryTokens]
|
||||
[DenyLocalLoginAuthorization]
|
||||
public ActionResult<UserDetail> GetCurrentInvitedUser()
|
||||
{
|
||||
var user = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
|
||||
@@ -266,6 +290,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[SetAngularAntiForgeryTokens]
|
||||
[DenyLocalLoginAuthorization]
|
||||
public async Task<UserDetail> PostLogin(LoginModel loginModel)
|
||||
{
|
||||
// Sign the user in with username/password, this also gives a chance for developers to
|
||||
@@ -332,6 +357,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[SetAngularAntiForgeryTokens]
|
||||
[DenyLocalLoginAuthorization]
|
||||
public async Task<IActionResult> PostRequestPasswordReset(RequestPasswordResetModel model)
|
||||
{
|
||||
// If this feature is switched off in configuration the UI will be amended to not make the request to reset password available.
|
||||
@@ -546,11 +572,19 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public IActionResult PostLogout()
|
||||
{
|
||||
HttpContext.SignOutAsync(Core.Constants.Security.BackOfficeAuthenticationType);
|
||||
HttpContext.SignOutAsync(Constants.Security.BackOfficeAuthenticationType);
|
||||
|
||||
_logger.LogInformation("User {UserName} from IP address {RemoteIpAddress} has logged out", User.Identity == null ? "UNKNOWN" : User.Identity.Name, HttpContext.Connection.RemoteIpAddress);
|
||||
|
||||
_userManager.RaiseLogoutSuccessEvent(User, int.Parse(User.Identity.GetUserId()));
|
||||
var userId = int.Parse(User.Identity.GetUserId());
|
||||
var args = _userManager.RaiseLogoutSuccessEvent(User, userId);
|
||||
if (!args.SignOutRedirectUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new ObjectResult(new
|
||||
{
|
||||
signOutRedirectUrl = args.SignOutRedirectUrl
|
||||
});
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -19,6 +19,7 @@ using Umbraco.Web.BackOffice.Profiling;
|
||||
using Umbraco.Web.BackOffice.PropertyEditors;
|
||||
using Umbraco.Web.BackOffice.Routing;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Security;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Features;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -44,7 +45,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly RuntimeSettings _runtimeSettings;
|
||||
private readonly SecuritySettings _securitySettings;
|
||||
private readonly IRuntimeMinifier _runtimeMinifier;
|
||||
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
|
||||
private readonly IBackOfficeExternalLoginProviders _externalLogins;
|
||||
private readonly IImageUrlGenerator _imageUrlGenerator;
|
||||
private readonly PreviewRoutes _previewRoutes;
|
||||
|
||||
@@ -61,7 +62,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
IOptions<RuntimeSettings> runtimeSettings,
|
||||
IOptions<SecuritySettings> securitySettings,
|
||||
IRuntimeMinifier runtimeMinifier,
|
||||
IAuthenticationSchemeProvider authenticationSchemeProvider,
|
||||
IBackOfficeExternalLoginProviders externalLogins,
|
||||
IImageUrlGenerator imageUrlGenerator,
|
||||
PreviewRoutes previewRoutes)
|
||||
{
|
||||
@@ -77,7 +78,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_runtimeSettings = runtimeSettings.Value;
|
||||
_securitySettings = securitySettings.Value;
|
||||
_runtimeMinifier = runtimeMinifier;
|
||||
_authenticationSchemeProvider = authenticationSchemeProvider;
|
||||
_externalLogins = externalLogins;
|
||||
_imageUrlGenerator = imageUrlGenerator;
|
||||
_previewRoutes = previewRoutes;
|
||||
}
|
||||
@@ -419,19 +420,11 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
"externalLogins", new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"providers", (await _authenticationSchemeProvider.GetAllSchemesAsync())
|
||||
// Filter only external providers
|
||||
.Where(x => !x.DisplayName.IsNullOrWhiteSpace())
|
||||
// TODO: We need to filter only back office enabled schemes.
|
||||
// Before we used to have a property bag to check, now we don't so need to investigate the easiest/best
|
||||
// way to do this. We have the type so maybe we check for a marker interface, but maybe there's another way,
|
||||
// just need to investigate.
|
||||
//.Where(p => p.Properties.ContainsKey("UmbracoBackOffice"))
|
||||
"providers", _externalLogins.GetBackOfficeProviders()
|
||||
.Select(p => new
|
||||
{
|
||||
authType = p.Name, caption = p.DisplayName,
|
||||
// TODO: See above, if we need this property bag in the vars then we'll need to figure something out
|
||||
// properties = p.Properties
|
||||
authType = p.AuthenticationType, caption = p.Name,
|
||||
properties = p.Properties
|
||||
})
|
||||
.ToArray()
|
||||
}
|
||||
|
||||
@@ -201,10 +201,10 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
[AppendUserModifiedHeader]
|
||||
public async Task<IActionResult> PostSetAvatar(IList<IFormFile> files)
|
||||
public IActionResult PostSetAvatar(IList<IFormFile> files)
|
||||
{
|
||||
//borrow the logic from the user controller
|
||||
return await UsersController.PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
|
||||
return UsersController.PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -216,6 +216,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// </returns>
|
||||
public async Task<ModelWithNotifications<string>> PostChangePassword(ChangingPasswordModel data)
|
||||
{
|
||||
// TODO: Why don't we inject this? Then we can just inject a logger
|
||||
var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger<PasswordChanger>());
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, data, _backOfficeUserManager);
|
||||
|
||||
@@ -240,7 +241,15 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
public async Task<Dictionary<string, string>> GetCurrentUserLinkedLogins()
|
||||
{
|
||||
var identityUser = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0).ToString());
|
||||
return identityUser.Logins.ToDictionary(x => x.LoginProvider, x => x.ProviderKey);
|
||||
|
||||
// deduplicate in case there are duplicates (there shouldn't be now since we have a unique constraint on the external logins
|
||||
// but there didn't used to be)
|
||||
var result = new Dictionary<string, string>();
|
||||
foreach (var l in identityUser.Logins)
|
||||
{
|
||||
result[l.LoginProvider] = l.ProviderKey;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
internal class DenyLocalLoginAuthorizationAttribute : AuthorizeAttribute
|
||||
{
|
||||
protected override bool IsAuthorized(HttpActionContext actionContext)
|
||||
{
|
||||
var owinContext = actionContext.Request.TryGetOwinContext().Result;
|
||||
|
||||
// no authorization if any external logins deny local login
|
||||
return !owinContext.Authentication.HasDenyLocalLogin();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,13 +38,12 @@ using Umbraco.Web.Common.ActionResults;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Exceptions;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.Common.ActionsResults;
|
||||
using Umbraco.Web.Common.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
@@ -72,9 +71,11 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IBackOfficeUserManager _backOfficeUserManager;
|
||||
private readonly IBackOfficeUserManager _userManager;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly IBackOfficeExternalLoginProviders _externalLogins;
|
||||
private readonly ILogger<UsersController> _logger;
|
||||
|
||||
public UsersController(
|
||||
IMediaFileSystem mediaFileSystem,
|
||||
@@ -97,7 +98,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IBackOfficeUserManager backOfficeUserManager,
|
||||
ILoggerFactory loggerFactory,
|
||||
LinkGenerator linkGenerator)
|
||||
LinkGenerator linkGenerator,
|
||||
IBackOfficeExternalLoginProviders externalLogins)
|
||||
{
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
_contentSettings = contentSettings.Value;
|
||||
@@ -117,9 +119,11 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_mediaService = mediaService;
|
||||
_contentService = contentService;
|
||||
_globalSettings = globalSettings.Value;
|
||||
_backOfficeUserManager = backOfficeUserManager;
|
||||
_userManager = backOfficeUserManager;
|
||||
_loggerFactory = loggerFactory;
|
||||
_linkGenerator = linkGenerator;
|
||||
_externalLogins = externalLogins;
|
||||
_logger = _loggerFactory.CreateLogger<UsersController>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -137,12 +141,12 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
[AppendUserModifiedHeader("id")]
|
||||
[AdminUsersAuthorize]
|
||||
public async Task<IActionResult> PostSetAvatar(int id, IList<IFormFile> files)
|
||||
public IActionResult PostSetAvatar(int id, IList<IFormFile> files)
|
||||
{
|
||||
return await PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, id);
|
||||
return PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, id);
|
||||
}
|
||||
|
||||
internal static async Task<IActionResult> PostSetAvatarInternal(IList<IFormFile> files, IUserService userService, IAppCache cache, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper, ContentSettings contentSettings, IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, int id)
|
||||
internal static IActionResult PostSetAvatarInternal(IList<IFormFile> files, IUserService userService, IAppCache cache, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper, ContentSettings contentSettings, IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, int id)
|
||||
{
|
||||
if (files is null)
|
||||
{
|
||||
@@ -375,16 +379,16 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage);
|
||||
identityUser.Name = userSave.Name;
|
||||
|
||||
var created = await _backOfficeUserManager.CreateAsync(identityUser);
|
||||
var created = await _userManager.CreateAsync(identityUser);
|
||||
if (created.Succeeded == false)
|
||||
{
|
||||
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
|
||||
}
|
||||
|
||||
string resetPassword;
|
||||
var password = _backOfficeUserManager.GeneratePassword();
|
||||
var password = _userManager.GeneratePassword();
|
||||
|
||||
var result = await _backOfficeUserManager.AddPasswordAsync(identityUser, password);
|
||||
var result = await _userManager.AddPasswordAsync(identityUser, password);
|
||||
if (result.Succeeded == false)
|
||||
{
|
||||
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
|
||||
@@ -416,7 +420,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <remarks>
|
||||
/// This will email the user an invite and generate a token that will be validated in the email
|
||||
/// </remarks>
|
||||
public async Task<UserDisplay> PostInviteUser(UserInvite userSave)
|
||||
public async Task<ActionResult<UserDisplay>> PostInviteUser(UserInvite userSave)
|
||||
{
|
||||
if (userSave == null) throw new ArgumentNullException("userSave");
|
||||
|
||||
@@ -425,7 +429,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
if (ModelState.IsValid == false)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
|
||||
return new ValidationErrorResult(ModelState);
|
||||
}
|
||||
|
||||
IUser user;
|
||||
@@ -441,12 +445,9 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
|
||||
|
||||
var userMgr = TryGetOwinContext().Result.GetBackOfficeUserManager();
|
||||
|
||||
if (!EmailSender.CanSendRequiredEmail(GlobalSettings) && !userMgr.HasSendingUserInviteEventHandler)
|
||||
if (!EmailSender.CanSendRequiredEmail(_globalSettings) && !_userManager.HasSendingUserInviteEventHandler)
|
||||
{
|
||||
throw new HttpResponseException(
|
||||
Request.CreateNotificationValidationErrorResponse("No Email server is configured"));
|
||||
return new ValidationErrorResult("No Email server is configured");
|
||||
}
|
||||
|
||||
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
||||
@@ -454,7 +455,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
var canSaveUser = authHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups);
|
||||
if (canSaveUser == false)
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.Unauthorized, canSaveUser.Result);
|
||||
return new ValidationErrorResult(canSaveUser.Result, StatusCodes.Status401Unauthorized);
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
@@ -464,10 +465,10 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage);
|
||||
identityUser.Name = userSave.Name;
|
||||
|
||||
var created = await _backOfficeUserManager.CreateAsync(identityUser);
|
||||
var created = await _userManager.CreateAsync(identityUser);
|
||||
if (created.Succeeded == false)
|
||||
{
|
||||
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
|
||||
return ValidationErrorResult.CreateNotificationValidationErrorResult(created.Errors.ToErrorMessage());
|
||||
}
|
||||
|
||||
//now re-look the user back up
|
||||
@@ -484,21 +485,16 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_userService.Save(user);
|
||||
var display = _umbracoMapper.Map<UserDisplay>(user);
|
||||
|
||||
var inviteArgs = new UserInviteEventArgs(
|
||||
Request.TryGetHttpContext().Result.GetCurrentRequestIpAddress(),
|
||||
performingUser: Security.GetUserId().Result,
|
||||
userSave,
|
||||
user);
|
||||
UserInviteEventArgs inviteArgs;
|
||||
|
||||
try
|
||||
{
|
||||
userMgr.RaiseSendingUserInvite(inviteArgs);
|
||||
inviteArgs = _userManager.RaiseSendingUserInvite(User, userSave, user);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error<UsersController>(ex, "An error occured in a custom event handler while inviting the user");
|
||||
throw new HttpResponseException(
|
||||
Request.CreateNotificationValidationErrorResponse($"An error occured inviting the user (check logs for more info): {ex.Message}"));
|
||||
_logger.LogError(ex, "An error occured in a custom event handler while inviting the user");
|
||||
return ValidationErrorResult.CreateNotificationValidationErrorResult($"An error occured inviting the user (check logs for more info): {ex.Message}");
|
||||
}
|
||||
|
||||
// If the event is handled then no need to send the email
|
||||
@@ -553,8 +549,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, string fromEmail, IUser to, string message)
|
||||
{
|
||||
var user = await _backOfficeUserManager.FindByIdAsync(((int) userDisplay.Id).ToString());
|
||||
var token = await _backOfficeUserManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
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,
|
||||
@@ -625,8 +621,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
var hasErrors = false;
|
||||
|
||||
// we need to check if there's any Deny Local login providers present, if so we need to ensure that the user's email address cannot be changed
|
||||
var owinContext = Request.TryGetOwinContext().Result;
|
||||
var hasDenyLocalLogin = owinContext.Authentication.HasDenyLocalLogin();
|
||||
var hasDenyLocalLogin = _externalLogins.HasDenyLocalLogin();
|
||||
if (hasDenyLocalLogin)
|
||||
{
|
||||
userSave.Email = found.Email; // it cannot change, this would only happen if people are mucking around with the request
|
||||
@@ -706,8 +701,9 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
// TODO: Why don't we inject this? Then we can just inject a logger
|
||||
var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger<PasswordChanger>());
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, changingPasswordModel, _backOfficeUserManager);
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, changingPasswordModel, _userManager);
|
||||
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
@@ -792,14 +788,14 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
foreach (var u in userIds)
|
||||
{
|
||||
var user = await _backOfficeUserManager.FindByIdAsync(u.ToString());
|
||||
var user = await _userManager.FindByIdAsync(u.ToString());
|
||||
if (user == null)
|
||||
{
|
||||
notFound.Add(u);
|
||||
continue;
|
||||
}
|
||||
|
||||
var unlockResult = await _backOfficeUserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
|
||||
var unlockResult = await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
|
||||
if (unlockResult.Succeeded == false)
|
||||
{
|
||||
throw HttpResponseException.CreateValidationErrorResponse(
|
||||
|
||||
Reference in New Issue
Block a user