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:
parent
0f8d11c124
commit
a421d334a4
@ -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:
|
||||
// shouldn’t 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:
|
||||
// // shouldn’t ever hit this
|
||||
// throw new InvalidOperationException($"Unknown auth method: {method.GetType()}");
|
||||
// }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private GrantValidationResult BuildBaseSuccessResult(string sendId)
|
||||
|
Loading…
x
Reference in New Issue
Block a user