Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 14 additions & 1 deletion src/Core/AdminConsole/Repositories/IPolicyRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,29 @@ namespace Bit.Core.AdminConsole.Repositories;
public interface IPolicyRepository : IRepository<Policy, Guid>
{
/// <summary>
/// Gets all policies of a given type for an organization.
/// Gets all policies of a given type for an organization where the user is in the Confirmed status.
/// </summary>
/// <remarks>
/// WARNING: do not use this to enforce policies against a user! It returns raw data and does not take into account
/// various business rules. Use <see cref="IPolicyRequirementQuery"/> instead.
/// </remarks>
Task<Policy?> GetByOrganizationIdTypeAsync(Guid organizationId, PolicyType type);
Task<ICollection<Policy>> GetManyByOrganizationIdAsync(Guid organizationId);

/// <summary>
/// Gets all policies for a user across organizations where the user is in the Confirmed status.
/// </summary>
Task<ICollection<Policy>> GetManyByUserIdAsync(Guid userId);

/// <summary>
/// Gets all policies for a user across organizations where the user is in the Confirmed or Accepted status.
/// </summary>
/// <remarks>
/// WARNING: do not use this to enforce policies against a user! It returns raw data and does not take into account
/// various business rules. Use <see cref="IPolicyRequirementQuery"/> instead.
/// </remarks>
Task<ICollection<Policy>> GetManyConfirmedAcceptedByUserIdAsync(Guid userId);

/// <summary>
/// Retrieves <see cref="OrganizationPolicyDetails"/> of the specified <paramref name="policyType"/>
/// for users in the given organization and for any other organizations those users belong to.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ await stripeAdapter.UpdateSubscriptionScheduleAsync(activeSchedule.Id,
EndDate = phase1.EndDate,
Items = phase1.Items.Select(i => new SubscriptionSchedulePhaseItemOptions
{
Price = i.PriceId, Quantity = i.Quantity
Price = i.PriceId,
Quantity = i.Quantity
}).ToList(),
Discounts = phase1.Discounts?.Select(d =>
new SubscriptionSchedulePhaseDiscountOptions { Coupon = d.CouponId }).ToList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ public async Task<ICollection<Policy>> GetManyByUserIdAsync(Guid userId)
}
}

public async Task<ICollection<Policy>> GetManyConfirmedAcceptedByUserIdAsync(Guid userId)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<Policy>(
$"[{Schema}].[{Table}_ReadConfirmedAcceptedByUserId]",
new { UserId = userId },
commandType: CommandType.StoredProcedure);

return results.ToList();
}
}

public async Task<IEnumerable<OrganizationPolicyDetails>> GetPolicyDetailsByUserIdsAndPolicyType(IEnumerable<Guid> userIds, PolicyType type)
{
await using var connection = new SqlConnection(ConnectionString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ public PolicyRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper
}
}

public async Task<ICollection<AdminConsoleEntities.Policy>> GetManyConfirmedAcceptedByUserIdAsync(Guid userId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);

var query = new PolicyReadByUserIdConfirmedAndAcceptedQuery(userId);
var results = await query.Run(dbContext).ToListAsync();
return Mapper.Map<List<AdminConsoleEntities.Policy>>(results);
}
}

