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-14381] Add POST /tasks/bulk-create endpoint #5188

Open
wants to merge 51 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
69da981
[PM-14378] Introduce GetCipherPermissionsForOrganization query for Daโ€ฆ
shane-melton Dec 6, 2024
c85a930
[PM-14378] Introduce GetCipherPermissionsForOrganization method for Eโ€ฆ
shane-melton Dec 6, 2024
db5bd64
[PM-14378] Add integration tests for new repository method
shane-melton Dec 6, 2024
a03ddee
[PM-14378] Introduce IGetCipherPermissionsForUserQuery CQRS query
shane-melton Dec 6, 2024
532dd07
[PM-14378] Introduce SecurityTaskOperationRequirement
shane-melton Dec 6, 2024
5c38328
[PM-14378] Introduce SecurityTaskAuthorizationHandler.cs
shane-melton Dec 6, 2024
19a1814
[PM-14378] Introduce SecurityTaskOrganizationAuthorizationHandler.cs
shane-melton Dec 6, 2024
df3e424
[PM-14378] Register new authorization handlers
shane-melton Dec 6, 2024
78ea8b5
[PM-14378] Formatting
shane-melton Dec 6, 2024
4d80238
[PM-14378] Add unit tests for GetCipherPermissionsForUserQuery
shane-melton Dec 11, 2024
b40d144
[PM-15378] Cleanup SecurityTaskAuthorizationHandler and add tests
shane-melton Dec 12, 2024
ca15550
[PM-14378] Add tests for SecurityTaskOrganizationAuthorizationHandler
shane-melton Dec 12, 2024
bcf3210
[PM-14378] Formatting
shane-melton Dec 12, 2024
021634c
[PM-14378] Update date in migration file
shane-melton Dec 12, 2024
d222923
Merge branch 'main' into vault/pm-14378/security-task-auth-handler
shane-melton Dec 12, 2024
6eb33cf
Merge branch 'main' into vault/pm-14378/security-task-auth-handler
shane-melton Dec 12, 2024
b10df9b
[PM-14378] Add missing awaits
shane-melton Dec 13, 2024
3a04e88
Added bulk create request model
gbubemismith Dec 13, 2024
9cef90b
Merge branch 'main' into vault/pm-14378/security-task-auth-handler
shane-melton Dec 16, 2024
d00b25b
Created sproc to create bulk security tasks
gbubemismith Dec 20, 2024
3198ea7
Renamed tasks to SecurityTasksInput
gbubemismith Dec 20, 2024
4fabb8a
Added create many implementation for sqlserver and ef core
gbubemismith Dec 20, 2024
ef37e9d
removed trailing comma
gbubemismith Dec 23, 2024
0edb923
created ef implementatin for create many and added integration test
gbubemismith Dec 23, 2024
2684901
Refactored request model
gbubemismith Dec 23, 2024
0adb3e3
Refactored request model
gbubemismith Dec 23, 2024
ba848ce
created create many tasks command interface and class
gbubemismith Dec 23, 2024
74b25da
Merge commit 'refs/pull/5039/head' of https://github.com/bitwarden/seโ€ฆ
gbubemismith Dec 23, 2024
7738ce4
added security authorization handler work temp
gbubemismith Dec 23, 2024
67f3215
Added the implementation for the create manys tasks command
gbubemismith Dec 23, 2024
d2bed09
Added comment
gbubemismith Dec 23, 2024
6aaba7e
Changed return to return list of created security tasks
gbubemismith Dec 23, 2024
04c7c56
Registered command
gbubemismith Dec 23, 2024
6df6132
Completed bulk create action
gbubemismith Dec 23, 2024
e3352f4
Added unit tests for the command
gbubemismith Dec 23, 2024
e7abb09
removed hard coded table name
gbubemismith Dec 23, 2024
8868b3c
Fixed lint issue
gbubemismith Dec 23, 2024
72b121d
Added JsonConverter attribute to allow enum value to be passed as string
gbubemismith Dec 27, 2024
fff7707
Fixed merge conflicts
gbubemismith Jan 9, 2025
770fa34
Removed makshift security task operations
gbubemismith Jan 9, 2025
239a191
Fixed references
gbubemismith Jan 9, 2025
714666b
Removed old migration
gbubemismith Jan 9, 2025
a3ffb5f
Rebased
gbubemismith Jan 9, 2025
a99afb6
[PM-14378] Introduce GetCipherPermissionsForOrganization query for Daโ€ฆ
shane-melton Dec 6, 2024
4cfbd3d
[PM-14378] Introduce GetCipherPermissionsForOrganization method for Eโ€ฆ
shane-melton Dec 6, 2024
2481ca1
[PM-14378] Add unit tests for GetCipherPermissionsForUserQuery
shane-melton Dec 11, 2024
b4e890a
Completed bulk create action
gbubemismith Dec 23, 2024
c1c7f3f
Fixed conflicts
gbubemismith Jan 9, 2025
6740471
Merge branch 'main' into vault/PM-14381
gbubemismith Jan 9, 2025
897285f
bumped migration version
gbubemismith Jan 9, 2025
e6c7722
Fixed lint issue
gbubemismith Jan 9, 2025
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
21 changes: 20 additions & 1 deletion src/Api/Vault/Controllers/SecurityTaskController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
๏ปฟusing Bit.Api.Models.Response;
using Bit.Api.Vault.Models.Request;
using Bit.Api.Vault.Models.Response;
using Bit.Core;
using Bit.Core.Services;
Expand All @@ -20,17 +21,20 @@
private readonly IGetTaskDetailsForUserQuery _getTaskDetailsForUserQuery;
private readonly IMarkTaskAsCompleteCommand _markTaskAsCompleteCommand;
private readonly IGetTasksForOrganizationQuery _getTasksForOrganizationQuery;
private readonly ICreateManyTasksCommand _createManyTasksCommand;

