Commit e98161eb authored by Mihai Sardarescu's avatar Mihai Sardarescu Committed by Commit Bot

Clear managed profile on start-up if having a primary account is not allowed.

This CL clears the profile when it is loaded is the primary account is no longer
allowed (e.g. profile was marked as a managed profile or the profile).

As this is a very destructive action (the profile directory is removed from disk),
the user is presented with a permanent browser modal dialog.

Screenshots for each platform:
Linux: https://drive.google.com/open?id=1HO693VkDnC0qjPxHJdFZr3CPqOs2Hidf
Windows: https://drive.google.com/open?id=1gvmvt2wUsA42nmcVWTZCB1F5encHJRRH

Design doc [Google internal only]:
https://docs.google.com/document/d/1QqWoV1I7WgrzeHJwm9agG56ZqXD_Q2L3LB3K7Ck89GI/edit?usp=sharing

Bug: 907474, 887756

Change-Id: Ib1187d79a45e0829b57768582c5d11126b0a5c46
Reviewed-on: https://chromium-review.googlesource.com/c/1288829
Commit-Queue: Mihai Sardarescu <msarda@chromium.org>
Reviewed-by: default avatarJulian Pastarmov <pastarmovj@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: default avatarDavid Roger <droger@chromium.org>
Reviewed-by: default avatarOwen Min <zmin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#611135}
parent 351b5444
......@@ -6262,6 +6262,14 @@ the Bookmarks menu.">
This account is managed by <ph name="DOMAIN">$1<ex>example.com</ex></ph>
</message>
<!-- Clear profile dialog-->
<message name="IDS_PROFILE_WILL_BE_DELETED_DIALOG_TITLE" desc="The title of the dialog shown when the user is syncing with an enterprise account and when this account is no longer allowed as the primary account.">
Profile will be deleted
</message>
<message name="IDS_PROFILE_WILL_BE_DELETED_DIALOG_DESCRIPTION" desc="The description of the dialog shown when the user closes a browser and force sign in is enabled and local auth info is not valid.">
Your account <ph name="EMAIL">$1<ex>john.doe@example.com</ex></ph> is no longer allowed as the primary account. Because this account is managed by <ph name="DOMAIN">$2<ex>example.com</ex></ph>, your bookmarks, history, passwords, and other settings will be cleared from this device.
</message>
<!-- Cookies Window -->
<message name="IDS_COOKIES_REMOVE_LABEL" desc="The label of the 'Remove' button in the Cookies Window">
Remove
......
......@@ -3509,6 +3509,7 @@ jumbo_split_static_library("browser") {
"policy/cloud/machine_level_user_cloud_policy_helper.h",
"policy/cloud/user_policy_signin_service.cc",
"policy/cloud/user_policy_signin_service.h",
"policy/cloud/user_policy_signin_service_internal.h",
"policy/machine_level_user_cloud_policy_controller.cc",
"policy/machine_level_user_cloud_policy_controller.h",
"policy/machine_level_user_cloud_policy_register_watcher.cc",
......
......@@ -11,6 +11,7 @@
#include "base/callback.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/policy/cloud/user_policy_signin_service_internal.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/signin_util.h"
......@@ -25,6 +26,9 @@
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace policy {
namespace internal {
bool g_force_prohibit_signout_for_tests = false;
}
UserPolicySigninService::UserPolicySigninService(
Profile* profile,
......@@ -224,7 +228,8 @@ void UserPolicySigninService::OnRegistrationComplete() {
}
void UserPolicySigninService::ProhibitSignoutIfNeeded() {
if (policy_manager()->IsClientRegistered()) {
if (policy_manager()->IsClientRegistered() ||
internal::g_force_prohibit_signout_for_tests) {
DVLOG(1) << "User is registered for policy - prohibiting signout";
signin_util::SetUserSignoutAllowedForProfile(profile_, false);
}
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_POLICY_CLOUD_USER_POLICY_SIGNIN_SERVICE_INTERNAL_H_
#define CHROME_BROWSER_POLICY_CLOUD_USER_POLICY_SIGNIN_SERVICE_INTERNAL_H_
namespace policy {
namespace internal {
// Allows tests to force all |UserPolicySigninService| to prohibit signout
// even when the policy manager is not registered.
extern bool g_force_prohibit_signout_for_tests;
} // namespace internal
} // namespace policy
#endif // CHROME_BROWSER_POLICY_CLOUD_USER_POLICY_SIGNIN_SERVICE_INTERNAL_H_
......@@ -1276,6 +1276,12 @@ void ProfileManager::DoFinalInit(Profile* profile, bool go_off_the_record) {
chrome::NOTIFICATION_PROFILE_ADDED,
content::Source<Profile>(profile),
content::NotificationService::NoDetails());
// At this point, the user policy service and the child account service
// had enough time to initialize and should have updated the user signout
// flag attached to the profile.
signin_util::EnsureUserSignoutAllowedIsInitializedForProfile(profile);
signin_util::EnsurePrimaryAccountAllowedForProfile(profile);
}
void ProfileManager::DoFinalInitForServices(Profile* profile,
......
......@@ -47,9 +47,12 @@ class ProfileMetrics {
DELETE_PROFILE_SETTINGS_SHOW_WARNING,
// Aborts profile deletion in an OnBeforeUnload event in any browser tab.
DELETE_PROFILE_ABORTED,
// Delete profile from web signout with Dice, when Chrome signout is
// prohibited.
DELETE_PROFILE_DICE_WEB_SIGNOUT,
// Commented out as it is not used anymore (kept in the enum as it was used
// as a bucket in a histogram).
// DELETE_PROFILE_DICE_WEB_SIGNOUT
// Delete profile internally when Chrome signout is prohibited and the
// username is no longer allowed.
DELETE_PROFILE_PRIMARY_ACCOUNT_NOT_ALLOWED = DELETE_PROFILE_ABORTED + 2,
NUM_DELETE_PROFILE_METRICS
};
......
......@@ -66,47 +66,32 @@
#endif
namespace {
// List of sources for which sign out is always allowed.
signin_metrics::ProfileSignout kAlwaysAllowedSignoutSources[] = {
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
// Allowed, because data has not been synced yet.
signin_metrics::ProfileSignout::ABORT_SIGNIN,
#endif
// Allowed, because only used on Android and the primary account must be
// cleared when the account is removed from device
signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE,
signin_metrics::ProfileSignout::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST};
SigninClient::SignoutDecision IsSignoutAllowed(
Profile* profile,
const signin_metrics::ProfileSignout signout_source_metric) {
// TODO(msarda): This logic should be reworked to only prohibit user-
// initiated sign-out. For now signin_util::IsUserSignoutAllowedForProfile()
// prohibits ALL sign-outs with the exception of ACCOUNT_REMOVED_FROM_DEVICE
// because this preserves the original behavior. A follow-up CL will make the
// slightly riskier change described above.
const signin_metrics::ProfileSignout signout_source) {
if (signin_util::IsUserSignoutAllowedForProfile(profile))
return SigninClient::SignoutDecision::ALLOW_SIGNOUT;
switch (signout_source_metric) {
case signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE:
return SigninClient::SignoutDecision::ALLOW_SIGNOUT;
case signin_metrics::ProfileSignout::ABORT_SIGNIN:
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
// Allowed, because data has not been synced yet.
for (const auto& always_allowed_source : kAlwaysAllowedSignoutSources) {
if (signout_source == always_allowed_source)
return SigninClient::SignoutDecision::ALLOW_SIGNOUT;
#else
// ABORT_SIGNIN is only used on Dice platforms.
NOTREACHED();
return SigninClient::SignoutDecision::DISALLOW_SIGNOUT;
#endif
case signin_metrics::ProfileSignout::SIGNOUT_PREF_CHANGED:
case signin_metrics::ProfileSignout::GOOGLE_SERVICE_NAME_PATTERN_CHANGED:
case signin_metrics::ProfileSignout::SIGNIN_PREF_CHANGED_DURING_SIGNIN:
case signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS:
case signin_metrics::ProfileSignout::SERVER_FORCED_DISABLE:
case signin_metrics::ProfileSignout::TRANSFER_CREDENTIALS:
case signin_metrics::ProfileSignout::
AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN:
case signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI:
return SigninClient::SignoutDecision::DISALLOW_SIGNOUT;
case signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS:
NOTREACHED();
return SigninClient::SignoutDecision::DISALLOW_SIGNOUT;
}
return SigninClient::SignoutDecision::DISALLOW_SIGNOUT;
}
} // namespace
ChromeSigninClient::ChromeSigninClient(
......
......@@ -268,33 +268,6 @@ TEST_F(ChromeSigninClientSignoutTest, SignOutWithoutForceSignin) {
manager_->SignOut(source_metric, delete_metric);
}
TEST_F(ChromeSigninClientSignoutTest, SignOutGuestSession) {
TestingProfile::Builder builder;
builder.SetGuestSession();
std::unique_ptr<TestingProfile> profile = builder.Build();
CreateClient(profile.get());
manager_ = std::make_unique<MockSigninManager>(client_.get(),
fake_controller_.get());
signin_metrics::ProfileSignout source_metric =
signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS;
signin_metrics::SignoutDelete delete_metric =
signin_metrics::SignoutDelete::IGNORE_METRIC;
EXPECT_CALL(*client_, ShowUserManager(browser()->profile()->GetPath()))
.Times(0);
EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath()))
.Times(0);
EXPECT_CALL(*manager_,
OnSignoutDecisionReached(
source_metric, delete_metric,
SigninManager::RemoveAccountsOption::kRemoveAllAccounts,
SigninClient::SignoutDecision::ALLOW_SIGNOUT))
.Times(1);
manager_->SignOut(source_metric, delete_metric);
}
class ChromeSigninClientSignoutSourceTest
: public ::testing::WithParamInterface<signin_metrics::ProfileSignout>,
public ChromeSigninClientSignoutTest {};
......@@ -312,6 +285,7 @@ bool IsSignoutDisallowedByPolicy(
case signin_metrics::ProfileSignout::TRANSFER_CREDENTIALS:
case signin_metrics::ProfileSignout::
AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN:
case signin_metrics::ProfileSignout::SIGNIN_NOT_ALLOWED_ON_PROFILE_INIT:
case signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI:
return true;
case signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE:
......@@ -323,6 +297,9 @@ bool IsSignoutDisallowedByPolicy(
case signin_metrics::ProfileSignout::ABORT_SIGNIN:
// Allow signout because data has not been synced yet.
return false;
case signin_metrics::ProfileSignout::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST:
// Allow signout for tests that want to force it.
return false;
case signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS:
NOTREACHED();
return false;
......@@ -339,8 +316,6 @@ TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutAllowed) {
CreateClient(profile.get());
manager_ = std::make_unique<MockSigninManager>(client_.get(),
fake_controller_.get());
// User sign-out is allowed for this test.
ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get()));
// Verify SigninManager gets callback indicating sign-out is always allowed.
......@@ -356,6 +331,7 @@ TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutAllowed) {
manager_->SignOut(signout_source, delete_metric);
}
#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_MACOSX)
TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutDisallowed) {
signin_metrics::ProfileSignout signout_source = GetParam();
......@@ -367,7 +343,6 @@ TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutDisallowed) {
manager_ = std::make_unique<MockSigninManager>(client_.get(),
fake_controller_.get());
// Disallow user sign-out.
ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get()));
signin_util::SetUserSignoutAllowedForProfile(profile.get(), false);
ASSERT_FALSE(signin_util::IsUserSignoutAllowedForProfile(profile.get()));
......@@ -389,9 +364,9 @@ TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutDisallowed) {
manager_->SignOut(signout_source, delete_metric);
}
#endif
const signin_metrics::ProfileSignout kSignoutSources[] = {
// NOTE: SIGNOUT_TEST == SIGNOUT_PREF_CHANGED.
signin_metrics::ProfileSignout::SIGNOUT_PREF_CHANGED,
signin_metrics::ProfileSignout::GOOGLE_SERVICE_NAME_PATTERN_CHANGED,
signin_metrics::ProfileSignout::SIGNIN_PREF_CHANGED_DURING_SIGNIN,
......@@ -402,6 +377,8 @@ const signin_metrics::ProfileSignout kSignoutSources[] = {
signin_metrics::ProfileSignout::AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN,
signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI,
signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE,
signin_metrics::ProfileSignout::SIGNIN_NOT_ALLOWED_ON_PROFILE_INIT,
signin_metrics::ProfileSignout::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST,
};
static_assert(base::size(kSignoutSources) ==
signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS,
......
This diff is collapsed.
......@@ -850,10 +850,18 @@ void MutableProfileOAuth2TokenServiceDelegate::RevokeAllCredentials() {
return;
DCHECK(thread_checker_.CalledOnValidThread());
ScopedBatchChange batch(this);
VLOG(1) << "MutablePO2TS::RevokeAllCredentials";
CancelWebTokenFetch();
ScopedBatchChange batch(this);
if (load_credentials_state() == LOAD_CREDENTIALS_IN_PROGRESS) {
VLOG(1) << "MutablePO2TS::RevokeAllCredentials before tokens are loaded.";
// If |RevokeAllCredentials| is called while credentials are being loaded,
// then the load must be cancelled and the load credentials state updated.
DCHECK_NE(0, web_data_service_request_);
CancelWebTokenFetch();
set_load_credentials_state(LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS);
FinishLoadingCredentials();
}
// Make a temporary copy of the account ids.
std::vector<std::string> accounts;
......@@ -864,7 +872,7 @@ void MutableProfileOAuth2TokenServiceDelegate::RevokeAllCredentials() {
DCHECK_EQ(0u, refresh_tokens_.size());
// Make sure all tokens are removed.
// Make sure all tokens are removed from storage.
if (token_web_data_)
token_web_data_->RemoveAllTokens();
}
......
......@@ -6,19 +6,112 @@
#include <memory>
#include "base/bind.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/supports_user_data.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/policy/cloud/user_policy_signin_service_internal.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/simple_message_box.h"
#include "chrome/browser/ui/startup/startup_types.h"
#include "chrome/browser/ui/webui/profile_helper.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/google/core/common/google_util.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/signin_manager.h"
#include "ui/base/l10n/l10n_util.h"
namespace signin_util {
namespace {
constexpr char kSignoutSettingKey[] = "signout_setting";
#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_MACOSX)
#define CAN_DELETE_PROFILE
#endif
#if defined(CAN_DELETE_PROFILE)
// Manager that presents the profile will be deleted dialog on the first active
// browser window.
class DeleteProfileDialogManager : public BrowserListObserver {
public:
class Delegate {
public:
// Called when the profile was marked for deletion. It is safe for the
// delegate to delete |manager| when this is called.
virtual void OnProfileDeleted(DeleteProfileDialogManager* manager) = 0;
};
DeleteProfileDialogManager(Profile* profile,
std::string primary_account_email,
Delegate* delegate)
: profile_(profile),
primary_account_email_(primary_account_email),
delegate_(delegate),
browser_observer_(this) {}
~DeleteProfileDialogManager() override {}
void PresentDialogOnAllBrowserWindows() {
browser_observer_.Add(BrowserList::GetInstance());
Browser* active_browser = chrome::FindLastActiveWithProfile(profile_);
if (active_browser)
OnBrowserSetLastActive(active_browser);
}
void OnBrowserSetLastActive(Browser* browser) override {
DCHECK(profile_);
if (browser->profile() != profile_)
return;
DCHECK(browser->window()->GetNativeWindow());
chrome::ShowWarningMessageBox(
browser->window()->GetNativeWindow(),
l10n_util::GetStringUTF16(IDS_PROFILE_WILL_BE_DELETED_DIALOG_TITLE),
l10n_util::GetStringFUTF16(
IDS_PROFILE_WILL_BE_DELETED_DIALOG_DESCRIPTION,
base::ASCIIToUTF16(primary_account_email_),
base::ASCIIToUTF16(
gaia::ExtractDomainName(primary_account_email_))),
/*can_close=*/false);
webui::DeleteProfileAtPath(
profile_->GetPath(),
ProfileMetrics::DELETE_PROFILE_PRIMARY_ACCOUNT_NOT_ALLOWED);
delegate_->OnProfileDeleted(this);
}
private:
Profile* profile_;
std::string primary_account_email_;
Delegate* delegate_;
ScopedObserver<BrowserList, DeleteProfileDialogManager> browser_observer_;
DISALLOW_COPY_AND_ASSIGN(DeleteProfileDialogManager);
};
#endif // defined(CAN_DELETE_PROFILE)
// Per-profile manager for the signout allowed setting.
#if defined(CAN_DELETE_PROFILE)
class UserSignoutSetting : public base::SupportsUserData::Data,
public DeleteProfileDialogManager::Delegate {
#else
class UserSignoutSetting : public base::SupportsUserData::Data {
#endif // defined(CAN_DELETE_PROFILE)
public:
enum class State { kUndefined, kAllowed, kDisallowed };
// Fetch from Profile. Make and store if not already present.
static UserSignoutSetting* GetForProfile(Profile* profile) {
UserSignoutSetting* signout_setting = static_cast<UserSignoutSetting*>(
......@@ -34,14 +127,31 @@ class UserSignoutSetting : public base::SupportsUserData::Data {
return signout_setting;
}
bool is_user_signout_allowed() const { return is_user_signout_allowed_; }
void set_is_user_signout_allowed(bool is_allowed) {
is_user_signout_allowed_ = is_allowed;
State state() const { return state_; }
void set_state(State state) { state_ = state; }
#if defined(CAN_DELETE_PROFILE)
// Shows the delete profile dialog on the first browser active window.
void ShowDeleteProfileDialog(Profile* profile, const std::string& email) {
if (delete_profile_dialog_manager_)
return;
delete_profile_dialog_manager_ =
std::make_unique<DeleteProfileDialogManager>(profile, email, this);
delete_profile_dialog_manager_->PresentDialogOnAllBrowserWindows();
}
void OnProfileDeleted(DeleteProfileDialogManager* dialog_manager) override {
DCHECK_EQ(delete_profile_dialog_manager_.get(), dialog_manager);
delete_profile_dialog_manager_.reset();
}
#endif
private:
// User sign-out allowed by default.
bool is_user_signout_allowed_ = true;
State state_ = State::kUndefined;
#if defined(CAN_DELETE_PROFILE)
std::unique_ptr<DeleteProfileDialogManager> delete_profile_dialog_manager_;
#endif
};
enum ForceSigninPolicyCache {
......@@ -75,13 +185,70 @@ void ResetForceSigninForTesting() {
g_is_force_signin_enabled_cache = NOT_CACHED;
}
bool IsUserSignoutAllowedForProfile(Profile* profile) {
return UserSignoutSetting::GetForProfile(profile)->state() ==
UserSignoutSetting::State::kAllowed;
}
void EnsureUserSignoutAllowedIsInitializedForProfile(Profile* profile) {
if (UserSignoutSetting::GetForProfile(profile)->state() ==
UserSignoutSetting::State::kUndefined) {
SetUserSignoutAllowedForProfile(profile, true);
}
}
void SetUserSignoutAllowedForProfile(Profile* profile, bool is_allowed) {
UserSignoutSetting::GetForProfile(profile)->set_is_user_signout_allowed(
is_allowed);
UserSignoutSetting::State new_state =
is_allowed ? UserSignoutSetting::State::kAllowed
: UserSignoutSetting::State::kDisallowed;
UserSignoutSetting::GetForProfile(profile)->set_state(new_state);
}
bool IsUserSignoutAllowedForProfile(Profile* profile) {
return UserSignoutSetting::GetForProfile(profile)->is_user_signout_allowed();
void EnsurePrimaryAccountAllowedForProfile(Profile* profile) {
// All primary accounts are allowed on ChromeOS, so this method is a no-op on
// ChromeOS.
#if !defined(OS_CHROMEOS)
SigninManager* signin_manager = SigninManagerFactory::GetForProfile(profile);
if (!signin_manager->IsAuthenticated())
return;
AccountInfo primary_account = signin_manager->GetAuthenticatedAccountInfo();
if (signin_manager->IsSigninAllowed() &&
signin_manager->IsAllowedUsername(primary_account.email)) {
return;
}
UserSignoutSetting* signout_setting =
UserSignoutSetting::GetForProfile(profile);
switch (signout_setting->state()) {
case UserSignoutSetting::State::kUndefined:
NOTREACHED();
break;
case UserSignoutSetting::State::kAllowed:
// Force clear the primary account if it is no longer allowed and if sign
// out is allowed.
signin_manager->SignOut(
signin_metrics::SIGNIN_NOT_ALLOWED_ON_PROFILE_INIT,
signin_metrics::SignoutDelete::IGNORE_METRIC);
break;
case UserSignoutSetting::State::kDisallowed:
#if defined(CAN_DELETE_PROFILE)
// Force remove the profile if sign out is not allowed and if the
// primary account is no longer allowed.
// This may be called while the profile is initializing, so it must be
// scheduled for later to allow the profile initialization to complete.
CHECK(profiles::IsMultipleProfilesEnabled());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&UserSignoutSetting::ShowDeleteProfileDialog,
base::Unretained(signout_setting), profile,
primary_account.email));
#else
CHECK(false) << "Deleting profiles is not supported.";
#endif // defined(CAN_DELETE_PROFILE)
break;
}
#endif // !defined(OS_CHROMEOS)
}
} // namespace signin_util
......@@ -19,6 +19,9 @@ void SetForceSigninForTesting(bool enable);
// Reset force sign in to uninitialized state for testing.
void ResetForceSigninForTesting();
// Returns true if clearing the primary profile is allowed.
bool IsUserSignoutAllowedForProfile(Profile* profile);
// Sign-out is allowed by default, but some Chrome profiles (e.g. for cloud-
// managed enterprise accounts) may wish to disallow user-initiated sign-out.
// Note that this exempts sign-outs that are not user-initiated (e.g. sign-out
......@@ -26,7 +29,18 @@ void ResetForceSigninForTesting();
// ChromeSigninClient::PreSignOut().
void SetUserSignoutAllowedForProfile(Profile* profile, bool is_allowed);
bool IsUserSignoutAllowedForProfile(Profile* profile);
// Updates the user sign-out state to |true| if is was never initialized.
// This should be called at the end of the flow to initialize a profile to
// ensure that the signout allowed flag is updated.
void EnsureUserSignoutAllowedIsInitializedForProfile(Profile* profile);
// Ensures that the primary account for |profile| is allowed:
// * If profile does not have any primary account, then this is a no-op.
// * If |IsUserSignoutAllowedForProfile| is allowed and the primary account
// is no longer allowed, then this clears the primary account.
// * If |IsUserSignoutAllowedForProfile| is not allowed and the primary account
// is not longer allowed, then this removes the profile.
void EnsurePrimaryAccountAllowedForProfile(Profile* profile);
} // namespace signin_util
......
......@@ -13,7 +13,8 @@ namespace chrome {
void ShowWarningMessageBox(gfx::NativeWindow parent,
const base::string16& title,
const base::string16& message) {
const base::string16& message,
bool can_close) {
NOTIMPLEMENTED();
}
......
......@@ -34,12 +34,16 @@ enum MessageBoxType {
// non-NULL, the box will be made modal to the |parent|, except on Mac, where it
// is always app-modal.
//
// If |can_close| is false, then this dialog will not show the close button and
// the dialog will only be dismissed when the user presses the OK button.
//
// NOTE: In general, you should avoid this since it's usually poor UI.
// We have a variety of other surfaces such as app menu notifications and
// infobars; consult the UI leads for a recommendation.
void ShowWarningMessageBox(gfx::NativeWindow parent,
const base::string16& title,
const base::string16& message);
const base::string16& message,
bool can_close = true);
// As above, but shows the dialog box asynchronously with a checkbox.
// |callback| will be invoked after the dialog is dismissed. It is invoked with
......
......@@ -58,7 +58,8 @@ chrome::MessageBoxResult ShowSync(gfx::NativeWindow parent,
chrome::MessageBoxType type,
const base::string16& yes_text,
const base::string16& no_text,
const base::string16& checkbox_text) {
const base::string16& checkbox_text,
bool can_close) {
chrome::MessageBoxResult result = chrome::MESSAGE_BOX_RESULT_NO;
// TODO(pkotwicz): Exit message loop when the dialog is closed by some other
......@@ -66,7 +67,7 @@ chrome::MessageBoxResult ShowSync(gfx::NativeWindow parent,
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
SimpleMessageBoxViews::Show(
parent, title, message, type, yes_text, no_text, checkbox_text,
parent, title, message, type, yes_text, no_text, checkbox_text, can_close,
base::Bind(
[](base::RunLoop* run_loop, chrome::MessageBoxResult* out_result,
chrome::MessageBoxResult messagebox_result) {
......@@ -92,10 +93,11 @@ chrome::MessageBoxResult SimpleMessageBoxViews::Show(
const base::string16& yes_text,
const base::string16& no_text,
const base::string16& checkbox_text,
bool can_close,
SimpleMessageBoxViews::MessageBoxResultCallback callback) {
if (!callback)
return ShowSync(parent, title, message, type, yes_text, no_text,
checkbox_text);
checkbox_text, can_close);
startup_metric_utils::SetNonBrowserUIDisplayed();
if (chrome::internal::g_should_skip_message_box_for_test) {
......@@ -148,8 +150,9 @@ chrome::MessageBoxResult SimpleMessageBoxViews::Show(
is_system_modal = false;
#endif
SimpleMessageBoxViews* dialog = new SimpleMessageBoxViews(
title, message, type, yes_text, no_text, checkbox_text, is_system_modal);
SimpleMessageBoxViews* dialog =
new SimpleMessageBoxViews(title, message, type, yes_text, no_text,
checkbox_text, is_system_modal, can_close);
views::Widget* widget =
constrained_window::CreateBrowserModalDialogViews(dialog, parent);
......@@ -180,6 +183,10 @@ base::string16 SimpleMessageBoxViews::GetDialogButtonLabel(
return yes_text_;
}
bool SimpleMessageBoxViews::Close() {
return can_close_ ? DialogDelegate::Close() : false;
}
bool SimpleMessageBoxViews::Cancel() {
result_ = chrome::MESSAGE_BOX_RESULT_NO;
Done();
......@@ -238,7 +245,8 @@ SimpleMessageBoxViews::SimpleMessageBoxViews(
const base::string16& yes_text,
const base::string16& no_text,
const base::string16& checkbox_text,
bool is_system_modal)
bool is_system_modal,
bool can_close)
: window_title_(title),
type_(type),
yes_text_(yes_text),
......@@ -246,7 +254,8 @@ SimpleMessageBoxViews::SimpleMessageBoxViews(
result_(chrome::MESSAGE_BOX_RESULT_NO),
message_box_view_(new views::MessageBoxView(
views::MessageBoxView::InitParams(message))),
is_system_modal_(is_system_modal) {
is_system_modal_(is_system_modal),
can_close_(can_close) {
if (yes_text_.empty()) {
yes_text_ =
type_ == chrome::MESSAGE_BOX_TYPE_QUESTION
......@@ -276,14 +285,19 @@ void SimpleMessageBoxViews::Done() {
std::move(result_callback_).Run(result_);
}
bool SimpleMessageBoxViews::ShouldShowCloseButton() const {
return can_close_;
}
namespace chrome {
void ShowWarningMessageBox(gfx::NativeWindow parent,
const base::string16& title,
const base::string16& message) {
const base::string16& message,
bool can_close) {
SimpleMessageBoxViews::Show(
parent, title, message, chrome::MESSAGE_BOX_TYPE_WARNING,
base::string16(), base::string16(), base::string16());
base::string16(), base::string16(), base::string16(), can_close);
}
void ShowWarningMessageBoxWithCheckbox(
......@@ -294,7 +308,7 @@ void ShowWarningMessageBoxWithCheckbox(
base::OnceCallback<void(bool checked)> callback) {
SimpleMessageBoxViews::Show(
parent, title, message, chrome::MESSAGE_BOX_TYPE_WARNING,
base::string16(), base::string16(), checkbox_text,
base::string16(), base::string16(), checkbox_text, /*can_close=*/true,
base::Bind(
[](base::OnceCallback<void(bool checked)> callback,
MessageBoxResult message_box_result) {
......@@ -309,7 +323,7 @@ MessageBoxResult ShowQuestionMessageBox(gfx::NativeWindow parent,
const base::string16& message) {
return SimpleMessageBoxViews::Show(
parent, title, message, chrome::MESSAGE_BOX_TYPE_QUESTION,
base::string16(), base::string16(), base::string16());
base::string16(), base::string16(), base::string16(), /*can_close=*/true);
}
MessageBoxResult ShowMessageBoxWithButtonText(gfx::NativeWindow parent,
......@@ -319,7 +333,8 @@ MessageBoxResult ShowMessageBoxWithButtonText(gfx::NativeWindow parent,
const base::string16& no_text) {
return SimpleMessageBoxViews::Show(parent, title, message,
chrome::MESSAGE_BOX_TYPE_QUESTION,
yes_text, no_text, base::string16());
yes_text, no_text, base::string16(),
/*can_close=*/true);
}
} // namespace chrome
......@@ -25,6 +25,7 @@ class SimpleMessageBoxViews : public views::DialogDelegate,
const base::string16& yes_text,
const base::string16& no_text,
const base::string16& checkbox_text,
bool can_close,
MessageBoxResultCallback callback = MessageBoxResultCallback());
// views::DialogDelegate:
......@@ -32,12 +33,14 @@ class SimpleMessageBoxViews : public views::DialogDelegate,
base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
bool Cancel() override;
bool Accept() override;
bool Close() override;
base::string16 GetWindowTitle() const override;
void DeleteDelegate() override;
ui::ModalType GetModalType() const override;
views::View* GetContentsView() override;
views::Widget* GetWidget() override;
const views::Widget* GetWidget() const override;
bool ShouldShowCloseButton() const override;
// views::WidgetObserver:
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
......@@ -49,7 +52,8 @@ class SimpleMessageBoxViews : public views::DialogDelegate,
const base::string16& yes_text,
const base::string16& no_text,
const base::string16& checkbox_text,
bool is_system_modal);
bool is_system_modal,
bool can_close);
~SimpleMessageBoxViews() override;
void Run(MessageBoxResultCallback result_callback);
......@@ -63,6 +67,7 @@ class SimpleMessageBoxViews : public views::DialogDelegate,
views::MessageBoxView* message_box_view_;
MessageBoxResultCallback result_callback_;
bool is_system_modal_;
bool can_close_;
DISALLOW_COPY_AND_ASSIGN(SimpleMessageBoxViews);
};
......
......@@ -46,6 +46,7 @@
#include "chrome/browser/profiles/storage_partition_descriptor.h"
#include "chrome/browser/search_engines/template_url_fetcher_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/sync/bookmark_sync_service_factory.h"
#include "chrome/browser/sync/glue/sync_start_util.h"
#include "chrome/browser/web_data_service_factory.h"
......@@ -491,8 +492,15 @@ void TestingProfile::FinishInit() {
if (profile_manager)
profile_manager->InitProfileUserPrefs(this);
if (delegate_)
if (delegate_) {
delegate_->OnProfileCreated(this, true, false);
} else {
// It is the role of the delegate to ensure that the signout allowed is
// properly updated after the profile is create is initialized.
// For testing profiles that do not have a delegate, the signout allowed
// must be initialized when the testing profile finishes its initialization.
signin_util::EnsureUserSignoutAllowedIsInitializedForProfile(this);
}
}
TestingProfile::~TestingProfile() {
......
......@@ -103,8 +103,9 @@ void FakeSigninManager::SignIn(const std::string& gaia_id,
}
void FakeSigninManager::ForceSignOut() {
// SigninClients should always allow sign-out for SIGNOUT_TEST.
SignOut(signin_metrics::SIGNOUT_TEST,
// SigninClients should always allow sign-out for
// |FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TESTS|.
SignOut(signin_metrics::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST,
signin_metrics::SignoutDelete::IGNORE_METRIC);
}
......
......@@ -292,6 +292,14 @@ void SigninManager::Initialize(PrefService* local_state) {
//
// Note: The token service has not yet loaded its credentials, so accounts
// cannot be revoked here.
//
// On desktop, when SigninManager is initializing, the profile was not yet
// marked with sign out allowed. Therefore sign out is not allowed and all
// calls to SignOut methods are no-op.
//
// TODO(msarda): SignOut methods do not gurantee that sign out can actually
// be done (this depends on whether sign out is allowed). Add a check here
// on desktop to make it clear that SignOut does not do anything.
SignOutAndKeepAllAccounts(signin_metrics::SIGNIN_PREF_CHANGED_DURING_SIGNIN,
signin_metrics::SignoutDelete::IGNORE_METRIC);
}
......
......@@ -29,7 +29,7 @@ enum DifferentPrimaryAccounts {
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.signin
// GENERATED_JAVA_CLASS_NAME_OVERRIDE: SignoutReason
enum ProfileSignout {
// The value used within unit tests
// The value used within unit tests.
SIGNOUT_TEST = 0,
// The preference or policy controlling if signin is valid has changed.
SIGNOUT_PREF_CHANGED = 0,
......@@ -55,6 +55,10 @@ enum ProfileSignout {
// Android specific. Signout forced because the account was removed from the
// device.
ACCOUNT_REMOVED_FROM_DEVICE,
// Signin is no longer allowed when the profile is initialized.
SIGNIN_NOT_ALLOWED_ON_PROFILE_INIT,
// Sign out is forced allowed. Only used for tests.
FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST,
// Keep this as the last enum.
NUM_PROFILE_SIGNOUT_METRICS,
};
......
......@@ -46492,9 +46492,16 @@ Called by update_net_trust_anchors.py.-->
<int value="8" label="User turned off sync from DICE UI.">
User turned off sync from the Desktop Identity Consistency internals UI.
</int>
<int value="9" label="(Android) Account removed from device">
<int value="9" label="[Android] Account removed from device">
Signout forced because account was removed from device.
</int>
<int value="10" label="Account removed from device">
Signout forced when profile is loaded as browser sign-in is no longer
allowed.
</int>
<int value="11" label="[Tests] Force sign-out">
Signout is forced. Used only for tests.
</int>
</enum>
<enum name="SigninSource">
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment