| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import '//resources/cr_elements/cr_button/cr_button.js'; |
| import '//resources/cr_elements/cr_input/cr_input.js'; |
| import '//resources/cr_elements/cr_radio_button/cr_radio_button.js'; |
| import '//resources/cr_elements/cr_radio_group/cr_radio_group.js'; |
| import '//resources/cr_elements/cr_shared_style.css.js'; |
| import '../settings_shared.css.js'; |
| import '../settings_vars.css.js'; |
| |
| import type {CrInputElement} from '//resources/cr_elements/cr_input/cr_input.js'; |
| // <if expr="is_chromeos"> |
| import type {CrRadioGroupElement} from '//resources/cr_elements/cr_radio_group/cr_radio_group.js'; |
| // </if> |
| |
| import {assert} from '//resources/js/assert.js'; |
| import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| import type {SyncPrefs, SyncStatus} from '/shared/settings/people_page/sync_browser_proxy.js'; |
| import {SyncBrowserProxyImpl} from '/shared/settings/people_page/sync_browser_proxy.js'; |
| |
| import {getTemplate} from './sync_encryption_options.html.js'; |
| |
| /** |
| * Names of the radio buttons which allow the user to choose their encryption |
| * mechanism. |
| */ |
| enum RadioButtonNames { |
| ENCRYPT_WITH_GOOGLE = 'encrypt-with-google', |
| ENCRYPT_WITH_PASSPHRASE = 'encrypt-with-passphrase', |
| } |
| |
| export class SettingsSyncEncryptionOptionsElement extends PolymerElement { |
| static get is() { |
| return 'settings-sync-encryption-options'; |
| } |
| |
| static get template() { |
| return getTemplate(); |
| } |
| |
| static get properties() { |
| return { |
| syncPrefs: { |
| type: Object, |
| notify: true, |
| }, |
| |
| syncStatus: Object, |
| |
| existingPassphraseLabel: { |
| type: String, |
| }, |
| |
| /** |
| * Whether the "create passphrase" inputs should be shown. These inputs |
| * give the user the opportunity to use a custom passphrase instead of |
| * authenticating with their Google credentials. |
| */ |
| creatingNewPassphrase_: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| /** |
| * The passphrase input field value. |
| */ |
| passphrase_: { |
| type: String, |
| value: '', |
| }, |
| |
| /** |
| * The passphrase confirmation input field value. |
| */ |
| confirmation_: { |
| type: String, |
| value: '', |
| }, |
| |
| disableEncryptionOptions_: { |
| type: Boolean, |
| computed: 'computeDisableEncryptionOptions_(' + |
| 'syncPrefs, syncStatus)', |
| observer: 'disableEncryptionOptionsChanged_', |
| }, |
| }; |
| } |
| |
| declare syncPrefs: SyncPrefs|null; |
| declare syncStatus: SyncStatus|null; |
| declare existingPassphraseLabel: string; |
| declare private creatingNewPassphrase_: boolean; |
| declare private passphrase_: string; |
| declare private confirmation_: string; |
| declare private disableEncryptionOptions_: boolean; |
| private isSettingEncryptionPassphrase_: boolean; |
| |
| constructor() { |
| super(); |
| |
| /** |
| * Whether there's a setEncryptionPassphrase() call pending response, in |
| * which case the component should wait before making a new call. |
| */ |
| this.isSettingEncryptionPassphrase_ = false; |
| } |
| |
| // <if expr="is_chromeos"> |
| /** |
| * Returns the encryption options CrRadioGroupElement. |
| */ |
| getEncryptionsRadioButtons(): CrRadioGroupElement|null { |
| return this.shadowRoot!.querySelector('cr-radio-group'); |
| } |
| // </if> |
| |
| /** |
| * Whether we should disable the radio buttons that allow choosing the |
| * encryption options for Sync. |
| * We disable the buttons if: |
| * (a) full data encryption is enabled, or, |
| * (b) full data encryption is not allowed (so far, only applies to |
| * supervised accounts), or, |
| * (c) current encryption keys are missing, or, |
| * (d) the user is a supervised account. |
| */ |
| private computeDisableEncryptionOptions_(): boolean { |
| return !!( |
| (this.syncPrefs && |
| (this.syncPrefs.encryptAllData || |
| !this.syncPrefs.customPassphraseAllowed || |
| this.syncPrefs.trustedVaultKeysRequired)) || |
| (this.syncStatus && this.syncStatus.supervisedUser)); |
| } |
| |
| private disableEncryptionOptionsChanged_() { |
| if (this.disableEncryptionOptions_) { |
| this.creatingNewPassphrase_ = false; |
| } |
| } |
| |
| /** |
| * @param passphrase The passphrase input field value |
| * @param confirmation The passphrase confirmation input field value. |
| * @return Whether the passphrase save button should be enabled. |
| */ |
| private isSaveNewPassphraseEnabled_(passphrase: string, confirmation: string): |
| boolean { |
| return passphrase !== '' && confirmation !== ''; |
| } |
| |
| private onNewPassphraseInputKeypress_(e: KeyboardEvent) { |
| if (e.type === 'keypress' && e.key !== 'Enter') { |
| return; |
| } |
| this.saveNewPassphrase_(); |
| } |
| |
| private onSaveNewPassphraseClick_() { |
| this.saveNewPassphrase_(); |
| } |
| |
| /** |
| * Sends the newly created custom sync passphrase to the browser. |
| */ |
| private saveNewPassphrase_() { |
| assert(this.creatingNewPassphrase_); |
| chrome.metricsPrivate.recordUserAction('Sync_SaveNewPassphraseClicked'); |
| |
| if (this.isSettingEncryptionPassphrase_) { |
| return; |
| } |
| |
| // If a new password has been entered but it is invalid, do not send the |
| // sync state to the API. |
| if (!this.validateCreatedPassphrases_()) { |
| return; |
| } |
| |
| this.isSettingEncryptionPassphrase_ = true; |
| SyncBrowserProxyImpl.getInstance() |
| .setEncryptionPassphrase(this.passphrase_) |
| .then(successfullySet => { |
| // TODO(crbug.com/40725814): Rename the event, there is no change if |
| // |successfullySet| is false. It should also mention 'encryption |
| // passphrase' in its name. |
| this.dispatchEvent(new CustomEvent('passphrase-changed', { |
| bubbles: true, |
| composed: true, |
| detail: {didChange: successfullySet}, |
| })); |
| this.isSettingEncryptionPassphrase_ = false; |
| }); |
| } |
| |
| private onEncryptionRadioSelectionChanged_(event: |
| CustomEvent<{value: string}>) { |
| this.creatingNewPassphrase_ = |
| event.detail.value === RadioButtonNames.ENCRYPT_WITH_PASSPHRASE; |
| } |
| |
| /** |
| * Computed binding returning the selected encryption radio button. |
| */ |
| private selectedEncryptionRadio_() { |
| return this.syncPrefs!.encryptAllData || this.creatingNewPassphrase_ ? |
| RadioButtonNames.ENCRYPT_WITH_PASSPHRASE : |
| RadioButtonNames.ENCRYPT_WITH_GOOGLE; |
| } |
| |
| /** |
| * Checks the supplied passphrases to ensure that they are not empty and that |
| * they match each other. Additionally, displays error UI if they are invalid. |
| * @return Whether the check was successful (i.e., that the passphrases were |
| * valid). |
| */ |
| private validateCreatedPassphrases_(): boolean { |
| const emptyPassphrase = !this.passphrase_; |
| const mismatchedPassphrase = this.passphrase_ !== this.confirmation_; |
| |
| this.shadowRoot!.querySelector<CrInputElement>( |
| '#passphraseInput')!.invalid = emptyPassphrase; |
| this.shadowRoot! |
| .querySelector<CrInputElement>( |
| '#passphraseConfirmationInput')!.invalid = |
| !emptyPassphrase && mismatchedPassphrase; |
| |
| return !emptyPassphrase && !mismatchedPassphrase; |
| } |
| |
| private onLearnMoreClick_(event: Event) { |
| if ((event.target as HTMLElement).tagName === 'A') { |
| // Stop the propagation of events, so that clicking on links inside |
| // checkboxes or radio buttons won't change the value. |
| event.stopPropagation(); |
| } |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'settings-sync-encryption-options': SettingsSyncEncryptionOptionsElement; |
| } |
| } |
| |
| customElements.define( |
| SettingsSyncEncryptionOptionsElement.is, |
| SettingsSyncEncryptionOptionsElement); |