1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-04 12:40:22 -05:00

[PM-18086] Add CanRestore and CanDelete authorization methods. (#5407)

This commit is contained in:
Jimmy Vo 2025-02-27 16:30:25 -05:00 committed by GitHub
parent 326ecebba1
commit 63f1c3cee3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 188 additions and 0 deletions

View File

@ -0,0 +1,38 @@
#nullable enable
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations;
using Bit.Core.Vault.Models.Data;
namespace Bit.Core.Vault.Authorization.Permissions;
public class NormalCipherPermissions
{
public static bool CanDelete(User user, CipherDetails cipherDetails, OrganizationAbility? organizationAbility)
{
if (cipherDetails.OrganizationId == null && cipherDetails.UserId == null)
{
throw new Exception("Cipher needs to belong to a user or an organization.");
}
if (user.Id == cipherDetails.UserId)
{
return true;
}
if (organizationAbility?.Id != cipherDetails.OrganizationId)
{
throw new Exception("Cipher does not belong to the input organization.");
}
if (organizationAbility is { LimitItemDeletion: true })
{
return cipherDetails.Manage;
}
return cipherDetails.Manage || cipherDetails.Edit;
}
public static bool CanRestore(User user, CipherDetails cipherDetails, OrganizationAbility? organizationAbility)
{
return CanDelete(user, cipherDetails, organizationAbility);
}
}

View File

@ -0,0 +1,150 @@
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations;
using Bit.Core.Vault.Authorization.Permissions;
using Bit.Core.Vault.Models.Data;
using Xunit;
namespace Bit.Core.Test.Vault.Authorization.Permissions;
public class NormalCipherPermissionTests
{
[Theory]
[InlineData(true, true, true, true)]
[InlineData(true, false, false, false)]
[InlineData(false, true, false, true)]
[InlineData(false, false, true, true)]
[InlineData(false, false, false, false)]
public void CanRestore_WhenCipherIsOwnedByOrganization(
bool limitItemDeletion, bool manage, bool edit, bool expectedResult)
{
// Arrange
var user = new User { Id = Guid.Empty };
var organizationId = Guid.NewGuid();
var cipherDetails = new CipherDetails { Manage = manage, Edit = edit, UserId = null, OrganizationId = organizationId };
var organizationAbility = new OrganizationAbility { Id = organizationId, LimitItemDeletion = limitItemDeletion };
// Act
var result = NormalCipherPermissions.CanRestore(user, cipherDetails, organizationAbility);
// Assert
Assert.Equal(result, expectedResult);
}
[Fact]
public void CanRestore_WhenCipherIsOwnedByUser()
{
// Arrange
var userId = Guid.NewGuid();
var user = new User { Id = userId };
var cipherDetails = new CipherDetails { UserId = userId };
var organizationAbility = new OrganizationAbility { };
// Act
var result = NormalCipherPermissions.CanRestore(user, cipherDetails, organizationAbility);
// Assert
Assert.True(result);
}
[Fact]
public void CanRestore_WhenCipherHasNoOwner_ShouldThrowException()
{
// Arrange
var user = new User { Id = Guid.NewGuid() };
var cipherDetails = new CipherDetails { UserId = null };
// Act
// Assert
Assert.Throws<Exception>(() => NormalCipherPermissions.CanRestore(user, cipherDetails, null));
}
public static List<object[]> TestCases =>
[
new object[] { new OrganizationAbility { Id = Guid.Empty } },
new object[] { null },
];
[Theory]
[MemberData(nameof(TestCases))]
public void CanRestore_WhenCipherDoesNotBelongToInputOrganization_ShouldThrowException(OrganizationAbility? organizationAbility)
{
// Arrange
var user = new User { Id = Guid.NewGuid() };
var cipherDetails = new CipherDetails { UserId = null, OrganizationId = Guid.NewGuid() };
// Act
var exception = Assert.Throws<Exception>(() => NormalCipherPermissions.CanDelete(user, cipherDetails, organizationAbility));
// Assert
Assert.Equal("Cipher does not belong to the input organization.", exception.Message);
}
[Theory]
[InlineData(true, true, true, true)]
[InlineData(true, false, false, false)]
[InlineData(false, true, false, true)]
[InlineData(false, false, true, true)]
[InlineData(false, false, false, false)]
public void CanDelete_WhenCipherIsOwnedByOrganization(
bool limitItemDeletion, bool manage, bool edit, bool expectedResult)
{
// Arrange
var user = new User { Id = Guid.Empty };
var organizationId = Guid.NewGuid();
var cipherDetails = new CipherDetails { Manage = manage, Edit = edit, UserId = null, OrganizationId = organizationId };
var organizationAbility = new OrganizationAbility { Id = organizationId, LimitItemDeletion = limitItemDeletion };
// Act
var result = NormalCipherPermissions.CanRestore(user, cipherDetails, organizationAbility);
// Assert
Assert.Equal(result, expectedResult);
}
[Fact]
public void CanDelete_WhenCipherIsOwnedByUser()
{
// Arrange
var userId = Guid.NewGuid();
var user = new User { Id = userId };
var cipherDetails = new CipherDetails { UserId = userId };
var organizationAbility = new OrganizationAbility { };
// Act
var result = NormalCipherPermissions.CanDelete(user, cipherDetails, organizationAbility);
// Assert
Assert.True(result);
}
[Fact]
public void CanDelete_WhenCipherHasNoOwner_ShouldThrowException()
{
// Arrange
var user = new User { Id = Guid.NewGuid() };
var cipherDetails = new CipherDetails { UserId = null };
// Act
var exception = Assert.Throws<Exception>(() => NormalCipherPermissions.CanDelete(user, cipherDetails, null));
// Assert
Assert.Equal("Cipher needs to belong to a user or an organization.", exception.Message);
}
[Theory]
[MemberData(nameof(TestCases))]
public void CanDelete_WhenCipherDoesNotBelongToInputOrganization_ShouldThrowException(OrganizationAbility? organizationAbility)
{
// Arrange
var user = new User { Id = Guid.NewGuid() };
var cipherDetails = new CipherDetails { UserId = null, OrganizationId = Guid.NewGuid() };
// Act
var exception = Assert.Throws<Exception>(() => NormalCipherPermissions.CanDelete(user, cipherDetails, organizationAbility));
// Assert
Assert.Equal("Cipher does not belong to the input organization.", exception.Message);
}
}