Skip to content

Commit

Permalink
[PM-14826] Add UsePolicies check to GET endpoints
Browse files Browse the repository at this point in the history
GetByToken and GetMasterPasswordPolicy endpoints provide policy information, so if the organization is not using policies, then we avoid the rest of the logic.
  • Loading branch information
JimmyVo16 committed Nov 19, 2024
1 parent b2b0f1e commit bcffef5
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 71 deletions.
89 changes: 30 additions & 59 deletions src/Api/AdminConsole/Controllers/PoliciesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ namespace Bit.Api.AdminConsole.Controllers;
[Authorize("Application")]
public class PoliciesController : Controller
{
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly IFeatureService _featureService;
private readonly GlobalSettings _globalSettings;
private readonly IOrganizationHasVerifiedDomainsQuery _organizationHasVerifiedDomainsQuery;
private readonly IOrganizationRepository _organizationRepository;
private readonly IDataProtector _organizationServiceDataProtector;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
private readonly IFeatureService _featureService;
private readonly IOrganizationHasVerifiedDomainsQuery _organizationHasVerifiedDomainsQuery;
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IUserService _userService;

public PoliciesController(
IPolicyRepository policyRepository,
public PoliciesController(IPolicyRepository policyRepository,
IPolicyService policyService,
IOrganizationUserRepository organizationUserRepository,
IUserService userService,
Expand All @@ -48,7 +48,8 @@ public PoliciesController(
IDataProtectionProvider dataProtectionProvider,
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
IFeatureService featureService,
IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery)
IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery,
IOrganizationRepository organizationRepository)
{
_policyRepository = policyRepository;
_policyService = policyService;
Expand All @@ -62,25 +63,18 @@ public PoliciesController(
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
_featureService = featureService;
_organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery;
_organizationRepository = organizationRepository;
}

[HttpGet("{type}")]
public async Task<PolicyDetailResponseModel> Get(Guid orgId, int type)
{
if (!await _currentContext.ManagePolicies(orgId))
{
throw new NotFoundException();
}
if (!await _currentContext.ManagePolicies(orgId)) throw new NotFoundException();
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type);
if (policy == null)
{
return new PolicyDetailResponseModel(new Policy { Type = (PolicyType)type });
}
if (policy == null) return new PolicyDetailResponseModel(new Policy { Type = (PolicyType)type });

if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && policy.Type is PolicyType.SingleOrg)
{
return await policy.GetSingleOrgPolicyDetailResponseAsync(_organizationHasVerifiedDomainsQuery);
}

return new PolicyDetailResponseModel(policy);
}
Expand All @@ -89,10 +83,7 @@ public async Task<PolicyDetailResponseModel> Get(Guid orgId, int type)
public async Task<ListResponseModel<PolicyResponseModel>> Get(string orgId)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
if (!await _currentContext.ManagePolicies(orgIdGuid)) throw new NotFoundException();

var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid);

