1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

[PM-6794] block legacy users from authN (#4088)

* block legacy users from authN

* undo change to GetDeviceFromRequest

* lint

* add feature flag

* format

* add web vault url to error message

* fix test

* format
This commit is contained in:
Jake Fink
2024-06-03 09:19:56 -04:00
committed by GitHub
parent 21a02054af
commit b072fc56b1
6 changed files with 107 additions and 0 deletions

View File

@ -129,6 +129,7 @@ public static class FeatureFlagKeys
public const string VaultBulkManagementAction = "vault-bulk-management-action";
public const string BulkDeviceApproval = "bulk-device-approval";
public const string MemberAccessReport = "ac-2059-member-access-report";
public const string BlockLegacyUsers = "block-legacy-users";
public static List<string> GetAllKeys()
{

View File

@ -76,4 +76,10 @@ public interface IUserService
Task SendOTPAsync(User user);
Task<bool> VerifyOTPAsync(User user, string token);
Task<bool> VerifySecretAsync(User user, string secret);
/// <summary>
/// Returns true if the user is a legacy user. Legacy users use their master key as their encryption key.
/// We force these users to the web to migrate their encryption scheme.
/// </summary>
Task<bool> IsLegacyUser(string userId);
}

View File

@ -1304,6 +1304,28 @@ public class UserService : UserManager<User>, IUserService, IDisposable
return IdentityResult.Success;
}
public async Task<bool> IsLegacyUser(string userId)
{
if (string.IsNullOrWhiteSpace(userId))
{
return false;
}
var user = await FindByIdAsync(userId);
if (user == null)
{
return false;
}
return IsLegacyUser(user);
}
/// <inheritdoc cref="IsLegacyUser(string)"/>
public static bool IsLegacyUser(User user)
{
return user.Key == null && user.MasterPassword != null && user.PrivateKey != null;
}
private async Task<IdentityResult> ValidatePasswordInternal(User user, string password)
{
var errors = new List<IdentityError>();

View File

@ -162,6 +162,17 @@ public abstract class BaseRequestValidator<T> where T : class
twoFactorToken = null;
}
// Force legacy users to the web for migration
if (FeatureService.IsEnabled(FeatureFlagKeys.BlockLegacyUsers))
{
if (UserService.IsLegacyUser(user) && request.ClientId != "web")
{
await FailAuthForLegacyUserAsync(user, context);
return;
}
}
// Returns true if can finish validation process
if (await IsValidAuthTypeAsync(user, request.GrantType))
{
@ -184,6 +195,13 @@ public abstract class BaseRequestValidator<T> where T : class
}
}
protected async Task FailAuthForLegacyUserAsync(User user, T context)
{
await BuildErrorResultAsync(
$"Encryption key migration is required. Please log in to the web vault at {_globalSettings.BaseServiceUri.VaultWithHash}",
false, context, user);
}
protected abstract Task<bool> ValidateContextAsync(T context, CustomValidatorRequestContext validatorContext);
protected async Task BuildSuccessResultAsync(User user, T context, Device device, bool sendRememberToken)

View File

@ -13,6 +13,7 @@ using Bit.Core.Settings;
using Bit.Core.Tokens;
using Duende.IdentityServer.Extensions;
using Duende.IdentityServer.Validation;
using HandlebarsDotNet;
using IdentityModel;
using Microsoft.AspNetCore.Identity;
@ -57,6 +58,17 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
{
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;
}
}
string[] allowedGrantTypes = { "authorization_code", "client_credentials" };
if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType)
|| context.Result.ValidatedRequest.ClientId.StartsWith("organization")
@ -70,6 +82,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
context.Result.CustomResponse = new Dictionary<string, object> { { "encrypted_payload", payload } };
}
return;
}