Merge branch 'v9/dev' into v9/task/more-flexible-startup
# Conflicts: # src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs
This commit is contained in:
@@ -181,14 +181,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
var opt = _externalAuthenticationOptions.Get(authType.Name);
|
||||
BackOfficeExternaLoginProviderScheme opt = await _externalAuthenticationOptions.GetAsync(authType.Name);
|
||||
if (opt == null)
|
||||
{
|
||||
return BadRequest($"Could not find external authentication options registered for provider {unlinkLoginModel.LoginProvider}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!opt.Options.AutoLinkOptions.AllowManualLinking)
|
||||
if (!opt.ExternalLoginProvider.Options.AutoLinkOptions.AllowManualLinking)
|
||||
{
|
||||
// If AllowManualLinking is disabled for this provider we cannot unlink
|
||||
return BadRequest();
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -17,6 +16,7 @@ using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Grid;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Manifest;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
@@ -33,6 +33,7 @@ using Umbraco.Cms.Web.Common.Controllers;
|
||||
using Umbraco.Cms.Web.Common.Filters;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
{
|
||||
@@ -49,6 +50,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
// this controller itself doesn't require authz but it's more clear what the intention is.
|
||||
|
||||
private readonly IBackOfficeUserManager _userManager;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly IRuntimeMinifier _runtimeMinifier;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
@@ -63,10 +65,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
private readonly IBackOfficeExternalLoginProviders _externalLogins;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions;
|
||||
private readonly IManifestParser _manifestParser;
|
||||
private readonly ServerVariablesParser _serverVariables;
|
||||
|
||||
public BackOfficeController(
|
||||
IBackOfficeUserManager userManager,
|
||||
IRuntimeState runtimeState,
|
||||
IRuntimeMinifier runtimeMinifier,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
@@ -81,9 +85,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
IBackOfficeExternalLoginProviders externalLogins,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions,
|
||||
IManifestParser manifestParser,
|
||||
ServerVariablesParser serverVariables)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_runtimeState = runtimeState;
|
||||
_runtimeMinifier = runtimeMinifier;
|
||||
_globalSettings = globalSettings.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
@@ -98,6 +104,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
_externalLogins = externalLogins;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_backOfficeTwoFactorOptions = backOfficeTwoFactorOptions;
|
||||
_manifestParser = manifestParser;
|
||||
_serverVariables = serverVariables;
|
||||
}
|
||||
|
||||
@@ -105,6 +112,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Default()
|
||||
{
|
||||
// TODO: It seems that if you login during an authorize upgrade and the upgrade fails, you can still
|
||||
// access the back office. This should redirect to the installer in that case?
|
||||
|
||||
// force authentication to occur since this is not an authorized endpoint
|
||||
var result = await this.AuthenticateBackOfficeAsync();
|
||||
|
||||
@@ -213,7 +223,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Application()
|
||||
{
|
||||
var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync(_globalSettings, _hostingEnvironment);
|
||||
var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync(
|
||||
_globalSettings,
|
||||
_hostingEnvironment,
|
||||
_manifestParser);
|
||||
|
||||
return new JavaScriptResult(result);
|
||||
}
|
||||
@@ -225,17 +238,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public Dictionary<string, Dictionary<string, string>> LocalizedText(string culture = null)
|
||||
public async Task<Dictionary<string, Dictionary<string, string>>> LocalizedText(string culture = null)
|
||||
{
|
||||
var isAuthenticated = _backofficeSecurityAccessor.BackOfficeSecurity.IsAuthenticated();
|
||||
CultureInfo cultureInfo;
|
||||
if (string.IsNullOrWhiteSpace(culture))
|
||||
{
|
||||
// Force authentication to occur since this is not an authorized endpoint, we need this to get a user.
|
||||
AuthenticateResult authenticationResult = await this.AuthenticateBackOfficeAsync();
|
||||
// We have to get the culture from the Identity, we can't rely on thread culture
|
||||
// It's entirely likely for a user to have a different culture in the backoffice, than their system.
|
||||
var user = authenticationResult.Principal?.Identity;
|
||||
|
||||
var cultureInfo = string.IsNullOrWhiteSpace(culture)
|
||||
//if the user is logged in, get their culture, otherwise default to 'en'
|
||||
? isAuthenticated
|
||||
//current culture is set at the very beginning of each request
|
||||
? Thread.CurrentThread.CurrentCulture
|
||||
: CultureInfo.GetCultureInfo(_globalSettings.DefaultUILanguage)
|
||||
: CultureInfo.GetCultureInfo(culture);
|
||||
cultureInfo = (authenticationResult.Succeeded && user is not null)
|
||||
? user.GetCulture()
|
||||
: CultureInfo.GetCultureInfo(_globalSettings.DefaultUILanguage);
|
||||
}
|
||||
else
|
||||
{
|
||||
cultureInfo = CultureInfo.GetCultureInfo(culture);
|
||||
}
|
||||
|
||||
var allValues = _textService.GetAllStoredValues(cultureInfo);
|
||||
var pathedValues = allValues.Select(kv =>
|
||||
@@ -400,7 +421,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
if (ViewData.FromBase64CookieData<BackOfficeExternalLoginProviderErrors>(_httpContextAccessor.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();
|
||||
@@ -430,16 +453,23 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
if (response == null) throw new ArgumentNullException(nameof(response));
|
||||
|
||||
// Sign in the user with this external login provider (which auto links, etc...)
|
||||
var result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false);
|
||||
SignInResult result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false);
|
||||
|
||||
var errors = new List<string>();
|
||||
|
||||
if (result == Microsoft.AspNetCore.Identity.SignInResult.Success)
|
||||
if (result == SignInResult.Success)
|
||||
{
|
||||
// Update any authentication tokens if succeeded
|
||||
await _signInManager.UpdateExternalAuthenticationTokensAsync(loginInfo);
|
||||
|
||||
// Check if we are in an upgrade state, if so we need to redirect
|
||||
if (_runtimeState.Level == Core.RuntimeLevel.Upgrade)
|
||||
{
|
||||
// redirect to the the installer
|
||||
return Redirect("/");
|
||||
}
|
||||
}
|
||||
else if (result == Microsoft.AspNetCore.Identity.SignInResult.TwoFactorRequired)
|
||||
else if (result == SignInResult.TwoFactorRequired)
|
||||
{
|
||||
|
||||
var attemptedUser = await _userManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
|
||||
@@ -467,17 +497,17 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
return verifyResponse;
|
||||
|
||||
}
|
||||
else if (result == Microsoft.AspNetCore.Identity.SignInResult.LockedOut)
|
||||
else if (result == SignInResult.LockedOut)
|
||||
{
|
||||
errors.Add($"The local user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out.");
|
||||
}
|
||||
else if (result == Microsoft.AspNetCore.Identity.SignInResult.NotAllowed)
|
||||
else if (result == SignInResult.NotAllowed)
|
||||
{
|
||||
// This occurs when SignInManager.CanSignInAsync fails which is when RequireConfirmedEmail , RequireConfirmedPhoneNumber or RequireConfirmedAccount fails
|
||||
// however since we don't enforce those rules (yet) this shouldn't happen.
|
||||
errors.Add($"The user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not confirmed their details and cannot sign in.");
|
||||
}
|
||||
else if (result == Microsoft.AspNetCore.Identity.SignInResult.Failed)
|
||||
else if (result == SignInResult.Failed)
|
||||
{
|
||||
// Failed only occurs when the user does not exist
|
||||
errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office.");
|
||||
@@ -494,6 +524,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
{
|
||||
errors.AddRange(autoLinkSignInResult.Errors);
|
||||
}
|
||||
else if (!result.Succeeded)
|
||||
{
|
||||
// this shouldn't occur, the above should catch the correct error but we'll be safe just in case
|
||||
errors.Add($"An unknown error with the requested provider ({loginInfo.LoginProvider}) occurred.");
|
||||
}
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
/// Returns the server variables for authenticated users
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal Task<Dictionary<string, object>> GetServerVariablesAsync()
|
||||
internal async Task<Dictionary<string, object>> GetServerVariablesAsync()
|
||||
{
|
||||
var globalSettings = _globalSettings;
|
||||
var backOfficeControllerName = ControllerExtensions.GetControllerName<BackOfficeController>();
|
||||
@@ -432,12 +432,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
{
|
||||
// TODO: It would be nicer to not have to manually translate these properties
|
||||
// but then needs to be changed in quite a few places in angular
|
||||
"providers", _externalLogins.GetBackOfficeProviders()
|
||||
"providers", (await _externalLogins.GetBackOfficeProvidersAsync())
|
||||
.Select(p => new
|
||||
{
|
||||
authType = p.AuthenticationType,
|
||||
caption = p.Name,
|
||||
properties = p.Options
|
||||
authType = p.ExternalLoginProvider.AuthenticationType,
|
||||
caption = p.AuthenticationScheme.DisplayName,
|
||||
properties = p.ExternalLoginProvider.Options
|
||||
})
|
||||
.ToArray()
|
||||
}
|
||||
@@ -456,7 +456,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
}
|
||||
};
|
||||
return Task.FromResult(defaultVals);
|
||||
|
||||
return defaultVals;
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
|
||||
@@ -28,12 +28,9 @@ using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Web.BackOffice.ActionResults;
|
||||
using Umbraco.Cms.Web.BackOffice.Authorization;
|
||||
using Umbraco.Cms.Web.BackOffice.Extensions;
|
||||
using Umbraco.Cms.Web.BackOffice.Filters;
|
||||
using Umbraco.Cms.Web.BackOffice.ModelBinders;
|
||||
using Umbraco.Cms.Web.Common.ActionsResults;
|
||||
using Umbraco.Cms.Web.Common.Attributes;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
@@ -478,6 +475,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
return result;
|
||||
}
|
||||
|
||||
private ActionResult<IDictionary<Guid, ContentItemDisplay>> GetEmptyByKeysInternal(Guid[] contentTypeKeys, int parentId)
|
||||
{
|
||||
using var scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
var contentTypes = _contentTypeService.GetAll(contentTypeKeys).ToList();
|
||||
return GetEmpties(contentTypes, parentId).ToDictionary(x => x.ContentTypeKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of empty content items for all document types.
|
||||
/// </summary>
|
||||
@@ -486,9 +490,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
[OutgoingEditorModelEvent]
|
||||
public ActionResult<IDictionary<Guid, ContentItemDisplay>> GetEmptyByKeys([FromQuery] Guid[] contentTypeKeys, [FromQuery] int parentId)
|
||||
{
|
||||
using var scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
var contentTypes = _contentTypeService.GetAll(contentTypeKeys).ToList();
|
||||
return GetEmpties(contentTypes, parentId).ToDictionary(x => x.ContentTypeKey);
|
||||
return GetEmptyByKeysInternal(contentTypeKeys, parentId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of empty content items for all document types.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a post request in order to support a large amount of GUIDs without hitting the URL length limit.
|
||||
/// </remarks>
|
||||
/// <param name="contentTypeByKeys"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[OutgoingEditorModelEvent]
|
||||
public ActionResult<IDictionary<Guid, ContentItemDisplay>> GetEmptyByKeys(ContentTypesByKeys contentTypeByKeys)
|
||||
{
|
||||
return GetEmptyByKeysInternal(contentTypeByKeys.ContentTypeKeys, contentTypeByKeys.ParentId);
|
||||
}
|
||||
|
||||
[OutgoingEditorModelEvent]
|
||||
|
||||
@@ -21,7 +21,6 @@ using Umbraco.Cms.Web.Common.Attributes;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Cms.Web.Common.Controllers;
|
||||
using Umbraco.Cms.Web.Common.Filters;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
|
||||
@@ -258,16 +258,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// Gets the URL of an entity
|
||||
/// </summary>
|
||||
/// <param name="udi">UDI of the entity to fetch URL for</param>
|
||||
/// <param name="id">UDI of the entity to fetch URL for</param>
|
||||
/// <param name="culture">The culture to fetch the URL for</param>
|
||||
/// <returns>The URL or path to the item</returns>
|
||||
public IActionResult GetUrl(Udi udi, string culture = "*")
|
||||
public IActionResult GetUrl(Udi id, string culture = "*")
|
||||
{
|
||||
var intId = _entityService.GetId(udi);
|
||||
var intId = _entityService.GetId(id);
|
||||
if (!intId.Success)
|
||||
return NotFound();
|
||||
UmbracoEntityTypes entityType;
|
||||
switch (udi.EntityType)
|
||||
switch (id.EntityType)
|
||||
{
|
||||
case Constants.UdiEntityType.Document:
|
||||
entityType = UmbracoEntityTypes.Document;
|
||||
|
||||
@@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
@@ -551,7 +552,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings),
|
||||
new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail });
|
||||
|
||||
var mailMessage = new EmailMessage(fromEmail, to.Email, emailSubject, emailBody, true);
|
||||
// This needs to be in the correct mailto format including the name, else
|
||||
// the name cannot be captured in the email sending notification.
|
||||
// i.e. "Some Person" <hello@example.com>
|
||||
var toMailBoxAddress = new MailboxAddress(to.Name, to.Email);
|
||||
|
||||
var mailMessage = new EmailMessage(fromEmail, toMailBoxAddress.ToString(), emailSubject, emailBody, true);
|
||||
|
||||
await _emailSender.SendAsync(mailMessage, true);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Cms.Web.Common.Security;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Web.BackOffice.Authorization;
|
||||
using Umbraco.Cms.Web.Common.Middleware;
|
||||
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -31,7 +32,7 @@ namespace Umbraco.Extensions
|
||||
/// <summary>
|
||||
/// Adds all required components to run the Umbraco back office
|
||||
/// </summary>
|
||||
public static IUmbracoBuilder AddBackOffice(this IUmbracoBuilder builder) => builder
|
||||
public static IUmbracoBuilder AddBackOffice(this IUmbracoBuilder builder, Action<IMvcBuilder> configureMvc = null) => builder
|
||||
.AddConfiguration()
|
||||
.AddUmbracoCore()
|
||||
.AddWebComponents()
|
||||
@@ -42,7 +43,7 @@ namespace Umbraco.Extensions
|
||||
.AddMembersIdentity()
|
||||
.AddBackOfficeAuthorizationPolicies()
|
||||
.AddUmbracoProfiler()
|
||||
.AddMvcAndRazor()
|
||||
.AddMvcAndRazor(configureMvc)
|
||||
.AddWebServer()
|
||||
.AddPreviewSupport()
|
||||
.AddHostedServices()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -54,18 +54,18 @@ namespace Umbraco.Extensions
|
||||
/// <param name="html"></param>
|
||||
/// <param name="externalLogins"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<IHtmlContent> AngularValueExternalLoginInfoScriptAsync(this IHtmlHelper html,
|
||||
public static async Task<IHtmlContent> AngularValueExternalLoginInfoScriptAsync(this IHtmlHelper html,
|
||||
IBackOfficeExternalLoginProviders externalLogins,
|
||||
BackOfficeExternalLoginProviderErrors externalLoginErrors)
|
||||
{
|
||||
var providers = externalLogins.GetBackOfficeProviders();
|
||||
var providers = await externalLogins.GetBackOfficeProvidersAsync();
|
||||
|
||||
var loginProviders = providers
|
||||
.Select(p => new
|
||||
{
|
||||
authType = p.AuthenticationType,
|
||||
caption = p.Name,
|
||||
properties = p.Options
|
||||
authType = p.ExternalLoginProvider.AuthenticationType,
|
||||
caption = p.AuthenticationScheme.DisplayName,
|
||||
properties = p.ExternalLoginProvider.Options
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Umbraco.Extensions
|
||||
sb.AppendLine(JsonConvert.SerializeObject(loginProviders));
|
||||
sb.AppendLine(@"});");
|
||||
|
||||
return Task.FromResult(html.Raw(sb.ToString()));
|
||||
return html.Raw(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
@@ -10,5 +12,6 @@ namespace Umbraco.Extensions
|
||||
|
||||
public static BackOfficeExternalLoginProviderErrors GetExternalLoginProviderErrors(this HttpContext httpContext)
|
||||
=> httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] as BackOfficeExternalLoginProviderErrors;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Manifest;
|
||||
using Umbraco.Cms.Core.WebAssets;
|
||||
using Umbraco.Cms.Infrastructure.WebAssets;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class RuntimeMinifierExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the JavaScript to load the back office's assets
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetScriptForLoadingBackOfficeAsync(
|
||||
this IRuntimeMinifier minifier,
|
||||
GlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IManifestParser manifestParser)
|
||||
{
|
||||
var files = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach(var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoCoreJsBundleName))
|
||||
{
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
foreach (var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoExtensionsJsBundleName))
|
||||
{
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
// process the independent bundles
|
||||
if (manifestParser.CombinedManifest.Scripts.TryGetValue(BundleOptions.Independent, out IReadOnlyList<ManifestAssets> independentManifestAssetsList))
|
||||
{
|
||||
foreach (ManifestAssets manifestAssets in independentManifestAssetsList)
|
||||
{
|
||||
var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Javascript);
|
||||
foreach(var asset in await minifier.GetJsAssetPathsAsync(bundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the "None" bundles, meaning we'll just render the script as-is
|
||||
foreach (var asset in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoNonOptimizedPackageJsBundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
|
||||
var result = BackOfficeJavaScriptInitializer.GetJavascriptInitialization(
|
||||
files,
|
||||
"umbraco",
|
||||
globalSettings,
|
||||
hostingEnvironment);
|
||||
|
||||
result += await GetStylesheetInitializationAsync(minifier, manifestParser);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the back office css bundle paths and formats a JS call to lazy load them
|
||||
/// </summary>
|
||||
private static async Task<string> GetStylesheetInitializationAsync(
|
||||
IRuntimeMinifier minifier,
|
||||
IManifestParser manifestParser)
|
||||
{
|
||||
var files = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach(var file in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName))
|
||||
{
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
// process the independent bundles
|
||||
if (manifestParser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.Independent, out IReadOnlyList<ManifestAssets> independentManifestAssetsList))
|
||||
{
|
||||
foreach (ManifestAssets manifestAssets in independentManifestAssetsList)
|
||||
{
|
||||
var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Css);
|
||||
foreach (var asset in await minifier.GetCssAssetPathsAsync(bundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the "None" bundles, meaning we'll just render the script as-is
|
||||
foreach (var asset in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoNonOptimizedPackageCssBundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (string file in files)
|
||||
{
|
||||
sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Umbraco.Cms.Web.BackOffice.Middleware;
|
||||
using Umbraco.Cms.Web.BackOffice.Routing;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
using Umbraco.Cms.Web.Common.Extensions;
|
||||
using Umbraco.Cms.Web.Common.Middleware;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
|
||||
@@ -9,12 +9,12 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
/// </summary>
|
||||
public class AutoLinkSignInResult : SignInResult
|
||||
{
|
||||
public static AutoLinkSignInResult FailedNotLinked => new AutoLinkSignInResult()
|
||||
public static AutoLinkSignInResult FailedNotLinked { get; } = new AutoLinkSignInResult()
|
||||
{
|
||||
Succeeded = false
|
||||
};
|
||||
|
||||
public static AutoLinkSignInResult FailedNoEmail => new AutoLinkSignInResult()
|
||||
public static AutoLinkSignInResult FailedNoEmail { get; } = new AutoLinkSignInResult()
|
||||
{
|
||||
Succeeded = false
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
@@ -12,18 +13,16 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
/// </summary>
|
||||
public class BackOfficeAuthenticationBuilder : AuthenticationBuilder
|
||||
{
|
||||
private readonly BackOfficeExternalLoginProviderOptions _loginProviderOptions;
|
||||
private readonly Action<BackOfficeExternalLoginProviderOptions> _loginProviderOptions;
|
||||
|
||||
public BackOfficeAuthenticationBuilder(IServiceCollection services, BackOfficeExternalLoginProviderOptions loginProviderOptions)
|
||||
public BackOfficeAuthenticationBuilder(
|
||||
IServiceCollection services,
|
||||
Action<BackOfficeExternalLoginProviderOptions> loginProviderOptions = null)
|
||||
: base(services)
|
||||
{
|
||||
_loginProviderOptions = loginProviderOptions;
|
||||
}
|
||||
=> _loginProviderOptions = loginProviderOptions ?? (x => { });
|
||||
|
||||
public string SchemeForBackOffice(string scheme)
|
||||
{
|
||||
return Constants.Security.BackOfficeExternalAuthenticationTypePrefix + scheme;
|
||||
}
|
||||
=> Constants.Security.BackOfficeExternalAuthenticationTypePrefix + scheme;
|
||||
|
||||
/// <summary>
|
||||
/// Overridden to track the final authenticationScheme being registered for the external login
|
||||
@@ -43,7 +42,13 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
}
|
||||
|
||||
// add our login provider to the container along with a custom options configuration
|
||||
Services.AddSingleton(x => new BackOfficeExternalLoginProvider(displayName, authenticationScheme, _loginProviderOptions));
|
||||
Services.Configure(authenticationScheme, _loginProviderOptions);
|
||||
base.Services.AddSingleton(services =>
|
||||
{
|
||||
return new BackOfficeExternalLoginProvider(
|
||||
authenticationScheme,
|
||||
services.GetRequiredService<IOptionsMonitor<BackOfficeExternalLoginProviderOptions>>());
|
||||
});
|
||||
Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<TOptions>, EnsureBackOfficeScheme<TOptions>>());
|
||||
|
||||
return base.AddRemoteScheme<TOptions, THandler>(authenticationScheme, displayName, configureOptions);
|
||||
|
||||
@@ -2,7 +2,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
@@ -23,6 +25,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly string[] _explicitPaths;
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private readonly IBasicAuthService _basicAuthService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackOfficeCookieManager"/> class.
|
||||
@@ -30,8 +33,9 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
public BackOfficeCookieManager(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtime,
|
||||
UmbracoRequestPaths umbracoRequestPaths)
|
||||
: this(umbracoContextAccessor, runtime, null, umbracoRequestPaths)
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IBasicAuthService basicAuthService)
|
||||
: this(umbracoContextAccessor, runtime, null, umbracoRequestPaths, basicAuthService)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -42,12 +46,14 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtime,
|
||||
IEnumerable<string> explicitPaths,
|
||||
UmbracoRequestPaths umbracoRequestPaths)
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IBasicAuthService basicAuthService)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_runtime = runtime;
|
||||
_explicitPaths = explicitPaths?.ToArray();
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
_basicAuthService = basicAuthService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -88,6 +94,11 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_basicAuthService.IsBasicAuthEnabled())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
{
|
||||
public class BackOfficeExternaLoginProviderScheme
|
||||
{
|
||||
public BackOfficeExternaLoginProviderScheme(
|
||||
BackOfficeExternalLoginProvider externalLoginProvider,
|
||||
AuthenticationScheme authenticationScheme)
|
||||
{
|
||||
ExternalLoginProvider = externalLoginProvider ?? throw new ArgumentNullException(nameof(externalLoginProvider));
|
||||
AuthenticationScheme = authenticationScheme ?? throw new ArgumentNullException(nameof(authenticationScheme));
|
||||
}
|
||||
|
||||
public BackOfficeExternalLoginProvider ExternalLoginProvider { get; }
|
||||
public AuthenticationScheme AuthenticationScheme { get; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
{
|
||||
@@ -7,33 +8,29 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
/// </summary>
|
||||
public class BackOfficeExternalLoginProvider : IEquatable<BackOfficeExternalLoginProvider>
|
||||
{
|
||||
public BackOfficeExternalLoginProvider(string name, string authenticationType, BackOfficeExternalLoginProviderOptions properties)
|
||||
public BackOfficeExternalLoginProvider(
|
||||
string authenticationType,
|
||||
IOptionsMonitor<BackOfficeExternalLoginProviderOptions> properties)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
if (properties is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(properties));
|
||||
}
|
||||
|
||||
AuthenticationType = authenticationType ?? throw new ArgumentNullException(nameof(authenticationType));
|
||||
Options = properties ?? throw new ArgumentNullException(nameof(properties));
|
||||
Options = properties.Get(authenticationType);
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
/// <summary>
|
||||
/// The authentication "Scheme"
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
public override bool Equals(object obj) => Equals(obj as BackOfficeExternalLoginProvider);
|
||||
public bool Equals(BackOfficeExternalLoginProvider other) => other != null && AuthenticationType == other.AuthenticationType;
|
||||
public override int GetHashCode() => HashCode.Combine(AuthenticationType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Options used to configure back office external login providers
|
||||
/// </summary>
|
||||
public class BackOfficeExternalLoginProviderOptions
|
||||
{
|
||||
public BackOfficeExternalLoginProviderOptions(
|
||||
string buttonStyle, string icon,
|
||||
string buttonStyle,
|
||||
string icon,
|
||||
ExternalSignInAutoLinkOptions autoLinkOptions = null,
|
||||
bool denyLocalLogin = false,
|
||||
bool autoRedirectLoginToExternalProvider = false,
|
||||
@@ -22,18 +21,23 @@
|
||||
CustomBackOfficeView = customBackOfficeView;
|
||||
}
|
||||
|
||||
public string ButtonStyle { get; }
|
||||
public string Icon { get; }
|
||||
public BackOfficeExternalLoginProviderOptions()
|
||||
{
|
||||
}
|
||||
|
||||
public string ButtonStyle { get; set; } = "btn-openid";
|
||||
|
||||
public string Icon { get; set; } = "fa fa-user";
|
||||
|
||||
/// <summary>
|
||||
/// Options used to control how users can be auto-linked/created/updated based on the external login provider
|
||||
/// </summary>
|
||||
public ExternalSignInAutoLinkOptions AutoLinkOptions { get; }
|
||||
public ExternalSignInAutoLinkOptions AutoLinkOptions { get; set; } = new ExternalSignInAutoLinkOptions();
|
||||
|
||||
/// <summary>
|
||||
/// When set to true will disable all local user login functionality
|
||||
/// </summary>
|
||||
public bool DenyLocalLogin { get; }
|
||||
public bool DenyLocalLogin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When specified this will automatically redirect to the OAuth login provider instead of prompting the user to click on the OAuth button first.
|
||||
@@ -42,7 +46,7 @@
|
||||
/// 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; }
|
||||
public bool AutoRedirectLoginToExternalProvider { get; set; }
|
||||
|
||||
/// <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
|
||||
@@ -51,6 +55,6 @@
|
||||
/// 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; }
|
||||
public string CustomBackOfficeView { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
{
|
||||
|
||||
/// <inheritdoc />
|
||||
public class BackOfficeExternalLoginProviders : IBackOfficeExternalLoginProviders
|
||||
{
|
||||
public BackOfficeExternalLoginProviders(IEnumerable<BackOfficeExternalLoginProvider> externalLogins)
|
||||
private readonly Dictionary<string, BackOfficeExternalLoginProvider> _externalLogins;
|
||||
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
|
||||
|
||||
public BackOfficeExternalLoginProviders(
|
||||
IEnumerable<BackOfficeExternalLoginProvider> externalLogins,
|
||||
IAuthenticationSchemeProvider authenticationSchemeProvider)
|
||||
{
|
||||
_externalLogins = externalLogins;
|
||||
_externalLogins = externalLogins.ToDictionary(x => x.AuthenticationType);
|
||||
_authenticationSchemeProvider = authenticationSchemeProvider;
|
||||
}
|
||||
|
||||
private readonly IEnumerable<BackOfficeExternalLoginProvider> _externalLogins;
|
||||
|
||||
/// <inheritdoc />
|
||||
public BackOfficeExternalLoginProvider Get(string authenticationType)
|
||||
public async Task<BackOfficeExternaLoginProviderScheme> GetAsync(string authenticationType)
|
||||
{
|
||||
return _externalLogins.FirstOrDefault(x => x.AuthenticationType == authenticationType);
|
||||
if (!_externalLogins.TryGetValue(authenticationType, out BackOfficeExternalLoginProvider provider))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the associated scheme
|
||||
AuthenticationScheme associatedScheme = await _authenticationSchemeProvider.GetSchemeAsync(provider.AuthenticationType);
|
||||
|
||||
if (associatedScheme == null)
|
||||
{
|
||||
throw new InvalidOperationException("No authentication scheme registered for " + provider.AuthenticationType);
|
||||
}
|
||||
|
||||
return new BackOfficeExternaLoginProviderScheme(provider, associatedScheme);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetAutoLoginProvider()
|
||||
{
|
||||
var found = _externalLogins.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList();
|
||||
var found = _externalLogins.Values.Where(x => x.Options.AutoRedirectLoginToExternalProvider).ToList();
|
||||
return found.Count > 0 ? found[0].AuthenticationType : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<BackOfficeExternalLoginProvider> GetBackOfficeProviders()
|
||||
public async Task<IEnumerable<BackOfficeExternaLoginProviderScheme>> GetBackOfficeProvidersAsync()
|
||||
{
|
||||
return _externalLogins;
|
||||
var providersWithSchemes = new List<BackOfficeExternaLoginProviderScheme>();
|
||||
foreach (BackOfficeExternalLoginProvider login in _externalLogins.Values)
|
||||
{
|
||||
// get the associated scheme
|
||||
AuthenticationScheme associatedScheme = await _authenticationSchemeProvider.GetSchemeAsync(login.AuthenticationType);
|
||||
|
||||
providersWithSchemes.Add(new BackOfficeExternaLoginProviderScheme(login, associatedScheme));
|
||||
}
|
||||
|
||||
return providersWithSchemes;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasDenyLocalLogin()
|
||||
{
|
||||
var found = _externalLogins.Where(x => x.Options.DenyLocalLogin).ToList();
|
||||
var found = _externalLogins.Values.Where(x => x.Options.DenyLocalLogin).ToList();
|
||||
return found.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
@@ -21,9 +21,9 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
/// <param name="loginProviderOptions"></param>
|
||||
/// <param name="build"></param>
|
||||
/// <returns></returns>
|
||||
public BackOfficeExternalLoginsBuilder AddBackOfficeLogin(
|
||||
BackOfficeExternalLoginProviderOptions loginProviderOptions,
|
||||
Action<BackOfficeAuthenticationBuilder> build)
|
||||
public BackOfficeExternalLoginsBuilder AddBackOfficeLogin(
|
||||
Action<BackOfficeAuthenticationBuilder> build,
|
||||
Action<BackOfficeExternalLoginProviderOptions> loginProviderOptions = null)
|
||||
{
|
||||
build(new BackOfficeAuthenticationBuilder(_services, loginProviderOptions));
|
||||
return this;
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs
|
||||
// to be able to deal with auto-linking and reduce duplicate lookups
|
||||
|
||||
var autoLinkOptions = _externalLogins.Get(loginInfo.LoginProvider)?.Options?.AutoLinkOptions;
|
||||
var autoLinkOptions = (await _externalLogins.GetAsync(loginInfo.LoginProvider))?.ExternalLoginProvider?.Options?.AutoLinkOptions;
|
||||
var user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
|
||||
if (user == null)
|
||||
{
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
private readonly IIpResolver _ipResolver;
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private readonly IBasicAuthService _basicAuthService;
|
||||
private readonly IOptionsMonitor<BasicAuthSettings> _optionsSnapshot;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureBackOfficeCookieOptions"/> class.
|
||||
@@ -59,7 +61,8 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
IUserService userService,
|
||||
IIpResolver ipResolver,
|
||||
ISystemClock systemClock,
|
||||
UmbracoRequestPaths umbracoRequestPaths)
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IBasicAuthService basicAuthService)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
@@ -72,6 +75,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
_ipResolver = ipResolver;
|
||||
_systemClock = systemClock;
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
_basicAuthService = basicAuthService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -115,7 +119,9 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
options.CookieManager = new BackOfficeCookieManager(
|
||||
_umbracoContextAccessor,
|
||||
_runtimeState,
|
||||
_umbracoRequestPaths);
|
||||
_umbracoRequestPaths,
|
||||
_basicAuthService
|
||||
);
|
||||
|
||||
options.Events = new CookieAuthenticationEvents
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
{
|
||||
@@ -13,13 +14,13 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
/// </summary>
|
||||
/// <param name="authenticationType"></param>
|
||||
/// <returns></returns>
|
||||
BackOfficeExternalLoginProvider Get(string authenticationType);
|
||||
Task<BackOfficeExternaLoginProviderScheme> GetAsync(string authenticationType);
|
||||
|
||||
/// <summary>
|
||||
/// Get all registered <see cref="BackOfficeExternalLoginProvider"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerable<BackOfficeExternalLoginProvider> GetBackOfficeProviders();
|
||||
Task<IEnumerable<BackOfficeExternaLoginProviderScheme>> GetBackOfficeProvidersAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the authentication type for the last registered external login (oauth) provider that specifies an auto-login redirect option
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="SignInManager{BackOfficeIdentityUser}"/> for the back office with a <seealso cref="BackOfficeIdentityUser"/>
|
||||
/// </summary>
|
||||
public interface IBackOfficeSignInManager
|
||||
{
|
||||
AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string userId = null);
|
||||
Task<SignInResult> ExternalLoginSignInAsync(ExternalLoginInfo loginInfo, bool isPersistent, bool bypassTwoFactor = false);
|
||||
Task<IEnumerable<AuthenticationScheme>> GetExternalAuthenticationSchemesAsync();
|
||||
Task<ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null);
|
||||
Task<BackOfficeIdentityUser> GetTwoFactorAuthenticationUserAsync();
|
||||
Task<SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure);
|
||||
Task SignOutAsync();
|
||||
Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent, string authenticationMethod = null);
|
||||
Task<ClaimsPrincipal> CreateUserPrincipalAsync(BackOfficeIdentityUser user);
|
||||
Task<SignInResult> TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient);
|
||||
Task<IdentityResult> UpdateExternalAuthenticationTokensAsync(ExternalLoginInfo externalLogin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Trees;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Trees
|
||||
{
|
||||
[Tree(Constants.Applications.Settings, "staticFiles", TreeTitle = "Static Files", TreeUse = TreeUse.Dialog)]
|
||||
public class StaticFilesTreeController : TreeController
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private const string AppPlugins = "App_Plugins";
|
||||
private const string Webroot = "wwwroot";
|
||||
|
||||
public StaticFilesTreeController(ILocalizedTextService localizedTextService,
|
||||
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, IEventAggregator eventAggregator,
|
||||
IPhysicalFileSystem fileSystem) :
|
||||
base(localizedTextService, umbracoApiControllerTypeCollection, eventAggregator)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
protected override ActionResult<TreeNodeCollection> GetTreeNodes(string id, FormCollection queryStrings)
|
||||
{
|
||||
var path = string.IsNullOrEmpty(id) == false && id != Constants.System.RootString
|
||||
? WebUtility.UrlDecode(id).TrimStart("/")
|
||||
: "";
|
||||
|
||||
var nodes = new TreeNodeCollection();
|
||||
var directories = _fileSystem.GetDirectories(path);
|
||||
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
// We don't want any other directories under the root node other than the ones serving static files - App_Plugins and wwwroot
|
||||
if (id == Constants.System.RootString && directory != AppPlugins && directory != Webroot)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var hasChildren = _fileSystem.GetFiles(directory).Any() || _fileSystem.GetDirectories(directory).Any();
|
||||
|
||||
var name = Path.GetFileName(directory);
|
||||
var node = CreateTreeNode(WebUtility.UrlEncode(directory), path, queryStrings, name, "icon-folder", hasChildren);
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
nodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Only get the files inside App_Plugins and wwwroot
|
||||
var files = _fileSystem.GetFiles(path).Where(x => x.StartsWith(AppPlugins) || x.StartsWith(Webroot));
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var name = Path.GetFileName(file);
|
||||
var node = CreateTreeNode(WebUtility.UrlEncode(file), path, queryStrings, name, "icon-document", false);
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
nodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
// We don't have any menu item options (such as create/delete/reload) & only use the root node to load a custom UI
|
||||
protected override ActionResult<MenuItemCollection> GetMenuForNode(string id, FormCollection queryStrings) => null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user