mirror of
https://github.com/bitwarden/server
synced 2026-02-21 11:53:42 +00:00
gate add/edit endpoints behind premium membership and add test coverage (#7043)
This commit is contained in:
@@ -7,6 +7,7 @@ using Bit.Api.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Bit.Core.Auth.UserFeatures.SendAccess;
|
||||
using Bit.Core.Billing.Premium.Queries;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Services;
|
||||
@@ -36,6 +37,7 @@ public class SendsController : Controller
|
||||
private readonly ILogger<SendsController> _logger;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IHasPremiumAccessQuery _hasPremiumAccessQuery;
|
||||
|
||||
public SendsController(
|
||||
ISendRepository sendRepository,
|
||||
@@ -47,7 +49,9 @@ public class SendsController : Controller
|
||||
ISendFileStorageService sendFileStorageService,
|
||||
ILogger<SendsController> logger,
|
||||
IFeatureService featureService,
|
||||
IPushNotificationService pushNotificationService)
|
||||
IPushNotificationService pushNotificationService,
|
||||
IHasPremiumAccessQuery hasPremiumAccessQuery
|
||||
)
|
||||
{
|
||||
_sendRepository = sendRepository;
|
||||
_userService = userService;
|
||||
@@ -59,6 +63,7 @@ public class SendsController : Controller
|
||||
_logger = logger;
|
||||
_featureService = featureService;
|
||||
_pushNotificationService = pushNotificationService;
|
||||
_hasPremiumAccessQuery = hasPremiumAccessQuery;
|
||||
}
|
||||
|
||||
#region Anonymous endpoints
|
||||
@@ -306,6 +311,13 @@ public class SendsController : Controller
|
||||
{
|
||||
model.ValidateCreation();
|
||||
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
|
||||
var hasPremium = await _hasPremiumAccessQuery.HasPremiumAccessAsync(userId);
|
||||
|
||||
if (!hasPremium && !string.IsNullOrWhiteSpace(model.Emails))
|
||||
{
|
||||
throw new BadRequestException("Email verified Sends require a premium membership");
|
||||
}
|
||||
|
||||
var send = model.ToSend(userId, _sendAuthorizationService);
|
||||
await _nonAnonymousSendCommand.SaveSendAsync(send);
|
||||
return new SendResponseModel(send);
|
||||
@@ -332,6 +344,13 @@ public class SendsController : Controller
|
||||
|
||||
model.ValidateCreation();
|
||||
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
|
||||
var hasPremium = await _hasPremiumAccessQuery.HasPremiumAccessAsync(userId);
|
||||
|
||||
if (!hasPremium && !string.IsNullOrWhiteSpace(model.Emails))
|
||||
{
|
||||
throw new BadRequestException("Email verified Sends require a premium membership");
|
||||
}
|
||||
|
||||
var (send, data) = model.ToSend(userId, model.File.FileName, _sendAuthorizationService);
|
||||
var uploadUrl = await _nonAnonymousSendCommand.SaveFileSendAsync(send, data, model.FileLength.Value);
|
||||
return new SendFileUploadDataResponseModel
|
||||
@@ -397,6 +416,13 @@ public class SendsController : Controller
|
||||
{
|
||||
model.ValidateEdit();
|
||||
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
|
||||
var hasPremium = await _hasPremiumAccessQuery.HasPremiumAccessAsync(userId);
|
||||
|
||||
if (!hasPremium && !string.IsNullOrWhiteSpace(model.Emails))
|
||||
{
|
||||
throw new BadRequestException("Email verified Sends require a premium membership");
|
||||
}
|
||||
|
||||
var send = await _sendRepository.GetByIdAsync(new Guid(id));
|
||||
if (send == null || send.UserId != userId)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using Bit.Api.Tools.Models;
|
||||
using Bit.Api.Tools.Models.Request;
|
||||
using Bit.Api.Tools.Models.Response;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Billing.Premium.Queries;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Platform.Push;
|
||||
@@ -39,6 +40,7 @@ public class SendsControllerTests : IDisposable
|
||||
private readonly ILogger<SendsController> _logger;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IHasPremiumAccessQuery _hasPremiumAccessQuery;
|
||||
|
||||
public SendsControllerTests()
|
||||
{
|
||||
@@ -52,6 +54,7 @@ public class SendsControllerTests : IDisposable
|
||||
_logger = Substitute.For<ILogger<SendsController>>();
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
||||
_hasPremiumAccessQuery = Substitute.For<IHasPremiumAccessQuery>();
|
||||
|
||||
_sut = new SendsController(
|
||||
_sendRepository,
|
||||
@@ -63,7 +66,8 @@ public class SendsControllerTests : IDisposable
|
||||
_sendFileStorageService,
|
||||
_logger,
|
||||
_featureService,
|
||||
_pushNotificationService
|
||||
_pushNotificationService,
|
||||
_hasPremiumAccessQuery
|
||||
);
|
||||
}
|
||||
|
||||
@@ -212,6 +216,7 @@ public class SendsControllerTests : IDisposable
|
||||
public async Task Post_WithEmails_InfersAuthTypeEmail(Guid userId)
|
||||
{
|
||||
_userService.GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId);
|
||||
_hasPremiumAccessQuery.HasPremiumAccessAsync(userId).Returns(true);
|
||||
var request = new SendRequestModel
|
||||
{
|
||||
Type = SendType.Text,
|
||||
@@ -259,6 +264,68 @@ public class SendsControllerTests : IDisposable
|
||||
_userService.Received(1).GetProperUserId(Arg.Any<ClaimsPrincipal>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task Post_WithEmails_WhenNotPremium_ThrowsBadRequestException(Guid userId)
|
||||
{
|
||||
_userService.GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId);
|
||||
_hasPremiumAccessQuery.HasPremiumAccessAsync(userId).Returns(false);
|
||||
var request = new SendRequestModel
|
||||
{
|
||||
Type = SendType.Text,
|
||||
Key = "key",
|
||||
Text = new SendTextModel { Text = "text" },
|
||||
Emails = "test@example.com",
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7)
|
||||
};
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Post(request));
|
||||
|
||||
Assert.Equal("Email verified Sends require a premium membership", exception.Message);
|
||||
await _nonAnonymousSendCommand.DidNotReceive().SaveSendAsync(Arg.Any<Send>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task PostFile_WithEmails_WhenNotPremium_ThrowsBadRequestException(Guid userId)
|
||||
{
|
||||
_userService.GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId);
|
||||
_hasPremiumAccessQuery.HasPremiumAccessAsync(userId).Returns(false);
|
||||
var request = new SendRequestModel
|
||||
{
|
||||
Type = SendType.File,
|
||||
Key = "key",
|
||||
File = new SendFileModel { FileName = "test.txt" },
|
||||
FileLength = 1024L,
|
||||
Emails = "test@example.com",
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7)
|
||||
};
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostFile(request));
|
||||
|
||||
Assert.Equal("Email verified Sends require a premium membership", exception.Message);
|
||||
await _nonAnonymousSendCommand.DidNotReceive()
|
||||
.SaveFileSendAsync(Arg.Any<Send>(), Arg.Any<SendFileData>(), Arg.Any<long>());
|
||||
}
|
||||
|
||||
[Theory, AutoData]
|
||||
public async Task Put_WithEmails_WhenNotPremium_ThrowsBadRequestException(Guid userId, Guid sendId)
|
||||
{
|
||||
_userService.GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId);
|
||||
_hasPremiumAccessQuery.HasPremiumAccessAsync(userId).Returns(false);
|
||||
var request = new SendRequestModel
|
||||
{
|
||||
Type = SendType.Text,
|
||||
Key = "key",
|
||||
Text = new SendTextModel { Text = "text" },
|
||||
Emails = "test@example.com",
|
||||
DeletionDate = DateTime.UtcNow.AddDays(7)
|
||||
};
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Put(sendId.ToString(), request));
|
||||
|
||||
Assert.Equal("Email verified Sends require a premium membership", exception.Message);
|
||||
await _nonAnonymousSendCommand.DidNotReceive().SaveSendAsync(Arg.Any<Send>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(AuthType.Password)]
|
||||
[InlineData(AuthType.Email)]
|
||||
@@ -518,6 +585,7 @@ public class SendsControllerTests : IDisposable
|
||||
public async Task PostFile_WithEmails_InfersAuthTypeEmail(Guid userId)
|
||||
{
|
||||
_userService.GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId);
|
||||
_hasPremiumAccessQuery.HasPremiumAccessAsync(userId).Returns(true);
|
||||
_nonAnonymousSendCommand.SaveFileSendAsync(Arg.Any<Send>(), Arg.Any<SendFileData>(), Arg.Any<long>())
|
||||
.Returns("https://example.com/upload")
|
||||
.AndDoes(callInfo =>
|
||||
@@ -593,6 +661,7 @@ public class SendsControllerTests : IDisposable
|
||||
public async Task Put_ChangingFromPasswordToEmails_UpdatesAuthTypeToEmail(Guid userId, Guid sendId)
|
||||
{
|
||||
_userService.GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(userId);
|
||||
_hasPremiumAccessQuery.HasPremiumAccessAsync(userId).Returns(true);
|
||||
var existingSend = new Send
|
||||
{
|
||||
Id = sendId,
|
||||
|
||||
Reference in New Issue
Block a user