Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
2 changes: 1 addition & 1 deletion src/Api/Tools/Controllers/SendsController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
ο»Ώusing System.Text.Json;
using Azure.Messaging.EventGrid;
using Bit.Api.Models.Response;
using Bit.Api.Tools.Models.Request;
using Bit.Api.Tools.Models.Response;
using Bit.Api.Utilities;
Expand All @@ -21,6 +20,7 @@
using Bit.Core.Tools.SendFeatures.Services.Interfaces;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Bit.HttpExtensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand Down
10 changes: 10 additions & 0 deletions src/Api/Tools/Models/Request/SendRequestModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ public class SendRequestModel
/// </summary>
public SendTextModel? Text { get; set; }

/// <summary>
/// Encrypted string containing secret Send data
Comment thread
mcamirault marked this conversation as resolved.
Outdated
/// </summary>
[EncryptedString]
[EncryptedStringLength(500000)]
Comment thread
mcamirault marked this conversation as resolved.
Outdated
public string? Data { get; set; }

/// <summary>
/// Base64-encoded byte array of a password hash that grants access to the send.
/// Mutually exclusive with <see cref="Emails"/>.
Expand Down Expand Up @@ -165,6 +172,9 @@ public Send UpdateSend(Send existingSend, ISendAuthorizationService sendAuthoriz
case SendType.Text:
existingSend.Data = JsonSerializer.Serialize(ToSendTextData(), JsonHelpers.IgnoreWritingNull);
break;
case SendType.Item:
existingSend.Data = Data ?? throw new ArgumentNullException(nameof(Data), "Data is required for Sends of type Item");
break;
default:
throw new ArgumentException("Unsupported type: " + nameof(Type) + ".");
}
Expand Down
15 changes: 11 additions & 4 deletions src/Api/Tools/Models/Response/SendAccessResponseModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public SendAccessResponseModel(Send send)
Type = send.Type;
AuthType = send.AuthType;

SendData sendData;
switch (send.Type)
{
case SendType.File:
Expand All @@ -43,7 +42,7 @@ public SendAccessResponseModel(Send send)
throw new NullReferenceException(
"Send Data is required")) ??
throw new JsonException("Failed to deserialize send file data.");
sendData = fileData;
Name = fileData.Name;
File = new SendFileModel(fileData);
break;
case SendType.Text:
Expand All @@ -52,14 +51,17 @@ public SendAccessResponseModel(Send send)
throw new NullReferenceException(
"Send Data is required")) ??
throw new JsonException("Failed to deserialize send text data.");
sendData = textData;
Name = textData.Name;
Text = new SendTextModel(textData);
break;
case SendType.Item:
Name = "";
Comment thread
mcamirault marked this conversation as resolved.
Outdated
Data = send.Data ?? throw new NullReferenceException("Send Data is required");
break;
default:
throw new ArgumentException("Unsupported " + nameof(Type) + ".");
}

Name = sendData.Name;
ExpirationDate = send.ExpirationDate;
}

Expand Down Expand Up @@ -101,6 +103,11 @@ public SendAccessResponseModel(Send send)
/// </summary>
public SendTextModel? Text { get; set; }

/// <summary>
/// Encrypted string containing secret Send data
/// </summary>
public string? Data { get; set; }

