summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/gaia_auth_host/password_change_authenticator.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/gaia_auth_host/password_change_authenticator.js')
-rw-r--r--chromium/chrome/browser/resources/gaia_auth_host/password_change_authenticator.js159
1 files changed, 150 insertions, 9 deletions
diff --git a/chromium/chrome/browser/resources/gaia_auth_host/password_change_authenticator.js b/chromium/chrome/browser/resources/gaia_auth_host/password_change_authenticator.js
index 0f5fa464c5f..7854c1b20cb 100644
--- a/chromium/chrome/browser/resources/gaia_auth_host/password_change_authenticator.js
+++ b/chromium/chrome/browser/resources/gaia_auth_host/password_change_authenticator.js
@@ -12,9 +12,105 @@
cr.define('cr.samlPasswordChange', function() {
'use strict';
+ /** @const */
+ const oktaInjectedScriptName = 'oktaInjected';
+
+ /**
+ * The script to inject into Okta user settings page.
+ * @type {string}
+ */
+ const oktaInjectedJs = String.raw`
+ // <include src="okta_detect_success_injected.js">
+ `;
+
const BLANK_PAGE_URL = 'about:blank';
/**
+ * The different providers of password-change pages that we support, or are
+ * working on supporting.
+ * @enum {number}
+ */
+ const PasswordChangePageProvider = {
+ UNKNOWN: 0,
+ ADFS: 1,
+ AZURE: 2,
+ OKTA: 3,
+ PING: 4,
+ };
+
+ /**
+ * @param {URL?} url The url of the webpage that is being interacted with.
+ * @return {PasswordChangePageProvider} The provider of the password change
+ * page, as detected based on the URL.
+ */
+ function detectProvider_(url) {
+ if (!url) {
+ return null;
+ }
+ if (url.pathname.match(/\/updatepassword\/?$/)) {
+ return PasswordChangePageProvider.ADFS;
+ }
+ if (url.pathname.endsWith('/ChangePassword.aspx')) {
+ return PasswordChangePageProvider.AZURE;
+ }
+ if (url.host.match(/\.okta\.com$/)) {
+ return PasswordChangePageProvider.OKTA;
+ }
+ if (url.pathname.match('/password/chg/')) {
+ return PasswordChangePageProvider.PING;
+ }
+ return PasswordChangePageProvider.UNKNOWN;
+ }
+
+ /**
+ * @param {string?} str A string that should be a valid URL.
+ * @return {URL?} A valid URL object, or null.
+ */
+ function safeParseUrl_(str) {
+ try {
+ return new URL(str);
+ } catch (error) {
+ console.error('Invalid url: ' + str);
+ return null;
+ }
+ }
+
+ /**
+ * @param {Object} details The web-request details.
+ * @return {boolean} True if we detect that a password change was successful.
+ */
+ function detectPasswordChangeSuccess(details) {
+ const url = safeParseUrl_(details.url);
+ if (!url) {
+ return false;
+ }
+
+ // We count it as a success whenever "status=0" is in the query params.
+ // This is what we use for ADFS, but for now, we allow it for every IdP, so
+ // that an otherwise unsupported IdP can also send it as a success message.
+ // TODO(https://crbug.com/930109): Consider removing this entirely, or,
+ // using a more self-documenting parameter like 'passwordChanged=1'.
+ if (url.searchParams.get('status') == '0') {
+ return true;
+ }
+
+ const pageProvider = detectProvider_(url);
+ // These heuristics work for the following SAML IdPs:
+ if (pageProvider == PasswordChangePageProvider.ADFS) {
+ return url.searchParams.get('status') == '0';
+ }
+ if (pageProvider == PasswordChangePageProvider.AZURE) {
+ return url.searchParams.get('ReturnCode') == '0';
+ }
+
+ // We can't currently detect success for Okta or Ping just by inspecting the
+ // URL or even response headers. To inspect the response body, we need
+ // to inject scripts onto their page (see okta_detect_success_injected.js).
+
+ return false;
+ }
+
+ /**
* Initializes the authenticator component.
*/
class Authenticator extends cr.EventTarget {
@@ -67,8 +163,27 @@ cr.define('cr.samlPasswordChange', function() {
this.samlHandler_, 'authPageLoaded',
this.onAuthPageLoaded_.bind(this));
+ // Listen for completed main-frame requests to check for password-change
+ // success.
+ this.webviewEventManager_.addWebRequestEventListener(
+ this.webview_.request.onCompleted,
+ this.onCompleted_.bind(this),
+ {urls: ['*://*/*'], types: ['main_frame']},
+ );
+
+ // Inject a custom script for detecting password change success in Okta.
+ this.webview_.addContentScripts([{
+ name: oktaInjectedScriptName,
+ matches: ['*://*.okta.com/*'],
+ js: {code: oktaInjectedJs},
+ all_frames: true,
+ run_at: 'document_start'
+ }]);
+
+ // Okta-detect-success-inject script signals success by posting a message
+ // that says "passwordChangeSuccess", which we listen for:
this.webviewEventManager_.addEventListener(
- this.webview_, 'contentload', this.onContentLoad_.bind(this));
+ window, 'message', this.onMessageReceived_.bind(this));
}
/**
@@ -129,7 +244,7 @@ cr.define('cr.samlPasswordChange', function() {
* Sends scraped password and resets the state.
* @private
*/
- completeAuth_() {
+ onPasswordChangeSuccess_() {
const passwordsOnce = this.samlHandler_.getPasswordsScrapedTimes(1);
const passwordsTwice = this.samlHandler_.getPasswordsScrapedTimes(2);
@@ -151,17 +266,43 @@ cr.define('cr.samlPasswordChange', function() {
}
/**
- * Invoked when a new document is loaded.
+ * Invoked when a new document loading completes.
+ * @param {Object} details The web-request details.
* @private
*/
- onContentLoad_(e) {
- const currentUrl = this.webview_.src;
- // TODO(rsorokin): Implement more robust check.
- if (currentUrl.lastIndexOf('status=0') != -1) {
- this.completeAuth_();
+ onCompleted_(details) {
+ if (detectPasswordChangeSuccess(details)) {
+ this.onPasswordChangeSuccess_();
+ }
+
+ // Okta_detect_success_injected.js needs to be contacted by the parent,
+ // so that it can send messages back to the parent.
+ const pageProvider = detectProvider_(safeParseUrl_(details.url));
+ if (pageProvider == PasswordChangePageProvider.OKTA) {
+ // Using setTimeout gives the page time to finish initializing.
+ setTimeout(() => {
+ this.webview_.contentWindow.postMessage('connect', details.url);
+ }, 1000);
+ }
+ }
+
+ /**
+ * Invoked when the webview posts a message.
+ * @param {Object} event The message event.
+ * @private
+ */
+ onMessageReceived_(event) {
+ if (event.data == 'passwordChangeSuccess') {
+ const pageProvider = detectProvider_(safeParseUrl_(event.origin));
+ if (pageProvider == PasswordChangePageProvider.OKTA) {
+ this.onPasswordChangeSuccess_();
+ }
}
}
}
- return {Authenticator: Authenticator};
+ return {
+ Authenticator: Authenticator,
+ detectPasswordChangeSuccess: detectPasswordChangeSuccess,
+ };
});