mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
PM-15090 - unmark-critical-app (#13015)
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
import { Opaque } from "type-fest";
|
||||
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { BadgeVariant } from "@bitwarden/components";
|
||||
|
||||
@@ -113,3 +116,31 @@ export type AtRiskApplicationDetail = {
|
||||
applicationName: string;
|
||||
atRiskPasswordCount: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request to drop a password health report application
|
||||
* Model is expected by the API endpoint
|
||||
*/
|
||||
export interface PasswordHealthReportApplicationDropRequest {
|
||||
organizationId: OrganizationId;
|
||||
passwordHealthReportApplicationIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from the API after marking an app as critical
|
||||
*/
|
||||
export interface PasswordHealthReportApplicationsResponse {
|
||||
id: PasswordHealthReportApplicationId;
|
||||
organizationId: OrganizationId;
|
||||
uri: string;
|
||||
}
|
||||
/*
|
||||
* Request to save a password health report application
|
||||
* Model is expected by the API endpoint
|
||||
*/
|
||||
export interface PasswordHealthReportApplicationsRequest {
|
||||
organizationId: OrganizationId;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type PasswordHealthReportApplicationId = Opaque<string, "PasswordHealthReportApplicationId">;
|
||||
|
||||
@@ -3,12 +3,14 @@ import { mock } from "jest-mock-extended";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { CriticalAppsApiService } from "./critical-apps-api.service";
|
||||
import {
|
||||
PasswordHealthReportApplicationDropRequest,
|
||||
PasswordHealthReportApplicationId,
|
||||
PasswordHealthReportApplicationsRequest,
|
||||
PasswordHealthReportApplicationsResponse,
|
||||
} from "./critical-apps.service";
|
||||
} from "../models/password-health";
|
||||
|
||||
import { CriticalAppsApiService } from "./critical-apps-api.service";
|
||||
|
||||
describe("CriticalAppsApiService", () => {
|
||||
let service: CriticalAppsApiService;
|
||||
@@ -76,4 +78,24 @@ describe("CriticalAppsApiService", () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should call apiService.send with correct parameters for DropCriticalApp", (done) => {
|
||||
const request: PasswordHealthReportApplicationDropRequest = {
|
||||
organizationId: "org1" as OrganizationId,
|
||||
passwordHealthReportApplicationIds: ["123"],
|
||||
};
|
||||
|
||||
apiService.send.mockReturnValue(Promise.resolve());
|
||||
|
||||
service.dropCriticalApp(request).subscribe(() => {
|
||||
expect(apiService.send).toHaveBeenCalledWith(
|
||||
"DELETE",
|
||||
"/reports/password-health-report-application/",
|
||||
request,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,9 +4,10 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
PasswordHealthReportApplicationDropRequest,
|
||||
PasswordHealthReportApplicationsRequest,
|
||||
PasswordHealthReportApplicationsResponse,
|
||||
} from "./critical-apps.service";
|
||||
} from "../models/password-health";
|
||||
|
||||
export class CriticalAppsApiService {
|
||||
constructor(private apiService: ApiService) {}
|
||||
@@ -36,4 +37,16 @@ export class CriticalAppsApiService {
|
||||
|
||||
return from(dbResponse as Promise<PasswordHealthReportApplicationsResponse[]>);
|
||||
}
|
||||
|
||||
dropCriticalApp(request: PasswordHealthReportApplicationDropRequest): Observable<void> {
|
||||
const dbResponse = this.apiService.send(
|
||||
"DELETE",
|
||||
"/reports/password-health-report-application/",
|
||||
request,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
return from(dbResponse as Promise<void>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,14 @@ import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { CriticalAppsApiService } from "./critical-apps-api.service";
|
||||
import {
|
||||
CriticalAppsService,
|
||||
PasswordHealthReportApplicationId,
|
||||
PasswordHealthReportApplicationsRequest,
|
||||
PasswordHealthReportApplicationsResponse,
|
||||
} from "./critical-apps.service";
|
||||
} from "../models/password-health";
|
||||
|
||||
import { CriticalAppsApiService } from "./critical-apps-api.service";
|
||||
import { CriticalAppsService } from "./critical-apps.service";
|
||||
|
||||
describe("CriticalAppsService", () => {
|
||||
let service: CriticalAppsService;
|
||||
@@ -139,4 +140,54 @@ describe("CriticalAppsService", () => {
|
||||
expect(res).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
it("should drop a critical app", async () => {
|
||||
// arrange
|
||||
const orgId = "org1" as OrganizationId;
|
||||
const selectedUrl = "https://example.com";
|
||||
|
||||
const initialList = [
|
||||
{ id: "id1", organizationId: "org1", uri: "https://example.com" },
|
||||
{ id: "id2", organizationId: "org1", uri: "https://example.org" },
|
||||
] as PasswordHealthReportApplicationsResponse[];
|
||||
|
||||
service.setAppsInListForOrg(initialList);
|
||||
|
||||
// act
|
||||
await service.dropCriticalApp(orgId, selectedUrl);
|
||||
|
||||
// expectations
|
||||
expect(criticalAppsApiService.dropCriticalApp).toHaveBeenCalledWith({
|
||||
organizationId: orgId,
|
||||
passwordHealthReportApplicationIds: ["id1"],
|
||||
});
|
||||
expect(service.getAppsListForOrg(orgId)).toBeTruthy();
|
||||
service.getAppsListForOrg(orgId).subscribe((res) => {
|
||||
expect(res).toHaveLength(1);
|
||||
expect(res[0].uri).toBe("https://example.org");
|
||||
});
|
||||
});
|
||||
|
||||
it("should not drop a critical app if it does not exist", async () => {
|
||||
// arrange
|
||||
const orgId = "org1" as OrganizationId;
|
||||
const selectedUrl = "https://nonexistent.com";
|
||||
|
||||
const initialList = [
|
||||
{ id: "id1", organizationId: "org1", uri: "https://example.com" },
|
||||
{ id: "id2", organizationId: "org1", uri: "https://example.org" },
|
||||
] as PasswordHealthReportApplicationsResponse[];
|
||||
|
||||
service.setAppsInListForOrg(initialList);
|
||||
|
||||
// act
|
||||
await service.dropCriticalApp(orgId, selectedUrl);
|
||||
|
||||
// expectations
|
||||
expect(criticalAppsApiService.dropCriticalApp).not.toHaveBeenCalled();
|
||||
expect(service.getAppsListForOrg(orgId)).toBeTruthy();
|
||||
service.getAppsListForOrg(orgId).subscribe((res) => {
|
||||
expect(res).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
takeUntil,
|
||||
zip,
|
||||
} from "rxjs";
|
||||
import { Opaque } from "type-fest";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
@@ -20,6 +19,11 @@ import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import {
|
||||
PasswordHealthReportApplicationsRequest,
|
||||
PasswordHealthReportApplicationsResponse,
|
||||
} from "../models/password-health";
|
||||
|
||||
import { CriticalAppsApiService } from "./critical-apps-api.service";
|
||||
|
||||
/* Retrieves and decrypts critical apps for a given organization
|
||||
@@ -94,6 +98,25 @@ export class CriticalAppsService {
|
||||
this.orgId.next(orgId);
|
||||
}
|
||||
|
||||
// Drop a critical app for a given organization
|
||||
// Only one app may be dropped at a time
|
||||
async dropCriticalApp(orgId: OrganizationId, selectedUrl: string) {
|
||||
const app = this.criticalAppsList.value.find(
|
||||
(f) => f.organizationId === orgId && f.uri === selectedUrl,
|
||||
);
|
||||
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.criticalAppsApiService.dropCriticalApp({
|
||||
organizationId: app.organizationId,
|
||||
passwordHealthReportApplicationIds: [app.id],
|
||||
});
|
||||
|
||||
this.criticalAppsList.next(this.criticalAppsList.value.filter((f) => f.uri !== selectedUrl));
|
||||
}
|
||||
|
||||
private retrieveCriticalApps(
|
||||
orgId: OrganizationId | null,
|
||||
): Observable<PasswordHealthReportApplicationsResponse[]> {
|
||||
@@ -144,16 +167,3 @@ export class CriticalAppsService {
|
||||
return await Promise.all(criticalAppsPromises);
|
||||
}
|
||||
}
|
||||
|
||||
export interface PasswordHealthReportApplicationsRequest {
|
||||
organizationId: OrganizationId;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface PasswordHealthReportApplicationsResponse {
|
||||
id: PasswordHealthReportApplicationId;
|
||||
organizationId: OrganizationId;
|
||||
uri: string;
|
||||
}
|
||||
|
||||
export type PasswordHealthReportApplicationId = Opaque<string, "PasswordHealthReportApplicationId">;
|
||||
|
||||
Reference in New Issue
Block a user