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-03-30 21:27:35 +02:00
using Umbraco.Web.BackOffice.Filters ;
2020-03-31 10:57:56 +02:00
using Umbraco.Web.Common.ActionResults ;
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-03-30 21:27:35 +02:00
namespace Umbraco.Web.BackOffice.Controllers
{
2020-09-10 13:51:59 +02:00
//[UmbracoRequireHttps] //TODO Reintroduce
2020-05-27 18:27:49 +10:00
[PluginController(Constants.Web.Mvc.BackOfficeArea)]
2020-10-15 11:42:16 +02:00
public class BackOfficeController : UmbracoController
2020-03-30 21:27:35 +02:00
{
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-05-25 23:15:32 +10:00
private readonly BackOfficeSignInManager _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-21 16:51:00 +11:00
private readonly IExternalAuthenticationOptions _externalAuthenticationOptions ;
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-08-04 14:30:42 +10:00
BackOfficeSignInManager 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 ,
IExternalAuthenticationOptions externalAuthenticationOptions )
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-21 16:51:00 +11:00
_externalAuthenticationOptions = externalAuthenticationOptions ;
2020-03-30 21:27:35 +02:00
}
2020-05-13 14:49:00 +10:00
[HttpGet]
2020-05-21 15:43:33 +10:00
public async Task < IActionResult > Default ( )
2020-03-30 21:27:35 +02:00
{
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-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]
public async Task < IActionResult > VerifyInvite ( string invite )
{
//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-10-21 16:51:00 +11:00
if ( _backofficeSecurityAccessor . BackOfficeSecurity . IsAuthenticated ( ) )
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)]
public async Task < IActionResult > AuthorizeUpgrade ( )
{
var viewPath = Path . Combine ( _globalSettings . UmbracoPath , Umbraco . Core . Constants . Web . Mvc . BackOfficeArea , nameof ( AuthorizeUpgrade ) + ".cshtml" ) ;
return await RenderDefaultOrProcessExternalLoginAsync (
//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-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-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-05-18 15:19:52 +02:00
[UmbracoAuthorize(Order = 0)]
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>
[UmbracoAuthorize(Order = 0)]
[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]
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
}
var properties = _signInManager . ConfigureExternalAuthenticationProperties ( provider , redirectUrl ) ;
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>
[UmbracoAuthorize]
[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-10-21 16:51:00 +11:00
var properties = _signInManager . ConfigureExternalAuthenticationProperties ( provider , redirectUrl , User . Identity . GetUserId ( ) ) ;
return Challenge ( properties , provider ) ;
}
2020-05-20 15:25:42 +10:00
[HttpGet]
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>
[UmbracoAuthorize]
[HttpGet]
public async Task < IActionResult > ExternalLinkLoginCallback ( )
{
var loginInfo = await _signInManager . GetExternalLoginInfoAsync ( ) ;
if ( loginInfo = = null )
{
//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
}
var user = await _userManager . FindByIdAsync ( User . Identity . GetUserId ( ) ) ;
if ( user = = null )
{
// ... 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
}
var result2 = await _userManager . AddLoginAsync ( user , loginInfo ) ;
if ( result2 . Succeeded )
{
// 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
TempData [ ViewDataExtensions . TokenExternalSignInError ] = result2 . 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-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 ) ) ;
//check if there is the TempData with the any token name specified, if so, assign to view bag and render the view
if ( ViewData . FromTempData ( TempData , ViewDataExtensions . TokenExternalSignInError ) | |
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 ( ) ;
if ( loginInfo = = null | | loginInfo . Principal = = null )
{
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 ) ) ;
ExternalSignInAutoLinkOptions autoLinkOptions = null ;
2020-05-21 15:43:33 +10:00
2020-10-21 16:51:00 +11:00
var authType = ( await _signInManager . GetExternalAuthenticationSchemesAsync ( ) )
. FirstOrDefault ( x = > x . Name = = loginInfo . LoginProvider ) ;
if ( authType = = null )
{
_logger . LogWarning ( "Could not find external authentication provider registered: {LoginProvider}" , loginInfo . LoginProvider ) ;
}
else
{
autoLinkOptions = _externalAuthenticationOptions . Get ( authType . Name ) ;
}
// 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 )
{
// TODO: It might be worth keeping some of the claims associated with the ExternalLoginInfo, in which case we
// wouldn't necessarily sign the user in here with the standard login, instead we'd update the
// UseUmbracoBackOfficeExternalCookieAuthentication extension method to have the correct provider and claims factory,
// ticket format, etc.. to create our back office user including the claims assigned and in this method we'd just ensure
// that the ticket is created and stored and that the user is logged in.
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 . SetExternalSignInError ( new [ ] { "The requested provider (" + loginInfo . LoginProvider + ") has not been linked to an account" } ) ;
}
//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 true ; // TODO: This seems weird to return true, but it was like that before so must be a reason?
var email = loginInfo . Principal . FindFirstValue ( ClaimTypes . Email ) ;
//we are allowing auto-linking/creating of local accounts
if ( email . IsNullOrWhiteSpace ( ) )
{
ViewData . SetExternalSignInError ( 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 )
{
// TODO This will be filled out with 8.9 changes
throw new NotImplementedException ( "Merge 8.9 changes in!" ) ;
}
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
autoLinkOptions . OnAutoLinking ? . Invoke ( autoLinkUser , loginInfo ) ;
var userCreationResult = await _userManager . CreateAsync ( autoLinkUser ) ;
if ( userCreationResult . Succeeded = = false )
{
ViewData . SetExternalSignInError ( userCreationResult . Errors . Select ( x = > x . Description ) . ToList ( ) ) ;
}
else
{
var linkResult = await _userManager . AddLoginAsync ( autoLinkUser , loginInfo ) ;
if ( linkResult . Succeeded = = false )
{
ViewData . SetExternalSignInError ( 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 = = false )
{
//DOH! ... this isn't good, combine all errors to be shown
ViewData . SetExternalSignInError ( linkResult . Errors . Concat ( deleteResult . Errors ) . Select ( x = > x . Description ) . ToList ( ) ) ;
}
}
else
{
//sign in
await _signInManager . SignInAsync ( autoLinkUser , isPersistent : false ) ;
}
}
}
}
return true ;
2020-05-21 15:43:33 +10:00
}
// Used for XSRF protection when adding external logins
2020-10-21 16:51:00 +11:00
// TODO: This is duplicated in BackOfficeSignInManager
2020-05-21 15:43:33 +10:00
private const string XsrfKey = "XsrfId" ;
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
}
}