Skip to content

Commit

Permalink
Add CreateAccount API.
Browse files Browse the repository at this point in the history
The new CreateAccount API is used to notify ChromeOS of new account
creation during login. It is used with the existing add and confirm
credential passing API. This feature is enabled by default. This associated feature flag (GaiaRecordAccountCreation) is enabled by default.
DD: go/cros-gaia-info-exp-new-metric

Bug: b/307590266
Change-Id: Ie5257db2732b08a49440a4ae0ebff2ad740d32df
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4977107
Commit-Queue: Thu Huong Vu <thv@chromium.org>
Reviewed-by: Li Lin <llin@chromium.org>
Reviewed-by: Kyle Horimoto <khorimoto@chromium.org>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1216578}
  • Loading branch information
ThuHuong authored and Chromium LUCI CQ committed Oct 28, 2023
1 parent 8885faa commit f11f1b0
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 1 deletion.
9 changes: 9 additions & 0 deletions ash/constants/ash_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,11 @@ BASE_FEATURE(kGlanceablesTimeManagementStableLaunch,
"GlanceablesTimeManagementStableLaunch",
base::FEATURE_DISABLED_BY_DEFAULT);

// Enables logging new Gaia account creation event.
BASE_FEATURE(kGaiaRecordAccountCreation,
"GaiaRecordAccountCreation",
base::FEATURE_ENABLED_BY_DEFAULT);

// Enables the Gaia reauth endpoint for all online reauth flows on login screen.
// Note that the reauth endpoint is used when the user is a child user or in
// potential recovery flows, regardless of the flag value.
Expand Down Expand Up @@ -3406,6 +3411,10 @@ bool IsBlockFwupdClientEnabled() {
return base::FeatureList::IsEnabled(kBlockFwupdClient);
}

bool IsGaiaRecordAccountCreationEnabled() {
return base::FeatureList::IsEnabled(kGaiaRecordAccountCreation);
}

bool IsGaiaReauthEndpointEnabled() {
return base::FeatureList::IsEnabled(kGaiaReauthEndpoint);
}
Expand Down
3 changes: 3 additions & 0 deletions ash/constants/ash_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,8 @@ COMPONENT_EXPORT(ASH_CONSTANTS)
BASE_DECLARE_FEATURE(kGlanceablesV2ErrorMessage);
COMPONENT_EXPORT(ASH_CONSTANTS)
BASE_DECLARE_FEATURE(kGlanceablesTimeManagementStableLaunch);
COMPONENT_EXPORT(ASH_CONSTANTS)
BASE_DECLARE_FEATURE(kGaiaRecordAccountCreation);
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kGaiaReauthEndpoint);
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kGameDashboard);
COMPONENT_EXPORT(ASH_CONSTANTS)
Expand Down Expand Up @@ -984,6 +986,7 @@ bool CanEphemeralNetworkPoliciesBeEnabledByPolicy();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFullscreenAfterUnlockAllowed();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFullscreenAlertBubbleEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBlockFwupdClientEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsGaiaRecordAccountCreationEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsGaiaReauthEndpointEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsGalleryAppPdfEditNotificationEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsGameDashboardEnabled();
Expand Down
81 changes: 80 additions & 1 deletion chrome/browser/ash/login/saml/saml_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,35 @@ IN_PROC_BROWSER_TEST_P(SamlTestWithFeatures, SamlUI) {
test::OobeJS().ExpectHiddenPath(kSamlNoticeContainer);
}

// This test is run with both new API Create Account enable or disable.
using SamlWithCreateAccountAPITestParams = std::tuple<bool, bool>;
class SamlWithCreateAccountAPITest
: public SamlTestBase,
public testing::WithParamInterface<SamlWithCreateAccountAPITestParams> {
public:
SamlWithCreateAccountAPITest() {
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;

// Handle Gaia's create account message
if (IsRecordCreateAccountFeatureEnabled()) {
enabled_features.push_back(features::kGaiaRecordAccountCreation);
} else {
disabled_features.push_back(features::kGaiaRecordAccountCreation);
}
scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
}

protected:
bool IsRecordCreateAccountFeatureEnabled() const {
return std::get<0>(GetParam());
}
bool IsNewAccountSignedUp() const { return std::get<1>(GetParam()); }

private:
base::test::ScopedFeatureList scoped_feature_list_;
};

