0

Migrate ChromeOS lock_screen_reauth to TypeScript

Bug: https://issuetracker.google.com/315872665
Change-Id: If2e47035c38b4fd637d4fd6c07c9abcd3d594c69
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5501146
Reviewed-by: Nico Weber <thakis@chromium.org>
Commit-Queue: Manuel Gómez <mgomezch@chromium.org>
Reviewed-by: Leon Masopust <lmasopust@google.com>
Cr-Commit-Position: refs/heads/main@{#1341683}
This commit is contained in:
Manuel Gomez 2024-08-14 15:39:23 +00:00 committed by Chromium LUCI CQ
parent 35791f84aa
commit 1c890d9b6c
12 changed files with 590 additions and 651 deletions

@ -799,7 +799,7 @@ IN_PROC_BROWSER_TEST_F(SamlUnlockTest, ScrapedSingle) {
// Make sure that the password is scraped correctly.
ASSERT_TRUE(content::ExecJs(
reauth_dialog_helper->DialogWebContents(),
"$('main-element').authenticator_.addEventListener('authCompleted',"
"$('main-element').authenticator.addEventListener('authCompleted',"
" function(e) {"
" var password = e.detail.password;"
" window.domAutomationController.send(password);"

@ -156,7 +156,7 @@ constexpr char kClientCert2Name[] = "client_2";
constexpr char kLoadingDialog[] = "loadingDialog";
constexpr char kSigninWebview[] = "$('gaia-signin').getSigninFrame()";
constexpr char kSigninWebviewOnLockScreen[] =
"$('main-element').getSigninFrame_()";
"$('main-element').getSigninFrame()";
constexpr char kTestCookieHost[] = "host1.com";
constexpr char kTestCookieName[] = "TestCookie";
constexpr char kTestCookieValue[] = "present";

@ -3,125 +3,48 @@
# found in the LICENSE file.
import("//chrome/common/features.gni")
import("//third_party/closure_compiler/compile_js.gni")
import("//tools/grit/grit_rule.gni")
import("//tools/grit/preprocess_if_expr.gni")
import("//tools/polymer/html_to_js.gni")
import("//ui/webui/resources/tools/generate_grd.gni")
import("//ui/webui/resources/tools/build_webui.gni")
src_files_manifest = "$target_gen_dir/src_files_manifest.json"
gen_files_manifest = "$target_gen_dir/gen_files_manifest.json"
preprocess_folder = "$target_gen_dir/preprocessed"
grd_filename = "$target_gen_dir/resources.grd"
grit("resources") {
defines = chrome_grit_defines
# This is necessary since the GRD is generated during build time.
enable_input_discovery_for_gn_analyze = false
source = grd_filename
deps = [ ":build_grd" ]
outputs = [
"grit/lock_screen_reauth_resources.h",
"grit/lock_screen_reauth_resources_map.h",
"grit/lock_screen_reauth_resources_map.cc",
"lock_screen_reauth_resources.pak",
]
output_dir = "$root_gen_dir/chrome"
}
generate_grd("build_grd") {
build_webui("build") {
grd_prefix = "lock_screen_reauth"
out_grd = grd_filename
deps = [
":preprocess_gen",
":preprocess_src",
]
manifest_files = [
src_files_manifest,
gen_files_manifest,
]
}
preprocess_if_expr("preprocess_src") {
out_folder = preprocess_folder
out_manifest = src_files_manifest
in_files = [
static_files = [
"lock_screen_network.html",
"lock_screen_network.js",
"lock_screen_reauth_app.html",
"lock_screen_reauth_app.js",
]
}
web_component_files = [ "lock_screen_reauth.js" ]
web_component_files = [ "lock_screen_reauth.ts" ]
preprocess_if_expr("preprocess_gen") {
deps = [ ":web_components" ]
in_folder = target_gen_dir
out_folder = preprocess_folder
out_manifest = gen_files_manifest
in_files = web_component_files
}
html_to_js("web_components") {
js_files = web_component_files
}
js_type_check("closure_compile") {
is_polymer3 = true
closure_flags =
default_closure_args + mojom_js_args + [
"js_module_root=" +
rebase_path("//chrome/browser/resources/gaia_auth_host/",
root_build_dir),
"js_module_root=./gen/chrome/browser/resources/gaia_auth_host/",
]
deps = [
":lock_screen_network",
":lock_screen_reauth",
":lock_screen_reauth_app",
non_web_component_files = [
"lock_screen_network.ts",
"lock_screen_reauth_app.ts",
]
}
js_library("lock_screen_network") {
deps = [
"//ash/webui/common/resources:i18n_behavior",
"//ash/webui/common/resources:load_time_data.m",
"//ash/webui/common/resources/network:network_select",
"//ash/webui/common/resources/network:onc_mojo",
ts_definitions = [
"//chrome/browser/resources/gaia_auth_host/authenticator.d.ts",
"//chrome/browser/resources/gaia_auth_host/saml_password_attributes.d.ts",
"//tools/typescript/definitions/chrome_event.d.ts",
"//tools/typescript/definitions/chrome_send.d.ts",
"//tools/typescript/definitions/context_menus.d.ts",
"//tools/typescript/definitions/extension_types.d.ts",
"//tools/typescript/definitions/tabs.d.ts",
"//tools/typescript/definitions/web_request.d.ts",
"//tools/typescript/definitions/webview_tag.d.ts",
]
externs_list = [
"$externs_path/chrome_send.js",
"//ash/webui/common/resources/cr_elements/cr_button/cr_button_externs.js",
"//ash/webui/common/resources/cr_elements/cr_dialog/cr_dialog_externs.js",
]
}
js_library("lock_screen_reauth_app") {
sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/lock_screen_reauth/lock_screen_reauth.js" ]
deps = [
":lock_screen_reauth",
"//ash/webui/common/resources:load_time_data.m",
ts_deps = [
"//ash/webui/common/resources:build_ts",
"//ash/webui/common/resources/cr_elements:build_ts",
"//third_party/polymer/v3_0:library",
"//ui/webui/resources/cr_components/color_change_listener:build_ts",
"//ui/webui/resources/js:build_ts",
"//ui/webui/resources/mojo:build_ts",
]
extra_deps = [ ":web_components" ]
}
js_library("lock_screen_reauth") {
sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/lock_screen_reauth/lock_screen_reauth.js" ]
deps = [
"//ash/webui/common/resources:cr.m",
"//ash/webui/common/resources:i18n_behavior",
"//ash/webui/common/resources:load_time_data.m",
"//chrome/browser/resources/gaia_auth_host:authenticator",
]
externs_list = [
"$externs_path/chrome_send.js",
"//ash/webui/common/resources/cr_elements/cr_button/cr_button_externs.js",
"//ash/webui/common/resources/cr_elements/cr_dialog/cr_dialog_externs.js",
"//ash/webui/common/resources/cr_elements/cr_input/cr_input_externs.js",
"//ash/webui/common/resources/cr_elements/policy/cr_tooltip_icon_externs.js",
]
extra_deps = [ ":web_components" ]
ts_path_mappings =
[ "//lock-reauth/gaia_auth_host/*|" +
rebase_path("//chrome/browser/resources/gaia_auth_host/*",
target_gen_dir) ]
webui_context_type = "trusted"
}

@ -102,14 +102,14 @@ found in the LICENSE file.
<div slot="body">
<div id="select-div">
<network-select
on-network-item-selected="onNetworkItemSelected_"
on-custom-item-selected="onCustomItemSelected_">
on-network-item-selected="onNetworkItemSelected"
on-custom-item-selected="onCustomItemSelected">
</network-select>
</div>
</div>
<div slot="button-container" class="flex layout horizontal">
<cr-button id="cancelButton" class="cancel-button"
on-click="onCloseTap_">
on-click="onCloseClick">
$i18n{lockScreenCancelButton}
</cr-button>
</div>

@ -15,41 +15,40 @@ import './strings.m.js';
import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
import {CrosNetworkConfig, CrosNetworkConfigRemote, StartConnectResult} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
Polymer({
is: 'lock-screen-network-ui',
class LockScreenNetworkUi extends PolymerElement {
static get is() {
return 'lock-screen-network-ui' as const;
}
/** @type {?CrosNetworkConfigRemote} */
networkConfig_: null,
private networkConfig: CrosNetworkConfigRemote =
CrosNetworkConfig.getRemote();
/** @override */
attached() {
this.networkConfig_ = CrosNetworkConfig.getRemote();
const select = this.$$('network-select');
select.customItems = [
override connectedCallback() {
super.connectedCallback();
const select = this.shadowRoot!.querySelector('network-select');
select!.customItems = [
{
customItemName: 'addWiFiListItemName',
polymerIcon: 'cr:add',
customData: 'WiFi',
},
];
},
}
/** @override */
ready() {
override ready() {
super.ready();
chrome.send('initialize');
},
}
/**
* Handles clicks on network items in the <network-select> element by
* attempting a connection to the selected network or requesting a password
* if the network requires a password.
* @param {!Event<!OncMojo.NetworkStateProperties>} event
* @private
*/
onNetworkItemSelected_(event) {
private onNetworkItemSelected(
event: CustomEvent<OncMojo.NetworkStateProperties>) {
const networkState = event.detail;
// If the network is already connected, show network details.
@ -65,25 +64,22 @@ Polymer({
}
// Otherwise, connect.
this.networkConfig_.startConnect(networkState.guid).then(response => {
this.networkConfig.startConnect(networkState.guid).then(response => {
if (response.result === StartConnectResult.kSuccess) {
return;
}
chrome.send('showNetworkConfig', [networkState.guid]);
});
},
}
/**
* @param {!Event<!{detail:{customData: string}}>} event
* @private
*/
onCustomItemSelected_(event) {
private onCustomItemSelected(event: CustomEvent<{customData: string}>) {
chrome.send('addNetwork', [event.detail.customData]);
},
}
/** @private */
onCloseTap_() {
private onCloseClick() {
chrome.send('dialogClose');
},
}
});
}
customElements.define(LockScreenNetworkUi.is, LockScreenNetworkUi);

@ -255,7 +255,7 @@
transform: rotate(180deg);
}
</style>
<div class="content-wrapper" hidden="[[!isVerifyUser_]]" role="dialog"
<div class="content-wrapper" hidden="[[!isVerifyUser]]" role="dialog"
aria-modal="true" id="verifyAccountScreen"
aria-label="$i18n{loginWelcomeMessage}">
<div class="main-container">
@ -276,17 +276,17 @@
</div>
<div class="flex layout horizontal button-container">
<cr-button id="cancelButtonVerifyScreen" class="cancel-button"
on-click="onCloseTap_">
on-click="onCloseClick">
$i18n{lockScreenCancelButton}
</cr-button>
<cr-button id="nextButtonVerifyScreen" class="action-button"
on-click="onVerify_">
on-click="onVerify">
$i18n{lockScreenVerifyButton}
</cr-button>
</div>
</div>
<div class="content-wrapper" hidden="[[!isErrorDisplayed_]]" role="dialog"
<div class="content-wrapper" hidden="[[!isErrorDisplayed]]" role="dialog"
aria-modal="true" id="errorScreen"
aria-label="$i18n{loginWelcomeMessageWithError}">
<div class="main-container">
@ -307,33 +307,33 @@
</div>
<div class="flex layout horizontal button-container">
<cr-button id="cancelButtonErrorScreen" class="cancel-button"
on-click="onCloseTap_">
on-click="onCloseClick">
$i18n{lockScreenCancelButton}
</cr-button>
<cr-button id="nextButton" class="action-button" on-click="onVerify_">
<cr-button id="nextButton" class="action-button" on-click="onVerify">
$i18n{lockScreenVerifyAgainButton}
</cr-button>
</div>
</div>
<div id="body" hidden="[[!isSigninFrameDisplayed_]]">
<div id="body" hidden="[[!isSigninFrameDisplayed]]">
<div id="samlContainer">
<div id="policyCertIndicator"
hidden="[[!policyProvidedTrustedAnchorsUsed_()]]">
hidden="[[!policyProvidedTrustedAnchorsUsed()]]">
<cr-tooltip-icon id="policyCertIcon" icon-class="cr:domain"
tooltip-text="[[i18nDynamic(locale,
'policyProvidedCaCertsTooltipMessage', authDomain_)]]"
'policyProvidedCaCertsTooltipMessage', authDomain)]]"
icon-aria-label="[[i18nDynamic(locale,
'policyProvidedCaCertsTooltipMessage', authDomain_)]]"
'policyProvidedCaCertsTooltipMessage', authDomain)]]"
tooltip-position="bottom">
</cr-tooltip-icon>
</div>
<div id="samlHeader" saml-notice-message$="[[isSaml_]]">
<span id="samlNoticeMessage" hidden="[[!isSaml_]]">
[[i18n('samlNotice', authDomain_)]]
<div id="samlHeader" saml-notice-message$="[[isSaml]]">
<span id="samlNoticeMessage" hidden="[[!isSaml]]">
[[i18n('samlNotice', authDomain)]]
</span>
<cr-icon-button id="saml-close-button" iron-icon="cr:close"
on-click="onCloseTap_" aria-label="$i18n{lockScreenCloseButton}">
on-click="onCloseClick" aria-label="$i18n{lockScreenCloseButton}">
</cr-icon-button>
</div>
</div>
@ -344,54 +344,54 @@
<div>[[i18nDynamic(locale, 'samlChangeProviderMessage')]]</div>
<oobe-text-button id="change-account"
text-key="samlChangeProviderButton"
on-click="onChangeSigninProviderClicked_">
on-click="onChangeSigninProviderClicked">
</oobe-text-button>
</div>
<div id="buttons-container" class="flex layout horizontal button-container"
hidden="[[isSaml_]]">
hidden="[[isSaml]]">
<div class="action-buttons">
<gaia-action-buttons id="gaia-buttons"
authenticator="[[authenticator_]]"
authenticator="[[authenticator]]"
rounded-button="True"
on-set-focus-to-webview="setFocusToWebview_">
on-set-focus-to-webview="setFocusToWebview">
</gaia-action-buttons>
</div>
</div>
</div>
<div id="samlConfirmPasswordScreen" class="content-wrapper"
hidden="[[!isConfirmPassword_]]">
hidden="[[!isConfirmPassword]]">
<div class="main-container">
<div class="header">
<iron-icon class="title-icon" icon="oobe-32:lock"></iron-icon>
<div class="title">
[[email_]]
[[email]]
</div>
<div class="subtitle" hidden="[[isManualInput_]]">
<div class="subtitle" hidden="[[isManualInput]]">
$i18n{confirmPasswordSubtitle}
</div>
<div class="subtitle" hidden="[[!isManualInput_]]">
<div class="subtitle" hidden="[[!isManualInput]]">
$i18n{manualPasswordSubtitle}
</div>
</div>
<div class="input-container">
<cr-input type="password" id="passwordInput" required
placeholder="[[passwordPlaceholder_(locale, isManualInput_)]]"
error-message="[[passwordErrorText_(locale, isManualInput_)]]">
placeholder="[[passwordPlaceholder(locale, isManualInput)]]"
error-message="[[passwordErrorText(locale, isManualInput)]]">
</cr-input>
<cr-input type="password" id="confirmPasswordInput" required
placeholder="$i18n{confirmPasswordLabel}"
error-message="$i18n{manualPasswordMismatch}"
hidden="[[!isManualInput_]]">
hidden="[[!isManualInput]]">
</cr-input>
</div>
</div>
<div class="flex layout horizontal button-container">
<cr-button id="cancelButton" class="cancel-button" on-click="onCloseTap_">
<cr-button id="cancelButton" class="cancel-button" on-click="onCloseClick">
$i18n{lockScreenCancelButton}
</cr-button>
<cr-button id="nextButtonSamlConfirmPassword" class="action-button"
on-click="onConfirm_">
on-click="onConfirm">
$i18n{lockScreenNextButton}
<iron-icon id="arrowForward" icon="oobe-20:button-arrow-forward">
</iron-icon>
@ -399,7 +399,7 @@
</div>
</div>
<div class="content-wrapper" hidden="[[!isPasswordChanged_]]">
<div class="content-wrapper" hidden="[[!isPasswordChanged]]">
<div class="main-container">
<div class="header">
<iron-icon class="title-icon" icon="oobe-32:lock"></iron-icon>
@ -417,10 +417,10 @@
</div>
</div>
<div class="flex layout horizontal button-container">
<cr-button id="cancelButton" class="cancel-button" on-click="onCloseTap_">
<cr-button id="cancelButton" class="cancel-button" on-click="onCloseClick">
$i18n{lockScreenCancelButton}
</cr-button>
<cr-button id="nextButton" class="action-button" on-click="onNext_">
<cr-button id="nextButton" class="action-button" on-click="onNext">
$i18n{lockScreenNextButton}
<iron-icon icon="oobe-20:button-arrow-forward"></iron-icon>
</cr-button>