/// <summary>
/// The date after which a send cannot be accessed. When this value is
/// <see langword="null"/>, there is no expiration date.
Expand Down
25 changes: 17 additions & 8 deletions src/Api/Tools/Models/Response/SendResponseModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
ο»Ώusing System.Text.Json;
using Bit.Api.Tools.Utilities;
using Bit.Core.Models.Api;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Data;
Expand All @@ -12,7 +11,7 @@ namespace Bit.Api.Tools.Models.Response;
/// A response issued to a Bitwarden client in response to ownership operations.
/// </summary>
/// <seealso cref="SendAccessResponseModel" />
public class SendResponseModel : ResponseModel
public class SendResponseModel : HttpExtensions.ResponseModel
{
/// <summary>
/// Instantiates a send response model
Expand Down Expand Up @@ -47,7 +46,6 @@ public SendResponseModel(Send send)
Disabled = send.Disabled;
HideEmail = send.HideEmail.GetValueOrDefault();

SendData sendData;
switch (send.Type)
{
case SendType.File:
Expand All @@ -56,7 +54,8 @@ public SendResponseModel(Send send)
throw new NullReferenceException(
"Send Data is required")) ??
throw new JsonException("Failed to deserialize send file data.");
sendData = fileData;
Name = fileData.Name;
Notes = fileData.Notes;
File = new SendFileModel(fileData);
break;
case SendType.Text:
Expand All @@ -65,15 +64,20 @@ public SendResponseModel(Send send)
throw new NullReferenceException(
"Send Data is required")) ??
throw new JsonException("Failed to deserialize send text data.");
sendData = textData;
Name = textData.Name;
Notes = textData.Notes;
Text = new SendTextModel(textData);
break;
case SendType.Item:
// These fields are included in send.Data, but since the entire
// object is encrypted as a blob we can't extract them here.
Name = "";
Comment thread
mcamirault marked this conversation as resolved.
Outdated
Notes = "";
Comment thread
mcamirault marked this conversation as resolved.
Outdated
Data = send.Data ?? throw new NullReferenceException("Send Data is required");
break;
default:
throw new ArgumentException("Unsupported " + nameof(Type) + ".");
}

Name = sendData.Name;
Notes = sendData.Notes;
}

/// <summary>
Expand Down Expand Up @@ -126,6 +130,11 @@ public SendResponseModel(Send send)
/// </summary>
public SendTextModel? Text { get; set; }

/// <summary>
/// Encrypted string containing secret Send data
/// </summary>
public string? Data { get; set; }

/// <summary>
/// A base64-encoded byte array containing the Send's encryption key.
/// It's also provided to send recipients in the Send's URL.
Expand Down
3 changes: 2 additions & 1 deletion src/Core/Tools/Enums/SendType.cs
Comment thread
harr1424 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public enum SendType : byte
{
Text = 0,
File = 1
File = 1,
Item = 2
}
2 changes: 1 addition & 1 deletion test/Api.Test/Tools/Controllers/SendsControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Text;
using System.Text.Json;
using AutoFixture.Xunit2;
using Bit.Api.Models.Response;
using Bit.Api.Tools.Controllers;
using Bit.Api.Tools.Models;
using Bit.Api.Tools.Models.Request;
Expand All @@ -24,6 +23,7 @@
using Bit.Core.Tools.SendFeatures.Services.Interfaces;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Bit.HttpExtensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
Expand Down
62 changes: 62 additions & 0 deletions test/Api.Test/Tools/Models/Request/SendRequestModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,68 @@ public void ToSend_Text_Success()
Assert.Equal("encrypted_name", name);
}

[Fact]
public void ToSend_Item_Success()
{
var deletionDate = DateTime.UtcNow.AddDays(5);
var sendRequest = new SendRequestModel
{
AuthType = AuthType.Password,
DeletionDate = deletionDate,
Disabled = false,
ExpirationDate = null,
HideEmail = false,
Key = "encrypted_key",
MaxAccessCount = null,
Name = "encrypted_name",
Notes = null,
Password = "Password",
Data = "encrypted_data",
Type = SendType.Item,
};

var sendAuthorizationService = Substitute.For<ISendAuthorizationService>();
sendAuthorizationService.HashPassword(Arg.Any<string>())
.Returns((info) => $"hashed_{(string)info[0]}");

var send = sendRequest.ToSend(Guid.NewGuid(), sendAuthorizationService);

Assert.Equal(deletionDate, send.DeletionDate);
Assert.False(send.Disabled);
Assert.Null(send.ExpirationDate);
Assert.False(send.HideEmail);
Assert.Equal("encrypted_key", send.Key);
Assert.Equal("hashed_Password", send.Password);
Assert.Equal("encrypted_data", send.Data);
}

