1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00

feat : matching request email to session email (#5541)

* feat : matching request email to session email

* feat : implement AuthRequestHeaderValidator

* fix : matching table definitions between migrator and sql project.

* fix : fixing tests
This commit is contained in:
Ike 2025-03-21 18:18:17 -04:00 committed by GitHub
parent 6d4d7c7968
commit 85b299ccfc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 73 additions and 44 deletions

View File

@ -0,0 +1,39 @@
using Bit.Core.Context;
using Bit.Core.Utilities;
namespace Bit.Identity.IdentityServer.RequestValidators;
public class AuthRequestHeaderValidator : IAuthRequestHeaderValidator
{
private readonly ICurrentContext _currentContext;
public AuthRequestHeaderValidator(ICurrentContext currentContext)
{
_currentContext = currentContext;
}
public bool ValidateAuthEmailHeader(string userEmail)
{
if (_currentContext.HttpContext.Request.Headers.TryGetValue("Auth-Email", out var authEmailHeader))
{
try
{
var authEmailDecoded = CoreHelpers.Base64UrlDecodeString(authEmailHeader);
if (authEmailDecoded != userEmail)
{
return false;
}
}
catch (Exception e) when (e is InvalidOperationException || e is FormatException)
{
// Invalid B64 encoding
return false;
}
}
else
{
return false;
}
return true;
}
}

View File

@ -0,0 +1,12 @@
namespace Bit.Identity.IdentityServer.RequestValidators;
public interface IAuthRequestHeaderValidator
{
/// <summary>
/// This method matches the Email in the header the input email. Implementation depends on
/// GrantValidator.
/// </summary>
/// <param name="userEmail">email fetched by grantValidator</param>
/// <returns>true if the emails match false otherwise</returns>
bool ValidateAuthEmailHeader(string userEmail);
}

View File

@ -19,6 +19,7 @@ public class OpaqueKeyExchangeGrantValidator : BaseRequestValidator<ExtensionGra
public const string GrantType = "opaque-ke"; public const string GrantType = "opaque-ke";
private readonly IOpaqueKeyExchangeService _opaqueKeyExchangeService; private readonly IOpaqueKeyExchangeService _opaqueKeyExchangeService;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private readonly IAuthRequestHeaderValidator _authRequestHeaderValidator;
public OpaqueKeyExchangeGrantValidator( public OpaqueKeyExchangeGrantValidator(
UserManager<User> userManager, UserManager<User> userManager,
@ -28,15 +29,16 @@ public class OpaqueKeyExchangeGrantValidator : BaseRequestValidator<ExtensionGra
ITwoFactorAuthenticationValidator twoFactorAuthenticationValidator, ITwoFactorAuthenticationValidator twoFactorAuthenticationValidator,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IMailService mailService, IMailService mailService,
ILogger<CustomTokenRequestValidator> logger, ILogger<OpaqueKeyExchangeGrantValidator> logger,
ICurrentContext currentContext, ICurrentContext currentContext,
GlobalSettings globalSettings, GlobalSettings globalSettings,
ISsoConfigRepository ssoConfigRepository,
IUserRepository userRepository, IUserRepository userRepository,
IPolicyService policyService, IPolicyService policyService,
IFeatureService featureService, IFeatureService featureService,
ISsoConfigRepository ssoConfigRepository,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
IOpaqueKeyExchangeService opaqueKeyExchangeService) IOpaqueKeyExchangeService opaqueKeyExchangeService,
IAuthRequestHeaderValidator authRequestHeaderValidator)
: base( : base(
userManager, userManager,
userService, userService,
@ -56,6 +58,7 @@ public class OpaqueKeyExchangeGrantValidator : BaseRequestValidator<ExtensionGra
{ {
_opaqueKeyExchangeService = opaqueKeyExchangeService; _opaqueKeyExchangeService = opaqueKeyExchangeService;
_featureService = featureService; _featureService = featureService;
_authRequestHeaderValidator = authRequestHeaderValidator;
} }
string IExtensionGrantValidator.GrantType => "opaque-ke"; string IExtensionGrantValidator.GrantType => "opaque-ke";
@ -81,10 +84,12 @@ public class OpaqueKeyExchangeGrantValidator : BaseRequestValidator<ExtensionGra
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return; return;
} }
if (_authRequestHeaderValidator.ValidateAuthEmailHeader(user.Email))
// TODO: we need to validate that the email sent up is the same one pulled from the session {
// TODO: discuss with Ike if pulling over existing AuthEmailHeaderIsValid logic from context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
// ResourceOwnerPasswordValidator is best or if we should should refactor in some way. "Auth-Email header invalid.");
return;
}
await ValidateAsync(context, context.Request, new CustomValidatorRequestContext { User = user }); await ValidateAsync(context, context.Request, new CustomValidatorRequestContext { User = user });
} }

View File

