Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-14439] Add PolicyRequirements for enforcement logic #5336

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.Enums;
using Bit.Core.Utilities;

namespace Bit.Core.Models.Data.Organizations.OrganizationUsers;

Expand All @@ -22,4 +24,9 @@ public class OrganizationUserPolicyDetails
public string OrganizationUserPermissionsData { get; set; }

public bool IsProvider { get; set; }

public T GetDataModel<T>() where T : IPolicyDataModel, new()
{
return CoreHelpers.LoadClassFromJsonData<T>(PolicyData);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
๏ปฟusing Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies;

public interface IPolicyRequirementQuery
{
Task<T> GetAsync<T>(Guid userId) where T : IPolicyRequirement;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

There is an intentional split of responsibilities here: all business logic is in the policy requirements, which are written in a functional style. The query is agnostic about what policies are being handled - its only job is to connect policy requirements to dependencies.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
๏ปฟusing Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Settings;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;

public class PolicyRequirementQuery : IPolicyRequirementQuery
{
private readonly IPolicyRepository _policyRepository;
private readonly PolicyRequirementRegistry _policyRequirements = new();

public PolicyRequirementQuery(IGlobalSettings globalSettings, IPolicyRepository policyRepository)
{
_policyRepository = policyRepository;

// Register Policy Requirement factory functions below
_policyRequirements.Add(SendPolicyRequirement.Create);
_policyRequirements.Add(up
=> SsoPolicyRequirement.Create(up, globalSettings.Sso));
Comment on lines +18 to +20
Copy link
Member Author

@eliykat eliykat Jan 29, 2025

Choose a reason for hiding this comment

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

Example of how you can just register the factory function directly for simple requirements (which will be most of them), or inject additional values if needed (settings, feature flags, etc).

Copy link
Contributor

Choose a reason for hiding this comment

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

Just an idea but maybe we could use Reflection here to find all classes that inherit IPolicyRequirement and automatically register them. Maybe it adds some unnecessary complexity though.

}

public async Task<T> GetAsync<T>(Guid userId) where T : IPolicyRequirement
=> _policyRequirements.Get<T>()(await GetPolicyDetails(userId));

private Task<IEnumerable<OrganizationUserPolicyDetails>> GetPolicyDetails(Guid userId) =>
_policyRepository.GetPolicyDetailsByUserId(userId);

/// <summary>
/// Helper class used to register and retrieve Policy Requirement factories by type.
/// </summary>
private class PolicyRequirementRegistry
{
private readonly Dictionary<Type, CreateRequirement<IPolicyRequirement>> _registry = new();

public void Add<T>(CreateRequirement<T> factory) where T : IPolicyRequirement
{
// Explicitly convert T to an IPolicyRequirement (C# doesn't do this automatically).
IPolicyRequirement Converted(IEnumerable<OrganizationUserPolicyDetails> up) => factory(up);
_registry.Add(typeof(T), Converted);
}

public CreateRequirement<T> Get<T>() where T : IPolicyRequirement
{
if (!_registry.TryGetValue(typeof(T), out var factory))
{
throw new NotImplementedException("No Policy Requirement found for " + typeof(T));
}

// Explicitly convert IPolicyRequirement back to T (C# doesn't do this automatically).
// The cast here relies on the Register method correctly associating the type and factory function.
T Converted(IEnumerable<OrganizationUserPolicyDetails> up) => (T)factory(up);
return Converted;
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
๏ปฟusing Bit.Core.Models.Data.Organizations.OrganizationUsers;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

public interface IPolicyRequirement;

public delegate T CreateRequirement<T>(IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails)
where T : IPolicyRequirement;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

public class MasterPasswordPolicyRequirement : MasterPasswordPolicyData, IPolicyRequirement
{
public static MasterPasswordPolicyRequirement Create(IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails) =>
userPolicyDetails
.GetPolicyType(PolicyType.MasterPassword)
.ExcludeProviders()
.ExcludeRevokedAndInvitedUsers()
.Select(up => up.GetDataModel<MasterPasswordPolicyData>())
.Aggregate(
new MasterPasswordPolicyRequirement(),
(result, current) =>
{
result.CombineWith(current);
return result;
}
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

public class PersonalOwnershipPolicyRequirement : IPolicyRequirement
{
public bool DisablePersonalOwnership { get; init; }

public static PersonalOwnershipPolicyRequirement Create(IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails)
=> new()
{
DisablePersonalOwnership = userPolicyDetails
.GetPolicyType(PolicyType.PersonalOwnership)
.ExcludeOwnersAndAdmins()
.ExcludeProviders()
.ExcludeRevokedAndInvitedUsers()
.Any()
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is great!

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

public static class PolicyRequirementHelpers
{
public static IEnumerable<OrganizationUserPolicyDetails> GetPolicyType(
this IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails,
PolicyType type) =>
userPolicyDetails.Where(x => x.PolicyType == type);

public static IEnumerable<OrganizationUserPolicyDetails> ExcludeOwnersAndAdmins(
this IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails) =>
userPolicyDetails.Where(x => x.OrganizationUserType != OrganizationUserType.Owner);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
userPolicyDetails.Where(x => x.OrganizationUserType != OrganizationUserType.Owner);
userPolicyDetails.Where(x => x.OrganizationUserType is not OrganizationUserType.Owner and not OrganizationUserType.Admin);


public static IEnumerable<OrganizationUserPolicyDetails> ExcludeProviders(
this IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails) =>
userPolicyDetails.Where(x => !x.IsProvider);

public static IEnumerable<OrganizationUserPolicyDetails> ExcludeRevokedAndInvitedUsers(
this IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails) =>
userPolicyDetails.Where(x => x.OrganizationUserStatus >= OrganizationUserStatusType.Accepted);

public static IEnumerable<OrganizationUserPolicyDetails> ExcludeRevokedUsers(
this IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails) =>
userPolicyDetails.Where(x => x.OrganizationUserStatus >= OrganizationUserStatusType.Invited);
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a huge fan of using > and < on enum values. Maybe its better to add an explicit list of allowed types for each and just do a contains?

๐Ÿคท non-blocking. just a thought.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

public class SendPolicyRequirement : IPolicyRequirement
{
public bool DisableSend { get; init; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public bool DisableSend { get; init; }
public bool DisableSend { get; private init; }

public bool DisableHideEmail { get; init; }

public static SendPolicyRequirement Create(IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails)
{
var filteredPolicies = userPolicyDetails
.ExcludeOwnersAndAdmins()
.ExcludeRevokedAndInvitedUsers()
.ToList();

return new SendPolicyRequirement
{
DisableSend = filteredPolicies
.GetPolicyType(PolicyType.DisableSend)
.Any(),

DisableHideEmail = filteredPolicies
.GetPolicyType(PolicyType.SendOptions)
.Select(up => up.GetDataModel<SendOptionsPolicyData>())
.Any(d => d.DisableHideEmail)
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

public enum SingleOrganizationRequirementResult
{
Ok = 1,
RequiredByThisOrganization = 2,
RequiredByOtherOrganization = 3
}

public class SingleOrganizationPolicyRequirement : IPolicyRequirement
{
private IEnumerable<OrganizationUserPolicyDetails> PolicyDetails { get; }

public SingleOrganizationPolicyRequirement(IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails)
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 need a Create method here?

{
PolicyDetails = userPolicyDetails
.GetPolicyType(PolicyType.SingleOrg)
.ExcludeOwnersAndAdmins()
.ExcludeProviders()
.ToList();
}

public SingleOrganizationRequirementResult CanJoinOrganization(Guid organizationId)
{
// Check for the org the user is trying to join
if (PolicyDetails.Any(x => x.OrganizationId == organizationId))
{
return SingleOrganizationRequirementResult.RequiredByThisOrganization;
}

// Check for other orgs the user might already be a member of (accepted or confirmed status only)
if (PolicyDetails.ExcludeRevokedAndInvitedUsers().Any())
{
return SingleOrganizationRequirementResult.RequiredByOtherOrganization;
}

return SingleOrganizationRequirementResult.Ok;
}

public SingleOrganizationRequirementResult CanBeRestoredToOrganization(Guid organizationId) =>
CanJoinOrganization(organizationId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Settings;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

public class SsoPolicyRequirement : IPolicyRequirement
{
public bool RequireSso { get; init; }

public static SsoPolicyRequirement Create(
IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails,
ISsoSettings ssoSettings)
=> new()
{
RequireSso = userPolicyDetails
.GetPolicyType(PolicyType.RequireSso)
.ExcludeProviders()
// TODO: confirm minStatus - maybe confirmed?
.ExcludeRevokedAndInvitedUsers()
.Any(up =>
up.OrganizationUserType is not OrganizationUserType.Owner and not OrganizationUserType.Admin ||
ssoSettings.EnforceSsoPolicyForAllUsers)
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
๏ปฟusing Bit.Core.AdminConsole.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;

public class TwoFactorAuthenticationPolicyRequirement : IPolicyRequirement
{
private IEnumerable<OrganizationUserPolicyDetails> PolicyDetails { get; }

public TwoFactorAuthenticationPolicyRequirement(IEnumerable<OrganizationUserPolicyDetails> userPolicyDetails)
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 need a create method here?

{
PolicyDetails = userPolicyDetails
.GetPolicyType(PolicyType.TwoFactorAuthentication)
.ExcludeOwnersAndAdmins()
.ExcludeProviders()
.ToList();
}

public bool RequiredToJoinOrganization(Guid organizationId)
=> PolicyDetails.Any(x => x.OrganizationId == organizationId);

public IEnumerable<Guid> OrganizationsRequiringTwoFactor
=> PolicyDetails
.ExcludeRevokedAndInvitedUsers()
.Select(x => x.OrganizationId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public static void AddPolicyServices(this IServiceCollection services)
{
services.AddScoped<IPolicyService, PolicyService>();
services.AddScoped<ISavePolicyCommand, SavePolicyCommand>();
services.AddScoped<IPolicyRequirementQuery, PolicyRequirementQuery>();

services.AddScoped<IPolicyValidator, TwoFactorAuthenticationPolicyValidator>();
services.AddScoped<IPolicyValidator, SingleOrgPolicyValidator>();
Expand Down
2 changes: 2 additions & 0 deletions src/Core/AdminConsole/Repositories/IPolicyRepository.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
๏ปฟusing Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;

#nullable enable
Expand All @@ -11,4 +12,5 @@ public interface IPolicyRepository : IRepository<Policy, Guid>
Task<Policy?> GetByOrganizationIdTypeAsync(Guid organizationId, PolicyType type);
Task<ICollection<Policy>> GetManyByOrganizationIdAsync(Guid organizationId);
Task<ICollection<Policy>> GetManyByUserIdAsync(Guid userId);
Task<IEnumerable<OrganizationUserPolicyDetails>> GetPolicyDetailsByUserId(Guid userId);
}
22 changes: 10 additions & 12 deletions src/Core/Tools/Services/Implementations/SendService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
๏ปฟusing System.Text.Json;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Context;
Expand Down Expand Up @@ -36,6 +36,7 @@
private readonly IReferenceEventService _referenceEventService;
private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
private readonly IPolicyRequirementQuery _policyRequirementQuery;
private const long _fileSizeLeeway = 1024L * 1024L; // 1MB

public SendService(
Expand All @@ -50,7 +51,8 @@
GlobalSettings globalSettings,
IPolicyRepository policyRepository,
IPolicyService policyService,
ICurrentContext currentContext)
ICurrentContext currentContext,
IPolicyRequirementQuery policyRequirementQuery)
{
_sendRepository = sendRepository;
_userRepository = userRepository;
Expand All @@ -64,6 +66,7 @@
_referenceEventService = referenceEventService;
_globalSettings = globalSettings;
_currentContext = currentContext;
_policyRequirementQuery = policyRequirementQuery;
}

public async Task SaveSendAsync(Send send)
Expand Down Expand Up @@ -291,20 +294,15 @@
return;
}

var anyDisableSendPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId.Value,
PolicyType.DisableSend);
if (anyDisableSendPolicies)
var sendRequirement = await _policyRequirementQuery.GetAsync<SendPolicyRequirement>(userId.Value);
if (sendRequirement.DisableSend)

Check failure on line 298 in src/Core/Tools/Services/Implementations/SendService.cs

View workflow job for this annotation

GitHub Actions / Test Results

Bit.Core.Test.Tools.Services.SendServiceTests โ–บ SaveFileSendAsync_HasEnoughStorage_Success(sutProvider: SutProvider`1 { Sut = SendService { }, SutType = typeof(Bit.Core.Tools.Services.SendService) }, send: Send { AccessCount = 141, CipherId = 3dc62aea-...

Failed test found in: test/Core.Test/TestResults/oss-test-results.trx Error: System.NullReferenceException : Object reference not set to an instance of an object.
Raw output
System.NullReferenceException : Object reference not set to an instance of an object.
   at Bit.Core.Tools.Services.SendService.ValidateUserCanSaveAsync(Nullable`1 userId, Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 298
   at Bit.Core.Tools.Services.SendService.SaveSendAsync(Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 75
   at Bit.Core.Tools.Services.SendService.SaveFileSendAsync(Send send, SendFileData data, Int64 fileLength) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 119
   at Bit.Core.Tools.Services.SendService.SaveFileSendAsync(Send send, SendFileData data, Int64 fileLength) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 126
   at Bit.Core.Test.Tools.Services.SendServiceTests.SaveFileSendAsync_HasEnoughStorage_Success(SutProvider`1 sutProvider, Send send) in /home/runner/work/server/server/test/Core.Test/Tools/Services/SendServiceTests.cs:line 469
--- End of stack trace from previous location ---

Check failure on line 298 in src/Core/Tools/Services/Implementations/SendService.cs

View workflow job for this annotation

GitHub Actions / Test Results

Bit.Core.Test.Tools.Services.SendServiceTests โ–บ SaveSendAsync_DisableHideEmail_DoesntApply_success(sendType: File, sutProvider: SutProvider`1 { Sut = SendService { }, SutType = typeof(Bit.Core.Tools.Services.SendService) }, send: Send { AccessCount = 1...

Failed test found in: test/Core.Test/TestResults/oss-test-results.trx test/Core.Test/TestResults/oss-test-results.trx Error: System.NullReferenceException : Object reference not set to an instance of an object.
Raw output
System.NullReferenceException : Object reference not set to an instance of an object.
   at Bit.Core.Tools.Services.SendService.ValidateUserCanSaveAsync(Nullable`1 userId, Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 298
   at Bit.Core.Tools.Services.SendService.SaveSendAsync(Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 75
   at Bit.Core.Test.Tools.Services.SendServiceTests.SaveSendAsync_DisableHideEmail_DoesntApply_success(SendType sendType, SutProvider`1 sutProvider, Send send, Policy policy) in /home/runner/work/server/server/test/Core.Test/Tools/Services/SendServiceTests.cs:line 114
--- End of stack trace from previous location ---

Check failure on line 298 in src/Core/Tools/Services/Implementations/SendService.cs

View workflow job for this annotation

GitHub Actions / Test Results

Bit.Core.Test.Tools.Services.SendServiceTests โ–บ SaveSendAsync_DisableSend_DoesntApply_success(sendType: File, sutProvider: SutProvider`1 { Sut = SendService { }, SutType = typeof(Bit.Core.Tools.Services.SendService) }, send: Send { AccessCount = 137, C...

Failed test found in: test/Core.Test/TestResults/oss-test-results.trx test/Core.Test/TestResults/oss-test-results.trx Error: System.NullReferenceException : Object reference not set to an instance of an object.
Raw output
System.NullReferenceException : Object reference not set to an instance of an object.
   at Bit.Core.Tools.Services.SendService.ValidateUserCanSaveAsync(Nullable`1 userId, Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 298
   at Bit.Core.Tools.Services.SendService.SaveSendAsync(Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 75
   at Bit.Core.Test.Tools.Services.SendServiceTests.SaveSendAsync_DisableSend_DoesntApply_success(SendType sendType, SutProvider`1 sutProvider, Send send) in /home/runner/work/server/server/test/Core.Test/Tools/Services/SendServiceTests.cs:line 65
--- End of stack trace from previous location ---

Check failure on line 298 in src/Core/Tools/Services/Implementations/SendService.cs

View workflow job for this annotation

GitHub Actions / Test Results

Bit.Core.Test.Tools.Services.SendServiceTests โ–บ SaveSendAsync_ExistingSend_Updates(sutProvider: SutProvider`1 { Sut = SendService { }, SutType = typeof(Bit.Core.Tools.Services.SendService) }, send: Send { AccessCount = 216, CipherId = d2ba13f9-d8d2-48e...

Failed test found in: test/Core.Test/TestResults/oss-test-results.trx Error: System.NullReferenceException : Object reference not set to an instance of an object.
Raw output
System.NullReferenceException : Object reference not set to an instance of an object.
   at Bit.Core.Tools.Services.SendService.ValidateUserCanSaveAsync(Nullable`1 userId, Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 298
   at Bit.Core.Tools.Services.SendService.SaveSendAsync(Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 75
   at Bit.Core.Test.Tools.Services.SendServiceTests.SaveSendAsync_ExistingSend_Updates(SutProvider`1 sutProvider, Send send) in /home/runner/work/server/server/test/Core.Test/Tools/Services/SendServiceTests.cs:line 127
--- End of stack trace from previous location ---

Check failure on line 298 in src/Core/Tools/Services/Implementations/SendService.cs

View workflow job for this annotation

GitHub Actions / Test Results

Bit.Core.Test.Tools.Services.SendServiceTests โ–บ UpdateFileToExistingSendAsync_Success(sutProvider: SutProvider`1 { Sut = SendService { }, SutType = typeof(Bit.Core.Tools.Services.SendService) }, send: Send { AccessCount = 205, CipherId = accf1d9a-5ecd-...

Failed test found in: test/Core.Test/TestResults/oss-test-results.trx Error: System.NullReferenceException : Object reference not set to an instance of an object.
Raw output
System.NullReferenceException : Object reference not set to an instance of an object.
   at Bit.Core.Tools.Services.SendService.ValidateUserCanSaveAsync(Nullable`1 userId, Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 298
   at Bit.Core.Tools.Services.SendService.SaveSendAsync(Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 75
   at Bit.Core.Tools.Services.SendService.ValidateSendFile(Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 178
   at Bit.Core.Tools.Services.SendService.UploadFileToExistingSendAsync(Stream stream, Send send) in /home/runner/work/server/server/src/Core/Tools/Services/Implementations/SendService.cs:line 151
   at Bit.Core.Test.Tools.Services.SendServiceTests.UpdateFileToExistingSendAsync_Success(SutProvider`1 sutProvider, Send send) in /home/runner/work/server/server/test/Core.Test/Tools/Services/SendServiceTests.cs:line 604
--- End of stack trace from previous location ---
{
throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send.");
}

if (send.HideEmail.GetValueOrDefault())
if (send.HideEmail.GetValueOrDefault() && sendRequirement.DisableHideEmail)
{
var sendOptionsPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId.Value, PolicyType.SendOptions);
if (sendOptionsPolicies.Any(p => CoreHelpers.LoadClassFromJsonData<SendOptionsPolicyData>(p.PolicyData)?.DisableHideEmail ?? false))
{
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
}
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
}
}

Expand Down
Loading
Loading