[Fact]
public void ToSend_Item_NullData()
{
var deletionDate = DateTime.UtcNow.AddDays(5);
var sendRequest = new SendRequestModel
{
AuthType = AuthType.Password,
DeletionDate = deletionDate,
Disabled = false,
ExpirationDate = null,
HideEmail = false,
Key = "encrypted_key",
MaxAccessCount = null,
Name = "encrypted_name",
Notes = null,
Password = "Password",
Data = null,
Type = SendType.Item,
};

var sendAuthorizationService = Substitute.For<ISendAuthorizationService>();
sendAuthorizationService.HashPassword(Arg.Any<string>())
.Returns((info) => $"hashed_{(string)info[0]}");

Assert.Throws<ArgumentNullException>(() => sendRequest.ToSend(Guid.NewGuid(), sendAuthorizationService));
}

[Fact]
public void ValidateEdit_DeletionDateInPast_ThrowsBadRequestException()
{
Expand Down
114 changes: 114 additions & 0 deletions test/Api.Test/Tools/Models/Response/SendAccessResponseModelTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
ο»Ώusing System.Text.Json;
using Bit.Api.Tools.Models.Response;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Data;
using Xunit;

namespace Bit.Api.Test.Models.Response;

public class SendAccessResponseModelTests
{
[Fact]
public void NullSend_Throws()
{
Send send = null;
SendAccessResponseModel responseModel;
Assert.Throws<ArgumentNullException>(() => responseModel = new SendAccessResponseModel(send));
}

[Fact]
public void TextSend_NullData_Throws()
{
var send = new Send
{
Type = SendType.Text,
Data = null
};
SendAccessResponseModel responseModel;
Assert.Throws<NullReferenceException>(() => responseModel = new SendAccessResponseModel(send));
}

[Fact]
public void TextSend_NonDeserializableData_Throws()
{
var send = new Send
{
Type = SendType.Text,
Data = "bad_data"
};
SendAccessResponseModel responseModel;
Assert.Throws<JsonException>(() => responseModel = new SendAccessResponseModel(send));
}

[Fact]
public void FileSend_NullData_Throws()
{
var send = new Send
{
Type = SendType.File,
Data = null
};
SendAccessResponseModel responseModel;
Assert.Throws<NullReferenceException>(() => responseModel = new SendAccessResponseModel(send));
}

[Fact]
public void FileSend_NonDeserializableData_Throws()
{
var send = new Send
{
Type = SendType.File,
Data = "bad_data"
};
SendAccessResponseModel responseModel;
Assert.Throws<JsonException>(() => responseModel = new SendAccessResponseModel(send));
}

[Fact]
public void ItemSend_NullData_Throws()
{
var send = new Send
{
Type = SendType.Item,
Data = null
};
SendAccessResponseModel responseModel;
Assert.Throws<NullReferenceException>(() => responseModel = new SendAccessResponseModel(send));
}

[Fact]
public void FromSend_Success()
{
var send = new Send
{
Id = Guid.NewGuid(),
AuthType = AuthType.None,
Key = "encrypted_key",
MaxAccessCount = 5,
AccessCount = 0,
RevisionDate = new DateTime(),
ExpirationDate = new DateTime(),
DeletionDate = new DateTime(),
Password = null,
Emails = null,
Disabled = false,
HideEmail = false,
Type = SendType.Text,
Data = JsonSerializer.Serialize(new SendTextData
{
Hidden = false,
Name = "encrypted_name",
Notes = null,
Text = "encrypted_text"
})
};
var responseModel = new SendAccessResponseModel(send);
Assert.Equal(send.Type, responseModel.Type);
Assert.Equal(send.AuthType, responseModel.AuthType);
Assert.Equal("encrypted_name", responseModel.Name);
Assert.Equal("encrypted_text", responseModel.Text.Text);
Assert.False(responseModel.Text.Hidden);
Assert.Equal(send.ExpirationDate, responseModel.ExpirationDate);
}
}
Loading
Loading