Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2d625f6
update send api models to support new `email` field
audreyality May 28, 2025
cc71297
normalize authentication field evaluation order
audreyality Jun 26, 2025
74da4ab
document send response converters
audreyality Jul 1, 2025
360a3da
add FIXME to remove unused constructor argument
audreyality Jul 1, 2025
7b0b949
add FIXME to remove unused constructor argument
audreyality Jul 1, 2025
1e9023d
Merge branch 'main' into tools/pm-21918/send-authentication-commands
audreyality Aug 15, 2025
3271556
introduce `tools-send-email-otp-listing` feature flag
audreyality Aug 15, 2025
cd0a0ad
Merge branch 'main' into tools/pm-21918/send-authentication-commands
audreyality Aug 25, 2025
fb27708
add `ISendOwnerQuery` to dependency graph
audreyality Aug 26, 2025
d7a0282
Merge branch 'main' into tools/pm-21918/send-authentication-commands
audreyality Sep 17, 2025
481f94a
Merge branch 'main' into tools/pm-21918/send-authentication-commands
djsmith85 Sep 26, 2025
4217dba
Merge branch 'main' into tools/pm-21918/send-authentication-commands
itsadrago Nov 13, 2025
b2c0dc8
fix broken tests
harr1424 Dec 16, 2025
c29ad65
Merge branch 'main' into tools/pm-21918/send-authentication-commands
harr1424 Dec 16, 2025
bc22b1d
added AuthType prop to send related models with test coverage and deb…
harr1424 Dec 17, 2025
c7ba30b
dotnet format
harr1424 Dec 17, 2025
2fec3db
Merge branch 'main' into tools/pm-21918/send-authentication-commands
harr1424 Dec 17, 2025
ad4c002
add migrations
harr1424 Dec 17, 2025
68b6cad
Merge branch 'main' into tools/pm-21918/send-authentication-commands
harr1424 Dec 17, 2025
25b0ed6
dotnet format
harr1424 Dec 17, 2025
8a273fc
make SendsController null safe (tech debt)
harr1424 Dec 17, 2025
bfd06c0
add AuthType col to Sends table, change Emails col length to 4000, an…
harr1424 Dec 18, 2025
b52f79e
Merge branch 'main' into tools/pm-21918/send-authentication-commands
harr1424 Dec 18, 2025
4f4d63d
dotnet format
harr1424 Dec 18, 2025
179d724
update SPs to expect AuthType
harr1424 Dec 18, 2025
af39424
include SP updates in migrations
harr1424 Dec 18, 2025
7df56e3
remove migrations not intended for merge
harr1424 Dec 18, 2025
94926c5
Revert "remove migrations not intended for merge"
harr1424 Dec 18, 2025
8c1f7ee
Merge branch 'main' into tools/pm-21918/send-authentication-commands
harr1424 Dec 18, 2025
4a179af
extract AuthType inference to util method and remove SQLite file
harr1424 Dec 18, 2025
a934b43
fix lints
harr1424 Dec 18, 2025
47512f9
Merge branch 'main' into tools/pm-21918/send-authentication-commands
itsadrago Dec 20, 2025
c85bd3e
address review comments
harr1424 Dec 22, 2025
1e4b1f3
Merge branch 'main' into tools/pm-21918/send-authentication-commands
harr1424 Dec 22, 2025
f9bdfc0
fix incorrect assignment and adopt SQL conventions
harr1424 Dec 22, 2025
1240fa4
fix column assignment order in Send_Update.sql
harr1424 Dec 22, 2025
cae8b59
Merge branch 'main' into tools/pm-21918/send-authentication-commands
harr1424 Dec 23, 2025
f9b700e
remove space added to email list
harr1424 Dec 23, 2025
6ccb9af
assign SQL default value of NULL to AuthType
harr1424 Dec 23, 2025
52376e5
Merge branch 'main' into tools/pm-21918/send-authentication-commands
harr1424 Dec 23, 2025
f0ddab2
update SPs to match migration changes
harr1424 Dec 23, 2025
6bfacec
Merge branch 'tools/pm-21918/send-authentication-commands' of github.…
harr1424 Dec 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 127 additions & 1 deletion src/Api/Tools/Models/Request/SendRequestModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,114 @@
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;

using static System.StringSplitOptions;

namespace Bit.Api.Tools.Models.Request;

