Implements more BackOfficeController and AuthenticationController, some web security and more, gets the back office UI almost rendering

This commit is contained in:
Shannon
2020-05-25 23:15:32 +10:00
parent 0730867d74
commit 9dcad544a9
16 changed files with 611 additions and 222 deletions

View File

@@ -1,8 +1,23 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Net;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.BackOffice;
using Umbraco.Core.Configuration;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Security;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.Exceptions;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Security;
using Constants = Umbraco.Core.Constants;
@@ -12,14 +27,31 @@ namespace Umbraco.Web.BackOffice.Controllers
//[ValidationFilter] // TODO: I don't actually think this is required with our custom Application Model conventions applied
[TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions
[IsBackOffice] // TODO: This could be applied with our Application Model conventions
public class AuthenticationController : ControllerBase
public class AuthenticationController : UmbracoApiControllerBase
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController and it should not be an auto-routed api controller
private readonly BackOfficeUserManager _userManager;
private readonly BackOfficeSignInManager _signInManager;
private readonly IUserService _userService;
private readonly UmbracoMapper _umbracoMapper;
private readonly IGlobalSettings _globalSettings;
public AuthenticationController(IUmbracoContextAccessor umbracoContextAccessor)
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController
public AuthenticationController(
IUmbracoContextAccessor umbracoContextAccessor,
BackOfficeUserManager backOfficeUserManager,
BackOfficeSignInManager signInManager,
IUserService userService,
UmbracoMapper umbracoMapper,
IGlobalSettings globalSettings)
{
_umbracoContextAccessor = umbracoContextAccessor;
_userManager = backOfficeUserManager;
_signInManager = signInManager;
_userService = userService;
_umbracoMapper = umbracoMapper;
_globalSettings = globalSettings;
}
/// <summary>
@@ -37,5 +69,95 @@ namespace Umbraco.Web.BackOffice.Controllers
}
return false;
}
/// <summary>
/// Logs a user in
/// </summary>
/// <returns></returns>
[TypeFilter(typeof(SetAngularAntiForgeryTokens))]
public async Task<UserDetail> PostLogin(LoginModel loginModel)
{
// Sign the user in with username/password, this also gives a chance for developers to
// custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker
var result = await _signInManager.PasswordSignInAsync(
loginModel.Username, loginModel.Password, isPersistent: true, lockoutOnFailure: true);
if (result.Succeeded)
{
// get the user
var user = _userService.GetByUsername(loginModel.Username);
_userManager.RaiseLoginSuccessEvent(User, user.Id);
// TODO: This is not correct, no user will be set :/
return SetPrincipalAndReturnUserDetail(user, HttpContext.User);
}
if (result.RequiresTwoFactor)
{
throw new NotImplementedException("Implement MFA/2FA, we need to have some IOptions or similar to configure this");
//var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions;
//if (twofactorOptions == null)
//{
// throw new HttpResponseException(
// Request.CreateErrorResponse(
// HttpStatusCode.BadRequest,
// "UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions)));
//}
//var twofactorView = twofactorOptions.GetTwoFactorView(
// owinContext,
// UmbracoContext,
// loginModel.Username);
//if (twofactorView.IsNullOrWhiteSpace())
//{
// throw new HttpResponseException(
// Request.CreateErrorResponse(
// HttpStatusCode.BadRequest,
// typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string"));
//}
//var attemptedUser = Services.UserService.GetByUsername(loginModel.Username);
//// create a with information to display a custom two factor send code view
//var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new
//{
// twoFactorView = twofactorView,
// userId = attemptedUser.Id
//});
//_userManager.RaiseLoginRequiresVerificationEvent(User, attemptedUser.Id);
//return verifyResponse;
}
// return BadRequest (400), we don't want to return a 401 because that get's intercepted
// by our angular helper because it thinks that we need to re-perform the request once we are
// authorized and we don't want to return a 403 because angular will show a warning message indicating
// that the user doesn't have access to perform this function, we just want to return a normal invalid message.
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
/// <summary>
/// This is used when the user is auth'd successfully and we need to return an OK with user details along with setting the current Principal in the request
/// </summary>
/// <param name="user"></param>
/// <param name="principal"></param>
/// <returns></returns>
private UserDetail SetPrincipalAndReturnUserDetail(IUser user, ClaimsPrincipal principal)
{
if (user == null) throw new ArgumentNullException("user");
if (principal == null) throw new ArgumentNullException(nameof(principal));
var userDetail = _umbracoMapper.Map<UserDetail>(user);
// update the userDetail and set their remaining seconds
userDetail.SecondsUntilTimeout = TimeSpan.FromMinutes(_globalSettings.TimeOutInMinutes).TotalSeconds;
// ensure the user is set for the current request
HttpContext.SetPrincipalForRequest(principal);
return userDetail;
}
}
}

