2024-08-16 09:32:25 -04:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.Security.Claims;
|
2023-11-23 07:07:37 +10:00
|
|
|
|
using Bit.Core.AdminConsole.Services;
|
2023-06-19 10:16:15 -04:00
|
|
|
|
using Bit.Core.Auth.Models.Api.Response;
|
2023-04-14 13:25:56 -04:00
|
|
|
|
using Bit.Core.Auth.Repositories;
|
2021-02-04 12:54:21 -06:00
|
|
|
|
using Bit.Core.Context;
|
2022-01-11 10:40:51 +01:00
|
|
|
|
using Bit.Core.Entities;
|
2023-01-13 15:02:53 +01:00
|
|
|
|
using Bit.Core.IdentityServer;
|
2020-07-16 08:01:39 -04:00
|
|
|
|
using Bit.Core.Repositories;
|
|
|
|
|
|
using Bit.Core.Services;
|
|
|
|
|
|
using Bit.Core.Settings;
|
2023-11-20 16:32:23 -05:00
|
|
|
|
using Duende.IdentityServer.Extensions;
|
|
|
|
|
|
using Duende.IdentityServer.Validation;
|
2024-06-03 09:19:56 -04:00
|
|
|
|
using HandlebarsDotNet;
|
2020-11-10 15:15:29 -05:00
|
|
|
|
using IdentityModel;
|
2020-07-16 08:01:39 -04:00
|
|
|
|
using Microsoft.AspNetCore.Identity;
|
|
|
|
|
|
|
2023-06-19 10:16:15 -04:00
|
|
|
|
#nullable enable
|
|
|
|
|
|
|
2024-10-24 10:41:25 -07:00
|
|
|
|
namespace Bit.Identity.IdentityServer.RequestValidators;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2020-07-16 08:01:39 -04:00
|
|
|
|
public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenRequestValidationContext>,
|
|
|
|
|
|
ICustomTokenRequestValidator
|
|
|
|
|
|
{
|
2023-06-19 10:16:15 -04:00
|
|
|
|
private readonly UserManager<User> _userManager;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2020-07-16 08:01:39 -04:00
|
|
|
|
public CustomTokenRequestValidator(
|
|
|
|
|
|
UserManager<User> userManager,
|
|
|
|
|
|
IUserService userService,
|
|
|
|
|
|
IEventService eventService,
|
2024-10-24 10:41:25 -07:00
|
|
|
|
IDeviceValidator deviceValidator,
|
|
|
|
|
|
ITwoFactorAuthenticationValidator twoFactorAuthenticationValidator,
|
2020-07-16 08:01:39 -04:00
|
|
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
|
|
|
|
IMailService mailService,
|
2023-01-13 15:02:53 +01:00
|
|
|
|
ILogger<CustomTokenRequestValidator> logger,
|
2021-02-04 12:54:21 -06:00
|
|
|
|
ICurrentContext currentContext,
|
2020-07-16 08:01:39 -04:00
|
|
|
|
GlobalSettings globalSettings,
|
2023-04-17 07:35:47 -07:00
|
|
|
|
IUserRepository userRepository,
|
2023-05-04 15:12:03 -04:00
|
|
|
|
IPolicyService policyService,
|
2023-09-09 14:35:08 -07:00
|
|
|
|
IFeatureService featureService,
|
2024-10-24 10:41:25 -07:00
|
|
|
|
ISsoConfigRepository ssoConfigRepository,
|
|
|
|
|
|
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder
|
|
|
|
|
|
)
|
|
|
|
|
|
: base(
|
|
|
|
|
|
userManager,
|
|
|
|
|
|
userService,
|
|
|
|
|
|
eventService,
|
|
|
|
|
|
deviceValidator,
|
|
|
|
|
|
twoFactorAuthenticationValidator,
|
|
|
|
|
|
organizationUserRepository,
|
|
|
|
|
|
mailService,
|
|
|
|
|
|
logger,
|
|
|
|
|
|
currentContext,
|
|
|
|
|
|
globalSettings,
|
|
|
|
|
|
userRepository,
|
|
|
|
|
|
policyService,
|
|
|
|
|
|
featureService,
|
|
|
|
|
|
ssoConfigRepository,
|
2024-02-09 15:44:31 -05:00
|
|
|
|
userDecryptionOptionsBuilder)
|
2020-07-16 08:01:39 -04:00
|
|
|
|
{
|
|
|
|
|
|
_userManager = userManager;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2020-07-16 08:01:39 -04:00
|
|
|
|
|
|
|
|
|
|
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2024-08-16 09:32:25 -04:00
|
|
|
|
Debug.Assert(context.Result is not null);
|
2024-06-03 09:19:56 -04:00
|
|
|
|
if (context.Result.ValidatedRequest.GrantType == "refresh_token")
|
|
|
|
|
|
{
|
|
|
|
|
|
// Force legacy users to the web for migration
|
|
|
|
|
|
if (await _userService.IsLegacyUser(GetSubject(context)?.GetSubjectId()) &&
|
|
|
|
|
|
context.Result.ValidatedRequest.ClientId != "web")
|
|
|
|
|
|
{
|
|
|
|
|
|
await FailAuthForLegacyUserAsync(null, context);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-24 10:41:25 -07:00
|
|
|
|
string[] allowedGrantTypes = ["authorization_code", "client_credentials"];
|
2021-02-04 12:54:21 -06:00
|
|
|
|
if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType)
|
2020-07-16 08:01:39 -04:00
|
|
|
|
|| context.Result.ValidatedRequest.ClientId.StartsWith("organization")
|
2022-11-01 09:58:28 -04:00
|
|
|
|
|| context.Result.ValidatedRequest.ClientId.StartsWith("installation")
|
2023-01-13 15:02:53 +01:00
|
|
|
|
|| context.Result.ValidatedRequest.ClientId.StartsWith("internal")
|
|
|
|
|
|
|| context.Result.ValidatedRequest.Client.AllowedScopes.Contains(ApiScopes.ApiSecrets))
|
2020-07-16 08:01:39 -04:00
|
|
|
|
{
|
2023-01-13 15:02:53 +01:00
|
|
|
|
if (context.Result.ValidatedRequest.Client.Properties.TryGetValue("encryptedPayload", out var payload) &&
|
|
|
|
|
|
!string.IsNullOrWhiteSpace(payload))
|
|
|
|
|
|
{
|
|
|
|
|
|
context.Result.CustomResponse = new Dictionary<string, object> { { "encrypted_payload", payload } };
|
|
|
|
|
|
}
|
2021-10-25 15:09:14 +02:00
|
|
|
|
return;
|
2020-07-16 08:01:39 -04:00
|
|
|
|
}
|
2024-12-12 09:08:11 -08:00
|
|
|
|
await ValidateAsync(context, context.Result.ValidatedRequest, new CustomValidatorRequestContext { });
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2020-07-16 08:01:39 -04:00
|
|
|
|
|
|
|
|
|
|
protected async override Task<bool> ValidateContextAsync(CustomTokenRequestValidationContext context,
|
|
|
|
|
|
CustomValidatorRequestContext validatorContext)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2024-08-16 09:32:25 -04:00
|
|
|
|
Debug.Assert(context.Result is not null);
|
2020-07-16 08:01:39 -04:00
|
|
|
|
var email = context.Result.ValidatedRequest.Subject?.GetDisplayName()
|
2023-05-04 15:12:03 -04:00
|
|
|
|
?? context.Result.ValidatedRequest.ClientClaims
|
|
|
|
|
|
?.FirstOrDefault(claim => claim.Type == JwtClaimTypes.Email)?.Value;
|
2020-11-13 08:53:36 -05:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(email))
|
2020-07-16 08:01:39 -04:00
|
|
|
|
{
|
2022-05-09 12:25:13 -04:00
|
|
|
|
validatorContext.User = await _userManager.FindByEmailAsync(email);
|
2020-07-16 08:01:39 -04:00
|
|
|
|
}
|
2022-05-09 12:25:13 -04:00
|
|
|
|
return validatorContext.User != null;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2020-07-16 08:01:39 -04:00
|
|
|
|
|
2023-06-26 20:17:39 -04:00
|
|
|
|
protected override Task SetSuccessResult(CustomTokenRequestValidationContext context, User user,
|
2022-05-09 12:25:13 -04:00
|
|
|
|
List<Claim> claims, Dictionary<string, object> customResponse)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2024-08-16 09:32:25 -04:00
|
|
|
|
Debug.Assert(context.Result is not null);
|
2022-05-09 12:25:13 -04:00
|
|
|
|
context.Result.CustomResponse = customResponse;
|
|
|
|
|
|
if (claims?.Any() ?? false)
|
2020-07-16 08:01:39 -04:00
|
|
|
|
{
|
2020-11-10 15:15:29 -05:00
|
|
|
|
context.Result.ValidatedRequest.Client.AlwaysSendClientClaims = true;
|
2020-11-13 08:53:36 -05:00
|
|
|
|
context.Result.ValidatedRequest.Client.ClientClaimsPrefix = string.Empty;
|
|
|
|
|
|
foreach (var claim in claims)
|
2022-05-09 12:25:13 -04:00
|
|
|
|
{
|
|
|
|
|
|
context.Result.ValidatedRequest.ClientClaims.Add(claim);
|
|
|
|
|
|
}
|
2020-07-16 08:01:39 -04:00
|
|
|
|
}
|
2021-11-09 16:37:32 +01:00
|
|
|
|
if (context.Result.CustomResponse == null || user.MasterPassword != null)
|
2020-07-16 08:01:39 -04:00
|
|
|
|
{
|
2023-06-26 20:17:39 -04:00
|
|
|
|
return Task.CompletedTask;
|
2021-11-09 16:37:32 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// KeyConnector responses below
|
|
|
|
|
|
|
|
|
|
|
|
// Apikey login
|
|
|
|
|
|
if (context.Result.ValidatedRequest.GrantType == "client_credentials")
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2021-11-09 16:37:32 +01:00
|
|
|
|
if (user.UsesKeyConnector)
|
2022-08-29 15:53:48 -04:00
|
|
|
|
{
|
2021-11-09 16:37:32 +01:00
|
|
|
|
// KeyConnectorUrl is configured in the CLI client, we just need to tell the client to use it
|
|
|
|
|
|
context.Result.CustomResponse["ApiUseKeyConnector"] = true;
|
|
|
|
|
|
context.Result.CustomResponse["ResetMasterPassword"] = false;
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2023-06-26 20:17:39 -04:00
|
|
|
|
return Task.CompletedTask;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2021-11-09 16:37:32 +01:00
|
|
|
|
|
2023-06-26 20:17:39 -04:00
|
|
|
|
// Key connector data should have already been set in the decryption options
|
|
|
|
|
|
// for backwards compatibility we set them this way too. We can eventually get rid of this
|
|
|
|
|
|
// when all clients don't read them from the existing locations.
|
|
|
|
|
|
if (!context.Result.CustomResponse.TryGetValue("UserDecryptionOptions", out var userDecryptionOptionsObj) ||
|
|
|
|
|
|
userDecryptionOptionsObj is not UserDecryptionOptions userDecryptionOptions)
|
2021-11-09 16:37:32 +01:00
|
|
|
|
{
|
2023-06-26 20:17:39 -04:00
|
|
|
|
return Task.CompletedTask;
|
2023-06-19 10:16:15 -04:00
|
|
|
|
}
|
2023-06-26 20:17:39 -04:00
|
|
|
|
if (userDecryptionOptions is { KeyConnectorOption: { } })
|
2023-06-19 10:16:15 -04:00
|
|
|
|
{
|
2023-06-26 20:17:39 -04:00
|
|
|
|
context.Result.CustomResponse["KeyConnectorUrl"] = userDecryptionOptions.KeyConnectorOption.KeyConnectorUrl;
|
|
|
|
|
|
context.Result.CustomResponse["ResetMasterPassword"] = false;
|
2023-06-19 10:16:15 -04:00
|
|
|
|
}
|
2023-06-26 20:17:39 -04:00
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
}
|
2023-06-19 10:16:15 -04:00
|
|
|
|
|
2024-08-16 09:32:25 -04:00
|
|
|
|
protected override ClaimsPrincipal? GetSubject(CustomTokenRequestValidationContext context)
|
2023-06-26 20:17:39 -04:00
|
|
|
|
{
|
2024-08-16 09:32:25 -04:00
|
|
|
|
Debug.Assert(context.Result is not null);
|
2023-06-26 20:17:39 -04:00
|
|
|
|
return context.Result.ValidatedRequest.Subject;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2020-07-16 08:01:39 -04:00
|
|
|
|
|
2024-12-12 09:08:11 -08:00
|
|
|
|
[Obsolete("Consider using SetGrantValidationErrorResult instead.")]
|
2020-07-16 08:01:39 -04:00
|
|
|
|
protected override void SetTwoFactorResult(CustomTokenRequestValidationContext context,
|
|
|
|
|
|
Dictionary<string, object> customResponse)
|
|
|
|
|
|
{
|
2024-08-16 09:32:25 -04:00
|
|
|
|
Debug.Assert(context.Result is not null);
|
2020-07-16 08:01:39 -04:00
|
|
|
|
context.Result.Error = "invalid_grant";
|
|
|
|
|
|
context.Result.ErrorDescription = "Two factor required.";
|
|
|
|
|
|
context.Result.IsError = true;
|
|
|
|
|
|
context.Result.CustomResponse = customResponse;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-12 09:08:11 -08:00
|
|
|
|
[Obsolete("Consider using SetGrantValidationErrorResult instead.")]
|
2020-10-26 11:56:16 -05:00
|
|
|
|
protected override void SetSsoResult(CustomTokenRequestValidationContext context,
|
2021-05-10 11:13:37 -05:00
|
|
|
|
Dictionary<string, object> customResponse)
|
|
|
|
|
|
{
|
2024-08-16 09:32:25 -04:00
|
|
|
|
Debug.Assert(context.Result is not null);
|
2021-05-10 11:13:37 -05:00
|
|
|
|
context.Result.Error = "invalid_grant";
|
2024-12-12 09:08:11 -08:00
|
|
|
|
context.Result.ErrorDescription = "Sso authentication required.";
|
2021-05-10 11:13:37 -05:00
|
|
|
|
context.Result.IsError = true;
|
|
|
|
|
|
context.Result.CustomResponse = customResponse;
|
|
|
|
|
|
}
|
2020-10-26 11:56:16 -05:00
|
|
|
|
|
2024-12-12 09:08:11 -08:00
|
|
|
|
[Obsolete("Consider using SetGrantValidationErrorResult instead.")]
|
2020-07-16 08:01:39 -04:00
|
|
|
|
protected override void SetErrorResult(CustomTokenRequestValidationContext context,
|
|
|
|
|
|
Dictionary<string, object> customResponse)
|
|
|
|
|
|
{
|
2024-08-16 09:32:25 -04:00
|
|
|
|
Debug.Assert(context.Result is not null);
|
2020-07-16 08:01:39 -04:00
|
|
|
|
context.Result.Error = "invalid_grant";
|
|
|
|
|
|
context.Result.IsError = true;
|
|
|
|
|
|
context.Result.CustomResponse = customResponse;
|
|
|
|
|
|
}
|
2024-12-12 09:08:11 -08:00
|
|
|
|
|
|
|
|
|
|
protected override void SetValidationErrorResult(
|
|
|
|
|
|
CustomTokenRequestValidationContext context, CustomValidatorRequestContext requestContext)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.Assert(context.Result is not null);
|
|
|
|
|
|
context.Result.Error = requestContext.ValidationErrorResult.Error;
|
|
|
|
|
|
context.Result.IsError = requestContext.ValidationErrorResult.IsError;
|
|
|
|
|
|
context.Result.ErrorDescription = requestContext.ValidationErrorResult.ErrorDescription;
|
|
|
|
|
|
context.Result.CustomResponse = requestContext.CustomResponse;
|
|
|
|
|
|
}
|
2020-07-16 08:01:39 -04:00
|
|
|
|
}
|