// The SAML IdP requires HTTP Protocol-level authentication (Basic in this
// case).
IN_PROC_BROWSER_TEST_P(SamlTestWithFeatures, IdpRequiresHttpAuth) {
Expand Down Expand Up @@ -1010,6 +1039,54 @@ IN_PROC_BROWSER_TEST_P(SamlTestWithFeatures, MetaRefreshToHTTPDisallowed) {
WaitForSigninScreen();
}

// Tests the sign-in flow when the credentials passing API is used.
IN_PROC_BROWSER_TEST_P(SamlWithCreateAccountAPITest,
CredentialPassingAPIWithNewAccount) {
base::HistogramTester histogram_tester;
fake_saml_idp()->SetLoginHTMLTemplate("saml_api_login.html");
std::string login_auth_template = "saml_api_login_auth.html";
if (IsNewAccountSignedUp()) {
login_auth_template = "saml_api_login_auth_with_new_account.html";
}
fake_saml_idp()->SetLoginAuthHTMLTemplate(login_auth_template);
StartSamlAndWaitForIdpPageLoad(
saml_test_users::kFirstUserCorpExampleComEmail);

// Fill-in the SAML IdP form and submit.
SigninFrameJS().TypeIntoPath("fake_user", {"Email"});
SigninFrameJS().TypeIntoPath("not_the_password", {"Dummy"});
SigninFrameJS().TypeIntoPath("actual_password", {"Password"});

SigninFrameJS().TapOn("Submit");

// Login should finish login and a session should start.
test::WaitForPrimaryUserSessionStart();

// TODO (b/308176681): add test case for first user/non first user on the
// device.
if (IsNewAccountSignedUp() && IsRecordCreateAccountFeatureEnabled()) {
histogram_tester.ExpectUniqueSample(
"ChromeOS.Gaia.CreateAccount.IsFirstUser", 1, 1);
} else {
histogram_tester.ExpectTotalCount("ChromeOS.Gaia.CreateAccount.IsFirstUser",
0);
}

histogram_tester.ExpectUniqueSample("ChromeOS.SAML.APILogin", 1, 1);
histogram_tester.ExpectUniqueSample("ChromeOS.SAML.Provider", 1, 1);
histogram_tester.ExpectTotalCount("OOBE.GaiaLoginTime", 0);

histogram_tester.ExpectBucketCount("ChromeOS.Gaia.Message.Saml.UserInfo", 0,
0);
histogram_tester.ExpectBucketCount("ChromeOS.Gaia.Message.Saml.UserInfo", 1,
1);

histogram_tester.ExpectBucketCount("ChromeOS.Gaia.Message.Saml.CloseView", 0,
0);
histogram_tester.ExpectBucketCount("ChromeOS.Gaia.Message.Saml.CloseView", 1,
1);
}

class SAMLEnrollmentTest : public SamlTestBase {
public:
SAMLEnrollmentTest();
Expand Down Expand Up @@ -2361,5 +2438,7 @@ IN_PROC_BROWSER_TEST_F(SAMLDeviceTrustEnrolledTest, PolicyTwoEntriesSuccess) {
}

INSTANTIATE_TEST_SUITE_P(All, SamlTestWithFeatures, ::testing::Bool());

INSTANTIATE_TEST_SUITE_P(All,
SamlWithCreateAccountAPITest,
testing::Combine(testing::Bool(), testing::Bool()));
} // namespace ash
41 changes: 41 additions & 0 deletions chrome/browser/resources/gaia_auth_host/authenticator.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export let AuthCompletedCredentials;
* ssoProfile: string,
* urlParameterToAutofillSAMLUsername: string,
* frameUrl: URL,
* isFirstUser : (boolean|undefined),
* recordAccountCreation : (boolean|undefined),
* }}
*/
export let AuthParams;
Expand All @@ -121,6 +123,8 @@ const SAML_REDIRECTION_PATH = 'samlredirect';
const BLANK_PAGE_URL = 'about:blank';

const GAIA_DONE_ELAPSED_TIME = 'ChromeOS.Gaia.Done.ElapsedTime';
const GAIA_CREATE_ACCOUNT_FIRST_USER =
'ChromeOS.Gaia.CreateAccount.IsFirstUser';

// Metric names for messages we get from Gaia.
const GAIA_MESSAGE_SAML_USER_INFO = 'ChromeOS.Gaia.Message.Saml.UserInfo';
Expand Down Expand Up @@ -229,6 +233,8 @@ export const SUPPORTED_PARAMS = [
// A tri-state value which indicates the support level for passwordless login.
// Refer to `GaiaView::PasswordlessSupportLevel` for details.
'pwl',
// Control if the account creation during sign in flow should be handled.
'recordAccountCreation',
];

