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-14376] Add GET tasks endpoint #5089

Merged
merged 20 commits into from
Dec 12, 2024
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
40 changes: 40 additions & 0 deletions src/Api/Vault/Controllers/SecurityTaskController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Bit.Api.Models.Response;
using Bit.Api.Vault.Models.Response;
using Bit.Core;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Vault.Enums;
using Bit.Core.Vault.Queries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Bit.Api.Vault.Controllers;

[Route("tasks")]
[Authorize("Application")]
[RequireFeature(FeatureFlagKeys.SecurityTasks)]
public class SecurityTaskController : Controller
{
private readonly IUserService _userService;
private readonly IGetTaskDetailsForUserQuery _getTaskDetailsForUserQuery;

public SecurityTaskController(IUserService userService, IGetTaskDetailsForUserQuery getTaskDetailsForUserQuery)
{
_userService = userService;
_getTaskDetailsForUserQuery = getTaskDetailsForUserQuery;
}

Check warning on line 25 in src/Api/Vault/Controllers/SecurityTaskController.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Controllers/SecurityTaskController.cs#L21-L25

Added lines #L21 - L25 were not covered by tests

/// <summary>
/// Retrieves security tasks for the current user.
/// </summary>
/// <param name="status">Optional filter for task status. If not provided returns tasks of all statuses.</param>
/// <returns>A list response model containing the security tasks for the user.</returns>
[HttpGet("")]
public async Task<ListResponseModel<SecurityTasksResponseModel>> Get([FromQuery] SecurityTaskStatus? status)
{
var userId = _userService.GetProperUserId(User).Value;
var securityTasks = await _getTaskDetailsForUserQuery.GetTaskDetailsForUserAsync(userId, status);
var response = securityTasks.Select(x => new SecurityTasksResponseModel(x)).ToList();
return new ListResponseModel<SecurityTasksResponseModel>(response);
}

Check warning on line 39 in src/Api/Vault/Controllers/SecurityTaskController.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Controllers/SecurityTaskController.cs#L34-L39

Added lines #L34 - L39 were not covered by tests
}
30 changes: 30 additions & 0 deletions src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Bit.Core.Models.Api;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;

namespace Bit.Api.Vault.Models.Response;

public class SecurityTasksResponseModel : ResponseModel
{
public SecurityTasksResponseModel(SecurityTask securityTask, string obj = "securityTask")
: base(obj)
{
ArgumentNullException.ThrowIfNull(securityTask);

Check warning on line 12 in src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs#L10-L12

Added lines #L10 - L12 were not covered by tests

Id = securityTask.Id;
OrganizationId = securityTask.OrganizationId;
CipherId = securityTask.CipherId;
Type = securityTask.Type;
Status = securityTask.Status;
CreationDate = securityTask.CreationDate;
RevisionDate = securityTask.RevisionDate;
}

Check warning on line 21 in src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs#L14-L21

Added lines #L14 - L21 were not covered by tests

public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public Guid? CipherId { get; set; }
public SecurityTaskType Type { get; set; }
public SecurityTaskStatus Status { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }

Check warning on line 29 in src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Models/Response/SecurityTasksResponseModel.cs#L23-L29

Added lines #L23 - L29 were not covered by tests
}
13 changes: 13 additions & 0 deletions src/Core/Vault/Queries/GetTaskDetailsForUserQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;
using Bit.Core.Vault.Repositories;

namespace Bit.Core.Vault.Queries;

public class GetTaskDetailsForUserQuery(ISecurityTaskRepository securityTaskRepository) : IGetTaskDetailsForUserQuery

Check warning on line 7 in src/Core/Vault/Queries/GetTaskDetailsForUserQuery.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Vault/Queries/GetTaskDetailsForUserQuery.cs#L7

Added line #L7 was not covered by tests
{
/// <inheritdoc />
public async Task<IEnumerable<SecurityTask>> GetTaskDetailsForUserAsync(Guid userId,
SecurityTaskStatus? status = null)
=> await securityTaskRepository.GetManyByUserIdStatusAsync(userId, status);

Check warning on line 12 in src/Core/Vault/Queries/GetTaskDetailsForUserQuery.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Vault/Queries/GetTaskDetailsForUserQuery.cs#L12

Added line #L12 was not covered by tests
}
15 changes: 15 additions & 0 deletions src/Core/Vault/Queries/IGetTaskDetailsForUserQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;

namespace Bit.Core.Vault.Queries;

public interface IGetTaskDetailsForUserQuery
{
/// <summary>
/// Retrieves security tasks for a user based on their organization and cipher access permissions.
/// </summary>
/// <param name="userId">The Id of the user retrieving tasks</param>
/// <param name="status">Optional filter for task status. If not provided, returns tasks of all statuses</param>
/// <returns>A collection of security tasks</returns>
Task<IEnumerable<SecurityTask>> GetTaskDetailsForUserAsync(Guid userId, SecurityTaskStatus? status = null);
}
9 changes: 8 additions & 1 deletion src/Core/Vault/Repositories/ISecurityTaskRepository.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
using Bit.Core.Repositories;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;