Expand All @@ -104,6 +95,10 @@ public async Task<ListResponseModel<PolicyResponseModel>> Get(string orgId)
public async Task<ListResponseModel<PolicyResponseModel>> GetByToken(Guid orgId, [FromQuery] string email,
[FromQuery] string token, [FromQuery] Guid organizationUserId)
{
var organization = await _organizationRepository.GetByIdAsync(orgId);

if (organization is not { UsePolicies: true }) throw new NotFoundException();

// TODO: PM-4142 - remove old token validation logic once 3 releases of backwards compatibility are complete
var newTokenValid = OrgUserInviteTokenable.ValidateOrgUserInviteStringToken(
_orgUserInviteTokenDataFactory, token, organizationUserId, email);
Expand All @@ -112,16 +107,10 @@ public async Task<ListResponseModel<PolicyResponseModel>> GetByToken(Guid orgId,
_organizationServiceDataProtector, token, email, organizationUserId, _globalSettings
);

if (!tokenValid)
{
throw new NotFoundException();
}
if (!tokenValid) throw new NotFoundException();

var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
if (orgUser == null || orgUser.OrganizationId != orgId)
{
throw new NotFoundException();
}
if (orgUser == null || orgUser.OrganizationId != orgId) throw new NotFoundException();

var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgId);
var responses = policies.Where(p => p.Enabled).Select(p => new PolicyResponseModel(p));
Expand All @@ -135,20 +124,11 @@ public async Task<ListResponseModel<PolicyResponseModel>> GetByToken(Guid orgId,
public async Task<ListResponseModel<PolicyResponseModel>> GetByInvitedUser(Guid orgId, [FromQuery] Guid userId)
{
var user = await _userService.GetUserByIdAsync(userId);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (user == null) throw new UnauthorizedAccessException();
var orgUsersByUserId = await _organizationUserRepository.GetManyByUserAsync(user.Id);
var orgUser = orgUsersByUserId.SingleOrDefault(u => u.OrganizationId == orgId);
if (orgUser == null)
{
throw new NotFoundException();
}
if (orgUser.Status != OrganizationUserStatusType.Invited)
{
throw new UnauthorizedAccessException();
}
if (orgUser == null) throw new NotFoundException();
if (orgUser.Status != OrganizationUserStatusType.Invited) throw new UnauthorizedAccessException();

var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgId);
var responses = policies.Where(p => p.Enabled).Select(p => new PolicyResponseModel(p));
Expand All @@ -158,21 +138,19 @@ public async Task<ListResponseModel<PolicyResponseModel>> GetByInvitedUser(Guid
[HttpGet("master-password")]
public async Task<PolicyResponseModel> GetMasterPasswordPolicy(Guid orgId)
{
var organization = await _organizationRepository.GetByIdAsync(orgId);

if (organization is not { UsePolicies: true }) throw new NotFoundException();

var userId = _userService.GetProperUserId(User).Value;

var orgUser = await _organizationUserRepository.GetByOrganizationAsync(orgId, userId);

if (orgUser == null)
{
throw new NotFoundException();
}
if (orgUser == null) throw new NotFoundException();

var policy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.MasterPassword);

if (policy == null || !policy.Enabled)
{
throw new NotFoundException();
}
if (policy == null || !policy.Enabled) throw new NotFoundException();

return new PolicyResponseModel(policy);
}
Expand All @@ -181,19 +159,12 @@ public async Task<PolicyResponseModel> GetMasterPasswordPolicy(Guid orgId)
public async Task<PolicyResponseModel> Put(string orgId, int type, [FromBody] PolicyRequestModel model)
{
var orgIdGuid = new Guid(orgId);
if (!await _currentContext.ManagePolicies(orgIdGuid))
{
throw new NotFoundException();
}
if (!await _currentContext.ManagePolicies(orgIdGuid)) throw new NotFoundException();
var policy = await _policyRepository.GetByOrganizationIdTypeAsync(new Guid(orgId), (PolicyType)type);
if (policy == null)
{
policy = model.ToPolicy(orgIdGuid);
}
else
{
policy = model.ToPolicy(policy);
}

var userId = _userService.GetProperUserId(User);
await _policyService.SaveAsync(policy, userId);
Expand Down
97 changes: 85 additions & 12 deletions test/Api.Test/Controllers/PoliciesControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,19 @@ public class PoliciesControllerTests
[Theory]
[BitAutoData]
public async Task GetMasterPasswordPolicy_WhenCalled_ReturnsMasterPasswordPolicy(
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid userId, OrganizationUser orgUser,
Policy policy, MasterPasswordPolicyData mpPolicyData)
SutProvider<PoliciesController> sutProvider,
Guid orgId, Guid userId,
OrganizationUser orgUser,
Policy policy,
MasterPasswordPolicyData mpPolicyData,
Organization organization)
{
// Arrange
organization.UsePolicies = true;

var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);

sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns((Guid?)userId);
Expand Down Expand Up @@ -135,23 +144,56 @@ public async Task GetMasterPasswordPolicy_PolicyNotEnabled_ThrowsNotFoundExcepti
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
}

[Theory]
[BitAutoData]
public async Task GetMasterPasswordPolicy_WhenUsePoliciesIsFalse_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider,
Guid orgId)
{
// Arrange
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns((Organization)null);


// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
}

[Theory]
[BitAutoData]
public async Task GetMasterPasswordPolicy_WhenOrgIsNull_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider,
Guid orgId,
Organization organization)
{
// Arrange
organization.UsePolicies = false;

var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);


// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
}

[Theory]
[BitAutoData]
public async Task Get_WhenUserCanManagePolicies_WithExistingType_ReturnsExistingPolicy(
SutProvider<PoliciesController> sutProvider, Guid orgId, Policy policy, int type)
{
// Arrange
sutProvider.GetDependency<ICurrentContext>()
.ManagePolicies(orgId)
.Returns(true);
.ManagePolicies(orgId)
.Returns(true);

policy.Type = (PolicyType)type;
policy.Enabled = true;
policy.Data = null;

sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
.Returns(policy);
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
.Returns(policy);

// Act
var result = await sutProvider.Sut.Get(orgId, type);
Expand All @@ -171,12 +213,12 @@ public async Task Get_WhenUserCanManagePolicies_WithNonExistingType_ReturnsDefau
{
// Arrange
sutProvider.GetDependency<ICurrentContext>()
.ManagePolicies(orgId)
.Returns(true);
.ManagePolicies(orgId)
.Returns(true);

sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
.Returns((Policy)null);
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
.Returns((Policy)null);

// Act
var result = await sutProvider.Sut.Get(orgId, type);
Expand All @@ -194,11 +236,42 @@ public async Task Get_WhenUserCannotManagePolicies_ThrowsNotFoundException(
{
// Arrange
sutProvider.GetDependency<ICurrentContext>()
.ManagePolicies(orgId)
.Returns(false);
.ManagePolicies(orgId)
.Returns(false);

// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.Get(orgId, type));
}

[Theory]
[BitAutoData]
public async Task GetByTokenAsync_WhenOrganizationUseUsePoliciesIsFalse_Jimmy(
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid organizationUserId, string token, string email,
Organization organization)
{
// Arrange
organization.UsePolicies = false;

var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);


// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
}

[Theory]
[BitAutoData]
public async Task GetByTokenAsync_WhenOrganizationIsNull_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid organizationUserId, string token, string email)
{
// Arrange
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns((Organization)null);

// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
}
}

0 comments on commit bcffef5

Please sign in to comment.