@ -1,475 +0,0 @@
// 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.
/**
* @fileoverview An UI component to let user init online re-auth flow on
* the lock screen.
*/
import 'chrome://resources/ash/common/cr.m.js';
import 'chrome://resources/ash/common/event_target.js';
import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import 'chrome://resources/ash/common/cr_elements/icons.html.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
import './components/buttons/oobe_text_button.js';
import './components/oobe_icons.html.js';
import './components/oobe_illo_icons.html.js';
import './gaia_action_buttons/gaia_action_buttons.js';
import '//resources/ash/common/cr_elements/policy/cr_tooltip_icon.js';
import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
import {assert} from 'chrome://resources/ash/common/assert.js';
import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
import {sendWithPromise} from 'chrome://resources/js/cr.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {Authenticator, AuthFlow, AuthMode, AuthParams, SUPPORTED_PARAMS} from '../../gaia_auth_host/authenticator.js';
const clearDataType = {
appcache: true,
cache: true,
cookies: true,
};
/**
* @constructor
* @extends {PolymerElement}
* @implements {I18nBehaviorInterface}
*/
const LockReauthBase = mixinBehaviors([I18nBehavior], PolymerElement);
/**
* @polymer
*/
class LockReauth extends LockReauthBase {
static get is() {
return 'lock-reauth';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
/**
* User non-canonicalized email for display
*/
email_: {
type: String,
value: '',
},
/**
* Auth Domain property of the authenticator. Updated via events.
*/
authDomain_: {
type: String,
value: '',
},
/**
* Whether the verify user screen is shown.
*/
isVerifyUser_: {
type: Boolean,
value: false,
},
/**
* Whether the verify user again screen is shown.
*/
isErrorDisplayed_: {
type: Boolean,
value: false,
},
/**
* Whether the webview for online sign-in is shown.
*/
isSigninFrameDisplayed_: {
type: Boolean,
value: false,
},
/**
* Whether the authenticator is currently showing SAML IdP page.
* @private
*/
isSaml_: {
type: Boolean,
value: false,
},
/**
* Whether default SAML IdP is shown.
*/
isDefaultSsoProvider: {
type: Boolean,
value: false,
},
/**
* Whether there is a failure to scrape the user's password.
*/
isConfirmPassword_: {
type: Boolean,
value: false,
},
/**
* Whether no password is scraped or multiple passwords are scraped.
*/
isManualInput_: {
type: Boolean,
value: false,
},
/**
* Whether the user's password has changed.
*/
isPasswordChanged_: {
type: Boolean,
value: false,
},
passwordConfirmAttempt_: {
type: Number,
value: 0,
},
passwordChangeAttempt_: {
type: Number,
value: 0,
},
};
}
constructor() {
super();
/**
* Saved authenticator load params.
* @type {?AuthParams}
* @private
*/
this.authenticatorParams_ = null;
/**
* The UI component that hosts IdP pages.
* @type {!Authenticator|undefined}
*/
this.authenticator_ = undefined;
/**
* Webview that view IdP page
* @type {!WebView|undefined}
* @private
*/
this.signinFrame_ = undefined;
/**
* Gaia path which can serve as a fallback in reloading scenarios. Expected
* to correspond to editable Gaia username page.
* TODO(b/259181755): this should no longer be needed once we change the
* implementation of the "Enter Google Account info" button to fully reload
* the flow through cpp code.
* @type {string}
* @private
*/
this.fallbackGaiaPath_ = '';
}
/** @override */
ready() {
super.ready();
this.signinFrame_ = this.getSigninFrame_();
this.authenticator_ = new Authenticator(this.signinFrame_);
this.authenticator_.addEventListener('authDomainChange', (e) => {
this.authDomain_ = e.detail.newValue;
});
this.authenticator_.addEventListener(
'authCompleted', (e) => void this.onAuthCompletedMessage_(e));
this.authenticator_.addEventListener(
'loadAbort', (e) => void this.onLoadAbortMessage_(e.detail));
this.authenticator_.addEventListener('getDeviceId', (e) => {
sendWithPromise('getDeviceId')
.then(deviceId => this.authenticator_.getDeviceIdResponse(deviceId));
});
this.authenticator_.addEventListener('authFlowChange', (e) => {
this.isSaml_ = e.detail.newValue === AuthFlow.SAML;
});
chrome.send('initialize');
}
/** @private */
resetState_() {
this.isVerifyUser_ = false;
this.isErrorDisplayed_ = false;
this.isSaml_ = false;
this.isSigninFrameDisplayed_ = false;
this.isConfirmPassword_ = false;
this.isManualInput_ = false;
this.isPasswordChanged_ = false;
this.authDomain_ = '';
}
/**
* Set the orientation which will be used in styling webui.
* @param {!Object} is_horizontal whether the orientation is horizontal or
* vertical.
*/
setOrientation(is_horizontal) {
if (is_horizontal) {
document.documentElement.setAttribute('orientation', 'horizontal');
} else {
document.documentElement.setAttribute('orientation', 'vertical');
}
}
/**
* Set the width which will be used in styling webui.
* @param {!Object} width the width of the dialog.
*/
setWidth(width) {
document.documentElement.style.setProperty(
'--lock-screen-reauth-dialog-width', width + 'px');
}
/**
* Loads the authentication parameters.
* @param {!Object} data authenticator parameters bag.
* @suppress {missingProperties}
*/
loadAuthenticator(data) {
assert(
'webviewPartitionName' in data,
'ERROR: missing webview partition name');
this.authenticator_.setWebviewPartition(data.webviewPartitionName);
this.fallbackGaiaPath_ = data.fallbackGaiaPath;
const params = {};
SUPPORTED_PARAMS.forEach(name => {
if (data.hasOwnProperty(name)) {
params[name] = data[name];
}
});
params.enableGaiaActionButtons = data.enableGaiaActionButtons;
this.authenticatorParams_ = /** @type {AuthParams} */ (params);
this.email_ = data.email;
this.isDefaultSsoProvider = data.doSamlRedirect;
this.isSaml_ = this.isDefaultSsoProvider;
if (data.showVerificationNotice) {
this.isVerifyUser_ = true;
} else {
this.doGaiaRedirect_();
}
chrome.send('authenticatorLoaded');
}
/**
* This function is used when the wrong user is verified correctly
* It reset authenticator state and display error message.
*/
resetAuthenticator() {
this.signinFrame_.clearData({since: 0}, clearDataType, () => {
this.authenticator_.resetStates();
this.isButtonsEnabled_ = true;
this.isErrorDisplayed_ = true;
});
}
/**
* Reloads the page.
*/
reloadAuthenticator() {
this.signinFrame_.clearData({since: 0}, clearDataType, () => {
this.authenticator_.resetStates();
});
}
/**
* @return {!WebView}
* @private
*/
getSigninFrame_() {
// Note: Can't use |this.$|, since it returns cached references to elements
// originally present in DOM, while the signin-frame is dynamically
// recreated (see Authenticator.setWebviewPartition()).
const signinFrame = this.shadowRoot.getElementById('signin-frame');
assert(signinFrame);
return /** @type {!WebView} */ (signinFrame);
}
/** @private */
setFocusToWebview_() {
this.signinFrame_.focus();
}
onAuthCompletedMessage_(e) {
const credentials = e.detail;
chrome.send('completeAuthentication', [
credentials.gaiaId,
credentials.email,
credentials.password,
credentials.scrapedSAMLPasswords,
credentials.usingSAML,
credentials.services,
credentials.passwordAttributes,
]);
}
/**
* Invoked when onLoadAbort message received.
* @param {!Object} data Additional information about error event like:
* {number} error_code Error code such as net::ERR_INTERNET_DISCONNECTED.
* {string} src The URL that failed to load.
* @private
*/
onLoadAbortMessage_(data) {
chrome.send('webviewLoadAborted', [data.error_code]);
}
/**
* Invoked when the user has successfully authenticated via SAML,
* the Chrome Credentials Passing API was not used and the authenticator needs
* the user to confirm the scraped password.
* @param {number} passwordCount The number of passwords that were scraped.
*/
showSamlConfirmPassword(passwordCount) {
this.resetState_();
/**
* This statement override resetState_ calls.
* Thus have to be AFTER resetState_.
*/
this.isConfirmPassword_ = true;
this.isManualInput_ = (passwordCount === 0);
if (this.passwordConfirmAttempt_ > 0) {
this.$.passwordInput.value = '';
this.$.passwordInput.invalid = true;
}
this.passwordConfirmAttempt_++;
}
/**
* Invoked when the user's password doesn't match his old password.
* @private
*/
passwordChanged() {
this.resetState_();
this.isPasswordChanged_ = true;
this.passwordChangeAttempt_++;
if (this.passwordChangeAttempt_ > 1) {
this.$.oldPasswordInput.invalid = true;
}
}
/** @private */
onVerify_() {
this.authenticator_.load(
AuthMode.DEFAULT,
/** @type {AuthParams} */ (this.authenticatorParams_));
this.resetState_();
/**
* These statements override resetStates_ calls.
* Thus have to be AFTER resetState_.
*/
this.isSigninFrameDisplayed_ = true;
}
/** @private */
onConfirm_() {
if (!this.$.passwordInput.validate()) {
return;
}
if (this.isManualInput_) {
// When using manual password entry, both passwords must match.
const confirmPasswordInput =
this.shadowRoot.querySelector('#confirmPasswordInput');
if (!confirmPasswordInput.validate()) {
return;
}
if (confirmPasswordInput.value !== this.$.passwordInput.value) {
this.$.passwordInput.invalid = true;
confirmPasswordInput.invalid = true;
return;
}
}
chrome.send('onPasswordTyped', [this.$.passwordInput.value]);
}
/** @private */
onCloseTap_() {
chrome.send('dialogClose');
}
/** @private */
onNext_() {
if (!this.$.oldPasswordInput.validate()) {
this.$.oldPasswordInput.focusInput();
return;
}
chrome.send('updateUserPassword', [this.$.oldPasswordInput.value]);
this.$.oldPasswordInput.value = '';
}
/** @private */
doGaiaRedirect_() {
this.authenticator_.load(
AuthMode.DEFAULT,
/** @type {AuthParams} */ (this.authenticatorParams_));
this.resetState_();
/**
* These statements override resetStates_ calls.
* Thus have to be AFTER resetState_.
*/
this.isSigninFrameDisplayed_ = true;
}
/** @private */
passwordPlaceholder_(locale, isManualInput_) {
return this.i18n(
isManualInput_ ? 'manualPasswordInputLabel' : 'confirmPasswordLabel');
}
/** @private */
passwordErrorText_(locale, isManualInput_) {
return this.i18n(
isManualInput_ ? 'manualPasswordMismatch' :
'passwordChangedIncorrectOldPassword');
}
/**
* Invoked when "Enter Google Account info" button is pressed on SAML screen.
* @private
*/
onChangeSigninProviderClicked_() {
this.authenticatorParams_.doSamlRedirect = false;
this.authenticatorParams_.enableGaiaActionButtons = true;
this.isDefaultSsoProvider = false;
this.isSaml_ = false;
// Replace Gaia path with a fallback path to land on Gaia username page.
assert(
this.fallbackGaiaPath_,
'fallback Gaia path needed when trying to switch from SAML to Gaia');
this.authenticatorParams_.gaiaPath = this.fallbackGaiaPath_;
this.authenticator_.load(
AuthMode.DEFAULT,
/** @type {AuthParams} */ (this.authenticatorParams_));
}
/** @private */
policyProvidedTrustedAnchorsUsed_() {
return loadTimeData.getBoolean('policyProvidedCaCertsPresent');
}
}
customElements.define(LockReauth.is, LockReauth);

