mirror of
https://github.com/bitwarden/browser
synced 2025-12-23 19:53:43 +00:00
[PM-25306] Policy documentation and reorganization (#16193)
* Add README for adding policy UI in Admin Console * Update existing policy UIs to be standalone * Reorganize files and use barrel files * Use token to inject policies into PolicyListService
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
import { BasePolicy } from "../organizations/policies";
|
||||
|
||||
export class PolicyListService {
|
||||
private policies: BasePolicy[] = [];
|
||||
|
||||
addPolicies(policies: BasePolicy[]) {
|
||||
this.policies.push(...policies);
|
||||
}
|
||||
|
||||
getPolicies(): BasePolicy[] {
|
||||
return this.policies;
|
||||
}
|
||||
}
|
||||
234
apps/web/src/app/admin-console/organizations/policies/README.md
Normal file
234
apps/web/src/app/admin-console/organizations/policies/README.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Adding a New Policy in Admin Console
|
||||
|
||||
This README explains how to add a new policy type to the Admin Console. Policies are used to control organizational behavior and security settings for their members.
|
||||
|
||||
This README does not cover checking the policy status in order to enforce it in the domain code.
|
||||
|
||||
## Overview
|
||||
|
||||
Each policy consists of three main components:
|
||||
|
||||
1. **Policy Type Enum** - Defines the policy type identifier
|
||||
2. **Policy Definition & Component** - Implements the UI and business logic
|
||||
3. **Registration** - Registers the policy in the application
|
||||
|
||||
## Step 1: Adding the Enum
|
||||
|
||||
Add your new policy type to the `PolicyType` enum.
|
||||
|
||||
**Important**: You must also add the corresponding PolicyType enum value on the server.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
export enum PolicyType {
|
||||
// ... existing policies
|
||||
YourNewPolicy = 21, // Use the next available number
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2: Creating the Policy Definition and Component
|
||||
|
||||
### Policy Licensing and Location
|
||||
|
||||
The location where you create your policy depends on its licensing:
|
||||
|
||||
- **Open Source (OSS) Policies**: Create in `apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/`
|
||||
- **Bitwarden Licensed Policies**: Create in `bitwarden_license/bit-web/src/app/admin-console/policies/`
|
||||
|
||||
Most policies should be OSS licensed unless they specifically relate to premium/enterprise features that are part of Bitwarden's commercial offerings.
|
||||
|
||||
Create a new component file in the appropriate `policy-edit-definitions/` folder following the naming pattern `your-policy-name.component.ts`.
|
||||
|
||||
**Note:** you may also create the policy files in your own team's code if you prefer to own your own definition. The same licensing considerations apply.
|
||||
|
||||
### Basic Structure
|
||||
|
||||
```typescript
|
||||
import { Component } from "@angular/core";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
// Policy Definition Class
|
||||
export class YourNewPolicy extends BasePolicyEditDefinition {
|
||||
name = "yourPolicyNameTitle"; // i18n key for title
|
||||
description = "yourPolicyNameDesc"; // i18n key for description
|
||||
type = PolicyType.YourNewPolicy; // Reference to enum
|
||||
component = YourNewPolicyComponent; // Reference to component
|
||||
}
|
||||
|
||||
// Policy Component Class
|
||||
@Component({
|
||||
templateUrl: "your-policy-name.component.html",
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class YourNewPolicyComponent extends BasePolicyEditComponent {
|
||||
// Component implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Common Use Cases
|
||||
|
||||
#### Simple Toggle Policy (No Additional Configuration)
|
||||
|
||||
For policies that only need an enabled/disabled state:
|
||||
|
||||
```typescript
|
||||
export class SimpleTogglePolicy extends BasePolicyEditDefinition {
|
||||
name = "simpleTogglePolicyTitle";
|
||||
description = "simpleTogglePolicyDesc";
|
||||
type = PolicyType.SimpleToggle;
|
||||
component = SimpleTogglePolicyComponent;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: "simple-toggle.component.html",
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class SimpleTogglePolicyComponent extends BasePolicyEditComponent {}
|
||||
```
|
||||
|
||||
Template (`simple-toggle.component.html`):
|
||||
|
||||
```html
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox [formControl]="enabled" id="enabled" />
|
||||
<bit-label>{{ "turnOn" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
```
|
||||
|
||||
#### Policy with Configuration Data
|
||||
|
||||
For policies requiring additional settings beyond just enabled/disabled, you'll need to define a custom `data` FormGroup to handle the policy's configuration options:
|
||||
|
||||
```typescript
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { ControlsOf } from "@bitwarden/angular/types/controls-of";
|
||||
|
||||
interface YourPolicyOptions {
|
||||
minLength: number;
|
||||
requireSpecialChar: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: "your-policy.component.html",
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class YourPolicyComponent extends BasePolicyEditComponent implements OnInit {
|
||||
data: FormGroup<ControlsOf<YourPolicyOptions>> = this.formBuilder.group({
|
||||
minLength: [8, [Validators.min(1)]],
|
||||
requireSpecialChar: [false],
|
||||
});
|
||||
|
||||
constructor(private formBuilder: FormBuilder) {
|
||||
super();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
// Additional initialization logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Template (`your-policy.component.html`):
|
||||
|
||||
```html
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox [formControl]="enabled" id="enabled" />
|
||||
<bit-label>{{ "turnOn" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
|
||||
<ng-container [formGroup]="data">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "minimumLength" | i18n }}</bit-label>
|
||||
<input bitInput type="number" formControlName="minLength" />
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox formControlName="requireSpecialChar" />
|
||||
<bit-label>{{ "requireSpecialCharacter" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</ng-container>
|
||||
```
|
||||
|
||||
#### Feature Flagged Policy
|
||||
|
||||
To hide a policy behind a feature flag using ConfigService:
|
||||
|
||||
```typescript
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
export class NewPolicyBeta extends BasePolicyEditDefinition {
|
||||
name = "newPolicyTitle";
|
||||
description = "newPolicyDesc";
|
||||
type = PolicyType.NewPolicy;
|
||||
component = NewPolicyComponent;
|
||||
|
||||
// Only show if feature flag is enabled
|
||||
display$(organization: Organization, configService: ConfigService) {
|
||||
return configService.getFeatureFlag$(FeatureFlag.YourNewPolicyFeature);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Policy related to Organization Features
|
||||
|
||||
To show a policy only when the organization has a specific plan feature:
|
||||
|
||||
```typescript
|
||||
import { of } from "rxjs";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
export class RequireSsoPolicy extends BasePolicyEditDefinition {
|
||||
name = "requireSsoTitle";
|
||||
description = "requireSsoDesc";
|
||||
type = PolicyType.RequireSso;
|
||||
component = RequireSsoPolicyComponent;
|
||||
|
||||
// Only show if organization has SSO enabled
|
||||
display$(organization: Organization, configService: ConfigService) {
|
||||
return of(organization.useSso);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 3: Registering the Policy
|
||||
|
||||
### Export from Index File
|
||||
|
||||
Add your policy to the barrel file in its folder:
|
||||
|
||||
```typescript
|
||||
export { YourNewPolicy } from "./your-policy-name.component";
|
||||
```
|
||||
|
||||
### Register in Policy Register
|
||||
|
||||
Add your policy to the appropriate register in `policy-edit-register.ts`:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
// ... existing imports
|
||||
YourNewPolicy,
|
||||
} from "./policy-edit-definitions";
|
||||
|
||||
export const ossPolicyEditRegister: BasePolicyEditDefinition[] = [
|
||||
// ... existing policies
|
||||
new YourNewPolicy(),
|
||||
];
|
||||
```
|
||||
|
||||
**Note**: Use `ossPolicyEditRegister` for open-source policies and `bitPolicyEditRegister` for Bitwarden Licensed policies.
|
||||
|
||||
## Testing Your Policy
|
||||
|
||||
1. Build and run the application
|
||||
2. Navigate to Admin Console → Policies
|
||||
3. Verify your policy appears in the list
|
||||
4. Test the policy configuration UI
|
||||
5. Verify policy data saves correctly
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Directive, Input, OnInit } from "@angular/core";
|
||||
import { FormControl, UntypedFormGroup } from "@angular/forms";
|
||||
import { Observable, of } from "rxjs";
|
||||
import { Constructor } from "type-fest";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
|
||||
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
/**
|
||||
* A metadata class that defines how a policy is displayed in the Admin Console Policies page for editing.
|
||||
* Add this to the `ossPolicyRegister` or `bitPolicyRegister` file to register it in the application.
|
||||
*/
|
||||
export abstract class BasePolicyEditDefinition {
|
||||
/**
|
||||
* i18n string for the policy name.
|
||||
*/
|
||||
abstract name: string;
|
||||
/**
|
||||
* i18n string for the policy description.
|
||||
* This is shown in the list of policies.
|
||||
*/
|
||||
abstract description: string;
|
||||
/**
|
||||
* The PolicyType enum that this policy represents.
|
||||
*/
|
||||
abstract type: PolicyType;
|
||||
/**
|
||||
* The component used to edit this policy. See {@link BasePolicyEditComponent}.
|
||||
*/
|
||||
abstract component: Constructor<BasePolicyEditComponent>;
|
||||
|
||||
/**
|
||||
* If true, the {@link description} will be reused in the policy edit modal. Set this to false if you
|
||||
* have more complex requirements that you will implement in your template instead.
|
||||
**/
|
||||
showDescription: boolean = true;
|
||||
|
||||
/**
|
||||
* A method that determines whether to display this policy in the Admin Console Policies page.
|
||||
* The default implementation will always display the policy.
|
||||
* This can be used to hide the policy based on the organization's plan features or a feature flag value.
|
||||
* Note: this only hides the policy for editing in Admin Console, it does not affect its enforcement
|
||||
* if it has already been turned on. Enforcement should be feature flagged separately.
|
||||
*/
|
||||
display$(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
return of(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A component used to edit the policy settings in Admin Console. It is rendered inside the PolicyEditDialogComponent.
|
||||
* This should contain the form controls used to edit the policy (including the Enabled checkbox) and any additional
|
||||
* warnings or callouts.
|
||||
* See existing implementations as a guide.
|
||||
*/
|
||||
@Directive()
|
||||
export abstract class BasePolicyEditComponent implements OnInit {
|
||||
@Input() policyResponse: PolicyResponse | undefined;
|
||||
@Input() policy: BasePolicyEditDefinition | undefined;
|
||||
|
||||
/**
|
||||
* Whether the policy is enabled.
|
||||
*/
|
||||
enabled = new FormControl(false);
|
||||
|
||||
/**
|
||||
* An optional FormGroup for additional policy configuration. Required for more complex policies only.
|
||||
*/
|
||||
data: UntypedFormGroup | undefined;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.enabled.setValue(this.policyResponse?.enabled ?? false);
|
||||
|
||||
if (this.policyResponse?.data != null) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
buildRequest() {
|
||||
if (!this.policy) {
|
||||
throw new Error("Policy was not found");
|
||||
}
|
||||
|
||||
const request: PolicyRequest = {
|
||||
type: this.policy.type,
|
||||
enabled: this.enabled.value ?? false,
|
||||
data: this.buildRequestData(),
|
||||
};
|
||||
|
||||
return Promise.resolve(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called before the policy is saved. If it returns false, it will not be saved
|
||||
* and the user will remain on the policy edit dialog.
|
||||
* This can be used to trigger an additional confirmation modal before saving.
|
||||
* */
|
||||
confirm(): Promise<boolean> | boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected loadData() {
|
||||
this.data?.patchValue(this.policyResponse?.data ?? {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the {@link data} FormGroup to the policy data model for saving.
|
||||
*/
|
||||
protected buildRequestData() {
|
||||
if (this.data != null) {
|
||||
return this.data.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { Directive, Input, OnInit } from "@angular/core";
|
||||
import { UntypedFormControl, UntypedFormGroup } from "@angular/forms";
|
||||
import { Observable, of } from "rxjs";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
|
||||
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
export abstract class BasePolicy {
|
||||
abstract name: string;
|
||||
abstract description: string;
|
||||
abstract type: PolicyType;
|
||||
abstract component: any;
|
||||
|
||||
/**
|
||||
* If true, the description will be reused in the policy edit modal. Set this to false if you
|
||||
* have more complex requirements that you will implement in your template instead.
|
||||
**/
|
||||
showDescription: boolean = true;
|
||||
|
||||
display(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
return of(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Directive()
|
||||
export abstract class BasePolicyComponent implements OnInit {
|
||||
@Input() policyResponse: PolicyResponse | undefined;
|
||||
@Input() policy: BasePolicy | undefined;
|
||||
|
||||
enabled = new UntypedFormControl(false);
|
||||
data: UntypedFormGroup | undefined;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.enabled.setValue(this.policyResponse?.enabled);
|
||||
|
||||
if (this.policyResponse?.data != null) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
buildRequest() {
|
||||
if (!this.policy) {
|
||||
throw new Error("Policy was not found");
|
||||
}
|
||||
|
||||
const request: PolicyRequest = {
|
||||
type: this.policy.type,
|
||||
enabled: this.enabled.value,
|
||||
data: this.buildRequestData(),
|
||||
};
|
||||
|
||||
return Promise.resolve(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable optional validation before sumitting a respose for policy submission
|
||||
* */
|
||||
confirm(): Promise<boolean> | boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected loadData() {
|
||||
this.data?.patchValue(this.policyResponse?.data ?? {});
|
||||
}
|
||||
|
||||
protected buildRequestData() {
|
||||
if (this.data != null) {
|
||||
return this.data.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,4 @@
|
||||
export * from "./policies.module";
|
||||
export { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
export { DisableSendPolicy } from "./disable-send.component";
|
||||
export { MasterPasswordPolicy } from "./master-password.component";
|
||||
export { PasswordGeneratorPolicy } from "./password-generator.component";
|
||||
export { vNextOrganizationDataOwnershipPolicy } from "./vnext-organization-data-ownership.component";
|
||||
export { OrganizationDataOwnershipPolicy } from "./organization-data-ownership.component";
|
||||
export { RequireSsoPolicy } from "./require-sso.component";
|
||||
export { ResetPasswordPolicy } from "./reset-password.component";
|
||||
export { SendOptionsPolicy } from "./send-options.component";
|
||||
export { SingleOrgPolicy } from "./single-org.component";
|
||||
export { TwoFactorAuthenticationPolicy } from "./two-factor-authentication.component";
|
||||
export { PoliciesComponent } from "./policies.component";
|
||||
export { RemoveUnlockWithPinPolicy } from "./remove-unlock-with-pin.component";
|
||||
export { RestrictedItemTypesPolicy } from "./restricted-item-types.component";
|
||||
export { ossPolicyEditRegister } from "./policy-edit-register";
|
||||
export { BasePolicyEditDefinition, BasePolicyEditComponent } from "./base-policy-edit.component";
|
||||
export { POLICY_EDIT_REGISTER } from "./policy-register-token";
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<bit-table>
|
||||
<ng-template body>
|
||||
@for (p of policies; track p.name) {
|
||||
@if (p.display(organization, configService) | async) {
|
||||
@if (p.display$(organization, configService) | async) {
|
||||
<tr bitRow>
|
||||
<td bitCell ngPreserveWhitespaces>
|
||||
<button type="button" bitLink (click)="edit(p)">{{ p.name | i18n }}</button>
|
||||
|
||||
@@ -16,22 +16,31 @@ import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { safeProvider } from "@bitwarden/ui-common";
|
||||
|
||||
import { PolicyListService } from "../../core/policy-list.service";
|
||||
import { BasePolicy } from "../policies";
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.component";
|
||||
import { BasePolicyEditDefinition } from "./base-policy-edit.component";
|
||||
import { PolicyEditDialogComponent } from "./policy-edit-dialog.component";
|
||||
import { PolicyListService } from "./policy-list.service";
|
||||
import { POLICY_EDIT_REGISTER } from "./policy-register-token";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-policies",
|
||||
templateUrl: "policies.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule, HeaderModule],
|
||||
providers: [
|
||||
safeProvider({
|
||||
provide: PolicyListService,
|
||||
deps: [POLICY_EDIT_REGISTER],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class PoliciesComponent implements OnInit {
|
||||
loading = true;
|
||||
organizationId: string;
|
||||
policies: BasePolicy[];
|
||||
organization$: Observable<Organization>;
|
||||
policies: readonly BasePolicyEditDefinition[];
|
||||
protected organization$: Observable<Organization>;
|
||||
|
||||
private orgPolicies: PolicyResponse[];
|
||||
protected policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
|
||||
@@ -97,8 +106,8 @@ export class PoliciesComponent implements OnInit {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async edit(policy: BasePolicy) {
|
||||
const dialogRef = PolicyEditComponent.open(this.dialogService, {
|
||||
async edit(policy: BasePolicyEditDefinition) {
|
||||
const dialogRef = PolicyEditDialogComponent.open(this.dialogService, {
|
||||
data: {
|
||||
policy: policy,
|
||||
organizationId: this.organizationId,
|
||||
@@ -106,7 +115,7 @@ export class PoliciesComponent implements OnInit {
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === PolicyEditDialogResult.Saved) {
|
||||
if (result == "saved") {
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { DisableSendPolicyComponent } from "./disable-send.component";
|
||||
import { MasterPasswordPolicyComponent } from "./master-password.component";
|
||||
import { OrganizationDataOwnershipPolicyComponent } from "./organization-data-ownership.component";
|
||||
import { PasswordGeneratorPolicyComponent } from "./password-generator.component";
|
||||
import { PoliciesComponent } from "./policies.component";
|
||||
import { PolicyEditComponent } from "./policy-edit.component";
|
||||
import { RemoveUnlockWithPinPolicyComponent } from "./remove-unlock-with-pin.component";
|
||||
import { RequireSsoPolicyComponent } from "./require-sso.component";
|
||||
import { ResetPasswordPolicyComponent } from "./reset-password.component";
|
||||
import { RestrictedItemTypesPolicyComponent } from "./restricted-item-types.component";
|
||||
import { SendOptionsPolicyComponent } from "./send-options.component";
|
||||
import { SingleOrgPolicyComponent } from "./single-org.component";
|
||||
import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authentication.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, HeaderModule],
|
||||
declarations: [
|
||||
DisableSendPolicyComponent,
|
||||
MasterPasswordPolicyComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
OrganizationDataOwnershipPolicyComponent,
|
||||
RequireSsoPolicyComponent,
|
||||
ResetPasswordPolicyComponent,
|
||||
SendOptionsPolicyComponent,
|
||||
SingleOrgPolicyComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
PoliciesComponent,
|
||||
PolicyEditComponent,
|
||||
RemoveUnlockWithPinPolicyComponent,
|
||||
RestrictedItemTypesPolicyComponent,
|
||||
],
|
||||
exports: [
|
||||
DisableSendPolicyComponent,
|
||||
MasterPasswordPolicyComponent,
|
||||
PasswordGeneratorPolicyComponent,
|
||||
OrganizationDataOwnershipPolicyComponent,
|
||||
RequireSsoPolicyComponent,
|
||||
ResetPasswordPolicyComponent,
|
||||
SendOptionsPolicyComponent,
|
||||
SingleOrgPolicyComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
PoliciesComponent,
|
||||
PolicyEditComponent,
|
||||
RemoveUnlockWithPinPolicyComponent,
|
||||
],
|
||||
})
|
||||
export class PoliciesModule {}
|
||||
@@ -2,9 +2,10 @@ import { Component } from "@angular/core";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class DisableSendPolicy extends BasePolicy {
|
||||
export class DisableSendPolicy extends BasePolicyEditDefinition {
|
||||
name = "disableSend";
|
||||
description = "disableSendPolicyDesc";
|
||||
type = PolicyType.DisableSend;
|
||||
@@ -12,8 +13,7 @@ export class DisableSendPolicy extends BasePolicy {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-disable-send",
|
||||
templateUrl: "disable-send.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class DisableSendPolicyComponent extends BasePolicyComponent {}
|
||||
export class DisableSendPolicyComponent extends BasePolicyEditComponent {}
|
||||
@@ -0,0 +1,15 @@
|
||||
export { DisableSendPolicy } from "./disable-send.component";
|
||||
export { MasterPasswordPolicy } from "./master-password.component";
|
||||
export { OrganizationDataOwnershipPolicy } from "./organization-data-ownership.component";
|
||||
export { PasswordGeneratorPolicy } from "./password-generator.component";
|
||||
export { RemoveUnlockWithPinPolicy } from "./remove-unlock-with-pin.component";
|
||||
export { RequireSsoPolicy } from "./require-sso.component";
|
||||
export { ResetPasswordPolicy } from "./reset-password.component";
|
||||
export { RestrictedItemTypesPolicy } from "./restricted-item-types.component";
|
||||
export { SendOptionsPolicy } from "./send-options.component";
|
||||
export { SingleOrgPolicy } from "./single-org.component";
|
||||
export { TwoFactorAuthenticationPolicy } from "./two-factor-authentication.component";
|
||||
export {
|
||||
vNextOrganizationDataOwnershipPolicy,
|
||||
vNextOrganizationDataOwnershipPolicyComponent,
|
||||
} from "./vnext-organization-data-ownership.component";
|
||||
@@ -16,9 +16,10 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class MasterPasswordPolicy extends BasePolicy {
|
||||
export class MasterPasswordPolicy extends BasePolicyEditDefinition {
|
||||
name = "masterPassPolicyTitle";
|
||||
description = "masterPassPolicyDesc";
|
||||
type = PolicyType.MasterPassword;
|
||||
@@ -26,11 +27,10 @@ export class MasterPasswordPolicy extends BasePolicy {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-master-password",
|
||||
templateUrl: "master-password.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class MasterPasswordPolicyComponent extends BasePolicyComponent implements OnInit {
|
||||
export class MasterPasswordPolicyComponent extends BasePolicyEditComponent implements OnInit {
|
||||
MinPasswordLength = Utils.minimumPasswordLength;
|
||||
|
||||
data: FormGroup<ControlsOf<MasterPasswordPolicyOptions>> = this.formBuilder.group({
|
||||
@@ -6,15 +6,16 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class OrganizationDataOwnershipPolicy extends BasePolicy {
|
||||
export class OrganizationDataOwnershipPolicy extends BasePolicyEditDefinition {
|
||||
name = "organizationDataOwnership";
|
||||
description = "personalOwnershipPolicyDesc";
|
||||
type = PolicyType.OrganizationDataOwnership;
|
||||
component = OrganizationDataOwnershipPolicyComponent;
|
||||
|
||||
display(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
display$(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
return configService
|
||||
.getFeatureFlag$(FeatureFlag.CreateDefaultLocation)
|
||||
.pipe(map((enabled) => !enabled));
|
||||
@@ -22,8 +23,7 @@ export class OrganizationDataOwnershipPolicy extends BasePolicy {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-organization-data-ownership",
|
||||
templateUrl: "organization-data-ownership.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class OrganizationDataOwnershipPolicyComponent extends BasePolicyComponent {}
|
||||
export class OrganizationDataOwnershipPolicyComponent extends BasePolicyEditComponent {}
|
||||
@@ -9,9 +9,10 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { BuiltIn, Profile } from "@bitwarden/generator-core";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class PasswordGeneratorPolicy extends BasePolicy {
|
||||
export class PasswordGeneratorPolicy extends BasePolicyEditDefinition {
|
||||
name = "passwordGenerator";
|
||||
description = "passwordGeneratorPolicyDesc";
|
||||
type = PolicyType.PasswordGenerator;
|
||||
@@ -19,11 +20,10 @@ export class PasswordGeneratorPolicy extends BasePolicy {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-password-generator",
|
||||
templateUrl: "password-generator.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class PasswordGeneratorPolicyComponent extends BasePolicyComponent {
|
||||
export class PasswordGeneratorPolicyComponent extends BasePolicyEditComponent {
|
||||
// these properties forward the application default settings to the UI
|
||||
// for HTML attribute bindings
|
||||
protected readonly minLengthMin =
|
||||
@@ -1,11 +1,8 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ReactiveFormsModule } from "@angular/forms";
|
||||
import { By } from "@angular/platform-browser";
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -33,8 +30,6 @@ describe("RemoveUnlockWithPinPolicyComponent", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CommonModule, ReactiveFormsModule],
|
||||
declarations: [RemoveUnlockWithPinPolicyComponent, I18nPipe],
|
||||
providers: [
|
||||
{ provide: I18nService, useValue: mock<I18nService>() },
|
||||
{ provide: I18nService, useValue: i18nService },
|
||||
@@ -102,9 +97,6 @@ describe("RemoveUnlockWithPinPolicyComponent", () => {
|
||||
|
||||
const bitLabelElement = fixture.debugElement.query(By.css("bit-label"));
|
||||
expect(bitLabelElement).not.toBeNull();
|
||||
const textNodes = bitLabelElement.childNodes
|
||||
.filter((node) => node.nativeNode.nodeType === Node.TEXT_NODE)
|
||||
.map((node) => node.nativeNode.wholeText?.trim());
|
||||
expect(textNodes).toContain("Turn on");
|
||||
expect(bitLabelElement.nativeElement.textContent.trim()).toBe("Turn on");
|
||||
});
|
||||
});
|
||||
@@ -2,9 +2,10 @@ import { Component } from "@angular/core";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class RemoveUnlockWithPinPolicy extends BasePolicy {
|
||||
export class RemoveUnlockWithPinPolicy extends BasePolicyEditDefinition {
|
||||
name = "removeUnlockWithPinPolicyTitle";
|
||||
description = "removeUnlockWithPinPolicyDesc";
|
||||
type = PolicyType.RemoveUnlockWithPin;
|
||||
@@ -12,8 +13,7 @@ export class RemoveUnlockWithPinPolicy extends BasePolicy {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "remove-unlock-with-pin",
|
||||
templateUrl: "remove-unlock-with-pin.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class RemoveUnlockWithPinPolicyComponent extends BasePolicyComponent {}
|
||||
export class RemoveUnlockWithPinPolicyComponent extends BasePolicyEditComponent {}
|
||||
@@ -5,22 +5,22 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class RequireSsoPolicy extends BasePolicy {
|
||||
export class RequireSsoPolicy extends BasePolicyEditDefinition {
|
||||
name = "requireSso";
|
||||
description = "requireSsoPolicyDesc";
|
||||
type = PolicyType.RequireSso;
|
||||
component = RequireSsoPolicyComponent;
|
||||
|
||||
display(organization: Organization, configService: ConfigService) {
|
||||
display$(organization: Organization, configService: ConfigService) {
|
||||
return of(organization.useSso);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-require-sso",
|
||||
templateUrl: "require-sso.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class RequireSsoPolicyComponent extends BasePolicyComponent {}
|
||||
export class RequireSsoPolicyComponent extends BasePolicyEditComponent {}
|
||||
@@ -12,25 +12,25 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class ResetPasswordPolicy extends BasePolicy {
|
||||
export class ResetPasswordPolicy extends BasePolicyEditDefinition {
|
||||
name = "accountRecoveryPolicy";
|
||||
description = "accountRecoveryPolicyDesc";
|
||||
type = PolicyType.ResetPassword;
|
||||
component = ResetPasswordPolicyComponent;
|
||||
|
||||
display(organization: Organization, configService: ConfigService) {
|
||||
display$(organization: Organization, configService: ConfigService) {
|
||||
return of(organization.useResetPassword);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-reset-password",
|
||||
templateUrl: "reset-password.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class ResetPasswordPolicyComponent extends BasePolicyComponent implements OnInit {
|
||||
export class ResetPasswordPolicyComponent extends BasePolicyEditComponent implements OnInit {
|
||||
data = this.formBuilder.group({
|
||||
autoEnrollEnabled: false,
|
||||
});
|
||||
@@ -6,25 +6,25 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class RestrictedItemTypesPolicy extends BasePolicy {
|
||||
export class RestrictedItemTypesPolicy extends BasePolicyEditDefinition {
|
||||
name = "restrictedItemTypePolicy";
|
||||
description = "restrictedItemTypePolicyDesc";
|
||||
type = PolicyType.RestrictedItemTypes;
|
||||
component = RestrictedItemTypesPolicyComponent;
|
||||
|
||||
display(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
display$(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
return configService.getFeatureFlag$(FeatureFlag.RemoveCardItemTypePolicy);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-restricted-item-types",
|
||||
templateUrl: "restricted-item-types.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class RestrictedItemTypesPolicyComponent extends BasePolicyComponent {
|
||||
export class RestrictedItemTypesPolicyComponent extends BasePolicyEditComponent {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@@ -3,9 +3,10 @@ import { UntypedFormBuilder } from "@angular/forms";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class SendOptionsPolicy extends BasePolicy {
|
||||
export class SendOptionsPolicy extends BasePolicyEditDefinition {
|
||||
name = "sendOptions";
|
||||
description = "sendOptionsPolicyDesc";
|
||||
type = PolicyType.SendOptions;
|
||||
@@ -13,11 +14,10 @@ export class SendOptionsPolicy extends BasePolicy {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-send-options",
|
||||
templateUrl: "send-options.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class SendOptionsPolicyComponent extends BasePolicyComponent {
|
||||
export class SendOptionsPolicyComponent extends BasePolicyEditComponent {
|
||||
data = this.formBuilder.group({
|
||||
disableHideEmail: false,
|
||||
});
|
||||
@@ -2,9 +2,10 @@ import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class SingleOrgPolicy extends BasePolicy {
|
||||
export class SingleOrgPolicy extends BasePolicyEditDefinition {
|
||||
name = "singleOrg";
|
||||
description = "singleOrgPolicyDesc";
|
||||
type = PolicyType.SingleOrg;
|
||||
@@ -12,11 +13,10 @@ export class SingleOrgPolicy extends BasePolicy {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-single-org",
|
||||
templateUrl: "single-org.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnInit {
|
||||
export class SingleOrgPolicyComponent extends BasePolicyEditComponent implements OnInit {
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
@@ -2,9 +2,10 @@ import { Component } from "@angular/core";
|
||||
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
export class TwoFactorAuthenticationPolicy extends BasePolicy {
|
||||
export class TwoFactorAuthenticationPolicy extends BasePolicyEditDefinition {
|
||||
name = "twoStepLoginPolicyTitle";
|
||||
description = "twoStepLoginPolicyDesc";
|
||||
type = PolicyType.TwoFactorAuthentication;
|
||||
@@ -12,8 +13,7 @@ export class TwoFactorAuthenticationPolicy extends BasePolicy {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "policy-two-factor-authentication",
|
||||
templateUrl: "two-factor-authentication.component.html",
|
||||
standalone: false,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class TwoFactorAuthenticationPolicyComponent extends BasePolicyComponent {}
|
||||
export class TwoFactorAuthenticationPolicyComponent extends BasePolicyEditComponent {}
|
||||
@@ -12,9 +12,8 @@ import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { EncString } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component";
|
||||
|
||||
interface VNextPolicyRequest {
|
||||
policy: PolicyRequest;
|
||||
@@ -23,26 +22,24 @@ interface VNextPolicyRequest {
|
||||
};
|
||||
}
|
||||
|
||||
export class vNextOrganizationDataOwnershipPolicy extends BasePolicy {
|
||||
export class vNextOrganizationDataOwnershipPolicy extends BasePolicyEditDefinition {
|
||||
name = "organizationDataOwnership";
|
||||
description = "organizationDataOwnershipDesc";
|
||||
type = PolicyType.OrganizationDataOwnership;
|
||||
component = vNextOrganizationDataOwnershipPolicyComponent;
|
||||
showDescription = false;
|
||||
|
||||
override display(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
override display$(organization: Organization, configService: ConfigService): Observable<boolean> {
|
||||
return configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "vnext-policy-organization-data-ownership",
|
||||
templateUrl: "vnext-organization-data-ownership.component.html",
|
||||
standalone: true,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class vNextOrganizationDataOwnershipPolicyComponent
|
||||
extends BasePolicyComponent
|
||||
extends BasePolicyEditComponent
|
||||
implements OnInit
|
||||
{
|
||||
constructor(
|
||||
@@ -74,7 +71,7 @@ export class vNextOrganizationDataOwnershipPolicyComponent
|
||||
const request: VNextPolicyRequest = {
|
||||
policy: {
|
||||
type: this.policy.type,
|
||||
enabled: this.enabled.value,
|
||||
enabled: this.enabled.value ?? false,
|
||||
data: this.buildRequestData(),
|
||||
},
|
||||
metadata: {
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectorRef,
|
||||
@@ -9,7 +7,7 @@ import {
|
||||
ViewContainerRef,
|
||||
} from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { Observable, map, firstValueFrom, switchMap } from "rxjs";
|
||||
import { Observable, map, firstValueFrom, switchMap, filter, of } from "rxjs";
|
||||
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
@@ -29,37 +27,38 @@ import {
|
||||
} from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { BasePolicy, BasePolicyComponent } from "../policies";
|
||||
import { vNextOrganizationDataOwnershipPolicyComponent } from "../policies/vnext-organization-data-ownership.component";
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { BasePolicyEditDefinition, BasePolicyEditComponent } from "./base-policy-edit.component";
|
||||
import { vNextOrganizationDataOwnershipPolicyComponent } from "./policy-edit-definitions";
|
||||
|
||||
export type PolicyEditDialogData = {
|
||||
/** Returns policy abstracts. */
|
||||
policy: BasePolicy;
|
||||
/** Returns a unique organization id */
|
||||
/**
|
||||
* The metadata containing information about how to display and edit the policy.
|
||||
*/
|
||||
policy: BasePolicyEditDefinition;
|
||||
/**
|
||||
* The organization ID for the policy.
|
||||
*/
|
||||
organizationId: string;
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum PolicyEditDialogResult {
|
||||
Saved = "saved",
|
||||
}
|
||||
export type PolicyEditDialogResult = "saved";
|
||||
|
||||
@Component({
|
||||
selector: "app-policy-edit",
|
||||
templateUrl: "policy-edit.component.html",
|
||||
standalone: false,
|
||||
templateUrl: "policy-edit-dialog.component.html",
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class PolicyEditComponent implements AfterViewInit {
|
||||
export class PolicyEditDialogComponent implements AfterViewInit {
|
||||
@ViewChild("policyForm", { read: ViewContainerRef, static: true })
|
||||
policyFormRef: ViewContainerRef;
|
||||
policyFormRef: ViewContainerRef | undefined;
|
||||
|
||||
policyType = PolicyType;
|
||||
loading = true;
|
||||
enabled = false;
|
||||
saveDisabled$: Observable<boolean>;
|
||||
policyComponent: BasePolicyComponent;
|
||||
saveDisabled$: Observable<boolean> = of(false);
|
||||
policyComponent: BasePolicyEditComponent | undefined;
|
||||
|
||||
private policyResponse: PolicyResponse;
|
||||
formGroup = this.formBuilder.group({
|
||||
enabled: [this.enabled],
|
||||
});
|
||||
@@ -75,35 +74,43 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
private configService: ConfigService,
|
||||
private keyService: KeyService,
|
||||
) {}
|
||||
get policy(): BasePolicy {
|
||||
|
||||
get policy(): BasePolicyEditDefinition {
|
||||
return this.data.policy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates the child policy component and inserts it into the view.
|
||||
*/
|
||||
async ngAfterViewInit() {
|
||||
await this.load();
|
||||
const policyResponse = await this.load();
|
||||
this.loading = false;
|
||||
|
||||
this.policyComponent = this.policyFormRef.createComponent(this.data.policy.component)
|
||||
.instance as BasePolicyComponent;
|
||||
this.policyComponent.policy = this.data.policy;
|
||||
this.policyComponent.policyResponse = this.policyResponse;
|
||||
if (!this.policyFormRef) {
|
||||
throw new Error("Template not initialized.");
|
||||
}
|
||||
|
||||
this.saveDisabled$ = this.policyComponent.data.statusChanges.pipe(
|
||||
map((status) => status !== "VALID" || !this.policyResponse.canToggleState),
|
||||
);
|
||||
this.policyComponent = this.policyFormRef.createComponent(this.data.policy.component).instance;
|
||||
this.policyComponent.policy = this.data.policy;
|
||||
this.policyComponent.policyResponse = policyResponse;
|
||||
|
||||
if (this.policyComponent.data) {
|
||||
// If the policy has additional configuration, disable the save button if the form state is invalid
|
||||
this.saveDisabled$ = this.policyComponent.data.statusChanges.pipe(
|
||||
map((status) => status !== "VALID" || !policyResponse.canToggleState),
|
||||
);
|
||||
}
|
||||
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
async load() {
|
||||
try {
|
||||
this.policyResponse = await this.policyApiService.getPolicy(
|
||||
this.data.organizationId,
|
||||
this.data.policy.type,
|
||||
);
|
||||
} catch (e) {
|
||||
return await this.policyApiService.getPolicy(this.data.organizationId, this.data.policy.type);
|
||||
} catch (e: any) {
|
||||
// No policy exists yet, instantiate an empty one
|
||||
if (e.statusCode === 404) {
|
||||
this.policyResponse = new PolicyResponse({ Enabled: false });
|
||||
return new PolicyResponse({ Enabled: false });
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
@@ -111,6 +118,10 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
if (!this.policyComponent) {
|
||||
throw new Error("PolicyComponent not initialized.");
|
||||
}
|
||||
|
||||
if ((await this.policyComponent.confirm()) == false) {
|
||||
this.dialogRef.close();
|
||||
return;
|
||||
@@ -128,14 +139,12 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("editedPolicyId", this.i18nService.t(this.data.policy.name)),
|
||||
});
|
||||
this.dialogRef.close(PolicyEditDialogResult.Saved);
|
||||
} catch (error) {
|
||||
this.dialogRef.close("saved");
|
||||
} catch (error: any) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
@@ -150,6 +159,10 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
private async handleStandardSubmission(): Promise<void> {
|
||||
if (!this.policyComponent) {
|
||||
throw new Error("PolicyComponent not initialized.");
|
||||
}
|
||||
|
||||
const request = await this.policyComponent.buildRequest();
|
||||
await this.policyApiService.putPolicy(this.data.organizationId, this.data.policy.type, request);
|
||||
}
|
||||
@@ -161,10 +174,8 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.keyService.orgKeys$(userId)),
|
||||
map(
|
||||
(orgKeys: { [key: OrganizationId]: any }) =>
|
||||
orgKeys[this.data.organizationId as OrganizationId] ?? null,
|
||||
),
|
||||
filter((orgKeys) => orgKeys != null),
|
||||
map((orgKeys) => orgKeys[this.data.organizationId as OrganizationId] ?? null),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -181,6 +192,6 @@ export class PolicyEditComponent implements AfterViewInit {
|
||||
);
|
||||
}
|
||||
static open = (dialogService: DialogService, config: DialogConfig<PolicyEditDialogData>) => {
|
||||
return dialogService.open<PolicyEditDialogResult>(PolicyEditComponent, config);
|
||||
return dialogService.open<PolicyEditDialogResult>(PolicyEditDialogComponent, config);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { BasePolicyEditDefinition } from "./base-policy-edit.component";
|
||||
import {
|
||||
DisableSendPolicy,
|
||||
MasterPasswordPolicy,
|
||||
OrganizationDataOwnershipPolicy,
|
||||
PasswordGeneratorPolicy,
|
||||
RemoveUnlockWithPinPolicy,
|
||||
RequireSsoPolicy,
|
||||
ResetPasswordPolicy,
|
||||
RestrictedItemTypesPolicy,
|
||||
SendOptionsPolicy,
|
||||
SingleOrgPolicy,
|
||||
TwoFactorAuthenticationPolicy,
|
||||
vNextOrganizationDataOwnershipPolicy,
|
||||
} from "./policy-edit-definitions";
|
||||
|
||||
/**
|
||||
* The policy register for OSS policies.
|
||||
* Add your policy definition here if it is under the OSS license.
|
||||
*/
|
||||
export const ossPolicyEditRegister: BasePolicyEditDefinition[] = [
|
||||
new TwoFactorAuthenticationPolicy(),
|
||||
new MasterPasswordPolicy(),
|
||||
new RemoveUnlockWithPinPolicy(),
|
||||
new ResetPasswordPolicy(),
|
||||
new PasswordGeneratorPolicy(),
|
||||
new SingleOrgPolicy(),
|
||||
new RequireSsoPolicy(),
|
||||
new OrganizationDataOwnershipPolicy(),
|
||||
new vNextOrganizationDataOwnershipPolicy(),
|
||||
new DisableSendPolicy(),
|
||||
new SendOptionsPolicy(),
|
||||
new RestrictedItemTypesPolicy(),
|
||||
];
|
||||
@@ -0,0 +1,13 @@
|
||||
import { BasePolicyEditDefinition } from "./base-policy-edit.component";
|
||||
|
||||
export class PolicyListService {
|
||||
private policies: readonly BasePolicyEditDefinition[];
|
||||
|
||||
constructor(policies: BasePolicyEditDefinition[]) {
|
||||
this.policies = Object.freeze([...policies]);
|
||||
}
|
||||
|
||||
getPolicies() {
|
||||
return this.policies;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SafeInjectionToken } from "@bitwarden/ui-common";
|
||||
|
||||
import { BasePolicyEditDefinition } from "./base-policy-edit.component";
|
||||
|
||||
export const POLICY_EDIT_REGISTER = new SafeInjectionToken<BasePolicyEditDefinition[]>(
|
||||
"POLICY_EDIT_REGISTER",
|
||||
);
|
||||
@@ -7,7 +7,6 @@ import { DangerZoneComponent } from "../../../auth/settings/account/danger-zone.
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../shared";
|
||||
import { AccountFingerprintComponent } from "../../../shared/components/account-fingerprint/account-fingerprint.component";
|
||||
import { PoliciesModule } from "../../organizations/policies";
|
||||
|
||||
import { AccountComponent } from "./account.component";
|
||||
import { OrganizationSettingsRoutingModule } from "./organization-settings-routing.module";
|
||||
@@ -16,7 +15,6 @@ import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
PoliciesModule,
|
||||
OrganizationSettingsRoutingModule,
|
||||
AccountFingerprintComponent,
|
||||
DangerZoneComponent,
|
||||
|
||||
@@ -30,22 +30,6 @@ import { SearchService } from "@bitwarden/common/vault/abstractions/search.servi
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { KeyService, BiometricStateService } from "@bitwarden/key-management";
|
||||
|
||||
import { PolicyListService } from "./admin-console/core/policy-list.service";
|
||||
import {
|
||||
DisableSendPolicy,
|
||||
MasterPasswordPolicy,
|
||||
PasswordGeneratorPolicy,
|
||||
OrganizationDataOwnershipPolicy,
|
||||
vNextOrganizationDataOwnershipPolicy,
|
||||
RequireSsoPolicy,
|
||||
ResetPasswordPolicy,
|
||||
SendOptionsPolicy,
|
||||
SingleOrgPolicy,
|
||||
TwoFactorAuthenticationPolicy,
|
||||
RemoveUnlockWithPinPolicy,
|
||||
RestrictedItemTypesPolicy,
|
||||
} from "./admin-console/organizations/policies";
|
||||
|
||||
const BroadcasterSubscriptionId = "AppComponent";
|
||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||
|
||||
@@ -79,7 +63,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private serverNotificationsService: ServerNotificationsService,
|
||||
private stateService: StateService,
|
||||
private eventUploadService: EventUploadService,
|
||||
protected policyListService: PolicyListService,
|
||||
protected configService: ConfigService,
|
||||
private dialogService: DialogService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
@@ -238,21 +221,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.policyListService.addPolicies([
|
||||
new TwoFactorAuthenticationPolicy(),
|
||||
new MasterPasswordPolicy(),
|
||||
new RemoveUnlockWithPinPolicy(),
|
||||
new ResetPasswordPolicy(),
|
||||
new PasswordGeneratorPolicy(),
|
||||
new SingleOrgPolicy(),
|
||||
new RequireSsoPolicy(),
|
||||
new OrganizationDataOwnershipPolicy(),
|
||||
new vNextOrganizationDataOwnershipPolicy(),
|
||||
new DisableSendPolicy(),
|
||||
new SendOptionsPolicy(),
|
||||
new RestrictedItemTypesPolicy(),
|
||||
]);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@@ -113,7 +113,10 @@ import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarde
|
||||
import { WebOrganizationInviteService } from "@bitwarden/web-vault/app/auth/core/services/organization-invite/web-organization-invite.service";
|
||||
|
||||
import { flagEnabled } from "../../utils/flags";
|
||||
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
||||
import {
|
||||
POLICY_EDIT_REGISTER,
|
||||
ossPolicyEditRegister,
|
||||
} from "../admin-console/organizations/policies";
|
||||
import {
|
||||
WebChangePasswordService,
|
||||
WebRegistrationFinishService,
|
||||
@@ -152,7 +155,10 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider(InitService),
|
||||
safeProvider(RouterService),
|
||||
safeProvider(EventService),
|
||||
safeProvider(PolicyListService),
|
||||
safeProvider({
|
||||
provide: POLICY_EDIT_REGISTER,
|
||||
useValue: ossPolicyEditRegister,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: DEFAULT_VAULT_TIMEOUT,
|
||||
deps: [PlatformUtilsService],
|
||||
|
||||
Reference in New Issue
Block a user