mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
Defect/PM-1196 - SSO with Email 2FA Flow - Email Required error fixed (#2874)
* PM-1196 - Created first draft solution for solving SSO with Email 2FA serverside. Per architectural review discussion, will be replacing OTP use with expiring tokenable implementation in order to decouple the OTP implementation from the need for an auth factor when arriving on the email 2FA screen post SSO. * PM-1196 - Refactored OTP solution to leverage newly created SsoEmail2faSessionTokenable. Working now but some code cleanup required. Might revisit whether or not we still send down email alongside the token or not to make the SendEmailLoginAsync method more streamlined. * PM-1196 - Send down email separately on token rejection b/c of 2FA required so that 2FA Controller send email login can be refactored to be much cleaner with email required. * PM-1196 - Fix lint issues w/ dotnet format. * PM-1196 - More formatting issue fixes. * PM-1196 - Remove unnecessary check as email is required again on TwoFactorEmailRequestModel * PM-1196 - Update SsoEmail2faSessionTokenable to expire after just over 2 min to match client side auth service expiration of 2 min with small buffer. * PM-1196 - Fix lint issue w/ dotnet format. * PM-1196 - Per PR feedback, move CustomTokenRequestValidator constructor param to new line * PM-1196 - Per PR feedback, update ThrowDelayedBadRequestExceptionAsync to return a task so that it can be awaited and so that the calling code can handle any exceptions that occur during its execution * PM-1196 - Per PR feedback, refactor SsoEmail2faSessionTokenable to leverage TimeSpan vs double for token expiration lifetime.
This commit is contained in:
parent
b87846f97f
commit
2ac513e15a
@ -5,6 +5,7 @@ using Bit.Api.Models.Request;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.LoginFeatures.PasswordlessLogin.Interfaces;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Utilities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
@ -12,6 +13,7 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Core.Utilities;
|
||||
using Fido2NetLib;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -31,6 +33,7 @@ public class TwoFactorController : Controller
|
||||
private readonly UserManager<User> _userManager;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IVerifyAuthRequestCommand _verifyAuthRequestCommand;
|
||||
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _tokenDataFactory;
|
||||
|
||||
public TwoFactorController(
|
||||
IUserService userService,
|
||||
@ -39,7 +42,8 @@ public class TwoFactorController : Controller
|
||||
GlobalSettings globalSettings,
|
||||
UserManager<User> userManager,
|
||||
ICurrentContext currentContext,
|
||||
IVerifyAuthRequestCommand verifyAuthRequestCommand)
|
||||
IVerifyAuthRequestCommand verifyAuthRequestCommand,
|
||||
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory)
|
||||
{
|
||||
_userService = userService;
|
||||
_organizationRepository = organizationRepository;
|
||||
@ -48,6 +52,7 @@ public class TwoFactorController : Controller
|
||||
_userManager = userManager;
|
||||
_currentContext = currentContext;
|
||||
_verifyAuthRequestCommand = verifyAuthRequestCommand;
|
||||
_tokenDataFactory = tokenDataFactory;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
@ -85,7 +90,8 @@ public class TwoFactorController : Controller
|
||||
}
|
||||
|
||||
[HttpPost("get-authenticator")]
|
||||
public async Task<TwoFactorAuthenticatorResponseModel> GetAuthenticator([FromBody] SecretVerificationRequestModel model)
|
||||
public async Task<TwoFactorAuthenticatorResponseModel> GetAuthenticator(
|
||||
[FromBody] SecretVerificationRequestModel model)
|
||||
{
|
||||
var user = await CheckAsync(model, false);
|
||||
var response = new TwoFactorAuthenticatorResponseModel(user);
|
||||
@ -101,7 +107,7 @@ public class TwoFactorController : Controller
|
||||
model.ToUser(user);
|
||||
|
||||
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
|
||||
CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator), model.Token))
|
||||
CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator), model.Token))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException("Token", "Invalid token.");
|
||||
@ -158,7 +164,8 @@ public class TwoFactorController : Controller
|
||||
}
|
||||
catch (DuoException)
|
||||
{
|
||||
throw new BadRequestException("Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
|
||||
throw new BadRequestException(
|
||||
"Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
|
||||
}
|
||||
|
||||
model.ToUser(user);
|
||||
@ -215,7 +222,8 @@ public class TwoFactorController : Controller
|
||||
}
|
||||
catch (DuoException)
|
||||
{
|
||||
throw new BadRequestException("Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
|
||||
throw new BadRequestException(
|
||||
"Duo configuration settings are not valid. Please re-check the Duo Admin panel.");
|
||||
}
|
||||
|
||||
model.ToOrganization(organization);
|
||||
@ -254,12 +262,14 @@ public class TwoFactorController : Controller
|
||||
{
|
||||
throw new BadRequestException("Unable to complete WebAuthn registration.");
|
||||
}
|
||||
|
||||
var response = new TwoFactorWebAuthnResponseModel(user);
|
||||
return response;
|
||||
}
|
||||
|
||||
[HttpDelete("webauthn")]
|
||||
public async Task<TwoFactorWebAuthnResponseModel> DeleteWebAuthn([FromBody] TwoFactorWebAuthnDeleteRequestModel model)
|
||||
public async Task<TwoFactorWebAuthnResponseModel> DeleteWebAuthn(
|
||||
[FromBody] TwoFactorWebAuthnDeleteRequestModel model)
|
||||
{
|
||||
var user = await CheckAsync(model, true);
|
||||
await _userService.DeleteWebAuthnKeyAsync(user, model.Id.Value);
|
||||
@ -285,30 +295,46 @@ public class TwoFactorController : Controller
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("send-email-login")]
|
||||
public async Task SendEmailLogin([FromBody] TwoFactorEmailRequestModel model)
|
||||
public async Task SendEmailLoginAsync([FromBody] TwoFactorEmailRequestModel requestModel)
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(model.Email.ToLowerInvariant());
|
||||
var user = await _userManager.FindByEmailAsync(requestModel.Email.ToLowerInvariant());
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
// check if 2FA email is from passwordless
|
||||
if (!string.IsNullOrEmpty(model.AuthRequestAccessCode))
|
||||
if (!string.IsNullOrEmpty(requestModel.AuthRequestAccessCode))
|
||||
{
|
||||
if (await _verifyAuthRequestCommand
|
||||
.VerifyAuthRequestAsync(new Guid(model.AuthRequestId), model.AuthRequestAccessCode))
|
||||
.VerifyAuthRequestAsync(new Guid(requestModel.AuthRequestId),
|
||||
requestModel.AuthRequestAccessCode))
|
||||
{
|
||||
await _userService.SendTwoFactorEmailAsync(user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (await _userService.VerifySecretAsync(user, model.Secret))
|
||||
else if (!string.IsNullOrEmpty(requestModel.SsoEmail2FaSessionToken))
|
||||
{
|
||||
if (this.ValidateSsoEmail2FaToken(requestModel.SsoEmail2FaSessionToken, user))
|
||||
{
|
||||
await _userService.SendTwoFactorEmailAsync(user);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.ThrowDelayedBadRequestExceptionAsync(
|
||||
"Cannot send two-factor email: a valid, non-expired SSO Email 2FA Session token is required to send 2FA emails.",
|
||||
2000);
|
||||
}
|
||||
}
|
||||
else if (await _userService.VerifySecretAsync(user, requestModel.Secret))
|
||||
{
|
||||
await _userService.SendTwoFactorEmailAsync(user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException("Cannot send two-factor email.");
|
||||
await this.ThrowDelayedBadRequestExceptionAsync(
|
||||
"Cannot send two-factor email.", 2000);
|
||||
}
|
||||
|
||||
[HttpPut("email")]
|
||||
@ -319,7 +345,7 @@ public class TwoFactorController : Controller
|
||||
model.ToUser(user);
|
||||
|
||||
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
|
||||
CoreHelpers.CustomProviderName(TwoFactorProviderType.Email), model.Token))
|
||||
CoreHelpers.CustomProviderName(TwoFactorProviderType.Email), model.Token))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException("Token", "Invalid token.");
|
||||
@ -377,7 +403,7 @@ public class TwoFactorController : Controller
|
||||
public async Task PostRecover([FromBody] TwoFactorRecoveryRequestModel model)
|
||||
{
|
||||
if (!await _userService.RecoverTwoFactorAsync(model.Email, model.MasterPasswordHash, model.RecoveryCode,
|
||||
_organizationService))
|
||||
_organizationService))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException(string.Empty, "Invalid information. Try again.");
|
||||
@ -393,7 +419,8 @@ public class TwoFactorController : Controller
|
||||
|
||||
[Obsolete("Leaving this for backwards compatibilty on clients")]
|
||||
[HttpPut("device-verification-settings")]
|
||||
public Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
|
||||
public Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings(
|
||||
[FromBody] DeviceVerificationRequestModel model)
|
||||
{
|
||||
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
|
||||
}
|
||||
@ -428,7 +455,7 @@ public class TwoFactorController : Controller
|
||||
}
|
||||
|
||||
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
|
||||
CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey), value))
|
||||
CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey), value))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException(name, $"{name} is invalid.");
|
||||
@ -438,4 +465,16 @@ public class TwoFactorController : Controller
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateSsoEmail2FaToken(string ssoEmail2FaSessionToken, User user)
|
||||
{
|
||||
return _tokenDataFactory.TryUnprotect(ssoEmail2FaSessionToken, out var decryptedToken) &&
|
||||
decryptedToken.Valid && decryptedToken.TokenIsValid(user);
|
||||
}
|
||||
|
||||
private async Task ThrowDelayedBadRequestExceptionAsync(string message, int delayTime = 2000)
|
||||
{
|
||||
await Task.Delay(delayTime);
|
||||
throw new BadRequestException(message);
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ public class SecretVerificationRequestModel : IValidatableObject
|
||||
{
|
||||
if (string.IsNullOrEmpty(Secret) && string.IsNullOrEmpty(AuthRequestAccessCode))
|
||||
{
|
||||
yield return new ValidationResult("MasterPasswordHash, OTP or AccessCode must be supplied.");
|
||||
yield return new ValidationResult("MasterPasswordHash, OTP, or AccessCode must be supplied.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,9 +202,9 @@ public class TwoFactorEmailRequestModel : SecretVerificationRequestModel
|
||||
[EmailAddress]
|
||||
[StringLength(256)]
|
||||
public string Email { get; set; }
|
||||
|
||||
public string AuthRequestId { get; set; }
|
||||
|
||||
// An auth session token used for obtaining email and as an authN factor for the sending of emailed 2FA OTPs.
|
||||
public string SsoEmail2FaSessionToken { get; set; }
|
||||
public User ToUser(User extistingUser)
|
||||
{
|
||||
var providers = extistingUser.GetTwoFactorProviders();
|
||||
@ -225,6 +225,14 @@ public class TwoFactorEmailRequestModel : SecretVerificationRequestModel
|
||||
extistingUser.SetTwoFactorProviders(providers);
|
||||
return extistingUser;
|
||||
}
|
||||
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Secret) && string.IsNullOrEmpty(AuthRequestAccessCode) && string.IsNullOrEmpty((SsoEmail2FaSessionToken)))
|
||||
{
|
||||
yield return new ValidationResult("MasterPasswordHash, OTP, AccessCode, or SsoEmail2faSessionToken must be supplied.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TwoFactorWebAuthnRequestModel : TwoFactorWebAuthnDeleteRequestModel
|
||||
|
@ -0,0 +1,50 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Tokens;
|
||||
|
||||
namespace Bit.Core.Auth.Models.Business.Tokenables;
|
||||
|
||||
// This token just provides a verifiable authN mechanism for the API service
|
||||
// TwoFactorController.cs SendEmailLogin anonymous endpoint so it cannot be
|
||||
// used maliciously.
|
||||
public class SsoEmail2faSessionTokenable : ExpiringTokenable
|
||||
{
|
||||
// Just over 2 min expiration (client expires session after 2 min)
|
||||
private static readonly TimeSpan _tokenLifetime = TimeSpan.FromMinutes(2.05);
|
||||
public const string ClearTextPrefix = "BwSsoEmail2FaSessionToken_";
|
||||
public const string DataProtectorPurpose = "SsoEmail2faSessionTokenDataProtector";
|
||||
|
||||
public const string TokenIdentifier = "SsoEmail2faSessionToken";
|
||||
|
||||
public string Identifier { get; set; } = TokenIdentifier;
|
||||
public Guid Id { get; set; }
|
||||
public string Email { get; set; }
|
||||
|
||||
|
||||
[JsonConstructor]
|
||||
public SsoEmail2faSessionTokenable()
|
||||
{
|
||||
ExpirationDate = DateTime.UtcNow.Add(_tokenLifetime);
|
||||
}
|
||||
|
||||
public SsoEmail2faSessionTokenable(User user) : this()
|
||||
{
|
||||
Id = user?.Id ?? default;
|
||||
Email = user?.Email;
|
||||
}
|
||||
|
||||
public bool TokenIsValid(User user)
|
||||
{
|
||||
if (Id == default || Email == default || user == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Id == user.Id &&
|
||||
Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
// Validates deserialized
|
||||
protected override bool TokenIsValid() =>
|
||||
Identifier == TokenIdentifier && Id != default && !string.IsNullOrWhiteSpace(Email);
|
||||
}
|
@ -6,6 +6,7 @@ using Bit.Core;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@ -16,6 +17,7 @@ using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Core.Utilities;
|
||||
using IdentityServer4.Validation;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@ -40,6 +42,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _tokenDataFactory;
|
||||
|
||||
public BaseRequestValidator(
|
||||
UserManager<User> userManager,
|
||||
@ -57,7 +60,8 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
GlobalSettings globalSettings,
|
||||
IPolicyRepository policyRepository,
|
||||
IUserRepository userRepository,
|
||||
IPolicyService policyService)
|
||||
IPolicyService policyService,
|
||||
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_deviceRepository = deviceRepository;
|
||||
@ -75,6 +79,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
_policyRepository = policyRepository;
|
||||
_userRepository = userRepository;
|
||||
_policyService = policyService;
|
||||
_tokenDataFactory = tokenDataFactory;
|
||||
}
|
||||
|
||||
protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
|
||||
@ -92,7 +97,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
var twoFactorProvider = request.Raw["TwoFactorProvider"]?.ToString();
|
||||
var twoFactorRemember = request.Raw["TwoFactorRemember"]?.ToString() == "1";
|
||||
var twoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorToken) &&
|
||||
!string.IsNullOrWhiteSpace(twoFactorProvider);
|
||||
!string.IsNullOrWhiteSpace(twoFactorProvider);
|
||||
|
||||
var valid = await ValidateContextAsync(context, validatorContext);
|
||||
var user = validatorContext.User;
|
||||
@ -100,6 +105,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
{
|
||||
await UpdateFailedAuthDetailsAsync(user, false, !validatorContext.KnownDevice);
|
||||
}
|
||||
|
||||
if (!valid || isBot)
|
||||
{
|
||||
await BuildErrorResultAsync("Username or password is incorrect. Try again.", false, context, user);
|
||||
@ -150,14 +156,16 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
await BuildErrorResultAsync("No device information provided.", false, context, user);
|
||||
return;
|
||||
}
|
||||
|
||||
await BuildSuccessResultAsync(user, context, device, twoFactorRequest && twoFactorRemember);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSsoResult(context, new Dictionary<string, object>
|
||||
{{
|
||||
"ErrorModel", new ErrorResponseModel("SSO authentication is required.")
|
||||
}});
|
||||
SetSsoResult(context,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "ErrorModel", new ErrorResponseModel("SSO authentication is required.") }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,13 +248,23 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
providers.Add(((byte)provider.Key).ToString(), infoDict);
|
||||
}
|
||||
|
||||
SetTwoFactorResult(context,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "TwoFactorProviders", providers.Keys },
|
||||
{ "TwoFactorProviders2", providers },
|
||||
{ "MasterPasswordPolicy", await GetMasterPasswordPolicy(user) }
|
||||
});
|
||||
var twoFactorResultDict = new Dictionary<string, object>
|
||||
{
|
||||
{ "TwoFactorProviders", providers.Keys },
|
||||
{ "TwoFactorProviders2", providers },
|
||||
{ "MasterPasswordPolicy", await GetMasterPasswordPolicy(user) },
|
||||
};
|
||||
|
||||
// If we have email as a 2FA provider, we might need an SsoEmail2fa Session Token
|
||||
if (enabledProviders.Any(p => p.Key == TwoFactorProviderType.Email))
|
||||
{
|
||||
twoFactorResultDict.Add("SsoEmail2faSessionToken",
|
||||
_tokenDataFactory.Protect(new SsoEmail2faSessionTokenable(user)));
|
||||
|
||||
twoFactorResultDict.Add("Email", user.Email);
|
||||
}
|
||||
|
||||
SetTwoFactorResult(context, twoFactorResultDict);
|
||||
|
||||
if (enabledProviders.Count() == 1 && enabledProviders.First().Key == TwoFactorProviderType.Email)
|
||||
{
|
||||
@ -272,10 +290,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
|
||||
await Task.Delay(2000); // Delay for brute force.
|
||||
SetErrorResult(context,
|
||||
new Dictionary<string, object>
|
||||
{{
|
||||
"ErrorModel", new ErrorResponseModel(message)
|
||||
}});
|
||||
new Dictionary<string, object> { { "ErrorModel", new ErrorResponseModel(message) } });
|
||||
}
|
||||
|
||||
protected abstract void SetTwoFactorResult(T context, Dictionary<string, object> customResponse);
|
||||
@ -296,8 +311,8 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
}
|
||||
|
||||
var individualRequired = _userManager.SupportsUserTwoFactor &&
|
||||
await _userManager.GetTwoFactorEnabledAsync(user) &&
|
||||
(await _userManager.GetValidTwoFactorProvidersAsync(user)).Count > 0;
|
||||
await _userManager.GetTwoFactorEnabledAsync(user) &&
|
||||
(await _userManager.GetValidTwoFactorProvidersAsync(user)).Count > 0;
|
||||
|
||||
Organization firstEnabledOrg = null;
|
||||
var orgs = (await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id))
|
||||
@ -346,7 +361,8 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
PolicyType.RequireSso);
|
||||
// Owners and Admins are exempt from this policy
|
||||
if (orgPolicy != null && orgPolicy.Enabled &&
|
||||
(_globalSettings.Sso.EnforceSsoPolicyForAllUsers || (userOrg.Type != OrganizationUserType.Owner && userOrg.Type != OrganizationUserType.Admin)))
|
||||
(_globalSettings.Sso.EnforceSsoPolicyForAllUsers ||
|
||||
(userOrg.Type != OrganizationUserType.Owner && userOrg.Type != OrganizationUserType.Admin)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -361,7 +377,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
private bool OrgUsing2fa(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
|
||||
{
|
||||
return orgAbilities != null && orgAbilities.ContainsKey(orgId) &&
|
||||
orgAbilities[orgId].Enabled && orgAbilities[orgId].Using2fa;
|
||||
orgAbilities[orgId].Enabled && orgAbilities[orgId].Using2fa;
|
||||
}
|
||||
|
||||
private bool OrgCanUseSso(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
|
||||
@ -408,6 +424,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return await _userManager.VerifyTwoFactorTokenAsync(user,
|
||||
CoreHelpers.CustomProviderName(type), token);
|
||||
case TwoFactorProviderType.OrganizationDuo:
|
||||
@ -457,18 +474,13 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
}
|
||||
else if (type == TwoFactorProviderType.Email)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["Email"] = token
|
||||
};
|
||||
return new Dictionary<string, object> { ["Email"] = token };
|
||||
}
|
||||
else if (type == TwoFactorProviderType.YubiKey)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["Nfc"] = (bool)provider.MetaData["Nfc"]
|
||||
};
|
||||
return new Dictionary<string, object> { ["Nfc"] = (bool)provider.MetaData["Nfc"] };
|
||||
}
|
||||
|
||||
return null;
|
||||
case TwoFactorProviderType.OrganizationDuo:
|
||||
if (await _organizationDuoWebTokenProvider.CanGenerateTwoFactorTokenAsync(organization))
|
||||
@ -479,6 +491,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
["Signature"] = await _organizationDuoWebTokenProvider.GenerateAsync(organization, user)
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
@ -7,6 +8,7 @@ using Bit.Core.IdentityServer;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tokens;
|
||||
using IdentityModel;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Validation;
|
||||
@ -37,11 +39,12 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
||||
IPolicyRepository policyRepository,
|
||||
ISsoConfigRepository ssoConfigRepository,
|
||||
IUserRepository userRepository,
|
||||
IPolicyService policyService)
|
||||
IPolicyService policyService,
|
||||
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory)
|
||||
: base(userManager, deviceRepository, deviceService, userService, eventService,
|
||||
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
|
||||
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository,
|
||||
userRepository, policyService)
|
||||
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
|
||||
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository,
|
||||
userRepository, policyService, tokenDataFactory)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_ssoConfigRepository = ssoConfigRepository;
|
||||
@ -73,11 +76,13 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
||||
CustomValidatorRequestContext validatorContext)
|
||||
{
|
||||
var email = context.Result.ValidatedRequest.Subject?.GetDisplayName()
|
||||
?? context.Result.ValidatedRequest.ClientClaims?.FirstOrDefault(claim => claim.Type == JwtClaimTypes.Email)?.Value;
|
||||
?? context.Result.ValidatedRequest.ClientClaims
|
||||
?.FirstOrDefault(claim => claim.Type == JwtClaimTypes.Email)?.Value;
|
||||
if (!string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
validatorContext.User = await _userManager.FindByEmailAsync(email);
|
||||
}
|
||||
|
||||
return validatorContext.User != null;
|
||||
}
|
||||
|
||||
@ -111,6 +116,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
||||
context.Result.CustomResponse["ApiUseKeyConnector"] = true;
|
||||
context.Result.CustomResponse["ResetMasterPassword"] = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Core.Utilities;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Validation;
|
||||
@ -39,11 +41,12 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
|
||||
ICaptchaValidationService captchaValidationService,
|
||||
IAuthRequestRepository authRequestRepository,
|
||||
IUserRepository userRepository,
|
||||
IPolicyService policyService)
|
||||
IPolicyService policyService,
|
||||
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory)
|
||||
: base(userManager, deviceRepository, deviceService, userService, eventService,
|
||||
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
|
||||
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository,
|
||||
userRepository, policyService)
|
||||
userRepository, policyService, tokenDataFactory)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_userService = userService;
|
||||
|
@ -157,6 +157,12 @@ public static class ServiceCollectionExtensions
|
||||
SsoTokenable.DataProtectorPurpose,
|
||||
serviceProvider.GetDataProtectionProvider(),
|
||||
serviceProvider.GetRequiredService<ILogger<DataProtectorTokenFactory<SsoTokenable>>>()));
|
||||
services.AddSingleton<IDataProtectorTokenFactory<SsoEmail2faSessionTokenable>>(serviceProvider =>
|
||||
new DataProtectorTokenFactory<SsoEmail2faSessionTokenable>(
|
||||
SsoEmail2faSessionTokenable.ClearTextPrefix,
|
||||
SsoEmail2faSessionTokenable.DataProtectorPurpose,
|
||||
serviceProvider.GetDataProtectionProvider(),
|
||||
serviceProvider.GetRequiredService<ILogger<DataProtectorTokenFactory<SsoEmail2faSessionTokenable>>>()));
|
||||
}
|
||||
|
||||
public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
|
Loading…
Reference in New Issue
Block a user