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

Merged
merged 62 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 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
b067638
Removed complex sql data type in favour of json string
gbubemismith Jan 27, 2025
7e82d62
Merge branch 'main' into vault/PM-14381
gbubemismith Jan 27, 2025
e59946c
Register IGetTasksForOrganizationQuery
gbubemismith Jan 27, 2025
2b4c9a9
Fixed lint issue
gbubemismith Jan 27, 2025
2ef25f5
Removed tasks grouping
gbubemismith Jan 28, 2025
b4ac978
Fixed linting
gbubemismith Jan 28, 2025
a9629c8
Removed unused code
gbubemismith Jan 28, 2025
05561f1
Removed unused code
gbubemismith Jan 28, 2025
6f310ea
Aligned with client change
gbubemismith Jan 29, 2025
806d031
Fixed linting
gbubemismith Jan 29, 2025
528a8f5
Fixed merge conflicts
gbubemismith Jan 29, 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
9 changes: 9 additions & 0 deletions src/Core/Vault/Models/Api/SecurityTaskCreateRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
๏ปฟusing Bit.Core.Vault.Enums;

namespace Bit.Core.Vault.Models.Api;

public class SecurityTaskCreateRequest
{
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 @@ -21,5 +21,6 @@ private static void AddVaultQueries(this IServiceCollection services)
services.AddScoped<IMarkTaskAsCompleteCommand, MarkTaskAsCompletedCommand>();
services.AddScoped<IGetCipherPermissionsForUserQuery, GetCipherPermissionsForUserQuery>();
services.AddScoped<IGetTasksForOrganizationQuery, GetTasksForOrganizationQuery>();
services.AddScoped<ICreateManyTasksCommand, CreateManyTasksCommand>();
}
}
2 changes: 1 addition & 1 deletion src/Infrastructure.Dapper/DapperHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private static bool TryGetPropertyInfo(Expression<Func<T, object?>> columnExpres
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
๏ปฟusing System.Data;
using System.Text.Json;
using Bit.Core.Settings;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;
Expand Down Expand Up @@ -46,4 +47,29 @@

return results.ToList();
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

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

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

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

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs#L61-L63

Added lines #L61 - L63 were not covered by tests

var tasksJson = JsonSerializer.Serialize(tasksList);

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#L65

Added line #L65 was not covered by tests

await using var connection = new SqlConnection(ConnectionString);
await connection.ExecuteAsync(
$"[{Schema}].[{Table}_CreateMany]",
new { SecurityTasksJson = tasksJson },
commandType: CommandType.StoredProcedure);

Check warning on line 71 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-L71

Added lines #L67 - L71 were not covered by tests

return tasksList;
}

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L74 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,55 @@
CREATE PROCEDURE [dbo].[SecurityTask_CreateMany]
@SecurityTasksJson NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON

CREATE TABLE #TempSecurityTasks
(
[Id] UNIQUEIDENTIFIER,
[OrganizationId] UNIQUEIDENTIFIER,
[CipherId] UNIQUEIDENTIFIER,
[Type] TINYINT,
[Status] TINYINT,
[CreationDate] DATETIME2(7),
[RevisionDate] DATETIME2(7)
)

INSERT INTO #TempSecurityTasks
([Id],
[OrganizationId],
[CipherId],
[Type],
[Status],
[CreationDate],
[RevisionDate])
SELECT CAST(JSON_VALUE([value], '$.Id') AS UNIQUEIDENTIFIER),
CAST(JSON_VALUE([value], '$.OrganizationId') AS UNIQUEIDENTIFIER),
CAST(JSON_VALUE([value], '$.CipherId') AS UNIQUEIDENTIFIER),
CAST(JSON_VALUE([value], '$.Type') AS TINYINT),
CAST(JSON_VALUE([value], '$.Status') AS TINYINT),
CAST(JSON_VALUE([value], '$.CreationDate') AS DATETIME2(7)),
CAST(JSON_VALUE([value], '$.RevisionDate') AS DATETIME2(7))
FROM OPENJSON(@SecurityTasksJson) ST
shane-melton marked this conversation as resolved.
Show resolved Hide resolved

INSERT INTO [dbo].[SecurityTask]
(
[Id],
[OrganizationId],
[CipherId],
[Type],
[Status],
[CreationDate],
[RevisionDate]
)
SELECT [Id],
[OrganizationId],
[CipherId],
[Type],
[Status],
[CreationDate],
[RevisionDate]
FROM #TempSecurityTasks

DROP TABLE #TempSecurityTasks
END
Loading
Loading