diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 2e064e46086..fdb28832fd3 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 11fb78e41e0..00000000000 --- 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 2c3c8639c19..00000000000 --- 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 b494c6c817a..00000000000 --- 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 6cbd6ad663b..08f7dea6406 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 7c24e0686da..0f68788acd5 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 56619a08fbb..f6646bd2f97 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 d9be7274bb9..0c86a27151f 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 e681d2c27b9..98408712dcb 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 073e48654d3..4830bad1a24 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 47d7dfcd6a2..e8a05911b9f 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", }