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)); } }
/// <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)); } var shouldLinkUser = autoLinkOptions.OnExternalLogin == null || autoLinkOptions.OnExternalLogin(autoLinkUser, loginInfo); if (shouldLinkUser) { return(await LinkUser(autoLinkUser, loginInfo)); } else { LogFailedExternalLogin(loginInfo, autoLinkUser); return(ExternalLoginSignInResult.NotAllowed); } } 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 { var shouldLinkUser = autoLinkOptions.OnExternalLogin == null || autoLinkOptions.OnExternalLogin(autoLinkUser, loginInfo); if (shouldLinkUser) { return(await LinkUser(autoLinkUser, loginInfo)); } else { LogFailedExternalLogin(loginInfo, autoLinkUser); return(ExternalLoginSignInResult.NotAllowed); } } } } }