public SecurityTaskController(
IUserService userService,
IGetTaskDetailsForUserQuery getTaskDetailsForUserQuery,
IMarkTaskAsCompleteCommand markTaskAsCompleteCommand,
IGetTasksForOrganizationQuery getTasksForOrganizationQuery)
IGetTasksForOrganizationQuery getTasksForOrganizationQuery,
ICreateManyTasksCommand createManyTasksCommand)

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

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Controllers/SecurityTaskController.cs#L30-L31

Added lines #L30 - L31 were not covered by tests
{
_userService = userService;
_getTaskDetailsForUserQuery = getTaskDetailsForUserQuery;
_markTaskAsCompleteCommand = markTaskAsCompleteCommand;
_getTasksForOrganizationQuery = getTasksForOrganizationQuery;
_createManyTasksCommand = createManyTasksCommand;

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

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Controllers/SecurityTaskController.cs#L37

Added line #L37 was not covered by tests
}

/// <summary>
Expand Down Expand Up @@ -71,4 +75,19 @@
var response = securityTasks.Select(x => new SecurityTasksResponseModel(x)).ToList();
return new ListResponseModel<SecurityTasksResponseModel>(response);
}

/// <summary>
/// Bulk create security tasks for an organization.
/// </summary>
/// <param name="orgId"></param>
/// <param name="model"></param>
/// <returns>A list response model containing the security tasks created for the organization.</returns>
[HttpPost("{orgId:guid}/bulk-create")]
public async Task<ListResponseModel<SecurityTasksResponseModel>> BulkCreateTasks(Guid orgId,
[FromBody] BulkCreateSecurityTasksRequestModel model)
{
var securityTasks = await _createManyTasksCommand.CreateAsync(orgId, model.Tasks);
var response = securityTasks.Select(x => new SecurityTasksResponseModel(x)).ToList();
return new ListResponseModel<SecurityTasksResponseModel>(response);
}

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

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Controllers/SecurityTaskController.cs#L88-L92

Added lines #L88 - L92 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
๏ปฟusing Bit.Core.Vault.Models.Api;

namespace Bit.Api.Vault.Models.Request;

public class BulkCreateSecurityTasksRequestModel
{
public IEnumerable<SecurityTaskCreateRequest> Tasks { get; set; }

Check warning on line 7 in src/Api/Vault/Models/Request/BulkCreateSecurityTasksRequestModel.cs

View check run for this annotation

Codecov / codecov/patch

src/Api/Vault/Models/Request/BulkCreateSecurityTasksRequestModel.cs#L7

Added line #L7 was not covered by tests
}
16 changes: 0 additions & 16 deletions src/Core/Vault/Authorization/SecurityTaskOperations.cs

This file was deleted.

65 changes: 65 additions & 0 deletions src/Core/Vault/Commands/CreateManyTasksCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
๏ปฟusing Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using Bit.Core.Vault.Authorization.SecurityTasks;
using Bit.Core.Vault.Commands.Interfaces;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;
using Bit.Core.Vault.Models.Api;
using Bit.Core.Vault.Repositories;
using Microsoft.AspNetCore.Authorization;

namespace Bit.Core.Vault.Commands;

