mirror of
https://github.com/bitwarden/server
synced 2026-01-02 08:33:48 +00:00
pm-24210 (#6142)
This commit is contained in:
@@ -39,6 +39,8 @@ public class DeviceValidator(
|
||||
private readonly ILogger<DeviceValidator> _logger = logger;
|
||||
private readonly ITwoFactorEmailService _twoFactorEmailService = twoFactorEmailService;
|
||||
|
||||
private const string PasswordGrantType = "password";
|
||||
|
||||
public async Task<bool> ValidateRequestDeviceAsync(ValidatedTokenRequest request, CustomValidatorRequestContext context)
|
||||
{
|
||||
// Parse device from request and return early if no device information is provided
|
||||
@@ -68,10 +70,14 @@ public class DeviceValidator(
|
||||
}
|
||||
|
||||
// We have established that the device is unknown at this point; begin new device verification
|
||||
if (request.GrantType == "password" &&
|
||||
request.Raw["AuthRequest"] == null &&
|
||||
!context.TwoFactorRequired &&
|
||||
!context.SsoRequired &&
|
||||
// for standard password grant type requests
|
||||
// Note: the auth request flow re-uses the resource owner password flow but new device verification
|
||||
// is not required for auth requests
|
||||
var rawAuthRequestId = request.Raw["AuthRequest"]?.ToLowerInvariant();
|
||||
var isAuthRequest = !string.IsNullOrEmpty(rawAuthRequestId);
|
||||
if (request.GrantType == PasswordGrantType &&
|
||||
!isAuthRequest &&
|
||||
context is { TwoFactorRequired: false, SsoRequired: false } &&
|
||||
_globalSettings.EnableNewDeviceVerification)
|
||||
{
|
||||
var validationResult = await HandleNewDeviceVerificationAsync(context.User, request);
|
||||
@@ -87,6 +93,16 @@ public class DeviceValidator(
|
||||
}
|
||||
}
|
||||
|
||||
// Device still unknown, but if we are in an auth request flow, this is not valid
|
||||
// as we only support auth request authN requests on known devices
|
||||
if (request.GrantType == PasswordGrantType && isAuthRequest &&
|
||||
context is { TwoFactorRequired: false, SsoRequired: false })
|
||||
{
|
||||
(context.ValidationErrorResult, context.CustomResponse) =
|
||||
BuildDeviceErrorResult(DeviceValidationResultType.AuthRequestFlowUnknownDevice);
|
||||
return false;
|
||||
}
|
||||
|
||||
// At this point we have established either new device verification is not required or the NewDeviceOtp is valid,
|
||||
// so we save the device to the database and proceed with authentication
|
||||
requestDevice.UserId = context.User.Id;
|
||||
@@ -252,7 +268,7 @@ public class DeviceValidator(
|
||||
var customResponse = new Dictionary<string, object>();
|
||||
switch (errorType)
|
||||
{
|
||||
/*
|
||||
/*
|
||||
* The ErrorMessage is brittle and is used to control the flow in the clients. Do not change them without updating the client as well.
|
||||
* There is a backwards compatibility issue as well: if you make a change on the clients then ensure that they are backwards
|
||||
* compatible.
|
||||
@@ -273,6 +289,10 @@ public class DeviceValidator(
|
||||
result.ErrorDescription = "No device information provided";
|
||||
customResponse.Add("ErrorModel", new ErrorResponseModel("no device information provided"));
|
||||
break;
|
||||
case DeviceValidationResultType.AuthRequestFlowUnknownDevice:
|
||||
result.ErrorDescription = "Auth requests are not supported on unknown devices";
|
||||
customResponse.Add("ErrorModel", new ErrorResponseModel("auth request flow unsupported on unknown device"));
|
||||
break;
|
||||
}
|
||||
return (result, customResponse);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Duende.IdentityServer.Models;
|
||||
using Duende.IdentityServer.Validation;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -90,21 +89,30 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
|
||||
return false;
|
||||
}
|
||||
|
||||
var authRequestId = context.Request.Raw["AuthRequest"]?.ToString()?.ToLowerInvariant();
|
||||
if (!string.IsNullOrWhiteSpace(authRequestId) && Guid.TryParse(authRequestId, out var authRequestGuid))
|
||||
var authRequestId = context.Request.Raw["AuthRequest"]?.ToLowerInvariant();
|
||||
if (!string.IsNullOrEmpty(authRequestId))
|
||||
{
|
||||
var authRequest = await _authRequestRepository.GetByIdAsync(authRequestGuid);
|
||||
if (authRequest != null)
|
||||
// only allow valid guids
|
||||
if (!Guid.TryParse(authRequestId, out var authRequestGuid))
|
||||
{
|
||||
var requestAge = DateTime.UtcNow - authRequest.CreationDate;
|
||||
if (requestAge < TimeSpan.FromHours(1) &&
|
||||
CoreHelpers.FixedTimeEquals(authRequest.AccessCode, context.Password))
|
||||
{
|
||||
authRequest.AuthenticationDate = DateTime.UtcNow;
|
||||
await _authRequestRepository.ReplaceAsync(authRequest);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var authRequest = await _authRequestRepository.GetByIdAsync(authRequestGuid);
|
||||
|
||||
if (authRequest == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Auth request is non-null so validate it
|
||||
if (authRequest.IsValidForAuthentication(validatorContext.User.Id, context.Password))
|
||||
{
|
||||
authRequest.AuthenticationDate = DateTime.UtcNow;
|
||||
await _authRequestRepository.ReplaceAsync(authRequest);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user