Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions src/Core/AdminConsole/Repositories/IPolicyRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ public interface IPolicyRepository : IRepository<Policy, Guid>
Task<ICollection<Policy>> GetManyByOrganizationIdAsync(Guid organizationId);
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>> GetManyConfirmedAndAcceptedByUserAsync(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 @@ -61,6 +61,19 @@ public async Task<ICollection<Policy>> GetManyByUserIdAsync(Guid userId)
}
}

public async Task<ICollection<Policy>> GetManyConfirmedAndAcceptedByUserAsync(Guid userId)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<Policy>(
$"[{Schema}].[{Table}_ReadByUserIdWithConfirmedAndAccepted]",
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>> GetManyConfirmedAndAcceptedByUserAsync(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_ReadByUserIdWithConfirmedAndAccepted]
@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 GetManyConfirmedAndAcceptedByUserAsyncTests
{
[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.GetManyConfirmedAndAcceptedByUserAsync(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.GetManyConfirmedAndAcceptedByUserAsync(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.GetManyConfirmedAndAcceptedByUserAsync(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.GetManyConfirmedAndAcceptedByUserAsync(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.GetManyConfirmedAndAcceptedByUserAsync(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.GetManyConfirmedAndAcceptedByUserAsync(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,25 @@
IF OBJECT_ID('[dbo].[Policy_ReadByUserIdWithConfirmedAndAccepted]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Policy_ReadByUserIdWithConfirmedAndAccepted]
END
GO

CREATE PROCEDURE [dbo].[Policy_ReadByUserIdWithConfirmedAndAccepted]
@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