public class CreateManyTasksCommand : ICreateManyTasksCommand
{
private readonly IAuthorizationService _authorizationService;
private readonly ICurrentContext _currentContext;
private readonly ISecurityTaskRepository _securityTaskRepository;

public CreateManyTasksCommand(
ISecurityTaskRepository securityTaskRepository,
IAuthorizationService authorizationService,
ICurrentContext currentContext)
{
_securityTaskRepository = securityTaskRepository;
_authorizationService = authorizationService;
_currentContext = currentContext;
}

/// <inheritdoc />
public async Task<ICollection<SecurityTask>> CreateAsync(Guid organizationId,
IEnumerable<SecurityTaskCreateRequest> tasks)
{
if (!_currentContext.UserId.HasValue)
{
throw new NotFoundException();
}

var tasksList = tasks?.ToList();

if (tasksList is null || tasksList.Count == 0)
{
throw new BadRequestException("No tasks provided.");
}

var securityTasks = tasksList.Select(t => new SecurityTask
{
OrganizationId = organizationId,
CipherId = t.CipherId,
Type = t.Type,
Status = SecurityTaskStatus.Pending
}).ToList();

// Verify authorization for each task
foreach (var task in securityTasks)
{
await _authorizationService.AuthorizeOrThrowAsync(
_currentContext.HttpContext.User,
task,
SecurityTaskOperations.Create);
}

return await _securityTaskRepository.CreateManyAsync(securityTasks);
}
}
17 changes: 17 additions & 0 deletions src/Core/Vault/Commands/Interfaces/ICreateManyTasksCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
๏ปฟusing Bit.Core.Vault.Entities;
using Bit.Core.Vault.Models.Api;

namespace Bit.Core.Vault.Commands.Interfaces;

public interface ICreateManyTasksCommand
{
/// <summary>
/// Creates multiple security tasks for an organization.
/// Each task must be authorized and the user must have the Create permission
/// and associated ciphers must belong to the organization.
/// </summary>
/// <param name="organizationId">The </param>
/// <param name="tasks"></param>
/// <returns>Collection of created security tasks</returns>
Task<ICollection<SecurityTask>> CreateAsync(Guid organizationId, IEnumerable<SecurityTaskCreateRequest> tasks);
}
2 changes: 1 addition & 1 deletion src/Core/Vault/Commands/MarkTaskAsCompletedCommand.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
๏ปฟusing Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using Bit.Core.Vault.Authorization;
using Bit.Core.Vault.Authorization.SecurityTasks;
using Bit.Core.Vault.Commands.Interfaces;
using Bit.Core.Vault.Enums;
using Bit.Core.Vault.Repositories;
Expand Down
11 changes: 11 additions & 0 deletions src/Core/Vault/Models/Api/SecurityTaskCreateRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
๏ปฟusing System.Text.Json.Serialization;
using Bit.Core.Vault.Enums;

namespace Bit.Core.Vault.Models.Api;

public class SecurityTaskCreateRequest
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public SecurityTaskType Type { get; set; }
public Guid CipherId { get; set; }
}
7 changes: 7 additions & 0 deletions src/Core/Vault/Repositories/ISecurityTaskRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@ public interface ISecurityTaskRepository : IRepository<SecurityTask, Guid>
/// <param name="status">Optional filter for task status. If not provided, returns tasks of all statuses</param>
/// <returns></returns>
Task<ICollection<SecurityTask>> GetManyByOrganizationIdStatusAsync(Guid organizationId, SecurityTaskStatus? status = null);

/// <summary>
/// Creates bulk security tasks for an organization.
/// </summary>
/// <param name="tasks">Collection of tasks to create</param>
/// <returns>Collection of created security tasks</returns>
Task<ICollection<SecurityTask>> CreateManyAsync(IEnumerable<SecurityTask> tasks);
}
1 change: 1 addition & 0 deletions src/Core/Vault/VaultServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ private static void AddVaultQueries(this IServiceCollection services)
services.AddScoped<IGetTaskDetailsForUserQuery, GetTaskDetailsForUserQuery>();
services.AddScoped<IMarkTaskAsCompleteCommand, MarkTaskAsCompletedCommand>();
services.AddScoped<IGetCipherPermissionsForUserQuery, GetCipherPermissionsForUserQuery>();
services.AddScoped<ICreateManyTasksCommand, CreateManyTasksCommand>();
}
}
22 changes: 21 additions & 1 deletion src/Infrastructure.Dapper/DapperHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Reflection;
using Bit.Core.Entities;
using Bit.Core.Models.Data;
using Bit.Core.Vault.Entities;
using Dapper;

#nullable enable
Expand Down Expand Up @@ -81,7 +82,7 @@
return true;
}

// Value type properties will implicitly box into the object so
// Value type properties will implicitly box into the object so
// we need to look past the Convert expression
// i => (System.Object?)i.Id
if (
Expand Down Expand Up @@ -153,6 +154,18 @@
]
);

private static readonly DataTableBuilder<SecurityTask> _securityTaskTypeTableBuilder = new(
[
st => st.Id,
st => st.OrganizationId,
st => st.CipherId,
st => st.Type,
st => st.Status,
st => st.CreationDate,
st => st.RevisionDate,
]
);