/// <summary>
/// A send request issued by a Bitwarden client
/// </summary>
public class SendRequestModel
{
/// <summary>
/// Indicates whether the send contains text or file data.
/// </summary>
public SendType Type { get; set; }

/// <summary>
/// Estimated length of the file accompanying the send. <see langword="null"/> when
/// <see cref="Type"/> is <see cref="SendType.Text"/>.
/// </summary>
public long? FileLength { get; set; } = null;

/// <summary>
/// Label for the send.
/// </summary>
[EncryptedString]
[EncryptedStringLength(1000)]
public string Name { get; set; }

/// <summary>
/// Notes for the send. This is only visible to the owner of the send.
/// </summary>
[EncryptedString]
[EncryptedStringLength(1000)]
public string Notes { get; set; }

/// <summary>
/// A base64-encoded byte array containing the Send's encryption key. This key is
/// also provided to send recipients in the Send's URL.
/// </summary>
[Required]
[EncryptedString]
[EncryptedStringLength(1000)]
public string Key { get; set; }

/// <summary>
/// The maximum number of times a send can be accessed before it expires.
/// When this value is <see langword="null" />, there is no limit.
/// </summary>
[Range(1, int.MaxValue)]
public int? MaxAccessCount { get; set; }

/// <summary>
/// The date after which a send cannot be accessed. When this value is
/// <see langword="null"/>, there is no expiration date.
/// </summary>
public DateTime? ExpirationDate { get; set; }

/// <summary>
/// The date after which a send may be automatically deleted from the server.
/// When this is <see langword="null" />, the send may be deleted after it has
/// exceeded the global send timeout limit.
/// </summary>
[Required]
public DateTime? DeletionDate { get; set; }

/// <summary>
/// Contains file metadata uploaded with the send.
/// The file content is uploaded separately.
/// </summary>
public SendFileModel File { get; set; }

/// <summary>
/// Contains text data uploaded with the send.
/// </summary>
public SendTextModel Text { get; set; }

/// <summary>
/// Base64-encoded byte array of a password hash that grants access to the send.
/// Mutually exclusive with <see cref="Emails"/>.
/// </summary>
[StringLength(1000)]
public string Password { get; set; }

/// <summary>
/// Comma-separated list of emails that may access the send using OTP
/// authentication. Mutually exclusive with <see cref="Password"/>.
/// </summary>
[StringLength(1024)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be updated to a length of 4000

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

public string Emails { get; set; }

/// <summary>
/// When <see langword="true"/>, send access is disabled.
/// Defaults to <see langword="false"/>.
/// </summary>
[Required]
public bool? Disabled { get; set; }

/// <summary>
/// When <see langword="true"/> send access hides the user's email address
/// and displays a confirmation message instead. Defaults to <see langword="false"/>.
/// </summary>
public bool? HideEmail { get; set; }

/// <summary>
/// Transforms the request into a send object.
/// </summary>
/// <param name="userId">The user that owns the send.</param>
/// <param name="sendAuthorizationService">Hashes the send password.</param>
/// <returns>The send object</returns>
public Send ToSend(Guid userId, ISendAuthorizationService sendAuthorizationService)
{
var send = new Send
Expand All @@ -47,8 +126,17 @@
return send;
}

/// <summary>
/// Transforms the request into a send object and file data.
/// </summary>
/// <param name="userId">The user that owns the send.</param>
/// <param name="fileName">Name of the file uploaded with the send.</param>
/// <param name="sendAuthorizationService">Hashes the send password.</param>
/// <returns>The send object and file data.</returns>
public (Send, SendFileData) ToSend(Guid userId, string fileName, ISendAuthorizationService sendAuthorizationService)
{
// FIXME: This method does two things: creates a send and a send file data.
// It should only do one thing.
var send = ToSendBase(new Send
{
Type = Type,
Expand All @@ -58,6 +146,13 @@
return (send, data);
}

/// <summary>
/// Update a send object with request content
/// </summary>
/// <param name="existingSend">The send to update</param>
/// <param name="sendAuthorizationService">Hashes the send password.</param>
/// <returns>The send object</returns>
// FIXME: rename to `UpdateSend`
public Send ToSend(Send existingSend, ISendAuthorizationService sendAuthorizationService)
{
existingSend = ToSendBase(existingSend, sendAuthorizationService);
Expand All @@ -78,6 +173,12 @@
return existingSend;
}

/// <summary>
/// Validates that the request is internally consistent for send creation.
/// </summary>
/// <exception cref="BadRequestException">
/// Thrown when the send's expiration date has already expired.
/// </exception>
public void ValidateCreation()
{
var now = DateTime.UtcNow;
Expand All @@ -91,6 +192,13 @@
ValidateEdit();
}

/// <summary>
/// Validates that the request is internally consistent for send administration.
/// </summary>
/// <exception cref="BadRequestException">
/// Thrown when the send's deletion date has already expired or when its
/// expiration occurs after its deletion.
/// </exception>
public void ValidateEdit()
{
var now = DateTime.UtcNow;
Expand Down Expand Up @@ -131,12 +239,23 @@
existingSend.ExpirationDate = ExpirationDate;
existingSend.DeletionDate = DeletionDate.Value;
existingSend.MaxAccessCount = MaxAccessCount;
if (!string.IsNullOrWhiteSpace(Password))

if (!string.IsNullOrWhiteSpace(Emails))
{

Check warning on line 244 in src/Api/Tools/Models/Request/SendRequestModel.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Tools/Models/Request/SendRequestModel.cs#L244

Added line #L244 was not covered by tests
// normalize encoding
var emails = Emails.Split(',', RemoveEmptyEntries | TrimEntries);
existingSend.Emails = string.Join(", ", emails);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to add a space between email addresses? How you want to display the comma-separated string should be up to the presentation tier and not a part of the storage tier

Copy link
Contributor

@harr1424 harr1424 Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the spaces are necessary here, we don't expect many sends that use email OTP to push the 4000 character limit, but why waste the space? I don't think the server needs to be concerned with displaying these in a format optimized for human readability, but rather optimal with respect to space. On the client side we can do an inexpensive string.replace(',', ', ') or whatever we need to optimize for UI/UX on the clients.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100%

existingSend.Password = null;
}

Check warning on line 249 in src/Api/Tools/Models/Request/SendRequestModel.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Tools/Models/Request/SendRequestModel.cs#L246-L249

Added lines #L246 - L249 were not covered by tests
else if (!string.IsNullOrWhiteSpace(Password))
{
existingSend.Password = authorizationService.HashPassword(Password);
existingSend.Emails = null;
}

existingSend.Disabled = Disabled.GetValueOrDefault();
existingSend.HideEmail = HideEmail.GetValueOrDefault();

return existingSend;
}

Expand All @@ -146,8 +265,15 @@
}
}

/// <summary>
/// A send request issued by a Bitwarden client
/// </summary>
public class SendWithIdRequestModel : SendRequestModel
{
/// <summary>
/// Identifies the send. When this is <see langword="null" />, the client is requesting
/// a new send.
/// </summary>
[Required]
public Guid? Id { get; set; }
}
53 changes: 53 additions & 0 deletions src/Api/Tools/Models/Response/SendAccessResponseModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,25 @@

namespace Bit.Api.Tools.Models.Response;

/// <summary>
/// A response issued to a Bitwarden client in response to access operations.
/// </summary>
public class SendAccessResponseModel : ResponseModel
{
/// <summary>
/// Instantiates a send access response model
/// </summary>
/// <param name="send">Content to transmit to the client.</param>
/// <param name="globalSettings">
/// Settings that control response generation.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="send"/> is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="send" /> has an invalid <see cref="Send.Type"/>.
/// </exception>
// FIXME: remove `globalSettings` variable
public SendAccessResponseModel(Send send, GlobalSettings globalSettings)
: base("send-access")
{
Expand Down Expand Up @@ -42,11 +59,47 @@ public SendAccessResponseModel(Send send, GlobalSettings globalSettings)
ExpirationDate = send.ExpirationDate;
}

/// <summary>
/// Identifies the send in a send URL
/// </summary>
public string Id { get; set; }

/// <summary>
/// Indicates whether the send contains text or file data.
/// </summary>
public SendType Type { get; set; }

/// <summary>
/// Label for the send. This is only visible to the owner of the send.
/// </summary>
/// <remarks>
/// This field contains a base64-encoded byte array. The array contains
/// the E2E-encrypted encrypted content.
/// </remarks>
public string Name { get; set; }

/// <summary>
/// Describes the file attached to the send.
/// </summary>
/// <remarks>
/// File content is downloaded separately using
/// <see cref="Bit.Api.Tools.Controllers.SendsController.GetSendFileDownloadData" />
/// </remarks>
public SendFileModel File { get; set; }

/// <summary>
/// Contains text data uploaded with the send.
/// </summary>
public SendTextModel Text { get; set; }

/// <summary>
/// The date after which a send cannot be accessed. When this value is
/// <see langword="null"/>, there is no expiration date.
/// </summary>
public DateTime? ExpirationDate { get; set; }

/// <summary>
/// Indicates the person that created the send to the accessor.
/// </summary>
public string CreatorIdentifier { get; set; }
}
Loading
Loading