1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-25 12:15:18 +01:00

[AC-2303] Implement approveAllRequests method (#9031)

* [AC-2302] Move organization-auth-request.service to bit-common folder

* [AC-2302] Rename organization-auth-request.service to organization-auth-request-api.service

* [AC-2302] Move logic from component to organization-auth-request.service

* [AC-2302] Fix import path in OrganizationAuthRequestService

* [AC-2302] Move imports to OrganizationsModule and delete unused CoreOrganizationModule

* [AC-2302] Move the call to get userResetPasswordDetails into OrganizationAuthRequestService

* [AC-2302] Remove @Injectable() and manually configure dependencies

* [AC-2302] Add OrganizationAuthRequestService unit tests first draft

* [AC-2302] Refactor device-approvals.component.ts to remove unused imports

* [AC-2302] Set up jest on bit-common and add unit tests for OrganizationAuthRequestService

* [AC-2302] Add bit-common to jest.config.js

* [AC-2302] Update organizations.module.ts to include safeProviders declared in variable

* [AC-2302] Remove services and views folders from bit-common

* [AC-2302] Define path mapping

* Adjust an import path

The import path of `PendingAuthRequestView` in
`OrganizationAuthRequestApiService` was pointing to the wrong place. I
think this file was just recently moved, and the import didn't get
updated.

* Get paths working

* Fix import

* Update jest config to use ts-jest adn jsdom

* Copy-paste path mappings from bit-web

* Remove unnecessary test setup file

* Undo unnecessary change

* Fix remaining path mappings

* Remove Bitwarden License mapping from OSS code

* Fix bit-web so it uses its own tsconfig

* Fix import path

* Remove web-bit entrypoint from OSS tsconfig

* Make DeviceApprovalsComponent standalone

* Remove organization-auth-request-api.service export

* Add BulkApproveAuthRequestsRequest class for bulk approval of authentication requests

* Add api call for device bulk approvals

* Add bulk device approval to OrganizationAuthRequestService

* Add unit tests for bulk device approval method

* Remove OrganizationsRoutingModule from DeviceApprovalsComponent imports

* Remove CoreOrganizationModule from OrganizationsModule imports

* Remove NoItemsModule from OrganizationsModule imports

* Get keys for each item to approve

* Update approvePendingRequests unit test

* Use ApiService from JslibServicesModule

* Update providers in device-approvals.component.ts

* Add method to retrieve reset password details for multiple organization users

* Add organizationUserId property to OrganizationUserResetPasswordDetailsResponse class

* Use method to retrieve reset password details for multiple organization users

* Rename ResetPasswordDetails to AccountRecoveryDetails

* Update OrganizationAuthRequestService to use getManyOrganizationUserAccountRecoveryDetails

* Update AdminAuthRequestUpdateWithIdRequest property names and imports

* Refactor bulk approval functionality in organization auth requests

* Rename update request AdminAuthRequestUpdateWithIdRequest to OrganizationAuthRequestUpdateRequest

* Update organization-auth-request.service.spec.ts to use bulkUpdatePendingRequests method

---------

Co-authored-by: Addison Beck <hello@addisonbeck.com>
Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
Rui Tomé 2024-05-24 15:48:47 +01:00 committed by GitHub
parent f3b82c30f2
commit a49e7bb35f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 0 deletions

View File

@ -4,6 +4,7 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { AdminAuthRequestUpdateRequest } from "./admin-auth-request-update.request"; import { AdminAuthRequestUpdateRequest } from "./admin-auth-request-update.request";
import { BulkDenyAuthRequestsRequest } from "./bulk-deny-auth-requests.request"; import { BulkDenyAuthRequestsRequest } from "./bulk-deny-auth-requests.request";
import { OrganizationAuthRequestUpdateRequest } from "./organization-auth-request-update.request";
import { PendingAuthRequestView } from "./pending-auth-request.view"; import { PendingAuthRequestView } from "./pending-auth-request.view";
import { PendingOrganizationAuthRequestResponse } from "./pending-organization-auth-request.response"; import { PendingOrganizationAuthRequestResponse } from "./pending-organization-auth-request.response";
@ -34,6 +35,19 @@ export class OrganizationAuthRequestApiService {
); );
} }
async bulkUpdatePendingRequests(
organizationId: string,
items: OrganizationAuthRequestUpdateRequest[],
): Promise<void> {
await this.apiService.send(
"POST",
`/organizations/${organizationId}/auth-requests`,
items,
true,
false,
);
}
async approvePendingRequest( async approvePendingRequest(
organizationId: string, organizationId: string,
requestId: string, requestId: string,

View File

@ -0,0 +1,7 @@
export class OrganizationAuthRequestUpdateRequest {
constructor(
public id: string,
public approved: boolean,
public key?: string,
) {}
}

View File

@ -2,10 +2,12 @@ import { MockProxy, mock } from "jest-mock-extended";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; import { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service";
import { OrganizationAuthRequestUpdateRequest } from "./organization-auth-request-update.request";
import { OrganizationAuthRequestService } from "./organization-auth-request.service"; import { OrganizationAuthRequestService } from "./organization-auth-request.service";
import { PendingAuthRequestView } from "./pending-auth-request.view"; import { PendingAuthRequestView } from "./pending-auth-request.view";
@ -92,6 +94,55 @@ describe("OrganizationAuthRequestService", () => {
}); });
}); });
describe("approvePendingRequests", () => {
it("should approve the specified pending auth requests", async () => {
jest.spyOn(organizationAuthRequestApiService, "bulkUpdatePendingRequests");
const organizationId = "organizationId";
const organizationUserResetPasswordDetailsResponse = new ListResponse(
{
Data: [
{
organizationUserId: "organizationUserId1",
resetPasswordKey: "resetPasswordKey",
encryptedPrivateKey: "encryptedPrivateKey",
},
],
},
OrganizationUserResetPasswordDetailsResponse,
);
organizationUserService.getManyOrganizationUserAccountRecoveryDetails.mockResolvedValueOnce(
organizationUserResetPasswordDetailsResponse,
);
const encryptedUserKey = new EncString("encryptedUserKey");
cryptoService.rsaDecrypt.mockResolvedValue(new Uint8Array(32));
cryptoService.rsaEncrypt.mockResolvedValue(encryptedUserKey);
const mockPendingAuthRequest = new PendingAuthRequestView();
mockPendingAuthRequest.id = "requestId1";
mockPendingAuthRequest.organizationUserId = "organizationUserId1";
mockPendingAuthRequest.publicKey = "publicKey1";
await organizationAuthRequestService.approvePendingRequests(organizationId, [
mockPendingAuthRequest,
]);
expect(organizationAuthRequestApiService.bulkUpdatePendingRequests).toHaveBeenCalledWith(
organizationId,
[
new OrganizationAuthRequestUpdateRequest(
"requestId1",
true,
encryptedUserKey.encryptedString,
),
],
);
});
});
describe("approvePendingRequest", () => { describe("approvePendingRequest", () => {
it("should approve the specified pending auth request", async () => { it("should approve the specified pending auth request", async () => {
jest.spyOn(organizationAuthRequestApiService, "approvePendingRequest"); jest.spyOn(organizationAuthRequestApiService, "approvePendingRequest");

View File

@ -6,6 +6,7 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service";
import { OrganizationAuthRequestUpdateRequest } from "./organization-auth-request-update.request";
import { PendingAuthRequestView } from "./pending-auth-request.view"; import { PendingAuthRequestView } from "./pending-auth-request.view";
export class OrganizationAuthRequestService { export class OrganizationAuthRequestService {
@ -23,6 +24,42 @@ export class OrganizationAuthRequestService {
await this.organizationAuthRequestApiService.denyPendingRequests(organizationId, ...requestIds); await this.organizationAuthRequestApiService.denyPendingRequests(organizationId, ...requestIds);
} }
async approvePendingRequests(
organizationId: string,
authRequests: PendingAuthRequestView[],
): Promise<void> {
const organizationUserIds = authRequests.map((r) => r.organizationUserId);
const details =
await this.organizationUserService.getManyOrganizationUserAccountRecoveryDetails(
organizationId,
organizationUserIds,
);
if (
details == null ||
details.data.length == 0 ||
details.data.some((d) => d.resetPasswordKey == null)
) {
throw new Error(
"All users must be enrolled in account recovery (password reset) in order for the requests to be approved.",
);
}
const requestsToApprove = await Promise.all(
authRequests.map(async (r) => {
const detail = details.data.find((d) => d.organizationUserId === r.organizationUserId);
const encryptedKey = await this.getEncryptedUserKey(organizationId, r.publicKey, detail);
return new OrganizationAuthRequestUpdateRequest(r.id, true, encryptedKey.encryptedString);
}),
);
await this.organizationAuthRequestApiService.bulkUpdatePendingRequests(
organizationId,
requestsToApprove,
);
}
async approvePendingRequest(organizationId: string, authRequest: PendingAuthRequestView) { async approvePendingRequest(organizationId: string, authRequest: PendingAuthRequestView) {
const details = await this.organizationUserService.getOrganizationUserResetPasswordDetails( const details = await this.organizationUserService.getOrganizationUserResetPasswordDetails(
organizationId, organizationId,