Moves some files, adds notes, starts poc for back office login providers

This commit is contained in:
Shannon
2020-11-26 19:24:55 +11:00
parent 87b6c4852f
commit e01abf2802
17 changed files with 180 additions and 91 deletions

View File

@@ -21,6 +21,7 @@ using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Net;
using Umbraco.Web.BackOffice.Filters;
using Umbraco.Web.BackOffice.Security;
using Umbraco.Web.Common.ActionsResults;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Controllers;
@@ -163,7 +164,6 @@ namespace Umbraco.Web.BackOffice.Controllers
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);
@@ -173,11 +173,18 @@ namespace Umbraco.Web.BackOffice.Controllers
}
else
{
autoLinkOptions = _externalAuthenticationOptions.Get(authType.Name);
if (!autoLinkOptions.AllowManualLinking)
var opt = _externalAuthenticationOptions.Get(authType.Name);
if (opt == null)
{
// If AllowManualLinking is disabled for this provider we cannot unlink
return BadRequest();
return BadRequest($"Could not find external authentication options registered for provider {unlinkLoginModel.LoginProvider}");
}
else
{
if (!opt.AutoLinkOptions.AllowManualLinking)
{
// If AllowManualLinking is disabled for this provider we cannot unlink
return BadRequest();
}
}
}
@@ -243,7 +250,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// </remarks>
[UmbracoBackOfficeAuthorize]
[SetAngularAntiForgeryTokens]
//[CheckIfUserTicketDataIsStale] // TODO: Migrate this, though it will need to be done differently at the cookie auth level
[CheckIfUserTicketDataIsStale]
public UserDetail GetCurrentUser()
{
var user = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;

View File

@@ -34,10 +34,11 @@ using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Umbraco.Web.Security;
using Umbraco.Web.BackOffice.Security;
namespace Umbraco.Web.BackOffice.Controllers
{
[DisableBrowserCache] //TODO Reintroduce
[DisableBrowserCache]
//[UmbracoRequireHttps] //TODO Reintroduce
[PluginController(Constants.Web.Mvc.BackOfficeArea)]
public class BackOfficeController : UmbracoController
@@ -413,7 +414,7 @@ namespace Umbraco.Web.BackOffice.Controllers
}
else
{
autoLinkOptions = _externalLogins.Get(authType.Name);
autoLinkOptions = _externalLogins.Get(authType.Name)?.AutoLinkOptions;
}
// Sign in the user with this external login provider if the user already has a login
@@ -460,7 +461,9 @@ namespace Umbraco.Web.BackOffice.Controllers
return false;
if (autoLinkOptions.AutoLinkExternalAccount == false)
{
return true; // TODO: This seems weird to return true, but it was like that before so must be a reason?
}
var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email);
@@ -578,10 +581,6 @@ namespace Umbraco.Web.BackOffice.Controllers
}
}
// Used for XSRF protection when adding external logins
// TODO: This is duplicated in BackOfficeSignInManager
private const string XsrfKey = "XsrfId";
private IActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))

View File

@@ -17,9 +17,9 @@ using Umbraco.Web.BackOffice.HealthCheck;
using Umbraco.Web.BackOffice.Profiling;
using Umbraco.Web.BackOffice.PropertyEditors;
using Umbraco.Web.BackOffice.Routing;
using Umbraco.Web.BackOffice.Security;
using Umbraco.Web.BackOffice.Trees;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Security;
using Umbraco.Web.Features;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Trees;
@@ -422,7 +422,7 @@ namespace Umbraco.Web.BackOffice.Controllers
.Select(p => new
{
authType = p.AuthenticationType, caption = p.Name,
properties = p.Properties
properties = p.Options
})
.ToArray()
}

View File

