| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/policy/model/policy_watcher_browser_agent.h" |
| |
| #import <Foundation/Foundation.h> |
| |
| #import "base/apple/backup_util.h" |
| #import "base/apple/foundation_util.h" |
| #import "base/barrier_closure.h" |
| #import "base/functional/callback_helpers.h" |
| #import "base/metrics/histogram_functions.h" |
| #import "base/path_service.h" |
| #import "base/run_loop.h" |
| #import "base/task/sequenced_task_runner.h" |
| #import "base/task/thread_pool.h" |
| #import "components/policy/core/common/policy_pref_names.h" |
| #import "components/prefs/pref_change_registrar.h" |
| #import "components/prefs/pref_service.h" |
| #import "components/signin/public/base/signin_pref_names.h" |
| #import "components/signin/public/identity_manager/identity_manager.h" |
| #import "components/sync/base/pref_names.h" |
| #import "ios/chrome/app/application_delegate/app_state.h" |
| #import "ios/chrome/app/profile/profile_state.h" |
| #import "ios/chrome/browser/authentication/ui_bundled/signin/signin_utils.h" |
| #import "ios/chrome/browser/policy/model/policy_watcher_browser_agent_observer.h" |
| #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h" |
| #import "ios/chrome/browser/shared/model/application_context/application_context.h" |
| #import "ios/chrome/browser/shared/model/prefs/pref_names.h" |
| #import "ios/chrome/browser/shared/model/profile/profile_ios.h" |
| #import "ios/chrome/browser/shared/public/commands/policy_change_commands.h" |
| #import "ios/chrome/browser/signin/model/authentication_service_factory.h" |
| #import "ios/chrome/browser/signin/model/identity_manager_factory.h" |
| #import "ios/web/public/thread/web_task_traits.h" |
| |
| PolicyWatcherBrowserAgent::PolicyWatcherBrowserAgent(Browser* browser) |
| : BrowserUserData(browser) { |
| DCHECK(!browser->GetProfile()->IsOffTheRecord()); |
| prefs_change_observer_.Init(GetApplicationContext()->GetLocalState()); |
| profile_prefs_change_observer_.Init(browser->GetProfile()->GetPrefs()); |
| } |
| |
| PolicyWatcherBrowserAgent::~PolicyWatcherBrowserAgent() = default; |
| |
| void PolicyWatcherBrowserAgent::SignInUIDismissed() { |
| // Do nothing if the sign out is still in progress. |
| if (sign_out_in_progress_) { |
| return; |
| } |
| |
| [handler_ showForceSignedOutPrompt]; |
| } |
| |
| void PolicyWatcherBrowserAgent::Initialize(id<PolicyChangeCommands> handler) { |
| DCHECK(!handler_); |
| DCHECK(handler); |
| handler_ = handler; |
| |
| identity_manager_ = |
| IdentityManagerFactory::GetForProfile(browser_->GetProfile()); |
| CHECK(identity_manager_); |
| |
| auth_service_ = |
| AuthenticationServiceFactory::GetForProfile(browser_->GetProfile()); |
| DCHECK(auth_service_); |
| auth_service_observation_.Observe(auth_service_.get()); |
| |
| // BrowserSignin policy: start observing the kSigninAllowed pref. When the |
| // pref becomes false, send a UI command to sign the user out. This requires |
| // the given command dispatcher to be fully configured. |
| prefs_change_observer_.Add( |
| prefs::kBrowserSigninPolicy, |
| base::BindRepeating( |
| &PolicyWatcherBrowserAgent::ForceSignOutIfSigninDisabled, |
| base::Unretained(this))); |
| |
| // Try to sign out in case the policy changed since last time. This should be |
| // done after the handler is set to make sure the UI can be displayed. |
| ForceSignOutIfSigninDisabled(); |
| |
| // TODO(crbug.com/40265119): Instead of directly accessing internal sync |
| // prefs, go through proper APIs (SyncService/SyncUserSettings). |
| profile_prefs_change_observer_.Add( |
| syncer::prefs::internal::kSyncManaged, |
| base::BindRepeating( |
| &PolicyWatcherBrowserAgent::ShowSyncDisabledPromptIfNeeded, |
| base::Unretained(this))); |
| |
| // Try to show the alert in case the policy changed since last time. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&PolicyWatcherBrowserAgent::ShowSyncDisabledPromptIfNeeded, |
| weak_factory_.GetWeakPtr())); |
| |
| profile_prefs_change_observer_.Add( |
| prefs::kAllowChromeDataInBackups, |
| base::BindRepeating( |
| &PolicyWatcherBrowserAgent::UpdateAppContainerBackupExclusion, |
| base::Unretained(this))); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &PolicyWatcherBrowserAgent::UpdateAppContainerBackupExclusion, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void PolicyWatcherBrowserAgent::ForceSignOutIfSigninDisabled() { |
| DCHECK(handler_); |
| DCHECK(auth_service_); |
| if ((auth_service_->GetServiceStatus() == |
| AuthenticationService::ServiceStatus::SigninDisabledByPolicy)) { |
| for (auto& observer : observers_) { |
| observer.OnSignInDisallowed(this); |
| } |
| if (identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { |
| sign_out_in_progress_ = true; |
| base::UmaHistogramBoolean("Enterprise.BrowserSigninIOS.SignedOutByPolicy", |
| true); |
| signin::ProfileSignoutRequest( |
| signin_metrics::ProfileSignout::kPrefChanged) |
| .SetShouldRecordMetrics(false) |
| .Run(browser_); |
| } |
| } |
| } |
| |
| void PolicyWatcherBrowserAgent::ShowSyncDisabledPromptIfNeeded() { |
| auto* profile_prefs = browser_->GetProfile()->GetPrefs(); |
| BOOL syncDisabledAlertShown = |
| profile_prefs->GetBoolean(policy::policy_prefs::kSyncDisabledAlertShown); |
| // TODO(crbug.com/40265119): Instead of directly accessing internal sync |
| // prefs, go through proper APIs (SyncService/SyncUserSettings). |
| BOOL isSyncDisabledByAdministrator = |
| profile_prefs->GetBoolean(syncer::prefs::internal::kSyncManaged); |
| |
| if (!syncDisabledAlertShown && isSyncDisabledByAdministrator) { |
| SceneState* scene_state = browser_->GetSceneState(); |
| BOOL scene_is_active = |
| scene_state.activationLevel >= SceneActivationLevelForegroundActive; |
| if (scene_is_active) { |
| [handler_ showSyncDisabledPrompt]; |
| // Will never trigger again unless policy changes. |
| profile_prefs->SetBoolean(policy::policy_prefs::kSyncDisabledAlertShown, |
| true); |
| } |
| } else if (syncDisabledAlertShown && !isSyncDisabledByAdministrator) { |
| // Will trigger again, if policy is turned back on. |
| profile_prefs->SetBoolean(policy::policy_prefs::kSyncDisabledAlertShown, |
| false); |
| } |
| } |
| |
| void PolicyWatcherBrowserAgent::UpdateAppContainerBackupExclusion() { |
| bool backup_allowed = browser_->GetProfile()->GetPrefs()->GetBoolean( |
| prefs::kAllowChromeDataInBackups); |
| // TODO(crbug.com/40826035): If multiple profiles are supported on iOS, update |
| // this logic to work with multiple profiles having possibly-possibly |
| // conflicting preference values. |
| base::FilePath storage_dir = base::apple::GetUserLibraryPath(); |
| if (backup_allowed) { |
| base::ThreadPool::PostTask( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, |
| base::BindOnce(base::IgnoreResult(&base::apple::ClearBackupExclusion), |
| std::move(storage_dir))); |
| } else { |
| base::ThreadPool::PostTask( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, |
| base::BindOnce(base::IgnoreResult(&base::apple::SetBackupExclusion), |
| std::move(storage_dir))); |
| } |
| } |
| |
| void PolicyWatcherBrowserAgent::AddObserver( |
| PolicyWatcherBrowserAgentObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void PolicyWatcherBrowserAgent::RemoveObserver( |
| PolicyWatcherBrowserAgentObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void PolicyWatcherBrowserAgent::OnSignOutComplete() { |
| SceneState* scene_state = browser_->GetSceneState(); |
| sign_out_in_progress_ = false; |
| BOOL scene_is_active = |
| scene_state.activationLevel >= SceneActivationLevelForegroundActive; |
| if (scene_is_active) { |
| // Try to show the signout prompt in all cases: if there is a sign |
| // in in progress, the UI will prevent the prompt from showing. |
| [handler_ showForceSignedOutPrompt]; |
| } else { |
| scene_state.profileState.shouldShowForceSignOutPrompt = YES; |
| } |
| } |
| |
| void PolicyWatcherBrowserAgent::OnPrimaryAccountRestricted() { |
| [handler_ showRestrictAccountSignedOutPrompt]; |
| } |