Revert "Moves auto linking logic to the BackOfficeSignInManager"
Signed-off-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
@@ -403,55 +403,182 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
if (loginInfo == null) throw new ArgumentNullException(nameof(loginInfo));
|
||||
if (response == null) throw new ArgumentNullException(nameof(response));
|
||||
ExternalSignInAutoLinkOptions autoLinkOptions = null;
|
||||
|
||||
// Sign in the user with this external login provider (which auto links, etc...)
|
||||
var result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false);
|
||||
var authType = (await _signInManager.GetExternalAuthenticationSchemesAsync())
|
||||
.FirstOrDefault(x => x.Name == loginInfo.LoginProvider);
|
||||
|
||||
var errors = new List<string>();
|
||||
|
||||
if (result == Microsoft.AspNetCore.Identity.SignInResult.Success)
|
||||
if (authType == null)
|
||||
{
|
||||
|
||||
_logger.LogWarning("Could not find external authentication provider registered: {LoginProvider}", loginInfo.LoginProvider);
|
||||
}
|
||||
else if (result == Microsoft.AspNetCore.Identity.SignInResult.LockedOut)
|
||||
else
|
||||
{
|
||||
// TODO: We've never actually dealt with this before
|
||||
}
|
||||
else if (result == Microsoft.AspNetCore.Identity.SignInResult.TwoFactorRequired)
|
||||
{
|
||||
// TODO: We've never actually dealt with this before
|
||||
}
|
||||
else if (result == Microsoft.AspNetCore.Identity.SignInResult.NotAllowed)
|
||||
{
|
||||
// TODO: We've never actually dealt with this before
|
||||
}
|
||||
else if (result == Microsoft.AspNetCore.Identity.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.");
|
||||
}
|
||||
else if (result == AutoLinkSignInResult.FailedNotLinked)
|
||||
{
|
||||
errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office.");
|
||||
}
|
||||
else if (result == AutoLinkSignInResult.FailedNoEmail)
|
||||
{
|
||||
errors.Add($"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked.");
|
||||
}
|
||||
else if (result is AutoLinkSignInResult autoLinkSignInResult && autoLinkSignInResult.Errors.Count > 0)
|
||||
{
|
||||
errors.AddRange(autoLinkSignInResult.Errors);
|
||||
autoLinkOptions = _externalLogins.Get(authType.Name)?.Options?.AutoLinkOptions;
|
||||
}
|
||||
|
||||
if (errors.Count > 0)
|
||||
// Sign in the user with this external login provider if the user already has a login
|
||||
|
||||
var user = await _userManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
|
||||
if (user != null)
|
||||
{
|
||||
var shouldSignIn = true;
|
||||
if (autoLinkOptions != null && autoLinkOptions.OnExternalLogin != null)
|
||||
{
|
||||
shouldSignIn = autoLinkOptions.OnExternalLogin(user, loginInfo);
|
||||
if (shouldSignIn == false)
|
||||
{
|
||||
_logger.LogWarning("The AutoLinkOptions of the external authentication provider '{LoginProvider}' have refused the login based on the OnExternalLogin method. Affected user id: '{UserId}'", loginInfo.LoginProvider, user.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSignIn)
|
||||
{
|
||||
//sign in
|
||||
await _signInManager.SignInAsync(user, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await AutoLinkAndSignInExternalAccount(loginInfo, autoLinkOptions) == false)
|
||||
{
|
||||
ViewData.SetExternalSignInProviderErrors(
|
||||
new BackOfficeExternalLoginProviderErrors(
|
||||
loginInfo.LoginProvider,
|
||||
new[] { "The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office." }));
|
||||
}
|
||||
|
||||
//Remove the cookie otherwise this message will keep appearing
|
||||
Response.Cookies.Delete(Constants.Security.BackOfficeExternalCookieName);
|
||||
}
|
||||
|
||||
return response();
|
||||
}
|
||||
|
||||
private async Task<bool> AutoLinkAndSignInExternalAccount(ExternalLoginInfo loginInfo, ExternalSignInAutoLinkOptions autoLinkOptions)
|
||||
{
|
||||
if (autoLinkOptions == null)
|
||||
return false;
|
||||
|
||||
if (autoLinkOptions.AutoLinkExternalAccount == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email);
|
||||
|
||||
//we are allowing auto-linking/creating of local accounts
|
||||
if (email.IsNullOrWhiteSpace())
|
||||
{
|
||||
ViewData.SetExternalSignInProviderErrors(
|
||||
new BackOfficeExternalLoginProviderErrors(
|
||||
loginInfo.LoginProvider,
|
||||
errors));
|
||||
new[] { $"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked." }));
|
||||
}
|
||||
else
|
||||
{
|
||||
//Now we need to perform the auto-link, so first we need to lookup/create a user with the email address
|
||||
var autoLinkUser = await _userManager.FindByEmailAsync(email);
|
||||
if (autoLinkUser != null)
|
||||
{
|
||||
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);
|
||||
|
||||
foreach (var userGroup in autoLinkOptions.DefaultUserGroups)
|
||||
{
|
||||
autoLinkUser.AddRole(userGroup);
|
||||
}
|
||||
|
||||
//call the callback if one is assigned
|
||||
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.SetExternalSignInProviderErrors(
|
||||
new BackOfficeExternalLoginProviderErrors(
|
||||
loginInfo.LoginProvider,
|
||||
userCreationResult.Errors.Select(x => x.Description).ToList()));
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
return response();
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
private IActionResult RedirectToLocal(string returnUrl)
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
public class AutoLinkSignInResult : SignInResult
|
||||
{
|
||||
public static AutoLinkSignInResult FailedNotLinked = new()
|
||||
{
|
||||
Succeeded = false
|
||||
};
|
||||
|
||||
public static AutoLinkSignInResult FailedNoEmail = new()
|
||||
{
|
||||
Succeeded = false
|
||||
};
|
||||
|
||||
public static AutoLinkSignInResult FailedException(string error) => new(new[] { error })
|
||||
{
|
||||
Succeeded = false
|
||||
};
|
||||
|
||||
public static AutoLinkSignInResult FailedCreatingUser(IReadOnlyCollection<string> errors) => new(errors)
|
||||
{
|
||||
Succeeded = false
|
||||
};
|
||||
|
||||
public static AutoLinkSignInResult FailedLinkingUser(IReadOnlyCollection<string> errors) => new(errors)
|
||||
{
|
||||
Succeeded = false
|
||||
};
|
||||
|
||||
public AutoLinkSignInResult(IReadOnlyCollection<string> errors)
|
||||
{
|
||||
Errors = errors ?? throw new ArgumentNullException(nameof(errors));
|
||||
}
|
||||
|
||||
public AutoLinkSignInResult()
|
||||
{
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<string> Errors { get; } = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
public class BackOfficeExternalLoginProviderOptions
|
||||
{
|
||||
public BackOfficeExternalLoginProviderOptions(
|
||||
string buttonStyle, string icon,
|
||||
string buttonStyle, string icon, string callbackPath,
|
||||
ExternalSignInAutoLinkOptions autoLinkOptions = null,
|
||||
bool denyLocalLogin = false,
|
||||
bool autoRedirectLoginToExternalProvider = false,
|
||||
@@ -19,6 +19,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
ButtonStyle = buttonStyle;
|
||||
Icon = icon;
|
||||
CallbackPath = callbackPath;
|
||||
AutoLinkOptions = autoLinkOptions ?? new ExternalSignInAutoLinkOptions();
|
||||
DenyLocalLogin = denyLocalLogin;
|
||||
AutoRedirectLoginToExternalProvider = autoRedirectLoginToExternalProvider;
|
||||
@@ -27,6 +28,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
|
||||
public string ButtonStyle { 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
|
||||
|
||||
@@ -10,9 +10,7 @@ using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
@@ -29,25 +27,18 @@ namespace Umbraco.Web.Common.Security
|
||||
private const string XsrfKey = "XsrfId";
|
||||
|
||||
private BackOfficeUserManager _userManager;
|
||||
private readonly IBackOfficeExternalLoginProviders _externalLogins;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
|
||||
public BackOfficeSignInManager(
|
||||
BackOfficeUserManager userManager,
|
||||
IHttpContextAccessor contextAccessor,
|
||||
IBackOfficeExternalLoginProviders externalLogins,
|
||||
IUserClaimsPrincipalFactory<BackOfficeIdentityUser> claimsFactory,
|
||||
IOptions<IdentityOptions> optionsAccessor,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
ILogger<SignInManager<BackOfficeIdentityUser>> logger,
|
||||
IAuthenticationSchemeProvider schemes,
|
||||
IUserConfirmation<BackOfficeIdentityUser> confirmation)
|
||||
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_externalLogins = externalLogins;
|
||||
_globalSettings = globalSettings.Value;
|
||||
}
|
||||
|
||||
// TODO: Need to migrate more from Umbraco.Web.Security.BackOfficeSignInManager
|
||||
@@ -309,51 +300,7 @@ namespace Umbraco.Web.Common.Security
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom ExternalLoginSignInAsync overload for handling external sign in with auto-linking
|
||||
/// </summary>
|
||||
/// <param name="loginProvider"></param>
|
||||
/// <param name="providerKey"></param>
|
||||
/// <param name="isPersistent"></param>
|
||||
/// <param name="bypassTwoFactor"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<SignInResult> ExternalLoginSignInAsync(ExternalLoginInfo loginInfo, bool isPersistent, bool bypassTwoFactor = false)
|
||||
{
|
||||
// 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 user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
|
||||
if (user == null)
|
||||
{
|
||||
// user doesn't exist so see if we can auto link
|
||||
return await AutoLinkAndSignInExternalAccount(loginInfo, autoLinkOptions);
|
||||
}
|
||||
|
||||
if (autoLinkOptions != null && autoLinkOptions.OnExternalLogin != null)
|
||||
{
|
||||
var shouldSignIn = autoLinkOptions.OnExternalLogin(user, loginInfo);
|
||||
if (shouldSignIn == false)
|
||||
{
|
||||
Logger.LogWarning("The AutoLinkOptions of the external authentication provider '{LoginProvider}' have refused the login based on the OnExternalLogin method. Affected user id: '{UserId}'", loginInfo.LoginProvider, user.Id);
|
||||
}
|
||||
}
|
||||
|
||||
var error = await PreSignInCheck(user);
|
||||
if (error != null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
return await SignInOrTwoFactorAsync(user, isPersistent, loginInfo.LoginProvider, bypassTwoFactor);
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<AuthenticationScheme>> GetExternalAuthenticationSchemesAsync()
|
||||
{
|
||||
// TODO: We can filter these so that they only include the back office ones.
|
||||
// That can be done by either checking the scheme (maybe) or comparing it to what we have registered in the collection of BackOfficeExternalLoginProvider
|
||||
return base.GetExternalAuthenticationSchemesAsync();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<SignInResult> SignInOrTwoFactorAsync(BackOfficeIdentityUser user, bool isPersistent, string loginProvider = null, bool bypassTwoFactor = false)
|
||||
{
|
||||
@@ -524,117 +471,5 @@ namespace Umbraco.Web.Common.Security
|
||||
public string UserId { get; set; }
|
||||
public string LoginProvider { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used for auto linking/creating user accounts for external logins
|
||||
/// </summary>
|
||||
/// <param name="loginInfo"></param>
|
||||
/// <param name="autoLinkOptions"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<SignInResult> AutoLinkAndSignInExternalAccount(ExternalLoginInfo loginInfo, ExternalSignInAutoLinkOptions autoLinkOptions)
|
||||
{
|
||||
// If there are no autolink options then the attempt is failed (user does not exist)
|
||||
if (autoLinkOptions == null || !autoLinkOptions.AutoLinkExternalAccount)
|
||||
{
|
||||
return SignInResult.Failed;
|
||||
}
|
||||
|
||||
var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email);
|
||||
|
||||
//we are allowing auto-linking/creating of local accounts
|
||||
if (email.IsNullOrWhiteSpace())
|
||||
{
|
||||
return AutoLinkSignInResult.FailedNoEmail;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Now we need to perform the auto-link, so first we need to lookup/create a user with the email address
|
||||
var autoLinkUser = await UserManager.FindByEmailAsync(email);
|
||||
if (autoLinkUser != null)
|
||||
{
|
||||
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);
|
||||
return AutoLinkSignInResult.FailedException(ex.Message);
|
||||
}
|
||||
|
||||
return 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);
|
||||
|
||||
foreach (var userGroup in autoLinkOptions.DefaultUserGroups)
|
||||
{
|
||||
autoLinkUser.AddRole(userGroup);
|
||||
}
|
||||
|
||||
//call the callback if one is assigned
|
||||
try
|
||||
{
|
||||
autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider);
|
||||
return AutoLinkSignInResult.FailedException(ex.Message);
|
||||
}
|
||||
|
||||
var userCreationResult = await _userManager.CreateAsync(autoLinkUser);
|
||||
|
||||
if (!userCreationResult.Succeeded)
|
||||
{
|
||||
return AutoLinkSignInResult.FailedCreatingUser(userCreationResult.Errors.Select(x => x.Description).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
return await LinkUser(autoLinkUser, loginInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SignInResult> 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
|
||||
return await SignInOrTwoFactorAsync(autoLinkUser, isPersistent: false, loginInfo.LoginProvider);
|
||||
}
|
||||
|
||||
var linkResult = await _userManager.AddLoginAsync(autoLinkUser, loginInfo);
|
||||
if (linkResult.Succeeded)
|
||||
{
|
||||
//we're good! sign in
|
||||
return await SignInOrTwoFactorAsync(autoLinkUser, isPersistent: false, loginInfo.LoginProvider);
|
||||
}
|
||||
|
||||
//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)
|
||||
{
|
||||
var errors = linkResult.Errors.Select(x => x.Description).ToList();
|
||||
return AutoLinkSignInResult.FailedLinkingUser(errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
//DOH! ... this isn't good, combine all errors to be shown
|
||||
var errors = linkResult.Errors.Concat(deleteResult.Errors).Select(x => x.Description).ToList();
|
||||
return AutoLinkSignInResult.FailedLinkingUser(errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using System;
|
||||
@@ -29,7 +28,6 @@ namespace Umbraco.Web.Common.Filters
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly bool _redirectToUmbracoLogin;
|
||||
private string _redirectUrl;
|
||||
|
||||
|
||||
private UmbracoBackOfficeAuthorizeFilter(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
|
||||
Reference in New Issue
Block a user