@ -0,0 +1,483 @@
// 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.
/**
* @fileoverview An UI component to let user init online re-auth flow on
* the lock screen.
*/
import 'chrome://resources/ash/common/cr.m.js';
import 'chrome://resources/ash/common/event_target.js';
import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import 'chrome://resources/ash/common/cr_elements/icons.html.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
import './components/buttons/oobe_text_button.js';
import './components/oobe_icons.html.js';
import './components/oobe_illo_icons.html.js';
import './gaia_action_buttons/gaia_action_buttons.js';
import '//resources/ash/common/cr_elements/policy/cr_tooltip_icon.js';
import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
import {AuthCompletedCredentials, AuthCompletedEvent, AuthDomainChangeEvent, Authenticator, AuthFlow, AuthFlowChangeEvent, AuthMode, AuthParams, LoadAbortEvent, SUPPORTED_PARAMS} from '//lock-reauth/gaia_auth_host/authenticator.js';
import {CrInputElement} from '//resources/ash/common/cr_elements/cr_input/cr_input.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
import {sendWithPromise} from 'chrome://resources/js/cr.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {getTemplate} from './lock_screen_reauth.html.js';
const clearDataType: chrome.webviewTag.ClearDataTypeSet = {
appcache: true,
cache: true,
cookies: true,
};
interface LockReauthParams {
fallbackGaiaPath: string;
webviewPartitionName: string;
showVerificationNotice: boolean;
}
const LockReauthElementBase = I18nMixin(PolymerElement);
interface LockReauthElement {
$: {
confirmPasswordInput: CrInputElement,
oldPasswordInput: CrInputElement,
passwordInput: CrInputElement,
};
}
class LockReauthElement extends LockReauthElementBase {
static get is() {
return 'lock-reauth';
}
static get template() {
return getTemplate();
}
static get properties() {
return {
/**
* User non-canonicalized email for display
*/
email: {
type: String,
value: '',
},
/**
* Auth Domain property of the authenticator. Updated via events.
*/
authDomain: {
type: String,
value: '',
},
/**
* Whether the verify user screen is shown.
*/
isVerifyUser: {
type: Boolean,
value: false,
},
/**
* Whether the verify user again screen is shown.
*/
isErrorDisplayed: {
type: Boolean,
value: false,
},
/**
* Whether the webview for online sign-in is shown.
*/
isSigninFrameDisplayed: {
type: Boolean,
value: false,
},
/**
* Whether the authenticator is currently showing SAML IdP page.
*/
isSaml: {
type: Boolean,
value: false,
},
/**
* Whether default SAML IdP is shown.
*/
isDefaultSsoProvider: {
type: Boolean,
value: false,
},
/**
* Whether there is a failure to scrape the user's password.
*/
isConfirmPassword: {
type: Boolean,
value: false,
},
/**
* Whether no password is scraped or multiple passwords are scraped.
*/
isManualInput: {
type: Boolean,
value: false,
},
/**
* Whether the user's password has changed.
*/
isPasswordChanged: {
type: Boolean,
value: false,
},
passwordConfirmAttempt: {
type: Number,
value: 0,
},
passwordChangeAttempt: {
type: Number,
value: 0,
},
};
}
email: string;
authDomain: string;
isVerifyUser: boolean;
isButtonsEnabled: boolean;
isErrorDisplayed: boolean;
isSigninFrameDisplayed: boolean;
isSaml: boolean;
isDefaultSsoProvider: boolean;
isConfirmPassword: boolean;
isManualInput: boolean;
isPasswordChanged: boolean;
passwordConfirmAttempt: number;
passwordChangeAttempt: number;
/**
* Saved authenticator load params.
*/
private authenticatorParams: null|AuthParams = null;
/**
* The UI component that hosts IdP pages.
*/
authenticator?: Authenticator;
/**
* Webview that view IdP page
*/
private signinFrame?: chrome.webviewTag.WebView;
/**
* Gaia path which can serve as a fallback in reloading scenarios. Expected
* to correspond to editable Gaia username page.
* TODO(b/259181755): this should no longer be needed once we change the
* implementation of the "Enter Google Account info" button to fully reload
* the flow through cpp code.
*/
private fallbackGaiaPath?: string;
override ready() {
super.ready();
this.signinFrame = this.getSigninFrame();
const authenticator = this.authenticator =
new Authenticator(this.signinFrame);
const authenticatorEventListeners: Record<string, (e: any) => void> = {
'authDomainChange': (e: AuthDomainChangeEvent) => {
this.authDomain = e.detail.newValue;
},
'authCompleted': (e: AuthCompletedEvent) =>
void this.onAuthCompletedMessage(e.detail),
'loadAbort': (e: LoadAbortEvent) =>
void this.onLoadAbortMessage(e.detail),
'getDeviceId': (_: Event) => {
sendWithPromise('getDeviceId')
.then(deviceId => authenticator.getDeviceIdResponse(deviceId));
},
'authFlowChange': (e: AuthFlowChangeEvent) => {
this.isSaml = e.detail.newValue === AuthFlow.SAML;
},
};
for (const eventName in authenticatorEventListeners) {
this.authenticator.addEventListener(
eventName, authenticatorEventListeners[eventName].bind(this));
}
chrome.send('initialize');
}
private resetState() {
this.isVerifyUser = false;
this.isErrorDisplayed = false;
this.isSaml = false;
this.isSigninFrameDisplayed = false;
this.isConfirmPassword = false;
this.isManualInput = false;
this.isPasswordChanged = false;
this.authDomain = '';
}
/**
* Set the orientation which will be used in styling webui.
* @param isHorizontal whether the orientation is horizontal or
* vertical.
*/
setOrientation(isHorizontal: boolean) {
if (isHorizontal) {
document.documentElement.setAttribute('orientation', 'horizontal');
} else {
document.documentElement.setAttribute('orientation', 'vertical');
}
}
/**
* Set the width which will be used in styling webui.
* @param width the width of the dialog.
*/
setWidth(width: number) {
document.documentElement.style.setProperty(
'--lock-screen-reauth-dialog-width', width + 'px');
}
/**
* Loads the authentication parameters.
* @param data authenticator parameters bag.
*/
loadAuthenticator(data: LockReauthParams&AuthParams) {
assert(
'webviewPartitionName' in data,
'ERROR: missing webview partition name');
assert(this.authenticator, 'ERROR: Authenticator not yet initialized');
this.authenticator.setWebviewPartition(data.webviewPartitionName);
this.fallbackGaiaPath = data.fallbackGaiaPath;
const params: AuthParams = {} as AuthParams;
SUPPORTED_PARAMS.forEach((name: string) => {
if (data.hasOwnProperty(name)) {
params[name] = data[name];
}
});
params.enableGaiaActionButtons = data.enableGaiaActionButtons;
this.authenticatorParams = params;
this.email = data.email;
this.isDefaultSsoProvider = !!data.doSamlRedirect;
this.isSaml = this.isDefaultSsoProvider;
if (data.showVerificationNotice) {
this.isVerifyUser = true;
} else {
this.doGaiaRedirect();
}
chrome.send('authenticatorLoaded');
}
/**
* This function is used when the wrong user is verified correctly
* It reset authenticator state and display error message.
*/
resetAuthenticator() {
this.getSigninFrame().clearData({since: 0}, clearDataType, () => {
this.authenticator!.resetStates();
this.isButtonsEnabled = true;
this.isErrorDisplayed = true;
});
}
/**
* Reloads the page.
*/
reloadAuthenticator() {
this.getSigninFrame().clearData({since: 0}, clearDataType, () => {
this.authenticator!.resetStates();
});
}
private getSigninFrame(): chrome.webviewTag.WebView {
// Note: Can't use |this.$|, since it returns cached references to elements
// originally present in DOM, while the signin-frame is dynamically
// recreated (see Authenticator.setWebviewPartition()).
const signinFrame = this.shadowRoot!.getElementById('signin-frame');
assert(signinFrame, 'ERROR: signin-frame not found');
return signinFrame as chrome.webviewTag.WebView;
}
private setFocusToWebview() {
this.signinFrame!.focus();
}
onAuthCompletedMessage(credentials: AuthCompletedCredentials) {
chrome.send('completeAuthentication', [
credentials.gaiaId,
credentials.email,
credentials.password,
credentials.scrapedSAMLPasswords,
credentials.usingSAML,
credentials.services,
credentials.passwordAttributes,
]);
}
/**
* Invoked when onLoadAbort message received.
* @param data Additional information about error event like:
* {number} error_code Error code such as net::ERR_INTERNET_DISCONNECTED.
* {string} src The URL that failed to load.
*/
private onLoadAbortMessage(data: LoadAbortEvent['detail']) {
chrome.send('webviewLoadAborted', [data.error_code]);
}
/**
* Invoked when the user has successfully authenticated via SAML,
* the Chrome Credentials Passing API was not used and the authenticator needs
* the user to confirm the scraped password.
* @param passwordCount The number of passwords that were scraped.
*/
showSamlConfirmPassword(passwordCount: number) {
this.resetState();
/**
* This statement override resetState calls.
* Thus have to be AFTER resetState.
*/
this.isConfirmPassword = true;
this.isManualInput = (passwordCount === 0);
if (this.passwordConfirmAttempt > 0) {
this.$.passwordInput.value = '';
this.$.passwordInput.invalid = true;
}
this.passwordConfirmAttempt++;
}
/**
* Invoked when the user's password doesn't match his old password.
*/
private passwordChanged() {
this.resetState();
this.isPasswordChanged = true;
this.passwordChangeAttempt++;
if (this.passwordChangeAttempt > 1) {
this.$.oldPasswordInput.invalid = true;
}
}
private onVerify() {
assert(
this.authenticatorParams,
'ERROR: authenticator parameters not yet loaded');
this.authenticator!.load(AuthMode.DEFAULT, this.authenticatorParams);
this.resetState();
/**
* These statements override resetStates calls.
* Thus have to be AFTER resetState.
*/
this.isSigninFrameDisplayed = true;
}
private onConfirm() {
if (!this.$.passwordInput.validate()) {
return;
}
if (this.isManualInput) {
// When using manual password entry, both passwords must match.
if (!this.$.confirmPasswordInput.validate()) {
return;
}
if (this.$.confirmPasswordInput.value !== this.$.passwordInput.value) {
this.$.passwordInput.invalid = true;
this.$.confirmPasswordInput.invalid = true;
return;
}
}
chrome.send('onPasswordTyped', [this.$.passwordInput.value]);
}
private onCloseClick() {
chrome.send('dialogClose');
}
private onNext() {
if (!this.$.oldPasswordInput.validate()) {
this.$.oldPasswordInput.focusInput();
return;
}
chrome.send('updateUserPassword', [this.$.oldPasswordInput.value]);
this.$.oldPasswordInput.value = '';
}
private doGaiaRedirect() {
assert(
this.authenticatorParams,
'ERROR: authenticator parameters not yet loaded');
this.authenticator!.load(AuthMode.DEFAULT, this.authenticatorParams);
this.resetState();
/**
* These statements override resetStates calls.
* Thus have to be AFTER resetState.
*/
this.isSigninFrameDisplayed = true;
}
private passwordPlaceholder(_locale: string, isManualInput: boolean) {
return this.i18n(
isManualInput ? 'manualPasswordInputLabel' : 'confirmPasswordLabel');
}
private passwordErrorText(_locale: string, isManualInput: boolean) {
return this.i18n(
isManualInput ? 'manualPasswordMismatch' :
'passwordChangedIncorrectOldPassword');
}
/**
* Invoked when "Enter Google Account info" button is pressed on SAML screen.
*/
private onChangeSigninProviderClicked() {
assert(
this.authenticatorParams,
'ERROR: authenticator parameters not yet loaded');
this.authenticatorParams.doSamlRedirect = false;
this.authenticatorParams.enableGaiaActionButtons = true;
this.isDefaultSsoProvider = false;
this.isSaml = false;
// Replace Gaia path with a fallback path to land on Gaia username page.
assert(
this.fallbackGaiaPath,
'fallback Gaia path needed when trying to switch from SAML to Gaia');
this.authenticatorParams.gaiaPath = this.fallbackGaiaPath;
this.authenticator!.load(AuthMode.DEFAULT, this.authenticatorParams);
}
private policyProvidedTrustedAnchorsUsed() {
return loadTimeData.getBoolean('policyProvidedCaCertsPresent');
}
}
declare global {
interface HTMLElementTagNameMap {
'lock-reauth': LockReauthElement;
}
}
customElements.define(LockReauthElement.is, LockReauthElement);