namespace Bit.Core.Vault.Repositories;

public interface ISecurityTaskRepository : IRepository<SecurityTask, Guid>
{

/// <summary>
/// Retrieves security tasks for a user based on their organization and cipher access permissions.
/// </summary>
/// <param name="userId">The Id of the user retrieving tasks</param>
/// <param name="status">Optional filter for task status. If not provided, returns tasks of all statuses</param>
/// <returns></returns>
Task<ICollection<SecurityTask>> GetManyByUserIdStatusAsync(Guid userId, SecurityTaskStatus? status = null);
}
1 change: 1 addition & 0 deletions src/Core/Vault/VaultServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ public static IServiceCollection AddVaultServices(this IServiceCollection servic
private static void AddVaultQueries(this IServiceCollection services)
{
services.AddScoped<IOrganizationCiphersQuery, OrganizationCiphersQuery>();
services.AddScoped<IGetTaskDetailsForUserQuery, GetTaskDetailsForUserQuery>();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using Bit.Core.Settings;
using System.Data;
using Bit.Core.Settings;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;
using Bit.Core.Vault.Repositories;
using Bit.Infrastructure.Dapper.Repositories;
using Dapper;
using Microsoft.Data.SqlClient;

namespace Bit.Infrastructure.Dapper.Vault.Repositories;

Expand All @@ -15,4 +19,17 @@
: base(connectionString, readOnlyConnectionString)
{ }

/// <inheritdoc />
public async Task<ICollection<SecurityTask>> GetManyByUserIdStatusAsync(Guid userId,
SecurityTaskStatus? status = null)
{
await using var connection = new SqlConnection(ConnectionString);

Check warning on line 26 in src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs#L25-L26

Added lines #L25 - L26 were not covered by tests

var results = await connection.QueryAsync<SecurityTask>(
$"[{Schema}].[SecurityTask_ReadByUserIdStatus]",
new { UserId = userId, Status = status },
commandType: CommandType.StoredProcedure);

Check warning on line 31 in src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs#L28-L31

Added lines #L28 - L31 were not covered by tests

return results.ToList();
}

Check warning on line 34 in src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs#L34

Added line #L34 was not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using Bit.Core.Enums;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;
using Bit.Infrastructure.EntityFramework.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories.Queries;

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

public class SecurityTaskReadByUserIdStatusQuery : IQuery<SecurityTask>
{
private readonly Guid _userId;
private readonly SecurityTaskStatus? _status;

public SecurityTaskReadByUserIdStatusQuery(Guid userId, SecurityTaskStatus? status)
{
_userId = userId;
_status = status;
}

Check warning on line 18 in src/Infrastructure.EntityFramework/Vault/Repositories/Queries/SecurityTaskReadByUserIdStatusQuery.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Vault/Repositories/Queries/SecurityTaskReadByUserIdStatusQuery.cs#L14-L18

Added lines #L14 - L18 were not covered by tests

public IQueryable<SecurityTask> Run(DatabaseContext dbContext)
{
var query = from st in dbContext.SecurityTasks

join ou in dbContext.OrganizationUsers
on st.OrganizationId equals ou.OrganizationId

join o in dbContext.Organizations
on st.OrganizationId equals o.Id

join c in dbContext.Ciphers
on st.CipherId equals c.Id into c_g
from c in c_g.DefaultIfEmpty()

join cc in dbContext.CollectionCiphers
on c.Id equals cc.CipherId into cc_g
from cc in cc_g.DefaultIfEmpty()

join cu in dbContext.CollectionUsers
on new { cc.CollectionId, OrganizationUserId = ou.Id } equals
new { cu.CollectionId, cu.OrganizationUserId } into cu_g
from cu in cu_g.DefaultIfEmpty()

join gu in dbContext.GroupUsers
on new { CollectionId = (Guid?)cu.CollectionId, OrganizationUserId = ou.Id } equals
new { CollectionId = (Guid?)null, gu.OrganizationUserId } into gu_g
from gu in gu_g.DefaultIfEmpty()

join cg in dbContext.CollectionGroups
on new { cc.CollectionId, gu.GroupId } equals
new { cg.CollectionId, cg.GroupId } into cg_g
from cg in cg_g.DefaultIfEmpty()

where
ou.UserId == _userId &&
ou.Status == OrganizationUserStatusType.Confirmed &&
o.Enabled &&
(
st.CipherId == null ||
(
c != null &&
(
(cu != null && !cu.ReadOnly) || (cg != null && !cg.ReadOnly && cu == null)
)
)
) &&
(_status == null || st.Status == _status)
group st by new
{
st.Id,
st.OrganizationId,
st.CipherId,
st.Type,
st.Status,
st.CreationDate,
st.RevisionDate
} into g
select new SecurityTask
{
Id = g.Key.Id,
OrganizationId = g.Key.OrganizationId,
CipherId = g.Key.CipherId,
Type = g.Key.Type,
Status = g.Key.Status,
CreationDate = g.Key.CreationDate,
RevisionDate = g.Key.RevisionDate
};

Check warning on line 86 in src/Infrastructure.EntityFramework/Vault/Repositories/Queries/SecurityTaskReadByUserIdStatusQuery.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Vault/Repositories/Queries/SecurityTaskReadByUserIdStatusQuery.cs#L21-L86

Added lines #L21 - L86 were not covered by tests

return query.OrderByDescending(st => st.CreationDate);
}

Check warning on line 89 in src/Infrastructure.EntityFramework/Vault/Repositories/Queries/SecurityTaskReadByUserIdStatusQuery.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Vault/Repositories/Queries/SecurityTaskReadByUserIdStatusQuery.cs#L88-L89

Added lines #L88 - L89 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using AutoMapper;
using Bit.Core.Vault.Enums;
using Bit.Core.Vault.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories;
using Bit.Infrastructure.EntityFramework.Vault.Models;
using Bit.Infrastructure.EntityFramework.Vault.Repositories.Queries;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Bit.Infrastructure.EntityFramework.Vault.Repositories;
Expand All @@ -11,4 +14,15 @@
public SecurityTaskRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
: base(serviceScopeFactory, mapper, (context) => context.SecurityTasks)
{ }

/// <inheritdoc />
public async Task<ICollection<Core.Vault.Entities.SecurityTask>> GetManyByUserIdStatusAsync(Guid userId,
SecurityTaskStatus? status = null)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var query = new SecurityTaskReadByUserIdStatusQuery(userId, status);
var data = await query.Run(dbContext).ToListAsync();
return data;
}

Check warning on line 27 in src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs#L21-L27

Added lines #L21 - L27 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
CREATE PROCEDURE [dbo].[SecurityTask_ReadByUserIdStatus]
@UserId UNIQUEIDENTIFIER,
@Status TINYINT = NULL
AS
BEGIN
SET NOCOUNT ON

SELECT
ST.Id,
ST.OrganizationId,
ST.CipherId,
ST.Type,
ST.Status,
ST.CreationDate,
ST.RevisionDate
FROM
[dbo].[SecurityTaskView] ST
INNER JOIN
[dbo].[OrganizationUserView] OU ON OU.[OrganizationId] = ST.[OrganizationId]
INNER JOIN
[dbo].[Organization] O ON O.[Id] = ST.[OrganizationId]
LEFT JOIN
[dbo].[CipherView] C ON C.[Id] = ST.[CipherId]
LEFT JOIN
[dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id] AND C.[Id] IS NOT NULL
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] AND C.[Id] IS NOT NULL
LEFT JOIN
[dbo].[GroupUser] GU ON GU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] IS NULL AND C.[Id] IS NOT NULL
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = CC.[CollectionId]
WHERE
OU.[UserId] = @UserId
AND OU.[Status] = 2 -- Ensure user is confirmed
AND O.[Enabled] = 1
AND (
ST.[CipherId] IS NULL
OR (
C.[Id] IS NOT NULL
AND (
CU.[ReadOnly] = 0
OR CG.[ReadOnly] = 0
)
)
)
AND ST.[Status] = COALESCE(@Status, ST.[Status])
GROUP BY
ST.Id,
ST.OrganizationId,
ST.CipherId,
ST.Type,
ST.Status,
ST.CreationDate,
ST.RevisionDate
ORDER BY ST.[CreationDate] DESC
END
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Diagnostics.CodeAnalysis;
using Bit.Core.Vault.Entities;

