From 101cd940e903c8ce0e3f268947780dcbef71d7aa Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Mon, 3 Feb 2025 16:20:48 -0500 Subject: [PATCH] [PM-14417] Create admin TaskService (#12891) * [PM-14416] Add initial SecurityTask models and enums * [PM-14416] Add support for PATCH request method and 204 No Content response * [PM-14416] Add initial task service abstraction * [PM-14416] Add SecurityTask state/key definitions * [PM-14416] Add DefaultTaskService implementation * [PM-14416] Add DefaultTaskService tests * [PM-14416] Add better null checking to new models * [PM-14416] Improve null value filtering for task service * initial commit, added absract file and implementation file * Added abstract method and implemented bulk create method * Implemented get all api * created spec file * Fixed references * Added exports * Added tests * fixed suggestions * fixed test --------- Co-authored-by: Shane Melton --- .../abstractions/admin-task.abstraction.ts | 34 ++++++++++ .../default-admin-task.service.spec.ts | 65 +++++++++++++++++++ .../services/default-admin-task.service.ts | 48 ++++++++++++++ libs/vault/src/tasks/models/index.ts | 2 + 4 files changed, 149 insertions(+) create mode 100644 bitwarden_license/bit-web/src/app/vault/services/abstractions/admin-task.abstraction.ts create mode 100644 bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.spec.ts create mode 100644 bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.ts diff --git a/bitwarden_license/bit-web/src/app/vault/services/abstractions/admin-task.abstraction.ts b/bitwarden_license/bit-web/src/app/vault/services/abstractions/admin-task.abstraction.ts new file mode 100644 index 00000000000..014c9daa783 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/vault/services/abstractions/admin-task.abstraction.ts @@ -0,0 +1,34 @@ +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; +import { SecurityTask, SecurityTaskStatus, SecurityTaskType } from "@bitwarden/vault"; + +/** + * Request type for creating tasks. + * @property cipherId - Optional. The ID of the cipher to create the task for. + * @property type - The type of task to create. Currently defined as "updateAtRiskCredential". + */ +export type CreateTasksRequest = Readonly<{ + cipherId?: CipherId; + type: SecurityTaskType.UpdateAtRiskCredential; +}>; + +export abstract class AdminTaskService { + /** + * Retrieves all tasks for a given organization. + * @param organizationId - The ID of the organization to retrieve tasks for. + * @param status - Optional. The status of the tasks to retrieve. + */ + abstract getAllTasks( + organizationId: OrganizationId, + status?: SecurityTaskStatus | undefined, + ): Promise; + + /** + * Creates multiple tasks for a given organization and sends out notifications to applicable users. + * @param organizationId - The ID of the organization to create tasks for. + * @param tasks - The tasks to create. + */ + abstract bulkCreateTasks( + organizationId: OrganizationId, + tasks: CreateTasksRequest[], + ): Promise; +} diff --git a/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.spec.ts b/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.spec.ts new file mode 100644 index 00000000000..d6a686a071a --- /dev/null +++ b/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.spec.ts @@ -0,0 +1,65 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; +import { SecurityTaskStatus, SecurityTaskType } from "@bitwarden/vault"; + +import { CreateTasksRequest } from "./abstractions/admin-task.abstraction"; +import { DefaultAdminTaskService } from "./default-admin-task.service"; + +describe("DefaultAdminTaskService", () => { + let defaultAdminTaskService: DefaultAdminTaskService; + let apiService: MockProxy; + + beforeEach(() => { + apiService = mock(); + defaultAdminTaskService = new DefaultAdminTaskService(apiService); + }); + + describe("getAllTasks", () => { + it("should call the api service with the correct parameters with status", async () => { + const organizationId = "orgId" as OrganizationId; + const status = SecurityTaskStatus.Pending; + const expectedUrl = `/tasks/organization?organizationId=${organizationId}&status=0`; + + await defaultAdminTaskService.getAllTasks(organizationId, status); + + expect(apiService.send).toHaveBeenCalledWith("GET", expectedUrl, null, true, true); + }); + + it("should call the api service with the correct parameters without status", async () => { + const organizationId = "orgId" as OrganizationId; + const expectedUrl = `/tasks/organization?organizationId=${organizationId}`; + + await defaultAdminTaskService.getAllTasks(organizationId); + + expect(apiService.send).toHaveBeenCalledWith("GET", expectedUrl, null, true, true); + }); + }); + + describe("bulkCreateTasks", () => { + it("should call the api service with the correct parameters", async () => { + const organizationId = "orgId" as OrganizationId; + const tasks: CreateTasksRequest[] = [ + { + cipherId: "cipherId-1" as CipherId, + type: SecurityTaskType.UpdateAtRiskCredential, + }, + { + cipherId: "cipherId-2" as CipherId, + type: SecurityTaskType.UpdateAtRiskCredential, + }, + ]; + + await defaultAdminTaskService.bulkCreateTasks(organizationId, tasks); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + `/tasks/${organizationId}/bulk-create`, + tasks, + true, + true, + ); + }); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.ts b/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.ts new file mode 100644 index 00000000000..442fde9dbf6 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { + SecurityTask, + SecurityTaskData, + SecurityTaskResponse, + SecurityTaskStatus, +} from "@bitwarden/vault"; + +import { AdminTaskService, CreateTasksRequest } from "./abstractions/admin-task.abstraction"; + +@Injectable() +export class DefaultAdminTaskService implements AdminTaskService { + constructor(private apiService: ApiService) {} + + async getAllTasks( + organizationId: OrganizationId, + status?: SecurityTaskStatus | undefined, + ): Promise { + const queryParams = new URLSearchParams(); + + queryParams.append("organizationId", organizationId); + if (status !== undefined) { + queryParams.append("status", status.toString()); + } + + const r = await this.apiService.send( + "GET", + `/tasks/organization?${queryParams.toString()}`, + null, + true, + true, + ); + const response = new ListResponse(r, SecurityTaskResponse); + + return response.data.map((d) => new SecurityTask(new SecurityTaskData(d))); + } + + async bulkCreateTasks( + organizationId: OrganizationId, + tasks: CreateTasksRequest[], + ): Promise { + await this.apiService.send("POST", `/tasks/${organizationId}/bulk-create`, tasks, true, true); + } +} diff --git a/libs/vault/src/tasks/models/index.ts b/libs/vault/src/tasks/models/index.ts index 7e31c136629..e9b6e3352af 100644 --- a/libs/vault/src/tasks/models/index.ts +++ b/libs/vault/src/tasks/models/index.ts @@ -1 +1,3 @@ export * from "./security-task"; +export * from "./security-task.data"; +export * from "./security-task.response";