From ab4d8df2ae3bc45817dc17f49dbbb7fc64b5ace0 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 10 May 2023 12:51:56 -0700 Subject: [PATCH] [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 --- apps/web/src/locales/en/messages.json | 33 +++- .../components/base-cva.component.ts | 63 ------- .../components/input-checkbox.component.html | 16 -- .../components/input-checkbox.component.ts | 10 - .../organizations/organizations.module.ts | 2 - .../src/app/auth/sso/sso.component.html | 173 +++++++++++------- .../bit-web/src/app/auth/sso/sso.component.ts | 42 +++-- libs/common/src/auth/enums/sso.ts | 6 + .../src/auth/models/api/sso-config.api.ts | 7 +- .../src/auth/models/view/sso-config.view.ts | 5 +- libs/common/src/enums/feature-flag.enum.ts | 1 + 11 files changed, 168 insertions(+), 190 deletions(-) delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/organizations/components/base-cva.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/organizations/components/input-checkbox.component.html delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/organizations/components/input-checkbox.component.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 2e064e4608..fdb28832fd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5218,9 +5218,6 @@ "message": "to require all members to log in with SSO.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, - "ssoPolicyHelpKeyConnector": { - "message": "The require SSO authentication and single organization policies are required to set up Key Connector decryption." - }, "memberDecryptionOption": { "message": "Member decryption options" }, @@ -5230,8 +5227,17 @@ "keyConnector": { "message": "Key Connector" }, - "memberDecryptionKeyConnectorDesc": { - "message": "Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. Contact Bitwarden Support for set up assistance." + "memberDecryptionKeyConnectorDescStart": { + "message": "Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" + }, + "memberDecryptionKeyConnectorDescLink": { + "message": "require SSO authentication and single organization policies", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" + }, + "memberDecryptionKeyConnectorDescEnd": { + "message": "are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "keyConnectorPolicyRestriction": { "message": "\"Login with SSO and Key Connector Decryption\" is activated. This policy will only apply to owners and admins." @@ -5535,7 +5541,7 @@ }, "lastSync": { "message": "Last sync", - "Description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\"" + "description": "Used as a prefix to indicate the last time a sync occured. Example \"Last sync 1968-11-16 00:00:00\"" }, "sponsorshipsSynced": { "message": "Self-hosted sponsorships synced." @@ -6781,5 +6787,20 @@ }, "updateKdfSettings": { "message": "Update KDF settings" + }, + "trustedDeviceEncryption": { + "message": "Trusted device encryption" + }, + "memberDecryptionTdeDescStart": { + "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The master password reset policy with automatic enrollment will turn on when this option is used.'" + }, + "memberDecryptionTdeDescLink": { + "message": "master password reset policy", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The master password reset policy with automatic enrollment will turn on when this option is used.'" + }, + "memberDecryptionTdeDescEnd": { + "message": "with automatic enrollment will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The master password reset policy with automatic enrollment will turn on when this option is used.'" } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/components/base-cva.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/components/base-cva.component.ts deleted file mode 100644 index 11fb78e41e..0000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/components/base-cva.component.ts +++ /dev/null @@ -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 -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/components/input-checkbox.component.html b/bitwarden_license/bit-web/src/app/admin-console/organizations/components/input-checkbox.component.html deleted file mode 100644 index 2c3c8639c1..0000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/components/input-checkbox.component.html +++ /dev/null @@ -1,16 +0,0 @@ -
-
- - -
- {{ - helperText - }} -
diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/components/input-checkbox.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/components/input-checkbox.component.ts deleted file mode 100644 index b494c6c817..0000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/components/input-checkbox.component.ts +++ /dev/null @@ -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 {} diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations.module.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations.module.ts index 6cbd6ad663..08f7dea640 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations.module.ts @@ -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, diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html index 7c24e0686d..0f68788acd 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html @@ -8,32 +8,24 @@ title="{{ 'loading' | i18n }}" aria-hidden="true" > - {{ "loading" | i18n }} + {{ "loading" | i18n }} -
+

{{ "ssoPolicyHelpStart" | i18n }} {{ "ssoPolicyHelpLink" | i18n }} {{ "ssoPolicyHelpEnd" | i18n }}
- {{ "ssoPolicyHelpKeyConnector" | i18n }}

- + + {{ "allowSso" | i18n }} + + {{ "allowSsoDesc" | i18n }} + {{ "ssoIdentifier" | i18n }} @@ -43,31 +35,25 @@
-
- -
- - -
-
- - -
-
+ + + {{ "memberDecryptionKeyConnectorDescStart" | i18n }} + {{ "memberDecryptionKeyConnectorDescLink" | i18n }} + {{ "memberDecryptionKeyConnectorDescEnd" | i18n }} + + + + + + {{ "trustedDeviceEncryption" | i18n }} + + + {{ "memberDecryptionTdeDescStart" | i18n }} + {{ "memberDecryptionTdeDescLink" | i18n }} + {{ "memberDecryptionTdeDescEnd" | i18n }} + + + - + {{ "keyConnectorWarning" | i18n }} @@ -205,11 +216,15 @@
- + + {{ "getClaimsFromUserInfoEndpoint" | i18n }} + +
- + + {{ "spWantAssertionsSigned" | i18n }} + + - + + {{ "spValidateCertificates" | i18n }} + +
@@ -462,21 +485,29 @@ [label]="'idpAllowUnsolicitedAuthnResponse' | i18n" > --> - + + {{ "idpAllowOutboundLogoutRequests" | i18n }} + + - + + {{ "idpSignAuthenticationRequests" | i18n }} + + - diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts index 56619a08fb..f6646bd2f9 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts @@ -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(); + showTdeOptions = false; + showKeyConnectorOptions = false; showOpenIdCustomizations = false; @@ -90,7 +96,6 @@ export class SsoComponent implements OnInit, OnDestroy { haveTestedKeyConnector = false; organizationId: string; organization: Organization; - formPromise: Promise; callbackPath: string; signedOutCallbackPath: string; @@ -147,7 +152,7 @@ export class SsoComponent implements OnInit, OnDestroy { protected ssoConfigForm = this.formBuilder.group>({ 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) ); } diff --git a/libs/common/src/auth/enums/sso.ts b/libs/common/src/auth/enums/sso.ts index d9be7274bb..0c86a27151 100644 --- a/libs/common/src/auth/enums/sso.ts +++ b/libs/common/src/auth/enums/sso.ts @@ -4,6 +4,12 @@ export enum SsoType { Saml2 = 2, } +export enum MemberDecryptionType { + MasterPassword = 0, + KeyConnector = 1, + TrustedDeviceEncryption = 2, +} + export enum OpenIdConnectRedirectBehavior { RedirectGet = 0, FormPost = 1, diff --git a/libs/common/src/auth/models/api/sso-config.api.ts b/libs/common/src/auth/models/api/sso-config.api.ts index e681d2c27b..98408712dc 100644 --- a/libs/common/src/auth/models/api/sso-config.api.ts +++ b/libs/common/src/auth/models/api/sso-config.api.ts @@ -1,5 +1,6 @@ import { BaseResponse } from "../../../models/response/base.response"; import { + MemberDecryptionType, OpenIdConnectRedirectBehavior, Saml2BindingType, Saml2NameIdFormat, @@ -11,8 +12,8 @@ import { SsoConfigView } from "../view/sso-config.view"; export class SsoConfigApi extends BaseResponse { static fromView(view: SsoConfigView, api = new SsoConfigApi()) { api.configType = view.configType; + api.memberDecryptionType = view.memberDecryptionType; - api.keyConnectorEnabled = view.keyConnectorEnabled; api.keyConnectorUrl = view.keyConnectorUrl; if (api.configType === SsoType.OpenIdConnect) { @@ -52,8 +53,8 @@ export class SsoConfigApi extends BaseResponse { return api; } configType: SsoType; + memberDecryptionType: MemberDecryptionType; - keyConnectorEnabled: boolean; keyConnectorUrl: string; // OpenId @@ -95,8 +96,8 @@ export class SsoConfigApi extends BaseResponse { } this.configType = this.getResponseProperty("ConfigType"); + this.memberDecryptionType = this.getResponseProperty("MemberDecryptionType"); - this.keyConnectorEnabled = this.getResponseProperty("KeyConnectorEnabled"); this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl"); this.authority = this.getResponseProperty("Authority"); diff --git a/libs/common/src/auth/models/view/sso-config.view.ts b/libs/common/src/auth/models/view/sso-config.view.ts index 073e48654d..4830bad1a2 100644 --- a/libs/common/src/auth/models/view/sso-config.view.ts +++ b/libs/common/src/auth/models/view/sso-config.view.ts @@ -1,5 +1,6 @@ import { View } from "../../../models/view/view"; import { + MemberDecryptionType, OpenIdConnectRedirectBehavior, Saml2BindingType, Saml2NameIdFormat, @@ -14,7 +15,7 @@ export class SsoConfigView extends View { configType: SsoType; - keyConnectorEnabled: boolean; + memberDecryptionType: MemberDecryptionType; keyConnectorUrl: string; openId: { @@ -66,8 +67,8 @@ export class SsoConfigView extends View { } this.configType = orgSsoResponse.data.configType; + this.memberDecryptionType = orgSsoResponse.data.memberDecryptionType; - this.keyConnectorEnabled = orgSsoResponse.data.keyConnectorEnabled; this.keyConnectorUrl = orgSsoResponse.data.keyConnectorUrl; if (this.configType === SsoType.OpenIdConnect) { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 47d7dfcd6a..e8a05911b9 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -1,4 +1,5 @@ export enum FeatureFlag { DisplayEuEnvironmentFlag = "display-eu-environment", DisplayLowKdfIterationWarningFlag = "display-kdf-iteration-warning", + TrustedDeviceEncryption = "trusted-device-encryption", }