1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 06:43:35 +00:00

[PM-24279] Utilize Policy vNext endpoint (#16317)

This commit is contained in:
Jimmy Vo
2025-09-10 10:32:06 -04:00
committed by GitHub
parent af21ab96af
commit b76d437f9e
4 changed files with 140 additions and 20 deletions

View File

@@ -9,13 +9,17 @@ import {
ViewContainerRef, ViewContainerRef,
} from "@angular/core"; } from "@angular/core";
import { FormBuilder } from "@angular/forms"; import { FormBuilder } from "@angular/forms";
import { Observable, map } from "rxjs"; import { Observable, map, firstValueFrom, switchMap } from "rxjs";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { import {
DIALOG_DATA, DIALOG_DATA,
DialogConfig, DialogConfig,
@@ -23,8 +27,10 @@ import {
DialogService, DialogService,
ToastService, ToastService,
} from "@bitwarden/components"; } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { BasePolicy, BasePolicyComponent } from "../policies"; import { BasePolicy, BasePolicyComponent } from "../policies";
import { vNextOrganizationDataOwnershipPolicyComponent } from "../policies/vnext-organization-data-ownership.component";
export type PolicyEditDialogData = { export type PolicyEditDialogData = {
/** Returns policy abstracts. */ /** Returns policy abstracts. */
@@ -59,12 +65,15 @@ export class PolicyEditComponent implements AfterViewInit {
}); });
constructor( constructor(
@Inject(DIALOG_DATA) protected data: PolicyEditDialogData, @Inject(DIALOG_DATA) protected data: PolicyEditDialogData,
private accountService: AccountService,
private policyApiService: PolicyApiServiceAbstraction, private policyApiService: PolicyApiServiceAbstraction,
private i18nService: I18nService, private i18nService: I18nService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private dialogRef: DialogRef<PolicyEditDialogResult>, private dialogRef: DialogRef<PolicyEditDialogResult>,
private toastService: ToastService, private toastService: ToastService,
private configService: ConfigService,
private keyService: KeyService,
) {} ) {}
get policy(): BasePolicy { get policy(): BasePolicy {
return this.data.policy; return this.data.policy;
@@ -107,24 +116,70 @@ export class PolicyEditComponent implements AfterViewInit {
return; return;
} }
let request: PolicyRequest;
try { try {
request = await this.policyComponent.buildRequest(); if (
} catch (e) { this.policyComponent instanceof vNextOrganizationDataOwnershipPolicyComponent &&
this.toastService.showToast({ variant: "error", title: null, message: e.message }); (await this.isVNextEnabled())
return; ) {
await this.handleVNextSubmission(this.policyComponent);
} else {
await this.handleStandardSubmission();
} }
await this.policyApiService.putPolicy(this.data.organizationId, this.data.policy.type, request);
this.toastService.showToast({ this.toastService.showToast({
variant: "success", variant: "success",
title: null, title: null,
message: this.i18nService.t("editedPolicyId", this.i18nService.t(this.data.policy.name)), message: this.i18nService.t("editedPolicyId", this.i18nService.t(this.data.policy.name)),
}); });
this.dialogRef.close(PolicyEditDialogResult.Saved); this.dialogRef.close(PolicyEditDialogResult.Saved);
} catch (error) {
this.toastService.showToast({
variant: "error",
title: null,
message: error.message,
});
}
}; };
private async isVNextEnabled(): Promise<boolean> {
const isVNextFeatureEnabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation),
);
return isVNextFeatureEnabled;
}
private async handleStandardSubmission(): Promise<void> {
const request = await this.policyComponent.buildRequest();
await this.policyApiService.putPolicy(this.data.organizationId, this.data.policy.type, request);
}
private async handleVNextSubmission(
policyComponent: vNextOrganizationDataOwnershipPolicyComponent,
): Promise<void> {
const orgKey = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) => this.keyService.orgKeys$(userId)),
map(
(orgKeys: { [key: OrganizationId]: any }) =>
orgKeys[this.data.organizationId as OrganizationId] ?? null,
),
),
);
if (orgKey == null) {
throw new Error("No encryption key for this organization.");
}
const vNextRequest = await policyComponent.buildVNextRequest(orgKey);
await this.policyApiService.putPolicyVNext(
this.data.organizationId,
this.data.policy.type,
vNextRequest,
);
}
static open = (dialogService: DialogService, config: DialogConfig<PolicyEditDialogData>) => { static open = (dialogService: DialogService, config: DialogConfig<PolicyEditDialogData>) => {
return dialogService.open<PolicyEditDialogResult>(PolicyEditComponent, config); return dialogService.open<PolicyEditDialogResult>(PolicyEditComponent, config);
}; };

