mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 02:03:39 +00:00
[AC-1145] Add trusted devices option to encryption settings on sso config (#5383)
* [AC-1145] Add TDE feature flag * [AC-1145] Update sso-config to use new member decryption type and remove keyConnectorEnabled * [AC-1145] Add new TDE option to SSO config form and update to CL radio buttons * [AC-1145] Update checkboxes to CL checkboxes * [AC-1145] Fix messages.json warning * [AC-1145] Update to new form async actions * [AC-1145] Modify key connector option display logic to check for TDE feature flag * [AC-1145] Remove obsolete app-checkbox component * [AC-1145] Update TDE option description to refer to master password reset policy
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
import { Directive, Input, OnInit, Self } from "@angular/core";
|
||||
import { ControlValueAccessor, UntypedFormControl, NgControl, Validators } from "@angular/forms";
|
||||
|
||||
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||
@Directive()
|
||||
export abstract class BaseCvaComponent implements ControlValueAccessor, OnInit {
|
||||
get describedById() {
|
||||
return this.showDescribedBy ? this.controlId + "Desc" : null;
|
||||
}
|
||||
|
||||
get showDescribedBy() {
|
||||
return this.helperText != null || this.controlDir.control.hasError("required");
|
||||
}
|
||||
|
||||
get isRequired() {
|
||||
return this.controlDir.control.hasValidator(Validators.required);
|
||||
}
|
||||
|
||||
@Input() label: string;
|
||||
@Input() controlId: string;
|
||||
@Input() helperText: string;
|
||||
|
||||
internalControl = new UntypedFormControl("");
|
||||
|
||||
protected onChange: any;
|
||||
protected onTouched: any;
|
||||
|
||||
constructor(@Self() public controlDir: NgControl) {
|
||||
this.controlDir.valueAccessor = this;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.internalControl.valueChanges.subscribe(this.onValueChangesInternal);
|
||||
}
|
||||
|
||||
onBlurInternal() {
|
||||
this.onTouched();
|
||||
}
|
||||
|
||||
// CVA interfaces
|
||||
writeValue(value: string) {
|
||||
this.internalControl.setValue(value);
|
||||
}
|
||||
|
||||
registerOnChange(fn: any) {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any) {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean) {
|
||||
if (isDisabled) {
|
||||
this.internalControl.disable();
|
||||
} else {
|
||||
this.internalControl.enable();
|
||||
}
|
||||
}
|
||||
|
||||
protected onValueChangesInternal: any = (value: string) => this.onChange(value);
|
||||
// End CVA interfaces
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[attr.id]="controlId"
|
||||
[attr.aria-describedby]="describedById"
|
||||
[formControl]="internalControl"
|
||||
(blur)="onBlurInternal()"
|
||||
/>
|
||||
<label class="form-check-label" [attr.for]="controlId">{{ label }}</label>
|
||||
</div>
|
||||
<small *ngIf="showDescribedBy" [attr.id]="describedById" class="form-text text-muted">{{
|
||||
helperText
|
||||
}}</small>
|
||||
</div>
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { BaseCvaComponent } from "./base-cva.component";
|
||||
|
||||
/** For use in the SSO Config Form only - will be deprecated by the Component Library */
|
||||
@Component({
|
||||
selector: "app-input-checkbox",
|
||||
templateUrl: "input-checkbox.component.html",
|
||||
})
|
||||
export class InputCheckboxComponent extends BaseCvaComponent {}
|
||||
@@ -4,7 +4,6 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
|
||||
|
||||
import { SsoComponent } from "../../auth/sso/sso.component";
|
||||
|
||||
import { InputCheckboxComponent } from "./components/input-checkbox.component";
|
||||
import { DomainAddEditDialogComponent } from "./manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component";
|
||||
import { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component";
|
||||
import { ScimComponent } from "./manage/scim.component";
|
||||
@@ -13,7 +12,6 @@ import { OrganizationsRoutingModule } from "./organizations-routing.module";
|
||||
@NgModule({
|
||||
imports: [SharedModule, OrganizationsRoutingModule],
|
||||
declarations: [
|
||||
InputCheckboxComponent,
|
||||
SsoComponent,
|
||||
ScimComponent,
|
||||
DomainVerificationComponent,
|
||||
|
||||
@@ -8,32 +8,24 @@
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[formGroup]="ssoConfigForm"
|
||||
[appApiAction]="formPromise"
|
||||
*ngIf="!loading"
|
||||
>
|
||||
<form [formGroup]="ssoConfigForm" [bitSubmit]="submit" *ngIf="!loading">
|
||||
<p>
|
||||
{{ "ssoPolicyHelpStart" | i18n }}
|
||||
<a routerLink="../policies">{{ "ssoPolicyHelpLink" | i18n }}</a>
|
||||
{{ "ssoPolicyHelpEnd" | i18n }}
|
||||
<br />
|
||||
{{ "ssoPolicyHelpKeyConnector" | i18n }}
|
||||
</p>
|
||||
|
||||
<!-- Root form -->
|
||||
<ng-container>
|
||||
<app-input-checkbox
|
||||
controlId="enabled"
|
||||
formControlName="enabled"
|
||||
[label]="'allowSso' | i18n"
|
||||
[helperText]="'allowSsoDesc' | i18n"
|
||||
></app-input-checkbox>
|
||||
<bit-form-control>
|
||||
<bit-label>{{ "allowSso" | i18n }}</bit-label>
|
||||
<input bitCheckbox type="checkbox" formControlName="enabled" id="enabled" />
|
||||
<bit-hint>{{ "allowSsoDesc" | i18n }}</bit-hint>
|
||||
</bit-form-control>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "ssoIdentifier" | i18n }}</bit-label>
|
||||
@@ -43,31 +35,25 @@
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ "memberDecryptionOption" | i18n }}</label>
|
||||
<div class="form-check form-check-block">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
id="memberDecryptionPass"
|
||||
[value]="false"
|
||||
formControlName="keyConnectorEnabled"
|
||||
/>
|
||||
<label class="form-check-label" for="memberDecryptionPass">
|
||||
{{ "masterPass" | i18n }}
|
||||
<small>{{ "memberDecryptionPassDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2 form-check-block">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
id="memberDecryptionKey"
|
||||
[value]="true"
|
||||
formControlName="keyConnectorEnabled"
|
||||
[attr.disabled]="!organization.useKeyConnector || null"
|
||||
/>
|
||||
<label class="form-check-label" for="memberDecryptionKey">
|
||||
<bit-radio-group formControlName="memberDecryptionType">
|
||||
<bit-label>{{ "memberDecryptionOption" | i18n }}</bit-label>
|
||||
|
||||
<bit-radio-button
|
||||
class="tw-block"
|
||||
id="memberDecryptionPass"
|
||||
[value]="memberDecryptionType.MasterPassword"
|
||||
>
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
</bit-radio-button>
|
||||
|
||||
<bit-radio-button
|
||||
class="tw-block"
|
||||
id="memberDecryptionKey"
|
||||
[value]="memberDecryptionType.KeyConnector"
|
||||
[disabled]="!organization.useKeyConnector || null"
|
||||
*ngIf="showKeyConnectorOptions"
|
||||
>
|
||||
<bit-label>
|
||||
{{ "keyConnector" | i18n }}
|
||||
<a
|
||||
target="_blank"
|
||||
@@ -77,13 +63,38 @@
|
||||
>
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
<small>{{ "memberDecryptionKeyConnectorDesc" | i18n }}</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</bit-label>
|
||||
<bit-hint>
|
||||
{{ "memberDecryptionKeyConnectorDescStart" | i18n }}
|
||||
<a routerLink="../policies">{{ "memberDecryptionKeyConnectorDescLink" | i18n }}</a>
|
||||
{{ "memberDecryptionKeyConnectorDescEnd" | i18n }}
|
||||
</bit-hint>
|
||||
</bit-radio-button>
|
||||
|
||||
<bit-radio-button
|
||||
class="tw-block"
|
||||
id="memberDecryptionTde"
|
||||
[value]="memberDecryptionType.TrustedDeviceEncryption"
|
||||
*ngIf="showTdeOptions"
|
||||
>
|
||||
<bit-label>
|
||||
{{ "trustedDeviceEncryption" | i18n }}
|
||||
</bit-label>
|
||||
<bit-hint>
|
||||
{{ "memberDecryptionTdeDescStart" | i18n }}
|
||||
<a routerLink="../policies">{{ "memberDecryptionTdeDescLink" | i18n }}</a>
|
||||
{{ "memberDecryptionTdeDescEnd" | i18n }}
|
||||
</bit-hint>
|
||||
</bit-radio-button>
|
||||
</bit-radio-group>
|
||||
|
||||
<!-- Key Connector -->
|
||||
<ng-container *ngIf="ssoConfigForm.get('keyConnectorEnabled').value">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
ssoConfigForm.value.memberDecryptionType === memberDecryptionType.KeyConnector &&
|
||||
showKeyConnectorOptions
|
||||
"
|
||||
>
|
||||
<app-callout type="warning" [useAlertRole]="true">
|
||||
{{ "keyConnectorWarning" | i18n }}
|
||||
</app-callout>
|
||||
@@ -205,11 +216,15 @@
|
||||
</select>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-checkbox
|
||||
controlId="getClaimsFromUserInfoEndpoint"
|
||||
formControlName="getClaimsFromUserInfoEndpoint"
|
||||
[label]="'getClaimsFromUserInfoEndpoint' | i18n"
|
||||
></app-input-checkbox>
|
||||
<bit-form-control>
|
||||
<bit-label>{{ "getClaimsFromUserInfoEndpoint" | i18n }}</bit-label>
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="getClaimsFromUserInfoEndpoint"
|
||||
id="getClaimsFromUserInfoEndpoint"
|
||||
/>
|
||||
</bit-form-control>
|
||||
|
||||
<!-- Optional customizations -->
|
||||
<div
|
||||
@@ -381,17 +396,25 @@
|
||||
</select>
|
||||
</bit-form-field>
|
||||
|
||||
<app-input-checkbox
|
||||
controlId="spWantAssertionsSigned"
|
||||
formControlName="spWantAssertionsSigned"
|
||||
[label]="'spWantAssertionsSigned' | i18n"
|
||||
></app-input-checkbox>
|
||||
<bit-form-control>
|
||||
<bit-label>{{ "spWantAssertionsSigned" | i18n }}</bit-label>
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="spWantAssertionsSigned"
|
||||
id="spWantAssertionsSigned"
|
||||
/>
|
||||
</bit-form-control>
|
||||
|
||||
<app-input-checkbox
|
||||
controlId="spValidateCertificates"
|
||||
formControlName="spValidateCertificates"
|
||||
[label]="'spValidateCertificates' | i18n"
|
||||
></app-input-checkbox>
|
||||
<bit-form-control>
|
||||
<bit-label>{{ "spValidateCertificates" | i18n }}</bit-label>
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="spValidateCertificates"
|
||||
id="spValidateCertificates"
|
||||
/>
|
||||
</bit-form-control>
|
||||
</div>
|
||||
|
||||
<!-- SAML2 IDP -->
|
||||
@@ -462,21 +485,29 @@
|
||||
[label]="'idpAllowUnsolicitedAuthnResponse' | i18n"
|
||||
></app-input-checkbox> -->
|
||||
|
||||
<app-input-checkbox
|
||||
controlId="idpAllowOutboundLogoutRequests"
|
||||
formControlName="idpAllowOutboundLogoutRequests"
|
||||
[label]="'idpAllowOutboundLogoutRequests' | i18n"
|
||||
></app-input-checkbox>
|
||||
<bit-form-control>
|
||||
<bit-label>{{ "idpAllowOutboundLogoutRequests" | i18n }}</bit-label>
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="idpAllowOutboundLogoutRequests"
|
||||
id="idpAllowOutboundLogoutRequests"
|
||||
/>
|
||||
</bit-form-control>
|
||||
|
||||
<app-input-checkbox
|
||||
controlId="idpWantAuthnRequestsSigned"
|
||||
formControlName="idpWantAuthnRequestsSigned"
|
||||
[label]="'idpSignAuthenticationRequests' | i18n"
|
||||
></app-input-checkbox>
|
||||
<bit-form-control>
|
||||
<bit-label>{{ "idpSignAuthenticationRequests" | i18n }}</bit-label>
|
||||
<input
|
||||
bitCheckbox
|
||||
type="checkbox"
|
||||
formControlName="idpWantAuthnRequestsSigned"
|
||||
id="idpWantAuthnRequestsSigned"
|
||||
/>
|
||||
</bit-form-control>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
||||
<button type="submit" buttonType="primary" bitButton bitFormButton>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<bit-error-summary [formGroup]="ssoConfigForm"></bit-error-summary>
|
||||
|
||||
@@ -12,12 +12,14 @@ import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
import { SelectOptions } from "@bitwarden/angular/interfaces/selectOptions";
|
||||
import { ControlsOf } from "@bitwarden/angular/types/controls-of";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import {
|
||||
MemberDecryptionType,
|
||||
OpenIdConnectRedirectBehavior,
|
||||
Saml2BindingType,
|
||||
Saml2NameIdFormat,
|
||||
@@ -28,6 +30,7 @@ import { SsoConfigApi } from "@bitwarden/common/auth/models/api/sso-config.api";
|
||||
import { OrganizationSsoRequest } from "@bitwarden/common/auth/models/request/organization-sso.request";
|
||||
import { OrganizationSsoResponse } from "@bitwarden/common/auth/models/response/organization-sso.response";
|
||||
import { SsoConfigView } from "@bitwarden/common/auth/models/view/sso-config.view";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
import { ssoTypeValidator } from "./sso-type.validator";
|
||||
@@ -40,6 +43,7 @@ const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha2
|
||||
})
|
||||
export class SsoComponent implements OnInit, OnDestroy {
|
||||
readonly ssoType = SsoType;
|
||||
readonly memberDecryptionType = MemberDecryptionType;
|
||||
|
||||
readonly ssoTypeOptions: SelectOptions[] = [
|
||||
{ name: this.i18nService.t("selectType"), value: SsoType.None, disabled: true },
|
||||
@@ -83,6 +87,8 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
];
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
showTdeOptions = false;
|
||||
showKeyConnectorOptions = false;
|
||||
|
||||
showOpenIdCustomizations = false;
|
||||
|
||||
@@ -90,7 +96,6 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
haveTestedKeyConnector = false;
|
||||
organizationId: string;
|
||||
organization: Organization;
|
||||
formPromise: Promise<OrganizationSsoResponse>;
|
||||
|
||||
callbackPath: string;
|
||||
signedOutCallbackPath: string;
|
||||
@@ -147,7 +152,7 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected ssoConfigForm = this.formBuilder.group<ControlsOf<SsoConfigView>>({
|
||||
configType: new FormControl(SsoType.None),
|
||||
keyConnectorEnabled: new FormControl(false),
|
||||
memberDecryptionType: new FormControl(MemberDecryptionType.MasterPassword),
|
||||
keyConnectorUrl: new FormControl(""),
|
||||
openId: this.openIdForm,
|
||||
saml: this.samlForm,
|
||||
@@ -174,7 +179,8 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private organizationService: OrganizationService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private configService: ConfigServiceAbstraction
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -223,6 +229,15 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
const tdeFeatureFlag = await this.configService.getFeatureFlagBool(
|
||||
FeatureFlag.TrustedDeviceEncryption
|
||||
);
|
||||
|
||||
this.showTdeOptions = tdeFeatureFlag && !this.platformUtilsService.isSelfHost();
|
||||
// If the tde flag is not enabled, continue showing the key connector options to keep the UI the same
|
||||
// Once the flag is removed, we can rely on the platformUtilsService.isSelfHost() check alone
|
||||
this.showKeyConnectorOptions = !tdeFeatureFlag || this.platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -244,10 +259,10 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
submit = async () => {
|
||||
this.updateFormValidationState(this.ssoConfigForm);
|
||||
|
||||
if (this.ssoConfigForm.value.keyConnectorEnabled) {
|
||||
if (this.ssoConfigForm.value.memberDecryptionType === MemberDecryptionType.KeyConnector) {
|
||||
this.haveTestedKeyConnector = false;
|
||||
await this.validateKeyConnectorUrl();
|
||||
}
|
||||
@@ -262,18 +277,11 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
request.identifier = this.ssoIdentifierCtrl.value === "" ? null : this.ssoIdentifierCtrl.value;
|
||||
request.data = SsoConfigApi.fromView(this.ssoConfigForm.getRawValue());
|
||||
|
||||
this.formPromise = this.organizationApiService.updateSso(this.organizationId, request);
|
||||
const response = await this.organizationApiService.updateSso(this.organizationId, request);
|
||||
this.populateForm(response);
|
||||
|
||||
try {
|
||||
const response = await this.formPromise;
|
||||
this.populateForm(response);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
|
||||
} catch {
|
||||
// Logged by appApiAction, do nothing
|
||||
}
|
||||
|
||||
this.formPromise = null;
|
||||
}
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("ssoSettingsSaved"));
|
||||
};
|
||||
|
||||
async validateKeyConnectorUrl() {
|
||||
if (this.haveTestedKeyConnector) {
|
||||
@@ -313,7 +321,7 @@ export class SsoComponent implements OnInit, OnDestroy {
|
||||
|
||||
get enableTestKeyConnector() {
|
||||
return (
|
||||
this.ssoConfigForm.get("keyConnectorEnabled").value &&
|
||||
this.ssoConfigForm.value?.memberDecryptionType === MemberDecryptionType.KeyConnector &&
|
||||
!Utils.isNullOrWhitespace(this.keyConnectorUrl?.value)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user