@ -19,7 +19,7 @@ function initialize() {
// in chrome://resources/ash/common/util.js. If this function is not exposed
// via the global object, it would not be available to tests that inject
// JavaScript directly into the renderer.
window.$ = $;
(window as any).$ = $;
}
initialize();

@ -38,6 +38,7 @@ export interface AuthCompletedCredentials {
trusted: boolean;
usingSAML: boolean;
isAvailableInArc?: boolean;
scrapedSAMLPasswords?: string[];
}
export interface AuthParams {
@ -45,6 +46,7 @@ export interface AuthParams {
clientId: string;
clientVersion?: string;
constrained: string;
doSamlRedirect?: boolean;
dontResizeNonEmbeddedPages: boolean;
emailDomain: string;
email: string;
@ -53,6 +55,7 @@ export interface AuthParams {
extractSamlPasswordAttributes: boolean;
flow: string;
forceDarkMode: boolean;
frameUrl: URL;
gaiaPath: string;
gaiaUrl: string;
hl: string;
@ -66,9 +69,9 @@ export interface AuthParams {
samlAclUrl: string;
service: string;
showTos: string;
ssoProfile: string;
ssoProfile?: string;
urlParameterToAutofillSAMLUsername: string;
frameUrl: URL;
[key: string]: AuthParams[keyof AuthParams];
}
export enum AuthMode {
@ -84,6 +87,13 @@ export enum AuthFlow {
export const SUPPORTED_PARAMS: string[];
type ChangeEvent<T> = CustomEvent<{oldValue: T, newValue: T}>;
export type AuthCompletedEvent = CustomEvent<AuthCompletedCredentials>;
export type AuthDomainChangeEvent = ChangeEvent<string>;
export type AuthFlowChangeEvent = ChangeEvent<AuthFlow>;
export type LoadAbortEvent = CustomEvent<{error_code: number, src: string}>;
export class Authenticator extends EventTarget {
constructor(webview: HTMLElement|string);
getAccountsResponse(accounts: string[]): void;

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type {AuthCompletedCredentials, AuthMode, AuthParams} from 'chrome://chrome-signin/gaia_auth_host/authenticator.js';
import {type AuthCompletedCredentials, type AuthMode, type AuthParams} from 'chrome://chrome-signin/gaia_auth_host/authenticator.js';
import type {InlineLoginBrowserProxy} from 'chrome://chrome-signin/inline_login_browser_proxy.js';
import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';

@ -266,6 +266,8 @@ declare global {
reload(): void;
addContentScripts(contentScriptList: ContentScriptDetails[]): void;
removeContentScripts(scriptNameList?: string[]): void;
clearData(options: ClearDataOptions, types: ClearDataTypeSet, callback?:
(results: any[]) => void): void;
executeScript(
details: InjectDetails, callback?: (results: any[]) => void): void;
insertCSS(details: InjectDetails,