[PM-10104] add generator password type policy (#10275)

* add generator password type policy
* limit options display to only valid options when override in effect
* remove defaultType i18n message
This commit is contained in:
✨ Audrey ✨ 2024-08-06 11:49:27 -04:00 committed by GitHub
parent 2ce8500391
commit 4a5a0ca537
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 130 additions and 59 deletions

View File

@ -6,59 +6,70 @@
<div class="tw-grid tw-grid-cols-12 tw-gap-4"> <div class="tw-grid tw-grid-cols-12 tw-gap-4">
<bit-form-field class="tw-col-span-6 tw-mb-0"> <bit-form-field class="tw-col-span-6 tw-mb-0">
<bit-label>{{ "defaultType" | i18n }}</bit-label> <bit-label>{{ "overridePasswordTypePolicy" | i18n }}</bit-label>
<bit-select formControlName="defaultType" id="defaultType"> <bit-select formControlName="overridePasswordType" id="overrideType">
<bit-option *ngFor="let o of defaultTypes" [value]="o.value" [label]="o.name"></bit-option> <bit-option
*ngFor="let o of overridePasswordTypeOptions"
[value]="o.value"
[label]="o.name"
></bit-option>
</bit-select> </bit-select>
</bit-form-field> </bit-form-field>
</div> </div>
<h3 bitTypography="h3" class="tw-mt-4">{{ "password" | i18n }}</h3> <!-- password-specific policies -->
<div class="tw-grid tw-grid-cols-12 tw-gap-4"> <div *ngIf="showPasswordPolicies$ | async">
<bit-form-field class="tw-col-span-6"> <h3 bitTypography="h3" class="tw-mt-4">{{ "password" | i18n }}</h3>
<bit-label>{{ "minLength" | i18n }}</bit-label> <div class="tw-grid tw-grid-cols-12 tw-gap-4">
<input bitInput type="number" min="5" max="128" formControlName="minLength" /> <bit-form-field class="tw-col-span-6">
</bit-form-field> <bit-label>{{ "minLength" | i18n }}</bit-label>
<input bitInput type="number" min="5" max="128" formControlName="minLength" />
</bit-form-field>
</div>
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
<bit-form-field class="tw-col-span-6">
<bit-label>{{ "minNumbers" | i18n }}</bit-label>
<input bitInput type="number" min="0" max="9" formControlName="minNumbers" />
</bit-form-field>
<bit-form-field class="tw-col-span-6">
<bit-label>{{ "minSpecial" | i18n }}</bit-label>
<input bitInput type="number" min="0" max="9" formControlName="minSpecial" />
</bit-form-field>
</div>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="useUpper" id="useUpper" />
<bit-label>A-Z</bit-label>
</bit-form-control>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="useLower" id="useLower" />
<bit-label>a-z</bit-label>
</bit-form-control>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="useNumbers" id="useNumbers" />
<bit-label>0-9</bit-label>
</bit-form-control>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="useSpecial" id="useSpecial" />
<bit-label>!&#64;#$%^&amp;*</bit-label>
</bit-form-control>
</div> </div>
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
<bit-form-field class="tw-col-span-6"> <!-- passphrase-specific policies -->
<bit-label>{{ "minNumbers" | i18n }}</bit-label> <div *ngIf="showPassphrasePolicies$ | async">
<input bitInput type="number" min="0" max="9" formControlName="minNumbers" /> <h3 bitTypography="h3" class="tw-mt-4">{{ "passphrase" | i18n }}</h3>
</bit-form-field> <div class="tw-grid tw-grid-cols-12 tw-gap-4">
<bit-form-field class="tw-col-span-6"> <bit-form-field class="tw-col-span-6">
<bit-label>{{ "minSpecial" | i18n }}</bit-label> <bit-label>{{ "minimumNumberOfWords" | i18n }}</bit-label>
<input bitInput type="number" min="0" max="9" formControlName="minSpecial" /> <input bitInput type="number" min="3" max="20" formControlName="minNumberWords" />
</bit-form-field> </bit-form-field>
</div>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="capitalize" id="capitalize" />
<bit-label>{{ "capitalize" | i18n }}</bit-label>
</bit-form-control>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="includeNumber" id="includeNumber" />
<bit-label>{{ "includeNumber" | i18n }}</bit-label>
</bit-form-control>
</div> </div>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="useUpper" id="useUpper" />
<bit-label>A-Z</bit-label>
</bit-form-control>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="useLower" id="useLower" />
<bit-label>a-z</bit-label>
</bit-form-control>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="useNumbers" id="useNumbers" />
<bit-label>0-9</bit-label>
</bit-form-control>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="useSpecial" id="useSpecial" />
<bit-label>!&#64;#$%^&amp;*</bit-label>
</bit-form-control>
<h3 bitTypography="h3" class="tw-mt-4">{{ "passphrase" | i18n }}</h3>
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
<bit-form-field class="tw-col-span-6">
<bit-label>{{ "minimumNumberOfWords" | i18n }}</bit-label>
<input bitInput type="number" min="3" max="20" formControlName="minNumberWords" />
</bit-form-field>
</div>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="capitalize" id="capitalize" />
<bit-label>{{ "capitalize" | i18n }}</bit-label>
</bit-form-control>
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="includeNumber" id="includeNumber" />
<bit-label>{{ "includeNumber" | i18n }}</bit-label>
</bit-form-control>
</div> </div>

View File

@ -1,8 +1,11 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { UntypedFormBuilder, Validators } from "@angular/forms"; import { UntypedFormBuilder, Validators } from "@angular/forms";
import { BehaviorSubject, map } from "rxjs";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DefaultPassphraseBoundaries, DefaultPasswordBoundaries } from "@bitwarden/generator-core";
import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
@ -19,20 +22,59 @@ export class PasswordGeneratorPolicy extends BasePolicy {
}) })
export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { export class PasswordGeneratorPolicyComponent extends BasePolicyComponent {
data = this.formBuilder.group({ data = this.formBuilder.group({
defaultType: [null], overridePasswordType: [null],
minLength: [null, [Validators.min(5), Validators.max(128)]], minLength: [
null,
[
Validators.min(DefaultPasswordBoundaries.length.min),
Validators.max(DefaultPasswordBoundaries.length.max),
],
],
useUpper: [null], useUpper: [null],
useLower: [null], useLower: [null],
useNumbers: [null], useNumbers: [null],
useSpecial: [null], useSpecial: [null],
minNumbers: [null, [Validators.min(0), Validators.max(9)]], minNumbers: [
minSpecial: [null, [Validators.min(0), Validators.max(9)]], null,
minNumberWords: [null, [Validators.min(3), Validators.max(20)]], [
Validators.min(DefaultPasswordBoundaries.minDigits.min),
Validators.max(DefaultPasswordBoundaries.minDigits.max),
],
],
minSpecial: [
null,
[
Validators.min(DefaultPasswordBoundaries.minSpecialCharacters.min),
Validators.max(DefaultPasswordBoundaries.minSpecialCharacters.max),
],
],
minNumberWords: [
null,
[
Validators.min(DefaultPassphraseBoundaries.numWords.min),
Validators.max(DefaultPassphraseBoundaries.numWords.max),
],
],
capitalize: [null], capitalize: [null],
includeNumber: [null], includeNumber: [null],
}); });
defaultTypes: { name: string; value: string }[]; overridePasswordTypeOptions: { name: string; value: string }[];
// These subjects cache visibility of the sub-options for passwords
// and passphrases; without them policy controls don't show up at all.
private showPasswordPolicies = new BehaviorSubject<boolean>(true);
private showPassphrasePolicies = new BehaviorSubject<boolean>(true);
/** Emits `true` when the password policy options should be displayed */
get showPasswordPolicies$() {
return this.showPasswordPolicies.asObservable();
}
/** Emits `true` when the passphrase policy options should be displayed */
get showPassphrasePolicies$() {
return this.showPassphrasePolicies.asObservable();
}
constructor( constructor(
private formBuilder: UntypedFormBuilder, private formBuilder: UntypedFormBuilder,
@ -40,10 +82,27 @@ export class PasswordGeneratorPolicyComponent extends BasePolicyComponent {
) { ) {
super(); super();
this.defaultTypes = [ this.overridePasswordTypeOptions = [
{ name: i18nService.t("userPreference"), value: null }, { name: i18nService.t("userPreference"), value: null },
{ name: i18nService.t("password"), value: "password" }, { name: i18nService.t("password"), value: PASSWORD_POLICY_VALUE },
{ name: i18nService.t("passphrase"), value: "passphrase" }, { name: i18nService.t("passphrase"), value: "passphrase" },
]; ];
this.data.valueChanges
.pipe(isEnabled(PASSWORD_POLICY_VALUE), takeUntilDestroyed())
.subscribe(this.showPasswordPolicies);
this.data.valueChanges
.pipe(isEnabled(PASSPHRASE_POLICY_VALUE), takeUntilDestroyed())
.subscribe(this.showPassphrasePolicies);
} }
} }
const PASSWORD_POLICY_VALUE = "password";
const PASSPHRASE_POLICY_VALUE = "passphrase";
function isEnabled(enabledValue: string) {
return map((d: { overridePasswordType: string }) => {
const type = d?.overridePasswordType ?? enabledValue;
return type === enabledValue;
});
}

View File

@ -4145,8 +4145,9 @@
"minimumNumberOfWords": { "minimumNumberOfWords": {
"message": "Minimum number of words" "message": "Minimum number of words"
}, },
"defaultType": { "overridePasswordTypePolicy": {
"message": "Default type" "message": "Password Type",
"description": "Name of the password generator policy that overrides the user's password/passphrase selection."
}, },
"userPreference": { "userPreference": {
"message": "User preference" "message": "User preference"