2020-05-11 17:05:57 +02:00
using System ;
2020-05-25 19:37:16 +10:00
using System.Collections.Generic ;
2020-05-11 17:05:57 +02:00
using System.Globalization ;
2020-07-08 11:18:23 +02:00
using System.IO ;
2020-05-11 17:05:57 +02:00
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
2020-03-30 21:27:35 +02:00
using Microsoft.AspNetCore.Mvc ;
2020-08-21 14:52:47 +01:00
using Microsoft.Extensions.Options ;
2020-09-21 09:27:54 +02:00
using Microsoft.Extensions.Logging ;
2020-05-11 17:05:57 +02:00
using Umbraco.Core ;
2020-05-20 15:25:42 +10:00
using Umbraco.Core.BackOffice ;
2020-05-20 16:43:06 +10:00
using Umbraco.Core.Cache ;
2020-04-02 17:41:00 +11:00
using Umbraco.Core.Configuration ;
2020-05-11 17:05:57 +02:00
using Umbraco.Core.Configuration.Grid ;
2020-08-21 14:52:47 +01:00
using Umbraco.Core.Configuration.Models ;
2020-04-03 11:03:06 +11:00
using Umbraco.Core.Hosting ;
2020-09-22 10:01:00 +02:00
using Umbraco.Core.Security ;
2020-08-31 13:39:29 +02:00
using Umbraco.Core.Serialization ;
2020-05-11 17:05:57 +02:00
using Umbraco.Core.Services ;
2020-04-02 17:41:00 +11:00
using Umbraco.Core.WebAssets ;
2020-05-20 15:25:42 +10:00
using Umbraco.Extensions ;
2020-10-26 13:34:08 +01:00
using Umbraco.Web.BackOffice.ActionResults ;
2020-03-30 21:27:35 +02:00
using Umbraco.Web.BackOffice.Filters ;
2020-05-27 18:27:49 +10:00
using Umbraco.Web.Common.Attributes ;
2020-08-04 14:30:42 +10:00
using Umbraco.Web.Common.Filters ;
2020-06-02 13:28:30 +10:00
using Umbraco.Web.Common.Security ;
2020-05-20 15:25:42 +10:00
using Umbraco.Web.Models ;
2020-10-15 11:42:16 +02:00
using Umbraco.Web.Mvc ;
2020-04-02 21:19:42 +11:00
using Umbraco.Web.WebAssets ;
2020-05-14 20:59:29 +10:00
using Constants = Umbraco . Core . Constants ;
2020-10-21 16:51:00 +11:00
using Microsoft.AspNetCore.Identity ;
using System.Security.Claims ;
2020-10-23 10:36:47 +02:00
using Microsoft.AspNetCore.Http ;
2020-11-27 13:33:01 +01:00
using Umbraco.Web.BackOffice.Security ;
2020-12-02 12:22:08 +11:00
using Umbraco.Web.Common.ActionsResults ;
2020-11-20 15:32:36 +11:00
using Microsoft.AspNetCore.Authorization ;
using Umbraco.Web.Common.Authorization ;
2020-12-02 14:28:16 +11:00
using Microsoft.AspNetCore.Authentication ;
2020-03-30 21:27:35 +02:00
namespace Umbraco.Web.BackOffice.Controllers
{
2020-11-27 13:33:01 +01:00
[DisableBrowserCache]
2020-12-02 14:28:16 +11:00
[UmbracoRequireHttps]
2020-05-27 18:27:49 +10:00
[PluginController(Constants.Web.Mvc.BackOfficeArea)]
2020-11-27 13:35:22 +01:00
[IsBackOffice]
2020-10-15 11:42:16 +02:00
public class BackOfficeController : UmbracoController
2020-03-30 21:27:35 +02:00
{
2020-12-02 14:28:16 +11:00
// NOTE: Each action must either be explicitly authorized or explicitly [AllowAnonymous], the latter is optional because
// this controller itself doesn't require authz but it's more clear what the intention is.
2020-09-22 14:44:41 +02:00
private readonly IBackOfficeUserManager _userManager ;
2020-03-30 21:27:35 +02:00
private readonly IRuntimeMinifier _runtimeMinifier ;
2020-08-21 14:52:47 +01:00
private readonly GlobalSettings _globalSettings ;
2020-04-03 11:03:06 +11:00
private readonly IHostingEnvironment _hostingEnvironment ;
2020-05-11 17:05:57 +02:00
private readonly ILocalizedTextService _textService ;
private readonly IGridConfig _gridConfig ;
2020-05-20 16:43:06 +10:00
private readonly BackOfficeServerVariables _backOfficeServerVariables ;
private readonly AppCaches _appCaches ;
2020-11-30 22:46:05 +11:00
private readonly IBackOfficeSignInManager _signInManager ;
2020-10-21 16:51:00 +11:00
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor ;
2020-09-21 09:27:54 +02:00
private readonly ILogger < BackOfficeController > _logger ;
2020-08-31 13:39:29 +02:00
private readonly IJsonSerializer _jsonSerializer ;
2020-10-23 14:18:53 +11:00
private readonly IBackOfficeExternalLoginProviders _externalLogins ;
2020-10-23 10:36:47 +02:00
private readonly IHttpContextAccessor _httpContextAccessor ;
2020-12-02 12:22:08 +11:00
private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions ;
2020-03-30 21:27:35 +02:00
2020-05-20 15:25:42 +10:00
public BackOfficeController (
2020-09-22 14:44:41 +02:00
IBackOfficeUserManager userManager ,
2020-05-20 15:25:42 +10:00
IRuntimeMinifier runtimeMinifier ,
2020-08-23 23:36:48 +02:00
IOptions < GlobalSettings > globalSettings ,
2020-05-20 15:25:42 +10:00
IHostingEnvironment hostingEnvironment ,
ILocalizedTextService textService ,
2020-05-20 16:43:06 +10:00
IGridConfig gridConfig ,
BackOfficeServerVariables backOfficeServerVariables ,
2020-05-21 15:43:33 +10:00
AppCaches appCaches ,
2020-11-30 22:46:05 +11:00
IBackOfficeSignInManager signInManager ,
2020-10-21 16:51:00 +11:00
IBackOfficeSecurityAccessor backofficeSecurityAccessor ,
2020-09-23 13:32:49 +02:00
ILogger < BackOfficeController > logger ,
2020-10-21 16:51:00 +11:00
IJsonSerializer jsonSerializer ,
2020-10-23 10:36:47 +02:00
IBackOfficeExternalLoginProviders externalLogins ,
2020-12-02 12:22:08 +11:00
IHttpContextAccessor httpContextAccessor ,
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions )
2020-03-30 21:27:35 +02:00
{
2020-05-20 15:25:42 +10:00
_userManager = userManager ;
2020-03-30 21:27:35 +02:00
_runtimeMinifier = runtimeMinifier ;
2020-08-21 14:52:47 +01:00
_globalSettings = globalSettings . Value ;
2020-04-03 11:03:06 +11:00
_hostingEnvironment = hostingEnvironment ;
2020-05-11 17:05:57 +02:00
_textService = textService ;
_gridConfig = gridConfig ? ? throw new ArgumentNullException ( nameof ( gridConfig ) ) ;
2020-05-20 16:43:06 +10:00
_backOfficeServerVariables = backOfficeServerVariables ;
_appCaches = appCaches ;
2020-05-21 15:43:33 +10:00
_signInManager = signInManager ;
2020-09-22 10:01:00 +02:00
_backofficeSecurityAccessor = backofficeSecurityAccessor ;
2020-08-04 14:30:42 +10:00
_logger = logger ;
2020-08-31 13:39:29 +02:00
_jsonSerializer = jsonSerializer ;
2020-10-23 14:18:53 +11:00
_externalLogins = externalLogins ;
2020-10-23 10:36:47 +02:00
_httpContextAccessor = httpContextAccessor ;
2020-12-02 12:22:08 +11:00
_backOfficeTwoFactorOptions = backOfficeTwoFactorOptions ;
2020-03-30 21:27:35 +02:00
}
2020-05-13 14:49:00 +10:00
[HttpGet]
2020-12-02 14:28:16 +11:00
[AllowAnonymous]
2020-05-21 15:43:33 +10:00
public async Task < IActionResult > Default ( )
2020-03-30 21:27:35 +02:00
{
2020-12-02 14:28:16 +11:00
// force authentication to occur since this is not an authorized endpoint
var result = await HttpContext . AuthenticateAsync ( Constants . Security . BackOfficeAuthenticationType ) ;
2020-08-07 00:48:32 +10:00
var viewPath = Path . Combine ( _globalSettings . UmbracoPath , Constants . Web . Mvc . BackOfficeArea , nameof ( Default ) + ".cshtml" )
. Replace ( "\\" , "/" ) ; // convert to forward slashes since it's a virtual path
2020-09-16 09:58:07 +02:00
2020-05-21 15:43:33 +10:00
return await RenderDefaultOrProcessExternalLoginAsync (
2020-12-02 14:28:16 +11:00
result ,
2020-07-08 11:18:23 +02:00
( ) = > View ( viewPath ) ,
( ) = > View ( viewPath ) ) ;
2020-03-30 21:27:35 +02:00
}
2020-08-04 14:30:42 +10:00
[HttpGet]
2020-12-02 14:28:16 +11:00
[AllowAnonymous]
2020-08-04 14:30:42 +10:00
public async Task < IActionResult > VerifyInvite ( string invite )
{
2020-12-02 14:28:16 +11:00
var authenticate = await HttpContext . AuthenticateAsync ( Constants . Security . BackOfficeAuthenticationType ) ;
2020-08-04 14:30:42 +10:00
//if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid
2020-08-04 20:29:48 +02:00
//you'll exit on one of the return RedirectToAction(nameof(Default)) but you're still logged in so you just get
2020-08-04 14:30:42 +10:00
//dumped at the default admin view with no detail
2020-12-02 14:28:16 +11:00
if ( authenticate . Succeeded )
2020-08-04 14:30:42 +10:00
{
await _signInManager . SignOutAsync ( ) ;
}
if ( invite = = null )
{
2020-09-16 09:58:07 +02:00
_logger . LogWarning ( "VerifyUser endpoint reached with invalid token: NULL" ) ;
2020-08-04 14:30:42 +10:00
return RedirectToAction ( nameof ( Default ) ) ;
}
var parts = System . Net . WebUtility . UrlDecode ( invite ) . Split ( '|' ) ;
if ( parts . Length ! = 2 )
{
2020-09-16 09:58:07 +02:00
_logger . LogWarning ( "VerifyUser endpoint reached with invalid token: {Invite}" , invite ) ;
2020-08-04 14:30:42 +10:00
return RedirectToAction ( nameof ( Default ) ) ;
}
var token = parts [ 1 ] ;
var decoded = token . FromUrlBase64 ( ) ;
if ( decoded . IsNullOrWhiteSpace ( ) )
{
2020-09-16 09:58:07 +02:00
_logger . LogWarning ( "VerifyUser endpoint reached with invalid token: {Invite}" , invite ) ;
2020-08-04 14:30:42 +10:00
return RedirectToAction ( nameof ( Default ) ) ;
}
var id = parts [ 0 ] ;
var identityUser = await _userManager . FindByIdAsync ( id ) ;
if ( identityUser = = null )
{
2020-09-16 09:58:07 +02:00
_logger . LogWarning ( "VerifyUser endpoint reached with non existing user: {UserId}" , id ) ;
2020-08-04 14:30:42 +10:00
return RedirectToAction ( nameof ( Default ) ) ;
}
var result = await _userManager . ConfirmEmailAsync ( identityUser , decoded ) ;
if ( result . Succeeded = = false )
{
2020-09-16 09:58:07 +02:00
_logger . LogWarning ( "Could not verify email, Error: {Errors}, Token: {Invite}" , result . Errors . ToErrorMessage ( ) , invite ) ;
2020-08-04 14:30:42 +10:00
return new RedirectResult ( Url . Action ( nameof ( Default ) ) + "#/login/false?invite=3" ) ;
}
//sign the user in
DateTime ? previousLastLoginDate = identityUser . LastLoginDateUtc ;
await _signInManager . SignInAsync ( identityUser , false ) ;
2020-08-04 20:29:48 +02:00
//reset the lastlogindate back to previous as the user hasn't actually logged in, to add a flag or similar to BackOfficeSignInManager would be a breaking change
2020-08-04 14:30:42 +10:00
identityUser . LastLoginDateUtc = previousLastLoginDate ;
await _userManager . UpdateAsync ( identityUser ) ;
return new RedirectResult ( Url . Action ( nameof ( Default ) ) + "#/login/false?invite=1" ) ;
}
/// <summary>
/// This Action is used by the installer when an upgrade is detected but the admin user is not logged in. We need to
/// ensure the user is authenticated before the install takes place so we redirect here to show the standard login screen.
/// </summary>
/// <returns></returns>
[HttpGet]
[StatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable)]
2020-12-02 14:28:16 +11:00
[AllowAnonymous]
2020-08-04 14:30:42 +10:00
public async Task < IActionResult > AuthorizeUpgrade ( )
{
2020-12-02 14:28:16 +11:00
// force authentication to occur since this is not an authorized endpoint
var result = await HttpContext . AuthenticateAsync ( Constants . Security . BackOfficeAuthenticationType ) ;
var viewPath = Path . Combine ( _globalSettings . UmbracoPath , Constants . Web . Mvc . BackOfficeArea , nameof ( AuthorizeUpgrade ) + ".cshtml" ) ;
2020-08-04 14:30:42 +10:00
return await RenderDefaultOrProcessExternalLoginAsync (
2020-12-02 14:28:16 +11:00
result ,
2020-08-04 14:30:42 +10:00
//The default view to render when there is no external login info or errors
( ) = > View ( viewPath ) ,
2020-08-04 20:29:48 +02:00
//The IActionResult to perform if external login is successful
2020-08-04 14:30:42 +10:00
( ) = > Redirect ( "/" ) ) ;
}
2020-03-30 21:27:35 +02:00
/// <summary>
/// Returns the JavaScript main file including all references found in manifests
/// </summary>
/// <returns></returns>
[MinifyJavaScriptResult(Order = 0)]
2020-05-13 14:49:00 +10:00
[HttpGet]
2020-12-02 14:28:16 +11:00
[AllowAnonymous]
2020-03-30 21:27:35 +02:00
public async Task < IActionResult > Application ( )
{
2020-04-03 11:03:06 +11:00
var result = await _runtimeMinifier . GetScriptForLoadingBackOfficeAsync ( _globalSettings , _hostingEnvironment ) ;
2020-03-30 21:27:35 +02:00
return new JavaScriptResult ( result ) ;
}
2020-05-11 17:05:57 +02:00
/// <summary>
/// Get the json localized text for a given culture or the culture for the current user
/// </summary>
/// <param name="culture"></param>
/// <returns></returns>
[HttpGet]
2020-12-02 14:28:16 +11:00
[AllowAnonymous]
2020-05-25 19:37:16 +10:00
public Dictionary < string , Dictionary < string , string > > LocalizedText ( string culture = null )
2020-05-11 17:05:57 +02:00
{
2020-10-21 16:51:00 +11:00
var isAuthenticated = _backofficeSecurityAccessor . BackOfficeSecurity . IsAuthenticated ( ) ;
2020-05-11 17:05:57 +02:00
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 ) ;
var allValues = _textService . GetAllStoredValues ( cultureInfo ) ;
var pathedValues = allValues . Select ( kv = >
{
var slashIndex = kv . Key . IndexOf ( '/' ) ;
var areaAlias = kv . Key . Substring ( 0 , slashIndex ) ;
var valueAlias = kv . Key . Substring ( slashIndex + 1 ) ;
return new
{
areaAlias ,
valueAlias ,
value = kv . Value
} ;
} ) ;
var nestedDictionary = pathedValues
. GroupBy ( pv = > pv . areaAlias )
. ToDictionary ( pv = > pv . Key , pv = >
pv . ToDictionary ( pve = > pve . valueAlias , pve = > pve . value ) ) ;
2020-05-25 19:37:16 +10:00
return nestedDictionary ;
2020-05-11 17:05:57 +02:00
}
2020-11-20 15:32:36 +11:00
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
2020-05-11 17:05:57 +02:00
[HttpGet]
2020-05-25 19:37:16 +10:00
public IEnumerable < IGridEditorConfig > GetGridConfig ( )
2020-05-11 17:05:57 +02:00
{
2020-05-25 19:37:16 +10:00
return _gridConfig . EditorsConfig . Editors ;
2020-05-11 17:05:57 +02:00
}
2020-05-20 15:25:42 +10:00
2020-05-20 16:43:06 +10:00
/// <summary>
/// Returns the JavaScript object representing the static server variables javascript object
/// </summary>
/// <returns></returns>
2020-11-20 15:32:36 +11:00
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
2020-05-20 16:43:06 +10:00
[MinifyJavaScriptResult(Order = 1)]
public async Task < JavaScriptResult > ServerVariables ( )
{
//cache the result if debugging is disabled
var serverVars = ServerVariablesParser . Parse ( await _backOfficeServerVariables . GetServerVariablesAsync ( ) ) ;
var result = _hostingEnvironment . IsDebugMode
? serverVars
: _appCaches . RuntimeCache . GetCacheItem < string > (
typeof ( BackOfficeController ) + "ServerVariables" ,
( ) = > serverVars ,
new TimeSpan ( 0 , 10 , 0 ) ) ;
return new JavaScriptResult ( result ) ;
}
2020-10-21 16:51:00 +11:00
[HttpPost]
2020-12-02 14:28:16 +11:00
[AllowAnonymous]
2020-10-21 16:51:00 +11:00
public ActionResult ExternalLogin ( string provider , string redirectUrl = null )
{
if ( redirectUrl = = null )
{
2020-10-22 13:37:47 +02:00
redirectUrl = Url . Action ( nameof ( Default ) , this . GetControllerName ( ) ) ;
2020-10-21 16:51:00 +11:00
}
2020-11-30 22:23:10 +11:00
// Configures the redirect URL and user identifier for the specified external login
2020-10-21 16:51:00 +11:00
var properties = _signInManager . ConfigureExternalAuthenticationProperties ( provider , redirectUrl ) ;
2020-11-30 22:23:10 +11:00
2020-10-21 16:51:00 +11:00
return Challenge ( properties , provider ) ;
}
/// <summary>
/// Called when a user links an external login provider in the back office
/// </summary>
/// <param name="provider"></param>
/// <returns></returns>
2020-11-20 15:32:36 +11:00
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
2020-10-21 16:51:00 +11:00
[HttpPost]
public ActionResult LinkLogin ( string provider )
{
// Request a redirect to the external login provider to link a login for the current user
2020-10-22 13:37:47 +02:00
var redirectUrl = Url . Action ( nameof ( ExternalLinkLoginCallback ) , this . GetControllerName ( ) ) ;
2020-11-30 22:23:10 +11:00
// Configures the redirect URL and user identifier for the specified external login including xsrf data
var properties = _signInManager . ConfigureExternalAuthenticationProperties ( provider , redirectUrl , _userManager . GetUserId ( User ) ) ;
2020-10-21 16:51:00 +11:00
return Challenge ( properties , provider ) ;
}
2020-05-20 15:25:42 +10:00
[HttpGet]
2020-12-02 14:28:16 +11:00
[AllowAnonymous]
2020-05-25 19:37:16 +10:00
public async Task < IActionResult > ValidatePasswordResetCode ( [ Bind ( Prefix = "u" ) ] int userId , [ Bind ( Prefix = "r" ) ] string resetCode )
2020-05-20 15:25:42 +10:00
{
var user = await _userManager . FindByIdAsync ( userId . ToString ( ) ) ;
if ( user ! = null )
{
2020-08-31 13:39:29 +02:00
var result = await _userManager . VerifyUserTokenAsync ( user , "Default" , "ResetPassword" , resetCode ) ;
2020-05-20 15:25:42 +10:00
if ( result )
{
//Add a flag and redirect for it to be displayed
2020-08-31 13:39:29 +02:00
TempData [ ViewDataExtensions . TokenPasswordResetCode ] = _jsonSerializer . Serialize ( new ValidatePasswordResetCodeModel { UserId = userId , ResetCode = resetCode } ) ;
2020-10-22 13:37:47 +02:00
return RedirectToLocal ( Url . Action ( nameof ( Default ) , this . GetControllerName ( ) ) ) ;
2020-05-20 15:25:42 +10:00
}
}
//Add error and redirect for it to be displayed
TempData [ ViewDataExtensions . TokenPasswordResetCode ] = new [ ] { _textService . Localize ( "login/resetCodeExpired" ) } ;
2020-10-22 13:37:47 +02:00
return RedirectToLocal ( Url . Action ( nameof ( Default ) , this . GetControllerName ( ) ) ) ;
2020-05-20 15:25:42 +10:00
}
2020-10-21 16:51:00 +11:00
/// <summary>
/// Callback path when the user initiates a link login request from the back office to the external provider from the <see cref="LinkLogin(string)"/> action
/// </summary>
2020-11-20 15:32:36 +11:00
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
2020-10-21 16:51:00 +11:00
[HttpGet]
public async Task < IActionResult > ExternalLinkLoginCallback ( )
{
2020-11-30 22:23:10 +11:00
var user = await _userManager . GetUserAsync ( User ) ;
if ( user = = null )
2020-10-21 16:51:00 +11:00
{
2020-11-30 22:23:10 +11:00
// ... this should really not happen
TempData [ ViewDataExtensions . TokenExternalSignInError ] = new [ ] { "Local user does not exist" } ;
2020-10-22 13:37:47 +02:00
return RedirectToLocal ( Url . Action ( nameof ( Default ) , this . GetControllerName ( ) ) ) ;
2020-10-21 16:51:00 +11:00
}
2020-11-30 22:23:10 +11:00
var loginInfo = await _signInManager . GetExternalLoginInfoAsync ( await _userManager . GetUserIdAsync ( user ) ) ;
if ( loginInfo = = null )
2020-10-21 16:51:00 +11:00
{
2020-11-30 22:23:10 +11:00
//Add error and redirect for it to be displayed
TempData [ ViewDataExtensions . TokenExternalSignInError ] = new [ ] { "An error occurred, could not get external login info" } ;
2020-10-22 13:37:47 +02:00
return RedirectToLocal ( Url . Action ( nameof ( Default ) , this . GetControllerName ( ) ) ) ;
2020-10-21 16:51:00 +11:00
}
2020-10-23 14:18:53 +11:00
var addLoginResult = await _userManager . AddLoginAsync ( user , loginInfo ) ;
if ( addLoginResult . Succeeded )
2020-10-21 16:51:00 +11:00
{
// Update any authentication tokens if login succeeded
// TODO: This is a new thing that we need to implement and because we can store data with the external login now, this is exactly
// what this is for but we'll need to peek under the code here to figure out exactly what goes on.
//await _signInManager.UpdateExternalAuthenticationTokensAsync(loginInfo);
2020-10-22 13:37:47 +02:00
return RedirectToLocal ( Url . Action ( nameof ( Default ) , this . GetControllerName ( ) ) ) ;
2020-10-21 16:51:00 +11:00
}
//Add errors and redirect for it to be displayed
2020-10-23 14:18:53 +11:00
TempData [ ViewDataExtensions . TokenExternalSignInError ] = addLoginResult . Errors ;
2020-10-22 13:37:47 +02:00
return RedirectToLocal ( Url . Action ( nameof ( Default ) , this . GetControllerName ( ) ) ) ;
2020-10-21 16:51:00 +11:00
}
2020-05-21 15:43:33 +10:00
/// <summary>
/// Used by Default and AuthorizeUpgrade to render as per normal if there's no external login info,
/// otherwise process the external login info.
/// </summary>
/// <returns></returns>
2020-10-21 16:51:00 +11:00
private async Task < IActionResult > RenderDefaultOrProcessExternalLoginAsync (
2020-12-02 14:28:16 +11:00
AuthenticateResult authenticateResult ,
2020-05-25 19:37:16 +10:00
Func < IActionResult > defaultResponse ,
Func < IActionResult > externalSignInResponse )
2020-05-21 15:43:33 +10:00
{
if ( defaultResponse is null ) throw new ArgumentNullException ( nameof ( defaultResponse ) ) ;
if ( externalSignInResponse is null ) throw new ArgumentNullException ( nameof ( externalSignInResponse ) ) ;
ViewData . SetUmbracoPath ( _globalSettings . GetUmbracoMvcArea ( _hostingEnvironment ) ) ;
2020-10-23 14:18:53 +11:00
//check if there is the TempData or cookies with the any token name specified, if so, assign to view bag and render the view
2020-10-23 10:36:47 +02:00
if ( ViewData . FromBase64CookieData < BackOfficeExternalLoginProviderErrors > ( _httpContextAccessor . HttpContext , ViewDataExtensions . TokenExternalSignInError , _jsonSerializer ) | |
2020-10-23 14:18:53 +11:00
ViewData . FromTempData ( TempData , ViewDataExtensions . TokenExternalSignInError ) | |
2020-05-21 15:43:33 +10:00
ViewData . FromTempData ( TempData , ViewDataExtensions . TokenPasswordResetCode ) )
2020-10-21 16:51:00 +11:00
return defaultResponse ( ) ;
2020-05-21 15:43:33 +10:00
//First check if there's external login info, if there's not proceed as normal
2020-10-21 16:51:00 +11:00
var loginInfo = await _signInManager . GetExternalLoginInfoAsync ( ) ;
2020-10-23 14:18:53 +11:00
2020-10-21 16:51:00 +11:00
if ( loginInfo = = null | | loginInfo . Principal = = null )
{
2020-10-23 14:18:53 +11:00
// if the user is not logged in, check if there's any auto login redirects specified
2020-12-02 14:28:16 +11:00
if ( ! authenticateResult . Succeeded )
2020-10-23 14:18:53 +11:00
{
var oauthRedirectAuthProvider = _externalLogins . GetAutoLoginProvider ( ) ;
if ( ! oauthRedirectAuthProvider . IsNullOrWhiteSpace ( ) )
{
return ExternalLogin ( oauthRedirectAuthProvider ) ;
}
}
2020-10-21 16:51:00 +11:00
return defaultResponse ( ) ;
}
//we're just logging in with an external source, not linking accounts
return await ExternalSignInAsync ( loginInfo , externalSignInResponse ) ;
}
2020-05-21 15:43:33 +10:00
2020-10-21 16:51:00 +11:00
private async Task < IActionResult > ExternalSignInAsync ( ExternalLoginInfo loginInfo , Func < IActionResult > response )
{
if ( loginInfo = = null ) throw new ArgumentNullException ( nameof ( loginInfo ) ) ;
if ( response = = null ) throw new ArgumentNullException ( nameof ( response ) ) ;
2020-05-21 15:43:33 +10:00
2020-11-27 13:34:58 +01:00
// Sign in the user with this external login provider (which auto links, etc...)
var result = await _signInManager . ExternalLoginSignInAsync ( loginInfo , isPersistent : false ) ;
2020-10-21 16:51:00 +11:00
2020-11-27 13:34:58 +01:00
var errors = new List < string > ( ) ;
if ( result = = Microsoft . AspNetCore . Identity . SignInResult . Success )
2020-10-21 16:51:00 +11:00
{
2020-11-27 13:34:58 +01:00
2020-12-02 12:22:08 +11:00
}
else if ( result = = Microsoft . AspNetCore . Identity . SignInResult . TwoFactorRequired )
2020-10-21 16:51:00 +11:00
{
2020-12-02 12:22:08 +11:00
var attemptedUser = await _userManager . FindByLoginAsync ( loginInfo . LoginProvider , loginInfo . ProviderKey ) ;
if ( attemptedUser = = null )
{
return new ValidationErrorResult ( $"No local user found for the login provider {loginInfo.LoginProvider} - {loginInfo.ProviderKey}" ) ;
}
var twofactorView = _backOfficeTwoFactorOptions . GetTwoFactorView ( attemptedUser . UserName ) ;
if ( twofactorView . IsNullOrWhiteSpace ( ) )
{
return new ValidationErrorResult ( $"The registered {typeof(IBackOfficeTwoFactorOptions)} of type {_backOfficeTwoFactorOptions.GetType()} did not return a view for two factor auth " ) ;
}
// create a with information to display a custom two factor send code view
var verifyResponse = new ObjectResult ( new
{
twoFactorView = twofactorView ,
userId = attemptedUser . Id
} )
{
StatusCode = StatusCodes . Status402PaymentRequired
} ;
return verifyResponse ;
2020-10-21 16:51:00 +11:00
}
2020-12-02 12:22:08 +11:00
else if ( result = = Microsoft . AspNetCore . Identity . SignInResult . LockedOut )
2020-10-21 16:51:00 +11:00
{
2020-12-02 12:22:08 +11:00
errors . Add ( $"The local user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out." ) ;
2020-10-21 16:51:00 +11:00
}
2020-11-27 13:34:58 +01:00
else if ( result = = Microsoft . AspNetCore . Identity . SignInResult . NotAllowed )
2020-10-21 16:51:00 +11:00
{
2020-12-02 12:22:08 +11:00
// 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." ) ;
2020-10-21 16:51:00 +11:00
}
2020-11-27 13:34:58 +01:00
else if ( result = = Microsoft . AspNetCore . Identity . SignInResult . Failed )
2020-11-27 13:33:01 +01:00
{
2020-11-27 13:34:58 +01:00
// 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." ) ;
2020-10-21 16:51:00 +11:00
}
2020-11-27 13:34:58 +01:00
else if ( result = = AutoLinkSignInResult . FailedNotLinked )
2020-10-21 16:51:00 +11:00
{
2020-11-27 13:34:58 +01:00
errors . Add ( "The requested provider (" + loginInfo . LoginProvider + ") has not been linked to an account, the provider must be linked from the back office." ) ;
2020-10-21 16:51:00 +11:00
}
2020-11-27 13:34:58 +01:00
else if ( result = = AutoLinkSignInResult . FailedNoEmail )
2020-10-23 14:18:53 +11:00
{
2020-11-27 13:34:58 +01:00
errors . Add ( $"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked." ) ;
2020-10-23 14:18:53 +11:00
}
2020-11-27 13:34:58 +01:00
else if ( result is AutoLinkSignInResult autoLinkSignInResult & & autoLinkSignInResult . Errors . Count > 0 )
2020-10-23 14:18:53 +11:00
{
2020-11-27 13:34:58 +01:00
errors . AddRange ( autoLinkSignInResult . Errors ) ;
2020-10-23 14:18:53 +11:00
}
2020-11-27 13:34:58 +01:00
if ( errors . Count > 0 )
2020-10-23 14:18:53 +11:00
{
ViewData . SetExternalSignInProviderErrors (
new BackOfficeExternalLoginProviderErrors (
loginInfo . LoginProvider ,
2020-11-27 13:34:58 +01:00
errors ) ) ;
2020-10-23 14:18:53 +11:00
}
2020-11-27 13:34:58 +01:00
return response ( ) ;
2020-10-23 14:18:53 +11:00
}
2020-05-25 19:37:16 +10:00
private IActionResult RedirectToLocal ( string returnUrl )
2020-05-20 15:25:42 +10:00
{
if ( Url . IsLocalUrl ( returnUrl ) )
{
return Redirect ( returnUrl ) ;
}
return Redirect ( "/" ) ;
}
2020-05-21 15:46:43 +10:00
2020-05-19 09:52:58 +02:00
2020-03-30 21:27:35 +02:00
}
}