public async Task<IEnumerable<OrganizationPolicyDetails>> GetPolicyDetailsByOrganizationIdAsync(Guid organizationId, PolicyType policyType)
{
using var scope = ServiceScopeFactory.CreateScope();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
ο»Ώusing Bit.Core.Enums;
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
using Bit.Infrastructure.EntityFramework.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories.Queries;

namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories.Queries;

public class PolicyReadByUserIdConfirmedAndAcceptedQuery : IQuery<Policy>
{
private readonly Guid _userId;

public PolicyReadByUserIdConfirmedAndAcceptedQuery(Guid userId)
{
_userId = userId;
}

public IQueryable<Policy> Run(DatabaseContext dbContext)
{
var query = from p in dbContext.Policies
join ou in dbContext.OrganizationUsers
on p.OrganizationId equals ou.OrganizationId
join o in dbContext.Organizations
on ou.OrganizationId equals o.Id
where ou.UserId == _userId &&
(ou.Status == OrganizationUserStatusType.Confirmed ||
ou.Status == OrganizationUserStatusType.Accepted)
select p;

return query;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CREATE PROCEDURE [dbo].[Policy_ReadConfirmedAcceptedByUserId]
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON

SELECT
P.*
FROM
[dbo].[PolicyView] P
INNER JOIN
[dbo].[OrganizationUser] OU ON P.[OrganizationId] = OU.[OrganizationId]
INNER JOIN
[dbo].[Organization] O ON OU.[OrganizationId] = O.[Id]
WHERE
OU.[UserId] = @UserId
AND OU.[Status] IN (1, 2) -- 1 = Accepted, 2 = Confirmed
END
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
ο»Ώusing Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Xunit;

namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.PolicyRepository;

public class GetManyConfirmedAcceptedByUserIdAsyncTests
{
[Theory, DatabaseData]
public async Task ReturnsPolicies_WhenUserIsConfirmed(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository)
{
// Arrange
var user = await userRepository.CreateTestUserAsync();
var organization = await organizationRepository.CreateTestOrganizationAsync();
await organizationUserRepository.CreateConfirmedTestOrganizationUserAsync(organization, user);
var policy = await policyRepository.CreateAsync(new Policy
{
OrganizationId = organization.Id,
Type = PolicyType.TwoFactorAuthentication,
Enabled = true
});

// Act
var results = await policyRepository.GetManyConfirmedAcceptedByUserIdAsync(user.Id);

// Assert
Assert.Contains(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

[Theory, DatabaseData]
public async Task ReturnsPolicies_WhenUserIsAccepted(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository)
{
// Arrange
var user = await userRepository.CreateTestUserAsync();
var organization = await organizationRepository.CreateTestOrganizationAsync();
await organizationUserRepository.CreateAcceptedTestOrganizationUserAsync(organization, user);
var policy = await policyRepository.CreateAsync(new Policy
{
OrganizationId = organization.Id,
Type = PolicyType.TwoFactorAuthentication,
Enabled = true
});

// Act
var results = await policyRepository.GetManyConfirmedAcceptedByUserIdAsync(user.Id);

// Assert
Assert.Contains(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

[Theory, DatabaseData]
public async Task ReturnsPoliciesAcrossMultipleOrganizations_WhenUserIsConfirmedOrAccepted(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository)
{
// Arrange
var user = await userRepository.CreateTestUserAsync();

var confirmedOrg = await organizationRepository.CreateTestOrganizationAsync();
await organizationUserRepository.CreateConfirmedTestOrganizationUserAsync(confirmedOrg, user);
var confirmedPolicy = await policyRepository.CreateAsync(new Policy
{
OrganizationId = confirmedOrg.Id,
Type = PolicyType.TwoFactorAuthentication,
Enabled = true
});

var acceptedOrg = await organizationRepository.CreateTestOrganizationAsync();
await organizationUserRepository.CreateAcceptedTestOrganizationUserAsync(acceptedOrg, user);
var acceptedPolicy = await policyRepository.CreateAsync(new Policy
{
OrganizationId = acceptedOrg.Id,
Type = PolicyType.TwoFactorAuthentication,
Enabled = true
});

// Act
var results = await policyRepository.GetManyConfirmedAcceptedByUserIdAsync(user.Id);

// Assert
Assert.Contains(results, p => p.Id == confirmedPolicy.Id);
Assert.Contains(results, p => p.Id == acceptedPolicy.Id);

// Annul
await organizationRepository.DeleteAsync(confirmedOrg);
await organizationRepository.DeleteAsync(acceptedOrg);
await userRepository.DeleteAsync(user);
}

[Theory, DatabaseData]
public async Task DoesNotReturnPolicies_WhenUserIsInvited(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository)
{
// Arrange
var user = await userRepository.CreateTestUserAsync();
var organization = await organizationRepository.CreateTestOrganizationAsync();
await organizationUserRepository.CreateAsync(new OrganizationUser
{
OrganizationId = organization.Id,
UserId = null,
Email = user.Email,
Status = OrganizationUserStatusType.Invited,
Type = OrganizationUserType.User
});
var policy = await policyRepository.CreateAsync(new Policy
{
OrganizationId = organization.Id,
Type = PolicyType.TwoFactorAuthentication,
Enabled = true
});

// Act
var results = await policyRepository.GetManyConfirmedAcceptedByUserIdAsync(user.Id);

// Assert
Assert.DoesNotContain(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

[Theory, DatabaseData]
public async Task DoesNotReturnPolicies_WhenUserIsRevoked(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository)
{
// Arrange
var user = await userRepository.CreateTestUserAsync();
var organization = await organizationRepository.CreateTestOrganizationAsync();
await organizationUserRepository.CreateRevokedTestOrganizationUserAsync(organization, user);
var policy = await policyRepository.CreateAsync(new Policy
{
OrganizationId = organization.Id,
Type = PolicyType.TwoFactorAuthentication,
Enabled = true
});

// Act
var results = await policyRepository.GetManyConfirmedAcceptedByUserIdAsync(user.Id);

// Assert
Assert.DoesNotContain(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await userRepository.DeleteAsync(user);
}

[Theory, DatabaseData]
public async Task DoesNotReturnPolicies_ForOtherUsers(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository)
{
// Arrange
var targetUser = await userRepository.CreateTestUserAsync();
var otherUser = await userRepository.CreateTestUserAsync();

var organization = await organizationRepository.CreateTestOrganizationAsync();
await organizationUserRepository.CreateConfirmedTestOrganizationUserAsync(organization, otherUser);
var policy = await policyRepository.CreateAsync(new Policy
{
OrganizationId = organization.Id,
Type = PolicyType.TwoFactorAuthentication,
Enabled = true
});

// Act
var results = await policyRepository.GetManyConfirmedAcceptedByUserIdAsync(targetUser.Id);

// Assert
Assert.DoesNotContain(results, p => p.Id == policy.Id);

// Annul
await organizationRepository.DeleteAsync(organization);
await userRepository.DeleteManyAsync([targetUser, otherUser]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
CREATE OR ALTER PROCEDURE [dbo].[Policy_ReadConfirmedAcceptedByUserId]
@UserId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON

SELECT
P.*
FROM
[dbo].[PolicyView] P
INNER JOIN
[dbo].[OrganizationUser] OU ON P.[OrganizationId] = OU.[OrganizationId]
INNER JOIN
[dbo].[Organization] O ON OU.[OrganizationId] = O.[Id]
WHERE
OU.[UserId] = @UserId
AND OU.[Status] IN (1, 2) -- 1 = Accepted, 2 = Confirmed
END
GO
Loading