// 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/app/profile/certificate_policy_profile_agent.h"
#import "base/task/cancelable_task_tracker.h"
#import "base/task/single_thread_task_runner.h"
#import "ios/chrome/app/profile/profile_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/web/public/security/certificate_policy_cache.h"
#import "ios/web/public/session/session_certificate_policy_cache.h"
#import "ios/web/public/thread/web_task_traits.h"
#import "ios/web/public/thread/web_thread.h"
#import "ios/web/public/web_state.h"
namespace {
// Updates the BrowserState's policy cache from the `web_state` session policy
// cache.
void UpdateCertificatePolicyCacheFromWebState(const web::WebState* web_state) {
DCHECK(web_state);
// The WebState install its certificate policy cache upon realization, so
// unrealized WebState can be skipped (to avoid forcing their realization).
if (!web_state->IsRealized()) {
return;
}
web_state->GetSessionCertificatePolicyCache()->UpdateCertificatePolicyCache();
}
// Populates the certificate policy cache based on all of the WebStates in
// the `incognito` browsers of `weak_profile`. Because this is called
// asynchronously, it needs to be resilient to shutdown having happened before
// it is invoked.
void RestoreCertificatePolicyCacheFromBrowsers(
base::WeakPtr<ProfileIOS> weak_profile,
bool incognito) {
// If the ProfileIOS is destroyed, it's too late to do anything.
ProfileIOS* profile = weak_profile.get();
if (!profile) {
return;
}
BrowserList* browser_list = BrowserListFactory::GetForProfile(profile);
const BrowserList::BrowserType browser_types =
incognito ? BrowserList::BrowserType::kIncognito
: BrowserList::BrowserType::kRegularAndInactive;
std::set<Browser*> browsers = browser_list->BrowsersOfType(browser_types);
for (Browser* browser : browsers) {
WebStateList* web_state_list = browser->GetWebStateList();
for (int index = 0; index < web_state_list->count(); ++index) {
UpdateCertificatePolicyCacheFromWebState(
web_state_list->GetWebStateAt(index));
}
}
}
// Scrubs the certificate policy cache of all certificates policies except
// those for the current `incognito` browsers in `weak_profile`.
// Clearing the cache is done on the IO thread, and then cache repopulation is
// done on the UI thread.
void CleanCertificatePolicyCache(
base::CancelableTaskTracker* task_tracker,
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const scoped_refptr<web::CertificatePolicyCache>& policy_cache,
base::WeakPtr<ProfileIOS> weak_profile,
bool incognito) {
DCHECK(policy_cache);
DCHECK_CURRENTLY_ON(web::WebThread::UI);
task_tracker->PostTaskAndReply(
task_runner.get(), FROM_HERE,
base::BindOnce(&web::CertificatePolicyCache::ClearCertificatePolicies,
policy_cache),
base::BindOnce(&RestoreCertificatePolicyCacheFromBrowsers,
std::move(weak_profile), incognito));
}
} // anonymous namespace
@implementation CertificatePolicyProfileAgent {
// Used to ensure thread-safety of the certificate policy management code.
base::CancelableTaskTracker _clearPoliciesTaskTracker;
// Whether one scene reached foreground since last background.
BOOL _hasForegroundScenes;
}
- (void)dealloc {
// Clean up any remaining tasks.
_clearPoliciesTaskTracker.TryCancelAll();
}
- (BOOL)isWorking {
return static_cast<BOOL>(_clearPoliciesTaskTracker.HasTrackedTasks());
}
#pragma mark - SceneStateObserver
- (void)sceneState:(SceneState*)sceneState
transitionedToActivationLevel:(SceneActivationLevel)level {
if (level >= SceneActivationLevelForegroundInactive) {
_hasForegroundScenes = YES;
return;
}
if (self.profileState.initStage < ProfileInitStage::kFinal) {
return;
}
CHECK_LE(level, SceneActivationLevelBackground);
if (self.profileState.foregroundScenes.count == 0 && _hasForegroundScenes) {
_hasForegroundScenes = NO;
[self appDidEnterBackground];
}
}
#pragma mark - Private
- (void)appDidEnterBackground {
ProfileIOS* profile = self.profileState.profile;
CHECK(profile);
// Evict all the certificate policies except for the current entries of the
// active sessions, for the regular and incognito browsers.
CleanCertificatePolicyCache(
&_clearPoliciesTaskTracker, web::GetIOThreadTaskRunner({}),
web::BrowserState::GetCertificatePolicyCache(profile),
profile->AsWeakPtr(),
/*incognito=*/false);
if (profile->HasOffTheRecordProfile()) {
ProfileIOS* incognitoProfile = profile->GetOffTheRecordProfile();
CleanCertificatePolicyCache(
&_clearPoliciesTaskTracker, web::GetIOThreadTaskRunner({}),
web::BrowserState::GetCertificatePolicyCache(incognitoProfile),
profile->AsWeakPtr(), /*incognito=*/true);
}
}
@end