@@ -41,7 +41,6 @@ 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
{

View File

@@ -62,7 +62,7 @@ namespace Umbraco.Extensions
services.TryAddScoped<BackOfficeLookupNormalizer>();
services.TryAddScoped<BackOfficeIdentityErrorDescriber>();
services.TryAddScoped<IIpResolver, AspNetCoreIpResolver>();
services.TryAddSingleton<IBackOfficeExternalLoginProviders, NopBackOfficeExternalLoginProviders>();
services.TryAddSingleton<IBackOfficeExternalLoginProviders, BackOfficeExternalLoginProviders>();
/*
* IdentityBuilderExtensions.AddUserManager adds UserManager<BackOfficeIdentityUser> to service collection

View File

@@ -13,13 +13,13 @@ using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Hosting;
using Umbraco.Core.WebAssets;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.Common.Security;
using Umbraco.Web.Features;
using Umbraco.Web.Models;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebAssets;
using Umbraco.Core;
using Umbraco.Core.Security;
using Umbraco.Web.BackOffice.Security;
namespace Umbraco.Extensions
{
@@ -75,7 +75,7 @@ namespace Umbraco.Extensions
{
authType = p.AuthenticationType,
caption = p.Name,
properties = p.Properties
properties = p.Options
})
.ToArray();

View File

@@ -31,6 +31,14 @@ namespace Umbraco.Extensions
{
builder.Services.AddAntiforgery();
builder.Services.AddSingleton<IFilterProvider, OverrideAuthorizationFilterProvider>();
// TODO: We need to see if we are 'allowed' to do this, the docs say:
// "The call to AddIdentity configures the default scheme settings. The AddAuthentication(String) overload sets the DefaultScheme property. The AddAuthentication(Action<AuthenticationOptions>) overload allows configuring authentication options, which can be used to set up default authentication schemes for different purposes. Subsequent calls to AddAuthentication override previously configured AuthenticationOptions properties."
// So if someone calls services.AddAuthentication() ... in Startup does that overwrite all of this?
// It also says "When the app requires multiple providers, chain the provider extension methods behind AddAuthentication"
// Which leads me to believe it all gets overwritten? :/
// UPDATE: I have tested this breifly in Startup doing Services.AddAuthentication().AddGoogle() ... and the back office auth
// still seems to work. We'll see how it goes i guess.
builder.Services
.AddAuthentication(Core.Constants.Security.BackOfficeAuthenticationType)
.AddCookie(Core.Constants.Security.BackOfficeAuthenticationType)

View File

@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using Umbraco.Web.Common.Security;
using Umbraco.Web.BackOffice.Security;
namespace Umbraco.Web.Editors.Filters
{

View File

@@ -1,23 +1,46 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Web.Common.Security
namespace Umbraco.Web.BackOffice.Security
{
// TODO: This is only for the back office, does it need to be in common?
/// <summary>
/// Options used to configure back office external login providers
/// </summary>
public class BackOfficeExternalLoginProviderOptions
{
public BackOfficeExternalLoginProviderOptions(
string style, string icon, string callbackPath,
ExternalSignInAutoLinkOptions autoLinkOptions = null,
bool denyLocalLogin = false,
bool autoRedirectLoginToExternalProvider = false,
string customBackOfficeView = null)
{
Style = style;
Icon = icon;
CallbackPath = callbackPath;
AutoLinkOptions = autoLinkOptions ?? new ExternalSignInAutoLinkOptions();
DenyLocalLogin = denyLocalLogin;
AutoRedirectLoginToExternalProvider = autoRedirectLoginToExternalProvider;
CustomBackOfficeView = customBackOfficeView;
}
public string Style { get; }
public string Icon { get; }
public string CallbackPath { get; }
/// <summary>
/// Options used to control how users can be auto-linked/created/updated based on the external login provider
/// </summary>
public ExternalSignInAutoLinkOptions AutoLinkOptions { get; set; } = new ExternalSignInAutoLinkOptions();
public ExternalSignInAutoLinkOptions AutoLinkOptions { get; }
/// <summary>
/// When set to true will disable all local user login functionality
/// </summary>
public bool DenyLocalLogin { get; set; }
public bool DenyLocalLogin { get; }
/// <summary>
/// When specified this will automatically redirect to the OAuth login provider instead of prompting the user to click on the OAuth button first.
@@ -26,7 +49,7 @@ namespace Umbraco.Web.Common.Security
/// This is generally used in conjunction with <see cref="DenyLocalLogin"/>. If more than one OAuth provider specifies this, the last registered
/// provider's redirect settings will win.
/// </remarks>
public bool AutoRedirectLoginToExternalProvider { get; set; }
public bool AutoRedirectLoginToExternalProvider { get; }
/// <summary>
/// A virtual path to a custom angular view that is used to replace the entire UI that renders the external login button that the user interacts with
@@ -35,6 +58,6 @@ namespace Umbraco.Web.Common.Security
/// If this view is specified it is 100% up to the user to render the html responsible for rendering the link/un-link buttons along with showing any errors
/// that occur. This overrides what Umbraco normally does by default.
/// </remarks>
public string CustomBackOfficeView { get; set; }
public string CustomBackOfficeView { get; }
}
}

View File

@@ -14,6 +14,8 @@ using Umbraco.Extensions;
namespace Umbraco.Web.Common.Security
{
// TODO: This is only for the back office, does it need to be in common?
using Constants = Umbraco.Core.Constants;
// TODO: There's potential to extract an interface for this for only what we use and put that in Core without aspnetcore refs, but we need to wait till were done with it since there's a bit to implement
@@ -23,7 +25,7 @@ namespace Umbraco.Web.Common.Security
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
private const string LoginProviderKey = "LoginProvider";
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
private const string XsrfKey = "XsrfId"; // TODO: See BackOfficeController.XsrfKey
private const string XsrfKey = "XsrfId";
private BackOfficeUserManager _userManager;

View File

@@ -5,8 +5,9 @@ using Umbraco.Core.BackOffice;
using Umbraco.Core.Configuration.Models;
using SecurityConstants = Umbraco.Core.Constants.Security;
namespace Umbraco.Web.Common.Security
namespace Umbraco.Web.BackOffice.Security
{
// TODO: This is only for the back office, does it need to be in common?
/// <summary>
/// Options used to configure auto-linking external OAuth providers
@@ -22,10 +23,12 @@ namespace Umbraco.Web.Common.Security
public ExternalSignInAutoLinkOptions(
bool autoLinkExternalAccount = false,
string[] defaultUserGroups = null,
string defaultCulture = null)
string defaultCulture = null,
bool allowManualLinking = true)
{
DefaultUserGroups = defaultUserGroups ?? new[] { SecurityConstants.EditorGroupAlias };
AutoLinkExternalAccount = autoLinkExternalAccount;
AllowManualLinking = allowManualLinking;
_defaultCulture = defaultCulture;
}
@@ -33,7 +36,7 @@ namespace Umbraco.Web.Common.Security
/// By default this is true which allows the user to manually link and unlink the external provider, if set to false the back office user
/// will not see and cannot perform manual linking or unlinking of the external provider.
/// </summary>
public bool AllowManualLinking { get; set; } = true;
public bool AllowManualLinking { get; }
/// <summary>
/// A callback executed during account auto-linking and before the user is persisted

View File

@@ -0,0 +1,106 @@
using Microsoft.AspNetCore.Authentication.OAuth;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Umbraco.Web.BackOffice.Security
{
// TODO: This is only for the back office, does it need to be in common?
// TODO: We need to implement this and extend it to support the back office external login options
// basically migrate things from AuthenticationManagerExtensions & AuthenticationOptionsExtensions
// and use this to get the back office external login infos
public interface IBackOfficeExternalLoginProviders
{
/// <summary>
/// Register a login provider for the back office
/// </summary>
/// <param name="provider"></param>
void Register(BackOfficeExternalLoginProvider provider);
BackOfficeExternalLoginProviderOptions Get(string authenticationType);
IEnumerable<BackOfficeExternalLoginProvider> GetBackOfficeProviders();
/// <summary>
/// Returns the authentication type for the last registered external login (oauth) provider that specifies an auto-login redirect option
/// </summary>
/// <param name="manager"></param>
/// <returns></returns>
string GetAutoLoginProvider();
bool HasDenyLocalLogin();
}
// TODO: This class is just a placeholder for later
public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders
{
private ConcurrentDictionary<string, BackOfficeExternalLoginProvider> _providers = new ConcurrentDictionary<string, BackOfficeExternalLoginProvider>();
public void Register(BackOfficeExternalLoginProvider provider)
{
_providers.TryAdd(provider.AuthenticationType, provider);
// TODO: we need to be able to set things like we were doing in ForUmbracoBackOffice.
// Ok, most is done but we'll also need to take into account the callback path to ignore when we
// do front-end routing
}
public BackOfficeExternalLoginProviderOptions Get(string authenticationType)
{
return _providers.TryGetValue(authenticationType, out var opt) ? opt.Options : null;
}
public string GetAutoLoginProvider()
{
var found = _providers.Where(x => x.Value.Options.AutoRedirectLoginToExternalProvider).ToList();
return found.Count > 0 ? found[0].Key : null;
}
public IEnumerable<BackOfficeExternalLoginProvider> GetBackOfficeProviders()
{
return _providers.Values;
}
public bool HasDenyLocalLogin()
{
var found = _providers.Where(x => x.Value.Options.DenyLocalLogin).ToList();
return found.Count > 0;
}
}
public class BackOfficeExternalLoginProvider : IEquatable<BackOfficeExternalLoginProvider>
{
public BackOfficeExternalLoginProvider(string name, string authenticationType, BackOfficeExternalLoginProviderOptions properties)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
AuthenticationType = authenticationType ?? throw new ArgumentNullException(nameof(authenticationType));
Options = properties ?? throw new ArgumentNullException(nameof(properties));
}
public string Name { get; }
public string AuthenticationType { get; }
public BackOfficeExternalLoginProviderOptions Options { get; }
public override bool Equals(object obj)
{
return Equals(obj as BackOfficeExternalLoginProvider);
}
public bool Equals(BackOfficeExternalLoginProvider other)
{
return other != null &&
Name == other.Name &&
AuthenticationType == other.AuthenticationType;
}
public override int GetHashCode()
{
return HashCode.Combine(Name, AuthenticationType);
}
}
}

View File

@@ -13,6 +13,7 @@ using Umbraco.Extensions;
namespace Umbraco.Web.Common.Security
{
// TODO: This is only for the back office, does it need to be in common?
public class BackOfficeSecurity : IBackOfficeSecurity
{

View File

@@ -9,6 +9,8 @@ using Umbraco.Core.Services;
namespace Umbraco.Web.Common.Security
{
// TODO: This is only for the back office, does it need to be in common?
public class BackOfficeSecurityFactory: IBackOfficeSecurityFactory
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;

View File

@@ -1,61 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Umbraco.Web.Common.Security
{
// TODO: We need to implement this and extend it to support the back office external login options
// basically migrate things from AuthenticationManagerExtensions & AuthenticationOptionsExtensions
// and use this to get the back office external login infos
public interface IBackOfficeExternalLoginProviders
{
ExternalSignInAutoLinkOptions Get(string authenticationType);
IEnumerable<BackOfficeExternalLoginProvider> GetBackOfficeProviders();
/// <summary>
/// Returns the authentication type for the last registered external login (oauth) provider that specifies an auto-login redirect option
/// </summary>
/// <param name="manager"></param>
/// <returns></returns>
string GetAutoLoginProvider();
bool HasDenyLocalLogin();
}
// TODO: This class is just a placeholder for later
public class NopBackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders
{
public ExternalSignInAutoLinkOptions Get(string authenticationType)
{
return null;
}
public string GetAutoLoginProvider()
{
return null;
}
public IEnumerable<BackOfficeExternalLoginProvider> GetBackOfficeProviders()
{
return Enumerable.Empty<BackOfficeExternalLoginProvider>();
}
public bool HasDenyLocalLogin()
{
return false;
}
}
// TODO: we'll need to register these somehow
public class BackOfficeExternalLoginProvider
{
public string Name { get; set; }
public string AuthenticationType { get; set; }
// TODO: I believe this should be replaced with just a reference to BackOfficeExternalLoginProviderOptions
public IReadOnlyDictionary<string, object> Properties { get; set; }
}
}

View File

@@ -1,7 +1,7 @@
@using Microsoft.Extensions.Options;
@using Umbraco.Core
@using Umbraco.Web.WebAssets
@using Umbraco.Web.Common.Security
@using Umbraco.Web.BackOffice.Security
@using Umbraco.Core.WebAssets
@using Umbraco.Core.Configuration
@using Umbraco.Core.Configuration.Models

View File

@@ -1,7 +1,7 @@
@using Microsoft.Extensions.Options;
@using Umbraco.Core
@using Umbraco.Web.WebAssets
@using Umbraco.Web.Common.Security
@using Umbraco.Web.BackOffice.Security
@using Umbraco.Core.WebAssets
@using Umbraco.Core.Configuration
@using Umbraco.Core.Configuration.Models