OOBE: Prevent bypass of the auto-enrollment check
Introduce a mechanism to prevent skipping the auto-enrollment
check during OOBE.
Key changes:
- A new feature flag, `kOobeAutoEnrollmentCheckForced`, guards the
new logic.
- The preference `kAutoEnrollmentCheckExited` is introduced and
used to indicate that the enrollment state determination process
has run.
- OOBE is marked completed only if `kAutoEnrollmentCheckExited` is
set to true.
- Checks are added at:
- Before any user sign-in.
- When the Gaia screen is shown.
- If OOBE is not marked as complete (indicating the check was skipped),
Sign-in fatal error screen is displayed providing the user with the
option to "Restart and Powerwash" the device.
Bug: 439252187
Change-Id: I8b0d094d81890f94a0b5af677614b4aaf78bd984
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7015507
Reviewed-by: Danila Kuzmin <dkuzmin@google.com>
Commit-Queue: Osama Fathy <osamafathy@google.com>
Cr-Commit-Position: refs/heads/main@{#1537880}
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 8fa698d1..e7bf715 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1565,6 +1565,9 @@
// Enables or disables the OOBE QuickStart flow on the login screen.
BASE_FEATURE(kOobeQuickStartOnLoginScreen, base::FEATURE_DISABLED_BY_DEFAULT);
+// Enables the enforcement of AutoEnrollment check in OOBE.
+BASE_FEATURE(kOobeAutoEnrollmentCheckForced, base::FEATURE_DISABLED_BY_DEFAULT);
+
// Enables or disables Orca for ARC apps.
BASE_FEATURE(kOrcaArc, base::FEATURE_ENABLED_BY_DEFAULT);
@@ -3272,6 +3275,10 @@
base::FeatureList::IsEnabled(kOobeInputMethods);
}
+bool IsOobeAutoEnrollmentCheckForcedEnabled() {
+ return base::FeatureList::IsEnabled(kOobeAutoEnrollmentCheckForced);
+}
+
bool IsOobeSplitModifierKeyboardInfoEnabled() {
return base::FeatureList::IsEnabled(kOobeSplitModifierKeyboardInfo);
}
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 048fd939..797fe1a 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -697,6 +697,8 @@
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeDisplaySize);
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeInputMethods);
COMPONENT_EXPORT(ASH_CONSTANTS)
+BASE_DECLARE_FEATURE(kOobeAutoEnrollmentCheckForced);
+COMPONENT_EXPORT(ASH_CONSTANTS)
BASE_DECLARE_FEATURE(kOobeSplitModifierKeyboardInfo);
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOrcaArc);
COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOrcaElaborate);
@@ -1279,6 +1281,7 @@
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeDisplaySizeEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeSplitModifierKeyboardInfoEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeInputMethodsEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeAutoEnrollmentCheckForcedEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOsSyncConsentRevampEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsParentAccessJellyEnabled();
COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPcieBillboardNotificationEnabled();
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 04c4ab5..3eeed688 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -1881,6 +1881,13 @@
<message name="IDS_LOGIN_FATAL_ERROR_NO_AUTH_TOKEN" desc="Message to show when the authentication could not be completed because the user's auth token retrieved.">
Sign-in failed because your access token could not be retrieved. Please check your network connection and try again.
</message>
+ <message name="IDS_LOGIN_FATAL_ERROR_AUTO_ENROLLMENT_SKIPPED" desc="Message to show when the auto enrollment check is skipped.">
+ Sign-in failed due to a device configuration check issue. Please powerwash your <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph> and try again.
+ </message>
+ <message name="IDS_LOGIN_FATAL_ERROR_RESTART_AND_POWERWASH_BUTTON" desc="Button to trigger a reboot and a powerwash.">
+ Restart and Powerwash
+ </message>
+
<message name="IDS_LOGIN_SAML_INTERSTITIAL_MESSAGE" desc="Message to show on the SAML interstitial page to tell the user to continue signing in using their enterprise account. MANAGER can be a domain or an email address.">
This device is managed by <ph name="MANAGER">$1<ex>acmecorp.com</ex></ph> and requires you to sign in every time.
</message>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_FATAL_ERROR_AUTO_ENROLLMENT_SKIPPED.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_FATAL_ERROR_AUTO_ENROLLMENT_SKIPPED.png.sha1
new file mode 100644
index 0000000..fff79db
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_FATAL_ERROR_AUTO_ENROLLMENT_SKIPPED.png.sha1
@@ -0,0 +1 @@
+a6a7c64365d35a5e272dc6fa07a2c57fc3ab5824
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_FATAL_ERROR_RESTART_AND_POWERWASH_BUTTON.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_FATAL_ERROR_RESTART_AND_POWERWASH_BUTTON.png.sha1
new file mode 100644
index 0000000..fff79db
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_FATAL_ERROR_RESTART_AND_POWERWASH_BUTTON.png.sha1
@@ -0,0 +1 @@
+a6a7c64365d35a5e272dc6fa07a2c57fc3ab5824
\ No newline at end of file
diff --git a/chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.cc b/chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.cc
index 588d0cdf..cfd1313 100644
--- a/chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.cc
+++ b/chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.cc
@@ -6,12 +6,14 @@
#include <optional>
+#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/ash/login/error_screens_histogram_helper.h"
+#include "chrome/browser/ash/login/login_pref_names.h"
#include "chrome/browser/ash/login/screen_manager.h"
#include "chrome/browser/ash/login/screens/error_screen.h"
#include "chrome/browser/ash/login/screens/network_error.h"
@@ -19,6 +21,7 @@
#include "chrome/browser/ash/policy/enrollment/auto_enrollment_controller.h"
#include "chrome/browser/ash/policy/enrollment/auto_enrollment_state.h"
#include "chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker.h"
+#include "chrome/browser/browser_process.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
@@ -260,4 +263,12 @@
auto_enrollment_controller_->Start();
}
+void AutoEnrollmentCheckScreen::RunExitCallback(Result result) {
+ if (ash::features::IsOobeAutoEnrollmentCheckForcedEnabled()) {
+ g_browser_process->local_state()->SetBoolean(
+ ash::prefs::kAutoEnrollmentCheckExited, true);
+ }
+ exit_callback_.Run(result);
+}
+
} // namespace ash
diff --git a/chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.h b/chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.h
index cff69f29..254117a7 100644
--- a/chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.h
+++ b/chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.h
@@ -77,7 +77,7 @@
// Runs `exit_callback_` - used to prevent `exit_callback_` from running after
// `this` has been destroyed (by wrapping it with a callback bound to a weak
// ptr).
- void RunExitCallback(Result result) { exit_callback_.Run(result); }
+ void RunExitCallback(Result result);
private:
// Handles update notifications regarding the auto-enrollment check.
diff --git a/chrome/browser/ash/login/enrollment/enrollment_launcher.cc b/chrome/browser/ash/login/enrollment/enrollment_launcher.cc
index 2df4ea8..60bc6d6 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_launcher.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_launcher.cc
@@ -9,6 +9,7 @@
#include <string>
#include <utility>
+#include "ash/constants/ash_features.h"
#include "base/check.h"
#include "base/check_is_test.h"
#include "base/functional/bind.h"
@@ -26,6 +27,7 @@
#include "chrome/browser/ash/attestation/attestation_ca_client.h"
#include "chrome/browser/ash/login/enrollment/enrollment_uma.h"
#include "chrome/browser/ash/login/enrollment/oauth2_token_revoker.h"
+#include "chrome/browser/ash/login/login_pref_names.h"
#include "chrome/browser/ash/login/startup_utils.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/device_cloud_policy_client_factory_ash.h"
@@ -48,6 +50,7 @@
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/dm_auth.h"
#include "components/policy/core/common/cloud/enterprise_metrics.h"
+#include "components/prefs/pref_service.h"
#include "google_apis/gaia/gaia_auth_consumer.h"
#include "google_apis/gaia/gaia_auth_fetcher.h"
#include "google_apis/gaia/google_service_auth_error.h"
@@ -437,7 +440,14 @@
}
success_ = true;
- StartupUtils::MarkOobeCompleted();
+
+ // TODO(crbug.com/454136007): Investigate why OOBE is marked completed here
+ if (!features::IsOobeAutoEnrollmentCheckForcedEnabled() ||
+ g_browser_process->local_state()->GetBoolean(
+ prefs::kAutoEnrollmentCheckExited)) {
+ StartupUtils::MarkOobeCompleted();
+ }
+
status_consumer_->OnDeviceEnrolled();
}
diff --git a/chrome/browser/ash/login/existing_user_controller.cc b/chrome/browser/ash/login/existing_user_controller.cc
index cd0afe2..bbe93bd0 100644
--- a/chrome/browser/ash/login/existing_user_controller.cc
+++ b/chrome/browser/ash/login/existing_user_controller.cc
@@ -45,6 +45,7 @@
#include "chrome/browser/ash/login/auth/chrome_login_performer.h"
#include "chrome/browser/ash/login/demo_mode/demo_login_controller.h"
#include "chrome/browser/ash/login/helper.h"
+#include "chrome/browser/ash/login/oobe_metrics_helper.h"
#include "chrome/browser/ash/login/profile_auth_data.h"
#include "chrome/browser/ash/login/quick_unlock/pin_salt_storage.h"
#include "chrome/browser/ash/login/quick_unlock/pin_storage_cryptohome.h"
@@ -1398,6 +1399,24 @@
signin_ui->ShowSigninError(error, details);
}
+void ExistingUserController::ShowOobeNotCompletedError() {
+ CHECK(features::IsOobeAutoEnrollmentCheckForcedEnabled())
+ << "ExistingUserController::ShowOobeNotCompletedError() should only be "
+ "called when OobeAutoEnrollmentCheckForced is enabled";
+ auto* signin_ui = GetLoginDisplayHost()->GetSigninUI();
+ if (!signin_ui) {
+ DCHECK(session_manager::SessionManager::Get()->IsInSecondaryLoginScreen());
+ // Silently ignore the error on the secondary login screen. The screen is
+ // being deprecated anyway.
+ return;
+ }
+ GetLoginDisplayHost()
+ ->GetOobeMetricsHelper()
+ ->RecordOobeNotCompletedErrorTrigger(
+ OobeMetricsHelper::OobeNotCompletedTrigger::kExistingUserController);
+ signin_ui->ShowOobeNotCompletedError();
+}
+
void ExistingUserController::SendAccessibilityAlert(
const std::string& alert_text) {
AutomationManagerAura::GetInstance()->HandleAlert(alert_text);
@@ -1524,6 +1543,27 @@
return;
}
+ if (features::IsOobeAutoEnrollmentCheckForcedEnabled() &&
+ !StartupUtils::IsOobeCompleted()) {
+ // If OOBE is not yet completed, abort the current login attempt. This
+ // indicates a potential bypass attempt and an error screen is shown.
+ ++num_login_attempts_;
+
+ auto* wizard_controller = GetLoginDisplayHost()->GetWizardController();
+ CHECK(wizard_controller);
+ ShowOobeNotCompletedError();
+
+ // Re-enable clicking on other windows and the status area. Do not start the
+ // auto-login timer though. Without trusted `cros_settings_`, no auto-login
+ // can succeed.
+ if (GetLoginDisplayHost()->GetWebUILoginView()) {
+ GetLoginDisplayHost()
+ ->GetWebUILoginView()
+ ->SetKeyboardEventsAndSystemTrayEnabled(true);
+ }
+ return;
+ }
+
if (system::DeviceDisablingManager::IsDeviceDisabledDuringNormalOperation()) {
// If the device is disabled, bail out. A device disabled screen will be
// shown by the DeviceDisablingManager.
diff --git a/chrome/browser/ash/login/existing_user_controller.h b/chrome/browser/ash/login/existing_user_controller.h
index ba62c451..e573380 100644
--- a/chrome/browser/ash/login/existing_user_controller.h
+++ b/chrome/browser/ash/login/existing_user_controller.h
@@ -200,6 +200,10 @@
// not localized.
void ShowError(SigninError error, const std::string& details);
+ // Shows an error message because the OOBE is not marked as completed.
+ // This occurs if StartupUtils::IsOobeCompleted() returns false unexpectedly.
+ void ShowOobeNotCompletedError();
+
// Shows privacy notification in case of auto lunch managed guest session.
void ShowAutoLaunchManagedGuestSessionNotification();
diff --git a/chrome/browser/ash/login/login_pref_names.h b/chrome/browser/ash/login/login_pref_names.h
index dd88776e..09886a3b 100644
--- a/chrome/browser/ash/login/login_pref_names.h
+++ b/chrome/browser/ash/login/login_pref_names.h
@@ -193,6 +193,11 @@
// serialization obtained from PrefService::SetTime().
inline constexpr char kLastOnlineSignInTime[] = "last_online_sign_in_time";
+// A boolean pref that indicates whether the auto enrollment check has
+// completed and exited. This is used to prevent OOBE completion if the
+// auto enrollment check was bypassed.
+inline constexpr char kAutoEnrollmentCheckExited[] =
+ "AutoEnrollmentCheckExited";
} // namespace ash::prefs
#endif // CHROME_BROWSER_ASH_LOGIN_LOGIN_PREF_NAMES_H_
diff --git a/chrome/browser/ash/login/oobe_metrics_helper.cc b/chrome/browser/ash/login/oobe_metrics_helper.cc
index 0b870a0..e9007e5d 100644
--- a/chrome/browser/ash/login/oobe_metrics_helper.cc
+++ b/chrome/browser/ash/login/oobe_metrics_helper.cc
@@ -492,6 +492,11 @@
}
}
+void OobeMetricsHelper::RecordOobeNotCompletedErrorTrigger(
+ OobeNotCompletedTrigger trigger) {
+ base::UmaHistogramEnumeration("OOBE.OobeNotCompletedErrorTrigger", trigger);
+}
+
void OobeMetricsHelper::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
diff --git a/chrome/browser/ash/login/oobe_metrics_helper.h b/chrome/browser/ash/login/oobe_metrics_helper.h
index 2a88236..6bf2106 100644
--- a/chrome/browser/ash/login/oobe_metrics_helper.h
+++ b/chrome/browser/ash/login/oobe_metrics_helper.h
@@ -34,6 +34,17 @@
// or deleted. Only additions possible.
enum class ScreenShownStatus { kSkipped = 0, kShown = 1, kMaxValue = kShown };
+ // This enum is tied directly to a UMA enum defined in
+ // //tools/metrics/histograms/enums.xml, and should always reflect it (do not
+ // change one without changing the other). Entries should be never modified
+ // or deleted. Only additions possible.
+ enum class OobeNotCompletedTrigger {
+ kPerformOobeCompletedAction = 0,
+ kGaiaScreen = 1,
+ kExistingUserController = 2,
+ kMaxValue = kExistingUserController
+ };
+
// The type of flow completed when pre-login OOBE is completed.
enum class CompletedPreLoginOobeFlowType {
kAutoEnrollment = 0,
@@ -160,6 +171,8 @@
void RecordChromeVersion();
+ void RecordOobeNotCompletedErrorTrigger(OobeNotCompletedTrigger trigger);
+
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
diff --git a/chrome/browser/ash/login/screens/gaia_screen.cc b/chrome/browser/ash/login/screens/gaia_screen.cc
index f9875e11..47a27ed 100644
--- a/chrome/browser/ash/login/screens/gaia_screen.cc
+++ b/chrome/browser/ash/login/screens/gaia_screen.cc
@@ -13,6 +13,7 @@
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "chrome/browser/ash/login/demo_mode/demo_setup_controller.h"
+#include "chrome/browser/ash/login/startup_utils.h"
#include "chrome/browser/ash/login/wizard_context.h"
#include "chrome/browser/ash/login/wizard_controller.h"
#include "chrome/browser/ash/policy/enrollment/account_status_check_fetcher.h"
@@ -84,6 +85,7 @@
return "EnterpriseEnroll";
case Result::ENTER_QUICK_START:
return "EnterQuickStart";
+ case Result::ERROR_OOBE_NOT_COMPLETED:
case Result::QUICK_START_ONGOING:
return BaseScreen::kNotApplicable;
}
@@ -110,6 +112,12 @@
return true;
}
+ if (features::IsOobeAutoEnrollmentCheckForcedEnabled() &&
+ !StartupUtils::IsOobeCompleted()) {
+ exit_callback_.Run(Result::ERROR_OOBE_NOT_COMPLETED);
+ return true;
+ }
+
return false;
}
diff --git a/chrome/browser/ash/login/screens/gaia_screen.h b/chrome/browser/ash/login/screens/gaia_screen.h
index 0c1e584..e5a4ef62 100644
--- a/chrome/browser/ash/login/screens/gaia_screen.h
+++ b/chrome/browser/ash/login/screens/gaia_screen.h
@@ -47,6 +47,7 @@
ENTERPRISE_ENROLL,
ENTER_QUICK_START,
QUICK_START_ONGOING,
+ ERROR_OOBE_NOT_COMPLETED,
};
static std::string GetResultString(Result result);
diff --git a/chrome/browser/ash/login/screens/signin_fatal_error_screen.cc b/chrome/browser/ash/login/screens/signin_fatal_error_screen.cc
index 0f44f69..69e4c06 100644
--- a/chrome/browser/ash/login/screens/signin_fatal_error_screen.cc
+++ b/chrome/browser/ash/login/screens/signin_fatal_error_screen.cc
@@ -7,11 +7,13 @@
#include "base/values.h"
#include "chrome/browser/ui/ash/login/login_display_host.h"
#include "chrome/browser/ui/webui/ash/login/signin_fatal_error_screen_handler.h"
+#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
namespace ash {
namespace {
constexpr char kUserActionScreenDismissed[] = "screen-dismissed";
+constexpr char kUserActionRestartAndPowerwash[] = "restart-and-powerwash";
constexpr char kUserActionLearnMore[] = "learn-more";
} // namespace
@@ -65,6 +67,9 @@
const std::string& action_id = args[0].GetString();
if (action_id == kUserActionScreenDismissed) {
exit_callback_.Run();
+ } else if (action_id == kUserActionRestartAndPowerwash) {
+ CHECK(error_state_ == Error::kOobeCompletionSkipped);
+ SessionManagerClient::Get()->StartDeviceWipe(base::DoNothing());
} else if (action_id == kUserActionLearnMore) {
if (!help_app_.get()) {
help_app_ = new HelpAppLauncher(
diff --git a/chrome/browser/ash/login/screens/signin_fatal_error_screen.h b/chrome/browser/ash/login/screens/signin_fatal_error_screen.h
index be8aa5aa..eb49351 100644
--- a/chrome/browser/ash/login/screens/signin_fatal_error_screen.h
+++ b/chrome/browser/ash/login/screens/signin_fatal_error_screen.h
@@ -30,7 +30,8 @@
kScrapedPasswordVerificationFailure = 1,
kInsecureContentBlocked = 2,
kMissingGaiaInfo = 3,
- kCustom = 4,
+ kOobeCompletionSkipped = 4,
+ kCustom = 5,
};
explicit SignInFatalErrorScreen(base::WeakPtr<SignInFatalErrorView> view,
diff --git a/chrome/browser/ash/login/startup_utils.cc b/chrome/browser/ash/login/startup_utils.cc
index 699a171..f8befc2 100644
--- a/chrome/browser/ash/login/startup_utils.cc
+++ b/chrome/browser/ash/login/startup_utils.cc
@@ -137,6 +137,8 @@
registry->RegisterIntegerPref(
prefs::kAuthenticationFlowAutoReloadInterval,
constants::kDefaultAuthenticationFlowAutoReloadInterval);
+
+ registry->RegisterBooleanPref(prefs::kAutoEnrollmentCheckExited, false);
}
// static
@@ -271,6 +273,8 @@
prefs::kOobeScreenAfterConsumerUpdate);
g_browser_process->local_state()->ClearPref(
prefs::kOobeCriticalUpdateCompleted);
+ g_browser_process->local_state()->ClearPref(
+ prefs::kAutoEnrollmentCheckExited);
}
// static
diff --git a/chrome/browser/ash/login/wizard_controller.cc b/chrome/browser/ash/login/wizard_controller.cc
index 2fe12e5..be4a31f 100644
--- a/chrome/browser/ash/login/wizard_controller.cc
+++ b/chrome/browser/ash/login/wizard_controller.cc
@@ -1546,6 +1546,15 @@
case GaiaScreen::Result::QUICK_START_ONGOING:
ShowQuickStartScreen();
break;
+ case GaiaScreen::Result::ERROR_OOBE_NOT_COMPLETED:
+ GetLoginDisplayHost()
+ ->GetOobeMetricsHelper()
+ ->RecordOobeNotCompletedErrorTrigger(
+ OobeMetricsHelper::OobeNotCompletedTrigger::kGaiaScreen);
+ ShowSignInFatalErrorScreen(
+ SignInFatalErrorScreen::Error::kOobeCompletionSkipped,
+ base::Value::Dict());
+ break;
}
}
@@ -3049,6 +3058,25 @@
return;
}
+ if (features::IsOobeAutoEnrollmentCheckForcedEnabled()) {
+ // To prevent auto-enrollment bypass, check if `kAutoEnrollmentCheckExited`
+ // has been set. If it's false, the auto-enrollment check was not properly
+ // completed.
+ if (!GetLocalState()->GetBoolean(prefs::kAutoEnrollmentCheckExited)) {
+ GetLoginDisplayHost()
+ ->GetOobeMetricsHelper()
+ ->RecordOobeNotCompletedErrorTrigger(
+ OobeMetricsHelper::OobeNotCompletedTrigger::
+ kPerformOobeCompletedAction);
+
+ // Show a fatal error and do not mark OOBE as completed.
+ ShowSignInFatalErrorScreen(
+ SignInFatalErrorScreen::Error::kOobeCompletionSkipped,
+ base::Value::Dict());
+ return;
+ }
+ }
+
StartupUtils::MarkOobeCompleted();
GetLoginDisplayHost()->GetOobeMetricsHelper()->RecordPreLoginOobeComplete(
flow_type);
diff --git a/chrome/browser/resources/chromeos/login/components/oobe_types.ts b/chrome/browser/resources/chromeos/login/components/oobe_types.ts
index fdb05b337..cb8f672 100644
--- a/chrome/browser/resources/chromeos/login/components/oobe_types.ts
+++ b/chrome/browser/resources/chromeos/login/components/oobe_types.ts
@@ -149,7 +149,8 @@
SCRAPED_PASSWORD_VERIFICATION_FAILURE = 1,
INSECURE_CONTENT_BLOCKED = 2,
MISSING_GAIA_INFO = 3,
- CUSTOM = 4,
+ OOBE_COMPLETION_SKIPPED = 4,
+ CUSTOM = 5,
}
/**
diff --git a/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.ts b/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.ts
index 52d8017..2788f2c 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.ts
+++ b/chrome/browser/resources/chromeos/login/screens/common/signin_fatal_error.ts
@@ -127,7 +127,11 @@
}
private onClick(): void {
- this.userActed('screen-dismissed');
+ if (this.errorState === OobeTypes.FatalErrorCode.OOBE_COMPLETION_SKIPPED) {
+ this.userActed('restart-and-powerwash');
+ } else {
+ this.userActed('screen-dismissed');
+ }
}
/**
@@ -137,6 +141,8 @@
private computeButtonKey(errorState: OobeTypes.FatalErrorCode) {
if (errorState === OobeTypes.FatalErrorCode.INSECURE_CONTENT_BLOCKED) {
return 'fatalErrorDoneButton';
+ } else if (errorState == OobeTypes.FatalErrorCode.OOBE_COMPLETION_SKIPPED) {
+ return 'fatalErrorRestartAndPowerwash';
}
return 'fatalErrorTryAgainButton';
@@ -158,6 +164,8 @@
const url = params.url;
return this.i18nDynamic(
locale, 'fatalErrorMessageInsecureURL', url || '');
+ case OobeTypes.FatalErrorCode.OOBE_COMPLETION_SKIPPED:
+ return this.i18nDynamic(locale, 'fatalErrorAutoEnrollmentSkipped');
case OobeTypes.FatalErrorCode.CUSTOM:
return params.errorText || '';
default:
diff --git a/chrome/browser/ui/ash/login/login_display_host_common.cc b/chrome/browser/ui/ash/login/login_display_host_common.cc
index 174cbaaf..beca742 100644
--- a/chrome/browser/ui/ash/login/login_display_host_common.cc
+++ b/chrome/browser/ui/ash/login/login_display_host_common.cc
@@ -22,6 +22,7 @@
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/types/pass_key.h"
+#include "base/values.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/app_mode/kiosk_app_types.h"
#include "chrome/browser/ash/app_mode/kiosk_controller.h"
@@ -662,6 +663,13 @@
StartWizard(SignInFatalErrorView::kScreenId);
}
+void LoginDisplayHostCommon::ShowOobeNotCompletedError() {
+ GetWizardController()->GetScreen<SignInFatalErrorScreen>()->SetErrorState(
+ SignInFatalErrorScreen::Error::kOobeCompletionSkipped,
+ base::Value::Dict());
+ StartWizard(SignInFatalErrorView::kScreenId);
+}
+
void LoginDisplayHostCommon::SAMLConfirmPassword(
::login::StringList scraped_passwords,
std::unique_ptr<UserContext> user_context) {
diff --git a/chrome/browser/ui/ash/login/login_display_host_common.h b/chrome/browser/ui/ash/login/login_display_host_common.h
index 3c5c18f..6895033 100644
--- a/chrome/browser/ui/ash/login/login_display_host_common.h
+++ b/chrome/browser/ui/ash/login/login_display_host_common.h
@@ -85,6 +85,8 @@
base::OnceCallback<void(std::unique_ptr<UserContext>)> on_skip_migration)
final;
void ShowSigninError(SigninError error, const std::string& details) final;
+ void ShowOobeNotCompletedError() final;
+
void SAMLConfirmPassword(::login::StringList scraped_passwords,
std::unique_ptr<UserContext> user_context) final;
WizardContext* GetWizardContextForTesting() final;
diff --git a/chrome/browser/ui/ash/login/mock_signin_ui.h b/chrome/browser/ui/ash/login/mock_signin_ui.h
index dcfb22e..180b188a03 100644
--- a/chrome/browser/ui/ash/login/mock_signin_ui.h
+++ b/chrome/browser/ui/ash/login/mock_signin_ui.h
@@ -53,6 +53,7 @@
ShowSigninError,
(SigninError, const std::string&),
(override));
+ MOCK_METHOD(void, ShowOobeNotCompletedError, (), (override));
MOCK_METHOD(void,
SAMLConfirmPassword,
(::login::StringList, std::unique_ptr<UserContext>),
diff --git a/chrome/browser/ui/ash/login/signin_ui.h b/chrome/browser/ui/ash/login/signin_ui.h
index 4596e18d..dd0391d 100644
--- a/chrome/browser/ui/ash/login/signin_ui.h
+++ b/chrome/browser/ui/ash/login/signin_ui.h
@@ -83,6 +83,8 @@
virtual void ShowSigninError(SigninError error,
const std::string& details) = 0;
+ virtual void ShowOobeNotCompletedError() = 0;
+
// Show the SAML Confirm Password screen and continue authentication after
// that (or show the error screen).
virtual void SAMLConfirmPassword(
diff --git a/chrome/browser/ui/webui/ash/login/signin_fatal_error_screen_handler.cc b/chrome/browser/ui/webui/ash/login/signin_fatal_error_screen_handler.cc
index 76e0e50..46084b0a2 100644
--- a/chrome/browser/ui/webui/ash/login/signin_fatal_error_screen_handler.cc
+++ b/chrome/browser/ui/webui/ash/login/signin_fatal_error_screen_handler.cc
@@ -13,6 +13,7 @@
#include "chrome/grit/generated_resources.h"
#include "components/login/localized_values_builder.h"
#include "components/strings/grit/components_strings.h"
+#include "ui/chromeos/devicetype_utils.h"
namespace ash {
@@ -36,6 +37,11 @@
IDS_LOGIN_FATAL_ERROR_NO_ACCOUNT_DETAILS);
builder->Add("fatalErrorMessageInsecureURL",
IDS_LOGIN_FATAL_ERROR_TEXT_INSECURE_URL);
+ builder->AddF("fatalErrorAutoEnrollmentSkipped",
+ IDS_LOGIN_FATAL_ERROR_AUTO_ENROLLMENT_SKIPPED,
+ ui::GetChromeOSDeviceName());
+ builder->Add("fatalErrorRestartAndPowerwash",
+ IDS_LOGIN_FATAL_ERROR_RESTART_AND_POWERWASH_BUTTON);
}
void SignInFatalErrorScreenHandler::Show(SignInFatalErrorScreen::Error error,
diff --git a/tools/metrics/histograms/metadata/oobe/enums.xml b/tools/metrics/histograms/metadata/oobe/enums.xml
index f5bcb27f..6d31ee3 100644
--- a/tools/metrics/histograms/metadata/oobe/enums.xml
+++ b/tools/metrics/histograms/metadata/oobe/enums.xml
@@ -181,6 +181,12 @@
<int value="15" label="Unknown"/>
</enum>
+<enum name="OobeNotCompletedErrorTrigger">
+ <int value="0" label="PerformOOBECompletedAction"/>
+ <int value="1" label="GaiaScreen"/>
+ <int value="2" label="ExistingUserController"/>
+</enum>
+
<enum name="OobeWebViewLoadResult">
<int value="0" label="Success"/>
<int value="1" label="Load timeout"/>
diff --git a/tools/metrics/histograms/metadata/oobe/histograms.xml b/tools/metrics/histograms/metadata/oobe/histograms.xml
index 11027eb..0eb11285 100644
--- a/tools/metrics/histograms/metadata/oobe/histograms.xml
+++ b/tools/metrics/histograms/metadata/oobe/histograms.xml
@@ -1068,6 +1068,19 @@
</summary>
</histogram>
+<histogram name="OOBE.OobeNotCompletedErrorTrigger"
+ enum="OobeNotCompletedErrorTrigger" expires_after="2026-10-22">
+ <owner>osamafathy@google.com</owner>
+ <owner>cros-oobe@google.com</owner>
+ <summary>
+ Records the specific trigger cause when the "OOBE Not Completed"
+ error screen is displayed. This is emitted before showing the error screen.
+ The enum values capture the different locations in the code where the check
+ for OOBE completion was performed and determined to have failed, leading to
+ this error.
+ </summary>
+</histogram>
+
<histogram name="OOBE.OobeStartToOnboardingStartTime" units="ms"
expires_after="2026-04-01">
<owner>osamafathy@google.com</owner>