View File

@@ -16,6 +16,7 @@ using Umbraco.Core.Services;
using Umbraco.Core.WebAssets;
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Filters;
using Umbraco.Web.BackOffice.Security;
using Umbraco.Web.Common.ActionResults;
using Umbraco.Web.Models;
using Umbraco.Web.WebAssets;
@@ -36,20 +37,19 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly IGridConfig _gridConfig;
private readonly BackOfficeServerVariables _backOfficeServerVariables;
private readonly AppCaches _appCaches;
private readonly SignInManager<BackOfficeIdentityUser> _signInManager;
private readonly BackOfficeSignInManager _signInManager;
public BackOfficeController(
BackOfficeUserManager userManager,
IRuntimeMinifier runtimeMinifier,
IGlobalSettings globalSettings,
IHostingEnvironment hostingEnvironment,
IUmbracoContextAccessor umbracoContextAccessor,
ILocalizedTextService textService,
IGridConfig gridConfig,
BackOfficeServerVariables backOfficeServerVariables,
AppCaches appCaches,
SignInManager<BackOfficeIdentityUser> signInManager // TODO: Review this, do we want it/need it or create our own?
BackOfficeSignInManager signInManager // TODO: Review this, do we want it/need it or create our own?
)
{
_userManager = userManager;

View File

@@ -15,6 +15,7 @@ using Umbraco.Core.Hosting;
using Umbraco.Core.WebAssets;
using Umbraco.Extensions;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Editors;
using Umbraco.Web.Features;
using Umbraco.Web.Trees;
using Umbraco.Web.WebApi;
@@ -39,7 +40,6 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly ISecuritySettings _securitySettings;
private readonly IRuntimeMinifier _runtimeMinifier;
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
private readonly UmbracoApiControllerTypeCollection _apiControllers;
public BackOfficeServerVariables(
LinkGenerator linkGenerator,
@@ -54,8 +54,7 @@ namespace Umbraco.Web.BackOffice.Controllers
IRuntimeSettings settings,
ISecuritySettings securitySettings,
IRuntimeMinifier runtimeMinifier,
IAuthenticationSchemeProvider authenticationSchemeProvider,
UmbracoApiControllerTypeCollection apiControllers)
IAuthenticationSchemeProvider authenticationSchemeProvider)
{
_linkGenerator = linkGenerator;
_runtimeState = runtimeState;
@@ -70,7 +69,6 @@ namespace Umbraco.Web.BackOffice.Controllers
_securitySettings = securitySettings;
_runtimeMinifier = runtimeMinifier;
_authenticationSchemeProvider = authenticationSchemeProvider;
_apiControllers = apiControllers;
}
/// <summary>
@@ -114,7 +112,11 @@ namespace Umbraco.Web.BackOffice.Controllers
// TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address
// so based on compat and how things are currently working we need to replace the serverVarsJs one
((Dictionary<string, object>)defaults["umbracoUrls"])["serverVarsJs"] = _linkGenerator.GetPathByAction("ServerVariables", "BackOffice");
((Dictionary<string, object>)defaults["umbracoUrls"])["serverVarsJs"]
= _linkGenerator.GetPathByAction(
nameof(BackOfficeController.ServerVariables),
ControllerExtensions.GetControllerName<BackOfficeController>(),
new { area = Constants.Web.Mvc.BackOfficeArea });
return defaults;
}
@@ -126,6 +128,7 @@ namespace Umbraco.Web.BackOffice.Controllers
internal async Task<Dictionary<string, object>> GetServerVariablesAsync()
{
var globalSettings = _globalSettings;
var backOfficeControllerName = ControllerExtensions.GetControllerName<BackOfficeController>();
var defaultVals = new Dictionary<string, object>
{
{
@@ -136,211 +139,211 @@ namespace Umbraco.Web.BackOffice.Controllers
// having each url defined here explicitly - we can do that in v8! for now
// for umbraco services we'll stick to explicitly defining the endpoints.
{"externalLoginsUrl", _linkGenerator.GetPathByAction("ExternalLogin", "BackOffice")},
{"externalLinkLoginsUrl", _linkGenerator.GetPathByAction("LinkLogin", "BackOffice")},
{"gridConfig", _linkGenerator.GetPathByAction("GetGridConfig", "BackOffice")},
//{"externalLoginsUrl", _linkGenerator.GetPathByAction(nameof(BackOfficeController.ExternalLogin), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
//{"externalLinkLoginsUrl", _linkGenerator.GetPathByAction(nameof(BackOfficeController.LinkLogin), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
{"gridConfig", _linkGenerator.GetPathByAction(nameof(BackOfficeController.GetGridConfig), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
// TODO: This is ultra confusing! this same key is used for different things, when returning the full app when authenticated it is this URL but when not auth'd it's actually the ServerVariables address
{"serverVarsJs", _linkGenerator.GetPathByAction("Application", "BackOffice")},
{"serverVarsJs", _linkGenerator.GetPathByAction(nameof(BackOfficeController.Application), backOfficeControllerName, new { area = Constants.Web.Mvc.BackOfficeArea })},
//API URLs
{
"packagesRestApiBaseUrl", Constants.PackageRepository.RestApiBaseUrl
},
//{
// "redirectUrlManagementApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RedirectUrlManagementController>(
// _apiControllers, controller => controller.GetEnableState())
//},
//{
// "tourApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TourController>(
// _apiControllers, controller => controller.GetTours())
// controller => controller.GetEnableState())
//},
{
"tourApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<TourController>(
controller => controller.GetTours())
},
//{
// "embedApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RteEmbedController>(
// _apiControllers, controller => controller.GetEmbed("", 0, 0))
// controller => controller.GetEmbed("", 0, 0))
//},
//{
// "userApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<UsersController>(
// _apiControllers, controller => controller.PostSaveUser(null))
// controller => controller.PostSaveUser(null))
//},
//{
// "userGroupsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<UserGroupsController>(
// _apiControllers, controller => controller.PostSaveUserGroup(null))
// controller => controller.PostSaveUserGroup(null))
//},
//{
// "contentApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ContentController>(
// _apiControllers, controller => controller.PostSave(null))
// controller => controller.PostSave(null))
//},
//{
// "mediaApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MediaController>(
// _apiControllers, controller => controller.GetRootMedia())
//},
//{
// "imagesApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ImagesController>(
// _apiControllers, controller => controller.GetBigThumbnail(""))
// controller => controller.GetRootMedia())
//},
{
"imagesApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<ImagesController>(
controller => controller.GetBigThumbnail(""))
},
//{
// "sectionApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<SectionController>(
// _apiControllers, controller => controller.GetSections())
// controller => controller.GetSections())
//},
//{
// "treeApplicationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ApplicationTreeController>(
// _apiControllers, controller => controller.GetApplicationTrees(null, null, null, TreeUse.None))
// controller => controller.GetApplicationTrees(null, null, null, TreeUse.None))
//},
//{
// "contentTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ContentTypeController>(
// _apiControllers, controller => controller.GetAllowedChildren(0))
// controller => controller.GetAllowedChildren(0))
//},
//{
// "mediaTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MediaTypeController>(
// _apiControllers, controller => controller.GetAllowedChildren(0))
// controller => controller.GetAllowedChildren(0))
//},
//{
// "macroRenderingApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MacroRenderingController>(
// _apiControllers, controller => controller.GetMacroParameters(0))
// controller => controller.GetMacroParameters(0))
//},
//{
// "macroApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MacrosController>(
// _apiControllers, controller => controller.Create(null))
//},
//{
// "authenticationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<AuthenticationController>(
// _apiControllers, controller => controller.PostLogin(null))
// controller => controller.Create(null))
//},
{
"authenticationApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<AuthenticationController>(
controller => controller.PostLogin(null))
},
//{
// "currentUserApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<CurrentUserController>(
// _apiControllers, controller => controller.PostChangePassword(null))
// controller => controller.PostChangePassword(null))
//},
//{
// "entityApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<EntityController>(
// _apiControllers, controller => controller.GetById(0, UmbracoEntityTypes.Media))
// controller => controller.GetById(0, UmbracoEntityTypes.Media))
//},
//{
// "dataTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<DataTypeController>(
// _apiControllers, controller => controller.GetById(0))
//},
//{
// "dashboardApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<DashboardController>(
// _apiControllers, controller => controller.GetDashboard(null))
// controller => controller.GetById(0))
//},
{
"dashboardApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<DashboardController>(
controller => controller.GetDashboard(null))
},
//{
// "logApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<LogController>(
// _apiControllers, controller => controller.GetPagedEntityLog(0, 0, 0, Direction.Ascending, null))
// controller => controller.GetPagedEntityLog(0, 0, 0, Direction.Ascending, null))
//},
//{
// "memberApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MemberController>(
// _apiControllers, controller => controller.GetByKey(Guid.Empty))
// controller => controller.GetByKey(Guid.Empty))
//},
//{
// "packageInstallApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PackageInstallController>(
// _apiControllers, controller => controller.Fetch(string.Empty))
// controller => controller.Fetch(string.Empty))
//},
//{
// "packageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PackageController>(
// _apiControllers, controller => controller.GetCreatedPackages())
// controller => controller.GetCreatedPackages())
//},
//{
// "relationApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RelationController>(
// _apiControllers, controller => controller.GetById(0))
// controller => controller.GetById(0))
//},
//{
// "rteApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RichTextPreValueController>(
// _apiControllers, controller => controller.GetConfiguration())
// controller => controller.GetConfiguration())
//},
//{
// "stylesheetApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<StylesheetController>(
// _apiControllers, controller => controller.GetAll())
// controller => controller.GetAll())
//},
//{
// "memberTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MemberTypeController>(
// _apiControllers, controller => controller.GetAllTypes())
// controller => controller.GetAllTypes())
//},
//{
// "memberGroupApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MemberGroupController>(
// _apiControllers, controller => controller.GetAllGroups())
// controller => controller.GetAllGroups())
//},
//{
// "updateCheckApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<UpdateCheckController>(
// _apiControllers, controller => controller.GetCheck())
// controller => controller.GetCheck())
//},
//{
// "templateApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TemplateController>(
// _apiControllers, controller => controller.GetById(0))
// controller => controller.GetById(0))
//},
//{
// "memberTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MemberTreeController>(
// _apiControllers, controller => controller.GetNodes("-1", null))
// controller => controller.GetNodes("-1", null))
//},
//{
// "mediaTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<MediaTreeController>(
// _apiControllers, controller => controller.GetNodes("-1", null))
// controller => controller.GetNodes("-1", null))
//},
//{
// "contentTreeBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ContentTreeController>(
// _apiControllers, controller => controller.GetNodes("-1", null))
// controller => controller.GetNodes("-1", null))
//},
//{
// "tagsDataBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TagsDataController>(
// _apiControllers, controller => controller.GetTags("", "", null))
//},
//{
// "examineMgmtBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ExamineManagementController>(
// _apiControllers, controller => controller.GetIndexerDetails())
// controller => controller.GetTags("", "", null))
//},
{
"examineMgmtBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<ExamineManagementController>(
controller => controller.GetIndexerDetails())
},
//{
// "healthCheckBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<HealthCheckController>(
// _apiControllers, controller => controller.GetAllHealthChecks())
// controller => controller.GetAllHealthChecks())
//},
//{
// "templateQueryApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TemplateQueryController>(
// _apiControllers, controller => controller.PostTemplateQuery(null))
// controller => controller.PostTemplateQuery(null))
//},
//{
// "codeFileApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<CodeFileController>(
// _apiControllers, controller => controller.GetByPath("", ""))
// controller => controller.GetByPath("", ""))
//},
//{
// "publishedStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PublishedStatusController>(
// _apiControllers, controller => controller.GetPublishedStatusUrl())
// controller => controller.GetPublishedStatusUrl())
//},
//{
// "dictionaryApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<DictionaryController>(
// _apiControllers, controller => controller.DeleteById(int.MaxValue))
// controller => controller.DeleteById(int.MaxValue))
//},
//{
// "publishedSnapshotCacheStatusBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<PublishedSnapshotCacheStatusController>(
// _apiControllers, controller => controller.GetStatus())
//},
//{
// "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<HelpController>(
// _apiControllers, controller => controller.GetContextHelpForPage("","",""))
// controller => controller.GetStatus())
//},
{
"helpApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<HelpController>(
controller => controller.GetContextHelpForPage("","",""))
},
//{
// "backOfficeAssetsApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<BackOfficeAssetsController>(
// _apiControllers, controller => controller.GetSupportedLocales())
// controller => controller.GetSupportedLocales())
//},
//{
// "languageApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<LanguageController>(
// _apiControllers, controller => controller.GetAllLanguages())
// controller => controller.GetAllLanguages())
//},
//{
// "relationTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<RelationTypeController>(
// _apiControllers, controller => controller.GetById(1))
// controller => controller.GetById(1))
//},
//{
// "logViewerApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<LogViewerController>(
// _apiControllers, controller => controller.GetNumberOfErrors(null, null))
// controller => controller.GetNumberOfErrors(null, null))
//},
//{
// "webProfilingBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<WebProfilingController>(
// _apiControllers, controller => controller.GetStatus())
// controller => controller.GetStatus())
//},
//{
// "tinyMceApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TinyMceController>(
// _apiControllers, controller => controller.UploadImage())
//},
//{
// "imageUrlGeneratorApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<ImageUrlGeneratorController>(
// _apiControllers, controller => controller.GetCropUrl(null, null, null, null, null))
// controller => controller.UploadImage())
//},
{
"imageUrlGeneratorApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl<ImageUrlGeneratorController>(
controller => controller.GetCropUrl(null, null, null, null, null))
},
}
},
{
@@ -394,6 +397,8 @@ namespace Umbraco.Web.BackOffice.Controllers
"externalLogins", new Dictionary<string, object>
{
{
//TODO: I think this should be: _signInManager.GetExternalAuthenticationSchemesAsync() or however that works
"providers", (await _authenticationSchemeProvider.GetAllSchemesAsync())
// 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