// Timeout in ms to wait for the message from Gaia indicating end of the flow.
Expand Down Expand Up @@ -404,6 +410,7 @@ export class Authenticator extends EventTarget {
/** @type {AuthMode} */
this.authMode = AuthMode.DEFAULT;
this.dontResizeNonEmbeddedPages = false;
this.isFirstUser_ = false;

/**
* @type {!SamlHandler|undefined}
Expand Down Expand Up @@ -595,6 +602,9 @@ export class Authenticator extends EventTarget {
this.webviewEventManager_.addEventListener(
this.samlHandler_, 'apiPasswordAdded',
e => this.onSamlApiPasswordAdded_(e));
this.webviewEventManager_.addEventListener(
this.samlHandler_, 'apiAccountCreated',
e => this.onSamlApiAccountCreated_(e));
this.webviewEventManager_.addEventListener(
this.samlHandler_, 'apiPasswordConfirmed',
e => this.onSamlApiPasswordConfirmed_(e));
Expand Down Expand Up @@ -725,6 +735,14 @@ export class Authenticator extends EventTarget {
if (data.startsOnSamlPage) {
this.samlHandler_.startsOnSamlPage = true;
}

// True if this is non-enterprise device and there are no users yet.
this.isFirstUser_ = !!data.isFirstUser;

// Enable or disable handling account create message from Gaia.
this.samlHandler_.shouldHandleAccountCreationMessage =
!!data.recordAccountCreation;

// Don't block insecure content for desktop flow because it lands on
// http. Otherwise, block insecure content as long as gaia is https.
this.samlHandler_.blockInsecureContent =
Expand Down Expand Up @@ -1312,6 +1330,14 @@ export class Authenticator extends EventTarget {
}
}

/**
* Invoked when |samlHandler_| fires 'apiAccountCreated' event.
* @private
*/
onSamlApiAccountCreated_(e) {
this.recordAccountCreated_();
}

/**
* Invoked when |samlHandler_| fires 'apiPasswordConfirmed' event. Could be
* from 3rd-party SAML IdP or Gaia which also uses the API.
Expand Down Expand Up @@ -1484,6 +1510,21 @@ export class Authenticator extends EventTarget {
this.gaiaStartTime = null;
}

/**
* Record new account creation.
* @private
*/
recordAccountCreated_() {
// Record true account is created during the first sign in event
// and false if another account existed.
// TODO (b/307591058): add metric to track if account is created
// during login or not.
chrome.send('metricsHandler:recordBooleanHistogram',[
GAIA_CREATE_ACCOUNT_FIRST_USER,
this.isFirstUser_
]);
}

/**
* @private
*/
Expand Down
19 changes: 19 additions & 0 deletions chrome/browser/resources/gaia_auth_host/saml_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,13 @@ import {WebviewEventManager} from './webview_event_manager.js';
*/
this.verifiedAccessChallengeResponse_ = null;

/**
* If set, this should handle the account creation message.
* If not set, this will log any account creation message as invalid call.
* @public {?boolean}
*/
this.shouldHandleAccountCreationMessage = false;

/**
* Certificate that were extracted from the SAMLResponse.
* @public {?string}
Expand Down Expand Up @@ -872,6 +879,18 @@ import {WebviewEventManager} from './webview_event_manager.js';
// TODO(b/261613412): Change warn to info.
console.warn('SamlHandler.onAPICall_: password added');
this.dispatchEvent(new CustomEvent('apiPasswordAdded'));
} else if (call.method === 'createaccount') {
if (!this.shouldHandleAccountCreationMessage) {
console.warn('SamlHandler.onAPICall_: message not supported');
return;
}
if (!(call.token in this.apiTokenStore_)) {
console.error('SamlHandler.onAPICall_: token mismatch');
return;
}
// TODO(b/261613412): Change warn to info.
console.warn('SamlHandler.onAPICall_: new account created');
this.dispatchEvent(new CustomEvent('apiAccountCreated'));
} else if (call.method === 'confirm') {
if (!(call.token in this.apiTokenStore_)) {
console.error('SamlHandler.onAPICall_: token mismatch');
Expand Down
3 changes: 3 additions & 0 deletions chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,9 @@ void GaiaScreenHandler::LoadGaiaWithPartitionAndVersionAndConsent(
params.Set("extractSamlPasswordAttributes",
login::ExtractSamlPasswordAttributesEnabled());

params.Set("recordAccountCreation",
ash::features::IsGaiaRecordAccountCreationEnabled());

if (public_saml_url_fetcher_) {
params.Set("startsOnSamlPage", true);
DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
Expand Down
28 changes: 28 additions & 0 deletions chrome/test/data/login/saml_api_login_auth_with_new_account.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<html>
<head>
<script type="text/javascript">
function send_and_submit() {
var form = document.forms[0];
var token = form.elements['RelayState'].value;
window.setTimeout(function() {
window.postMessage({
type: 'gaia_saml_api',
call: {method: 'createaccount', token: token}}, '/');
}, 0);

window.setTimeout(function() {
window.postMessage({
type: 'gaia_saml_api',
call: {method: 'confirm', token: token}}, '/');
form.submit();
}, 0);
}
</script>
</head>
<body onload="send_and_submit();">
<form method=post action="$Post">
<input type=hidden name=SAMLResponse value="fake_response"/>
<input type=hidden name=RelayState value="$RelayState">
</form>
</body>
</html>
12 changes: 12 additions & 0 deletions tools/metrics/histograms/metadata/chromeos/histograms.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,18 @@ They are used for the ChromeOS.CertProvisioning.* histograms to distinguish
</token>
</histogram>

<histogram name="ChromeOS.Gaia.CreateAccount.IsFirstUser" enum="Boolean"
expires_after="2024-05-30">
<owner>thv@google.com</owner>
<owner>cros-growth@google.com</owner>
<summary>
Records the event of new Gaia account creation. Recorded during user sign in
when Chrome receives account creation message from Gaia. True is recorded if
the account creation happens on first login on the device. False is recorded
if another account existed on the device.
</summary>
</histogram>

<histogram name="ChromeOS.Gaia.Done.ElapsedTime" units="ms"
expires_after="2024-03-17">
<owner>bchikhaoui@google.com</owner>
Expand Down

0 comments on commit f11f1b0

Please sign in to comment.