@ -23,6 +23,7 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
private readonly ICaptchaValidationService _captchaValidationService; private readonly ICaptchaValidationService _captchaValidationService;
private readonly IAuthRequestRepository _authRequestRepository; private readonly IAuthRequestRepository _authRequestRepository;
private readonly IDeviceValidator _deviceValidator; private readonly IDeviceValidator _deviceValidator;
private readonly IAuthRequestHeaderValidator _authRequestHeaderValidator;
public ResourceOwnerPasswordValidator( public ResourceOwnerPasswordValidator(
UserManager<User> userManager, UserManager<User> userManager,
IUserService userService, IUserService userService,
@ -40,7 +41,8 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
IPolicyService policyService, IPolicyService policyService,
IFeatureService featureService, IFeatureService featureService,
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder) IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
IAuthRequestHeaderValidator authRequestHeaderValidator)
: base( : base(
userManager, userManager,
userService, userService,
@ -63,11 +65,12 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
_captchaValidationService = captchaValidationService; _captchaValidationService = captchaValidationService;
_authRequestRepository = authRequestRepository; _authRequestRepository = authRequestRepository;
_deviceValidator = deviceValidator; _deviceValidator = deviceValidator;
_authRequestHeaderValidator = authRequestHeaderValidator;
} }
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{ {
if (!AuthEmailHeaderIsValid(context)) if (!_authRequestHeaderValidator.ValidateAuthEmailHeader(context.UserName))
{ {
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
"Auth-Email header invalid."); "Auth-Email header invalid.");
@ -200,30 +203,4 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
{ {
return context.Result.Subject; return context.Result.Subject;
} }
private bool AuthEmailHeaderIsValid(ResourceOwnerPasswordValidationContext context)
{
if (_currentContext.HttpContext.Request.Headers.TryGetValue("Auth-Email", out var authEmailHeader))
{
try
{
var authEmailDecoded = CoreHelpers.Base64UrlDecodeString(authEmailHeader);
if (authEmailDecoded != context.UserName)
{
return false;
}
}
catch (Exception e) when (e is InvalidOperationException || e is FormatException)
{
// Invalid B64 encoding
return false;
}
}
else
{
return false;
}
return true;
}
} }

View File

@ -23,6 +23,7 @@ public static class ServiceCollectionExtensions
services.AddTransient<IUserDecryptionOptionsBuilder, UserDecryptionOptionsBuilder>(); services.AddTransient<IUserDecryptionOptionsBuilder, UserDecryptionOptionsBuilder>();
services.AddTransient<IDeviceValidator, DeviceValidator>(); services.AddTransient<IDeviceValidator, DeviceValidator>();
services.AddTransient<ITwoFactorAuthenticationValidator, TwoFactorAuthenticationValidator>(); services.AddTransient<ITwoFactorAuthenticationValidator, TwoFactorAuthenticationValidator>();
services.AddTransient<IAuthRequestHeaderValidator, AuthRequestHeaderValidator>();
var issuerUri = new Uri(globalSettings.BaseServiceUri.InternalIdentity); var issuerUri = new Uri(globalSettings.BaseServiceUri.InternalIdentity);
var identityServerBuilder = services var identityServerBuilder = services

View File

@ -110,7 +110,7 @@ public static class ServiceCollectionExtensions
public static void AddBaseServices(this IServiceCollection services, IGlobalSettings globalSettings) public static void AddBaseServices(this IServiceCollection services, IGlobalSettings globalSettings)
{ {
services.AddScoped<ICipherService, CipherService>(); services.AddScoped<ICipherService, CipherService>();
services.AddSingleton<IOpaqueKeyExchangeService, OpaqueKeyExchangeService>(); services.AddScoped<IOpaqueKeyExchangeService, OpaqueKeyExchangeService>();
services.AddUserServices(globalSettings); services.AddUserServices(globalSettings);
services.AddTrialInitiationServices(); services.AddTrialInitiationServices();
services.AddOrganizationServices(globalSettings); services.AddOrganizationServices(globalSettings);

View File

@ -8,7 +8,7 @@
[EncryptedPrivateKey] VARCHAR(MAX) NOT NULL, [EncryptedPrivateKey] VARCHAR(MAX) NOT NULL,
[EncryptedUserKey] VARCHAR(MAX) NULL, [EncryptedUserKey] VARCHAR(MAX) NULL,
[CreationDate] DATETIME2 (7) NOT NULL, [CreationDate] DATETIME2 (7) NOT NULL,
CONSTRAINT [PK_OpaqueKeyExchangeCredential] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [PK_OpaqueKeyExchangeCredential] PRIMARY KEY CLUSTERED ([UserId]),
CONSTRAINT [FK_OpaqueKeyExchangeCredential_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) CONSTRAINT [FK_OpaqueKeyExchangeCredential_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])
); );

View File

@ -43,7 +43,8 @@
"password", "password",
"urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:device_code",
"urn:openid:params:grant-type:ciba", "urn:openid:params:grant-type:ciba",
"webauthn" "webauthn",
"opaque-ke"
], ],
"response_types_supported": [ "response_types_supported": [
"code", "code",

View File

@ -1,8 +1,3 @@
IF OBJECT_ID('[dbo].[OpaqueKeyExchangeCredential]') IS NULL IF OBJECT_ID('[dbo].[OpaqueKeyExchangeCredential]') IS NULL
BEGIN BEGIN
CREATE TABLE [dbo].[OpaqueKeyExchangeCredential] CREATE TABLE [dbo].[OpaqueKeyExchangeCredential]
@ -28,7 +23,6 @@ BEGIN
END END
GO GO
CREATE OR ALTER PROCEDURE [dbo].[OpaqueKeyExchangeCredential_Create] CREATE OR ALTER PROCEDURE [dbo].[OpaqueKeyExchangeCredential_Create]
@Id UNIQUEIDENTIFIER OUTPUT, @Id UNIQUEIDENTIFIER OUTPUT,
@UserId UNIQUEIDENTIFIER, @UserId UNIQUEIDENTIFIER,