1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-03 17:50:32 -05:00

PM-20532 - SendAccessGrantValidator.cs - integrate call to sendAuthenticationQuery

This commit is contained in:
Jared Snider 2025-05-29 17:10:51 -04:00
parent 0f8d11c124
commit a421d334a4
No known key found for this signature in database
GPG Key ID: A149DDD612516286

View File

@ -1,21 +1,24 @@
using System.Security.Claims;
using Bit.Core.Entities;
using Bit.Core.Identity;
using Bit.Core.Tools.Repositories;
using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
using Bit.Core.Utilities;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Validation;
using Microsoft.AspNetCore.Identity;
namespace Bit.Identity.IdentityServer.RequestValidators;
public class SendAccessGrantValidator(ISendRepository sendRepository, IPasswordHasher<User> passwordHasher) : IExtensionGrantValidator
public class SendAccessGrantValidator(ISendAuthenticationQuery sendAuthenticationQuery, IPasswordHasher<User> passwordHasher) : IExtensionGrantValidator
{
public const string GrantType = "send_access";
string IExtensionGrantValidator.GrantType => GrantType;
private const string _invalidRequestMissingSendIdMessage = "Invalid request. send_id is required.";
private const string _invalidRequestPasswordRequiredMessage = "Invalid request. Password is required.";
private const string _invalidRequestMissingSendIdMessage = "send_id is required.";
private const string _invalidRequestPasswordRequiredMessage = "Password is required.";
private const string _invalidRequestEmailOtpRequiredMessage = "Email and OTP are required.";
private const string _invalidGrantPasswordInvalid = "Password invalid.";
// TODO: add email OTP validation error messages here.
@ -31,94 +34,63 @@ public class SendAccessGrantValidator(ISendRepository sendRepository, IPasswordH
return;
}
if (!Guid.TryParse(sendId, out var sendIdGuid))
var sendIdGuid = new Guid(CoreHelpers.Base64UrlDecode(sendId));
if (sendIdGuid == Guid.Empty)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, errorDescription: _invalidRequestMissingSendIdMessage);
return;
}
// TODO: replace repository look up & following logic with use of SendAuthenticationQuery.GetAuthenticationMethod from Tools
// See below for example of consumption of SendAuthQuery.GetAuthenticationMethod(sendId)
// Look up send by id
var send = await sendRepository.GetByIdAsync(sendIdGuid);
var method = await sendAuthenticationQuery.GetAuthenticationMethod(sendIdGuid);
if (send == null)
switch (method)
{
// TODO: Add send enumeration protection here (primarily benefits self hosted instances).
// We should only map to password or email + OTP protected. If user submits password guess for a
// falsely protected send, then we will return invalid password.
// TODO: we should re-use _invalidGrantPasswordInvalid or similar error message here.
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, "Invalid request");
return;
}
if (!string.IsNullOrEmpty(send.Password))
{
// Send is password protected so we need to validate the password.
var password = request.Get("password");
if (string.IsNullOrEmpty(password))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, errorDescription: _invalidRequestPasswordRequiredMessage);
case NeverAuthenticate:
// null send scenario.
// TODO: Add send enumeration protection here (primarily benefits self hosted instances).
// We should only map to password or email + OTP protected. If user submits password guess for a
// falsely protected send, then we will return invalid password.
// TODO: we should re-use _invalidGrantPasswordInvalid or similar error message here.
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, "Invalid request");
return;
}
var passwordValid = ValidateSendPassword(send.Password, password);
if (!passwordValid)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, errorDescription: _invalidGrantPasswordInvalid);
case NotAuthenticated:
// automatically issue access token
context.Result = BuildBaseSuccessResult(sendId);
return;
}
// password is valid, so we can issue an access token.
context.Result = BuildBaseSuccessResult(sendId);
return;
case ResourcePassword rp:
var password = request.Get("password_hash");
if (string.IsNullOrEmpty(password))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, errorDescription: _invalidRequestPasswordRequiredMessage);
return;
}
var passwordValid = ValidateSendPassword(rp.Hash, password);
if (!passwordValid)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, errorDescription: _invalidGrantPasswordInvalid);
return;
}
// password is valid, so we can issue an access token.
context.Result = BuildBaseSuccessResult(sendId);
return;
case EmailOtp eo:
// TODO: We will either send the OTP here or validate it based on if otp exists in the request.
// SendOtpToEmail(eo.Emails) or ValidateOtp(eo.Emails);
break;
default:
// shouldnt ever hit this
throw new InvalidOperationException($"Unknown auth method: {method.GetType()}");
}
// if send is anon, provide access token
context.Result = BuildBaseSuccessResult(sendId);
// Email + OTP - if we generate OTP here, we could run into rate limiting issues with re-hitting this endpoint
// We will generate & validate OTP here.
// if send is password protected, check if password is provided and validate if so
// if send is email + OTP protected, check if email and OTP are provided and validate if so
// TODO: Example Consumption of SendAuthQuery.GetAuthenticationMethod(sendId) to replace above logic.
// var method = await sendAuthQuery.GetAuthenticationMethod(sendId);
//
// switch (method)
// {
// case NeverAuthenticate:
// // null send scenario.
// HandleNullSend(); // this is where we add send enumeration protection
// break;
//
// case NotAuthenticated:
// // automatically issue access token
// break;
//
// case ResourcePassword rp:
// ValidatePassword(rp.Hash);
// break;
//
// case EmailOtp eo:
// We will either send the OTP here or validate it.
// SendOtpToEmails(eo.Emails) or ValidateOtp(eo.Emails);
// break;
//
// default:
// // shouldnt ever hit this
// throw new InvalidOperationException($"Unknown auth method: {method.GetType()}");
// }
}
private GrantValidationResult BuildBaseSuccessResult(string sendId)