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 0000000000..014c9daa78 --- /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 0000000000..d6a686a071 --- /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 0000000000..442fde9dbf --- /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 7e31c13662..e9b6e3352a 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";