namespace Bit.Infrastructure.IntegrationTest.Comparers;

/// <summary>
/// Determines the equality of two SecurityTask objects.
/// </summary>
public class SecurityTaskComparer : IEqualityComparer<SecurityTask>
{
public bool Equals(SecurityTask x, SecurityTask y)

Check warning on line 11 in test/Infrastructure.IntegrationTest/Comparers/SecurityTaskComparer.cs

View workflow job for this annotation

GitHub Actions / Run tests

Nullability of reference types in type of parameter 'x' of 'bool SecurityTaskComparer.Equals(SecurityTask x, SecurityTask y)' doesn't match implicitly implemented member 'bool IEqualityComparer<SecurityTask>.Equals(SecurityTask? x, SecurityTask? y)' (possibly because of nullability attributes).

Check warning on line 11 in test/Infrastructure.IntegrationTest/Comparers/SecurityTaskComparer.cs

View workflow job for this annotation

GitHub Actions / Run tests

Nullability of reference types in type of parameter 'y' of 'bool SecurityTaskComparer.Equals(SecurityTask x, SecurityTask y)' doesn't match implicitly implemented member 'bool IEqualityComparer<SecurityTask>.Equals(SecurityTask? x, SecurityTask? y)' (possibly because of nullability attributes).
{
return x.Id.Equals(y.Id) &&
x.Type.Equals(y.Type) &&
x.Status.Equals(y.Status);
}

public int GetHashCode([DisallowNull] SecurityTask obj)
{
return base.GetHashCode();
}
}
Loading
Loading