View File

@@ -3,14 +3,26 @@ import { lastValueFrom, Observable } from "rxjs";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { OrgKey } from "@bitwarden/common/types/key";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { EncString } from "@bitwarden/sdk-internal";
import { SharedModule } from "../../../shared"; import { SharedModule } from "../../../shared";
import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
interface VNextPolicyRequest {
policy: PolicyRequest;
metadata: {
defaultUserCollectionName: string;
};
}
export class vNextOrganizationDataOwnershipPolicy extends BasePolicy { export class vNextOrganizationDataOwnershipPolicy extends BasePolicy {
name = "organizationDataOwnership"; name = "organizationDataOwnership";
description = "organizationDataOwnershipDesc"; description = "organizationDataOwnershipDesc";
@@ -33,7 +45,11 @@ export class vNextOrganizationDataOwnershipPolicyComponent
extends BasePolicyComponent extends BasePolicyComponent
implements OnInit implements OnInit
{ {
constructor(private dialogService: DialogService) { constructor(
private dialogService: DialogService,
private i18nService: I18nService,
private encryptService: EncryptService,
) {
super(); super();
} }
@@ -47,4 +63,36 @@ export class vNextOrganizationDataOwnershipPolicyComponent
} }
return true; return true;
} }
async buildVNextRequest(orgKey: OrgKey): Promise<VNextPolicyRequest> {
if (!this.policy) {
throw new Error("Policy was not found");
}
const defaultUserCollectionName = await this.getEncryptedDefaultUserCollectionName(orgKey);
const request: VNextPolicyRequest = {
policy: {
type: this.policy.type,
enabled: this.enabled.value,
data: this.buildRequestData(),
},
metadata: {
defaultUserCollectionName,
},
};
return request;
}
private async getEncryptedDefaultUserCollectionName(orgKey: OrgKey): Promise<EncString> {
const defaultCollectionName = this.i18nService.t("myItems");
const encrypted = await this.encryptService.encryptString(defaultCollectionName, orgKey);
if (!encrypted.encryptedString) {
throw new Error("Encryption error");
}
return encrypted.encryptedString;
}
} }

View File

@@ -24,4 +24,5 @@ export abstract class PolicyApiServiceAbstraction {
type: PolicyType, type: PolicyType,
request: PolicyRequest, request: PolicyRequest,
) => Promise<any>; ) => Promise<any>;
abstract putPolicyVNext: (organizationId: string, type: PolicyType, request: any) => Promise<any>;
} }

View File

@@ -116,16 +116,32 @@ export class PolicyApiService implements PolicyApiServiceAbstraction {
} }
async putPolicy(organizationId: string, type: PolicyType, request: PolicyRequest): Promise<any> { async putPolicy(organizationId: string, type: PolicyType, request: PolicyRequest): Promise<any> {
const r = await this.apiService.send( const response = await this.apiService.send(
"PUT", "PUT",
"/organizations/" + organizationId + "/policies/" + type, "/organizations/" + organizationId + "/policies/" + type,
request, request,
true, true,
true, true,
); );
await this.handleResponse(response);
}
async putPolicyVNext(organizationId: string, type: PolicyType, request: any): Promise<any> {
const response = await this.apiService.send(
"PUT",
`/organizations/${organizationId}/policies/${type}/vnext`,
request,
true,
true,
);
await this.handleResponse(response);
}
private async handleResponse(response: any): Promise<void> {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const response = new PolicyResponse(r); const policyResponse = new PolicyResponse(response);
const data = new PolicyData(response); const data = new PolicyData(policyResponse);
await this.policyService.upsert(data, userId); await this.policyService.upsert(data, userId);
} }
} }