Check warning on line 167 in src/Infrastructure.Dapper/DapperHelpers.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/DapperHelpers.cs#L157-L167

Added lines #L157 - L167 were not covered by tests

public static DataTable ToGuidIdArrayTVP(this IEnumerable<Guid> ids)
{
return ids.ToArrayTVP("GuidId");
Expand Down Expand Up @@ -212,6 +225,13 @@
return table;
}

public static DataTable ToTvp(this IEnumerable<SecurityTask> securityTasks)
{

Check warning on line 229 in src/Infrastructure.Dapper/DapperHelpers.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/DapperHelpers.cs#L229

Added line #L229 was not covered by tests
var table = _securityTaskTypeTableBuilder.Build(securityTasks ?? []);
table.SetTypeName("[dbo].[SecurityTaskType]");
return table;
}

Check warning on line 233 in src/Infrastructure.Dapper/DapperHelpers.cs

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/DapperHelpers.cs#L231-L233

Added lines #L231 - L233 were not covered by tests

public static DataTable BuildTable<T>(this IEnumerable<T> entities, DataTable table,
List<(string name, Type type, Func<T, object?> getter)> columnData)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,29 @@

return results.ToList();
}

/// <inheritdoc />
public async Task<ICollection<SecurityTask>> CreateManyAsync(IEnumerable<SecurityTask> tasks)
{

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L52 was not covered by tests
var tasksList = tasks?.ToList();
if (tasksList is null || tasksList.Count == 0)
{
return Array.Empty<SecurityTask>();

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs#L55-L56

Added lines #L55 - L56 were not covered by tests
}

foreach (var task in tasksList)
{
task.SetNewId();
}

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs#L60-L62

Added lines #L60 - L62 were not covered by tests

var securityTasksTvp = tasksList.ToTvp();
await using var connection = new SqlConnection(ConnectionString);

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs#L64-L65

Added lines #L64 - L65 were not covered by tests

var results = await connection.ExecuteAsync(
$"[{Schema}].[{Table}_CreateMany]",
new { SecurityTasksInput = securityTasksTvp },
commandType: CommandType.StoredProcedure);

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs#L67-L70

Added lines #L67 - L70 were not covered by tests

return tasksList;
}

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L73 was not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,28 @@

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

/// <inheritdoc />
public async Task<ICollection<Core.Vault.Entities.SecurityTask>> CreateManyAsync(
IEnumerable<Core.Vault.Entities.SecurityTask> tasks)
{

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs#L59

Added line #L59 was not covered by tests
var tasksList = tasks?.ToList();
if (tasksList is null || tasksList.Count == 0)
{
return Array.Empty<SecurityTask>();

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs#L62-L63

Added lines #L62 - L63 were not covered by tests
}

foreach (var task in tasksList)
{
task.SetNewId();
}

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs#L67-L69

Added lines #L67 - L69 were not covered by tests

using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var entities = Mapper.Map<List<SecurityTask>>(tasksList);
await dbContext.AddRangeAsync(entities);
await dbContext.SaveChangesAsync();

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs#L71-L75

Added lines #L71 - L75 were not covered by tests

return tasksList;
}

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs#L77-L78

Added lines #L77 - L78 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
CREATE PROCEDURE [dbo].[SecurityTask_CreateMany]
@SecurityTasksInput AS [dbo].[SecurityTaskType] READONLY
AS
BEGIN
SET NOCOUNT ON

INSERT INTO [dbo].[SecurityTask]
(
[Id],
[OrganizationId],
[CipherId],
[Type],
[Status],
[CreationDate],
[RevisionDate]
)
SELECT
ST.[Id],
ST.[OrganizationId],
ST.[CipherId],
ST.[Type],
ST.[Status],
ST.[CreationDate],
ST.[RevisionDate]
FROM
@SecurityTasksInput ST
END
9 changes: 9 additions & 0 deletions src/Sql/dbo/User Defined Types/SecurityTaskType.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CREATE TYPE [dbo].[SecurityTaskType] AS TABLE(
[Id] UNIQUEIDENTIFIER NOT NULL,
Copy link
Contributor

Choose a reason for hiding this comment

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

We are trying to avoid custom data types that will need sql changes per schema changes.
The preference is to use the existing generic guidarray for bulk operations
CREATE TYPE [dbo].[GuidIdArray] AS TABLE(
[Id] [uniqueidentifier] NOT NULL
)

[OrganizationId] UNIQUEIDENTIFIER NOT NULL,
[CipherId] UNIQUEIDENTIFIER NOT NULL,
[Type] TINYINT NOT NULL,
[Status] TINYINT NOT NULL,
[CreationDate] DATETIME2(7) NOT NULL,
[RevisionDate] DATETIME2(7) NOT NULL
);
Loading
Loading