| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/enterprise/connectors/connectors_service.h" |
| |
| #include <memory> |
| #include <variant> |
| |
| #include "base/check_op.h" |
| #include "base/memory/singleton.h" |
| #include "base/no_destructor.h" |
| #include "base/path_service.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/enterprise/connectors/connectors_manager.h" |
| #include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client_factory.h" |
| #include "chrome/browser/enterprise/util/affiliation.h" |
| #include "chrome/browser/policy/chrome_browser_policy_connector.h" |
| #include "chrome/browser/policy/dm_token_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_attributes_entry.h" |
| #include "chrome/browser/profiles/profile_attributes_storage.h" |
| #include "chrome/browser/profiles/reporting_util.h" |
| #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/ui/managed_ui.h" |
| #include "components/embedder_support/user_agent_utils.h" |
| #include "components/enterprise/browser/controller/browser_dm_token_storage.h" |
| #include "components/enterprise/buildflags/buildflags.h" |
| #include "components/enterprise/common/proto/connectors.pb.h" |
| #include "components/enterprise/connectors/core/connectors_manager_base.h" |
| #include "components/enterprise/connectors/core/connectors_prefs.h" |
| #include "components/enterprise/connectors/core/service_provider_config.h" |
| #include "components/keyed_service/content/browser_context_dependency_manager.h" |
| #include "components/policy/core/common/cloud/cloud_policy_store.h" |
| #include "components/policy/core/common/cloud/cloud_policy_util.h" |
| #include "components/policy/core/common/cloud/dm_token.h" |
| #include "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h" |
| #include "components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h" |
| #include "components/policy/core/common/policy_types.h" |
| #include "components/safe_browsing/buildflags.h" |
| #include "components/safe_browsing/core/common/features.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/user_prefs/user_prefs.h" |
| #include "components/version_info/version_info.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/common/url_constants.h" |
| #include "device_management_backend.pb.h" |
| #include "google_apis/gaia/gaia_auth_util.h" |
| |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| #include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h" |
| #include "extensions/browser/extension_registry_factory.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h" |
| #include "chrome/browser/ash/settings/device_settings_service.h" |
| #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h" |
| #include "chromeos/components/mgs/managed_guest_session_utils.h" |
| #include "components/user_manager/user.h" |
| #include "components/user_manager/user_manager.h" |
| #include "extensions/common/constants.h" |
| #else |
| #include "components/policy/core/common/cloud/profile_cloud_policy_manager.h" |
| #endif |
| |
| #if BUILDFLAG(SAFE_BROWSING_AVAILABLE) |
| #include "chrome/browser/enterprise/connectors/common.h" |
| #endif |
| |
| namespace enterprise_connectors { |
| |
| namespace { |
| |
| std::string GetClientId(Profile* profile) { |
| std::string client_id; |
| #if BUILDFLAG(IS_CHROMEOS) |
| auto* manager = profile->GetUserCloudPolicyManagerAsh(); |
| if (manager && manager->core() && manager->core()->client()) |
| client_id = manager->core()->client()->client_id(); |
| #else |
| client_id = policy::BrowserDMTokenStorage::Get()->RetrieveClientId(); |
| #endif |
| return client_id; |
| } |
| |
| bool IsURLExemptFromAnalysis(const GURL& url) { |
| if (url.SchemeIs(content::kChromeUIScheme)) |
| return true; |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (url.SchemeIs(extensions::kExtensionScheme) && |
| extension_misc::IsSystemUIApp(url.host_piece())) { |
| return true; |
| } |
| #endif |
| |
| return false; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| std::optional<std::string> GetDeviceDMToken() { |
| const enterprise_management::PolicyData* policy_data = |
| ash::DeviceSettingsService::Get()->policy_data(); |
| if (policy_data && policy_data->has_request_token()) |
| return policy_data->request_token(); |
| return std::nullopt; |
| } |
| #endif |
| |
| bool IsManagedGuestSession() { |
| #if BUILDFLAG(IS_CHROMEOS) |
| return chromeos::IsManagedGuestSession(); |
| #else |
| return false; |
| #endif |
| } |
| } // namespace |
| |
| // -------------------------------- |
| // ConnectorsService implementation |
| // -------------------------------- |
| |
| ConnectorsService::ConnectorsService(content::BrowserContext* context, |
| std::unique_ptr<ConnectorsManager> manager) |
| : context_(context), connectors_manager_(std::move(manager)) { |
| DCHECK(context_); |
| DCHECK(connectors_manager_); |
| } |
| |
| ConnectorsService::~ConnectorsService() = default; |
| |
| std::unique_ptr<ClientMetadata> ConnectorsService::GetBasicClientMetadata( |
| Profile* profile) { |
| auto metadata = std::make_unique<ClientMetadata>(); |
| // We need to return profile and browser DM tokens, even in cases where the |
| // reporting policy is disabled, in order to support merging rules. |
| std::optional<std::string> browser_dm_token = GetBrowserDmToken(); |
| if (browser_dm_token.has_value()) { |
| metadata->mutable_device()->set_dm_token(*browser_dm_token); |
| } |
| |
| std::optional<std::string> profile_dm_token = |
| reporting::GetUserDmToken(profile); |
| if (profile_dm_token.has_value()) { |
| metadata->mutable_profile()->set_dm_token(*profile_dm_token); |
| } |
| |
| // In this case, we are just using the client metadata to indicate to |
| // WebProtect whether or not the request is coming from a Managed Guest |
| // Session on ChromeOS. |
| metadata->set_is_chrome_os_managed_guest_session(IsManagedGuestSession()); |
| return metadata; |
| } |
| |
| std::optional<ReportingSettings> ConnectorsService::GetReportingSettings() { |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (!ConnectorsEnabled()) { |
| return std::nullopt; |
| } |
| |
| std::optional<ReportingSettings> settings = |
| connectors_manager_->GetReportingSettings(); |
| if (!settings.has_value()) |
| return std::nullopt; |
| |
| #if BUILDFLAG(SAFE_BROWSING_AVAILABLE) |
| Profile* profile = Profile::FromBrowserContext(context_); |
| if (IncludeDeviceInfo(profile, /*per_profile=*/false)) { |
| // The device dm token includes additional information like a device id, |
| // which is relevant for reporting and should only be used for |
| // IncludeDeviceInfo==true. |
| std::optional<std::string> device_dm_token = GetDeviceDMToken(); |
| if (device_dm_token.has_value()) { |
| settings.value().dm_token = device_dm_token.value(); |
| settings.value().per_profile = false; |
| return settings; |
| } |
| } |
| #endif // BUILDFLAG(SAFE_BROWSING_AVAILABLE) |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| return ConnectorsServiceBase::GetReportingSettings(); |
| } |
| |
| std::optional<AnalysisSettings> ConnectorsService::GetAnalysisSettings( |
| const GURL& url, |
| AnalysisConnector connector) { |
| DCHECK_NE(connector, AnalysisConnector::FILE_TRANSFER); |
| if (!ConnectorsEnabled()) |
| return std::nullopt; |
| |
| if (IsURLExemptFromAnalysis(url)) |
| return std::nullopt; |
| |
| if (url.SchemeIsBlob() || url.SchemeIsFileSystem()) { |
| GURL inner = url.inner_url() ? *url.inner_url() : GURL(url.path()); |
| return GetCommonAnalysisSettings( |
| connectors_manager_->GetAnalysisSettings(inner, connector), connector); |
| } |
| |
| return GetCommonAnalysisSettings( |
| connectors_manager_->GetAnalysisSettings(url, connector), connector); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| std::optional<AnalysisSettings> ConnectorsService::GetAnalysisSettings( |
| const storage::FileSystemURL& source_url, |
| const storage::FileSystemURL& destination_url, |
| AnalysisConnector connector) { |
| DCHECK_EQ(connector, AnalysisConnector::FILE_TRANSFER); |
| if (!ConnectorsEnabled()) |
| return std::nullopt; |
| |
| return GetCommonAnalysisSettings( |
| connectors_manager_->GetAnalysisSettings(context_, source_url, |
| destination_url, connector), |
| connector); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| std::optional<AnalysisSettings> ConnectorsService::GetCommonAnalysisSettings( |
| std::optional<AnalysisSettings> settings, |
| AnalysisConnector connector) { |
| if (!settings.has_value()) |
| return std::nullopt; |
| |
| #if !BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS) |
| if (settings->cloud_or_local_settings.is_local_analysis()) { |
| return std::nullopt; |
| } |
| #endif |
| |
| std::optional<DmToken> dm_token = |
| GetDmToken(AnalysisConnectorScopePref(connector)); |
| bool is_cloud = settings.value().cloud_or_local_settings.is_cloud_analysis(); |
| |
| if (is_cloud) { |
| if (!dm_token.has_value()) |
| return std::nullopt; |
| |
| std::get<CloudAnalysisSettings>(settings.value().cloud_or_local_settings) |
| .dm_token = dm_token.value().value; |
| } |
| |
| settings.value().per_profile = |
| (dm_token.has_value() && |
| dm_token.value().scope == policy::POLICY_SCOPE_USER) || |
| GetPolicyScope(AnalysisConnectorScopePref(connector)) == |
| policy::POLICY_SCOPE_USER; |
| settings.value().client_metadata = BuildClientMetadata(is_cloud); |
| |
| return settings; |
| } |
| |
| bool ConnectorsService::IsConnectorEnabled(AnalysisConnector connector) const { |
| if (!ConnectorsEnabled()) |
| return false; |
| |
| return connectors_manager_->IsAnalysisConnectorEnabled(connector); |
| } |
| |
| std::vector<const AnalysisConfig*> ConnectorsService::GetAnalysisServiceConfigs( |
| AnalysisConnector connector) { |
| if (!ConnectorsEnabled()) |
| return {}; |
| |
| return connectors_manager_->GetAnalysisServiceConfigs(connector); |
| } |
| |
| bool ConnectorsService::DelayUntilVerdict(AnalysisConnector connector) { |
| if (!ConnectorsEnabled()) |
| return false; |
| |
| return connectors_manager_->DelayUntilVerdict(connector); |
| } |
| |
| std::optional<std::u16string> ConnectorsService::GetCustomMessage( |
| AnalysisConnector connector, |
| const std::string& tag) { |
| if (!ConnectorsEnabled()) |
| return std::nullopt; |
| |
| return connectors_manager_->GetCustomMessage(connector, tag); |
| } |
| |
| std::optional<GURL> ConnectorsService::GetLearnMoreUrl( |
| AnalysisConnector connector, |
| const std::string& tag) { |
| if (!ConnectorsEnabled()) |
| return std::nullopt; |
| |
| return connectors_manager_->GetLearnMoreUrl(connector, tag); |
| } |
| |
| bool ConnectorsService::GetBypassJustificationRequired( |
| AnalysisConnector connector, |
| const std::string& tag) { |
| if (!ConnectorsEnabled()) |
| return false; |
| |
| return connectors_manager_->GetBypassJustificationRequired(connector, tag); |
| } |
| |
| bool ConnectorsService::HasExtraUiToDisplay(AnalysisConnector connector, |
| const std::string& tag) { |
| return GetCustomMessage(connector, tag) || GetLearnMoreUrl(connector, tag) || |
| GetBypassJustificationRequired(connector, tag); |
| } |
| |
| std::vector<std::string> ConnectorsService::GetAnalysisServiceProviderNames( |
| AnalysisConnector connector) { |
| if (!ConnectorsEnabled()) |
| return {}; |
| |
| if (!GetDmToken(AnalysisConnectorScopePref(connector)).has_value()) { |
| return {}; |
| } |
| |
| return connectors_manager_->GetAnalysisServiceProviderNames(connector); |
| } |
| |
| std::string ConnectorsService::GetManagementDomain() { |
| if (!ConnectorsEnabled()) |
| return std::string(); |
| |
| std::optional<policy::PolicyScope> scope = std::nullopt; |
| for (const char* scope_pref : |
| {enterprise_connectors::kEnterpriseRealTimeUrlCheckScope, |
| AnalysisConnectorScopePref(AnalysisConnector::FILE_ATTACHED), |
| AnalysisConnectorScopePref(AnalysisConnector::FILE_DOWNLOADED), |
| AnalysisConnectorScopePref(AnalysisConnector::BULK_DATA_ENTRY), |
| AnalysisConnectorScopePref(AnalysisConnector::PRINT), |
| kOnSecurityEventScopePref}) { |
| std::optional<DmToken> dm_token = GetDmToken(scope_pref); |
| if (dm_token.has_value()) { |
| scope = dm_token.value().scope; |
| |
| // Having one CBCM Connector policy set implies that profile ones will be |
| // ignored for another domain, so the loop can stop immediately. |
| if (scope == policy::PolicyScope::POLICY_SCOPE_MACHINE) |
| break; |
| } |
| } |
| |
| if (!scope.has_value()) |
| return std::string(); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| return GetAccountManagerIdentity(Profile::FromBrowserContext(context_)) |
| .value_or(std::string()); |
| #else |
| if (scope.value() == policy::PolicyScope::POLICY_SCOPE_USER) { |
| return GetAccountManagerIdentity(Profile::FromBrowserContext(context_)) |
| .value_or(std::string()); |
| } |
| |
| policy::MachineLevelUserCloudPolicyManager* manager = |
| g_browser_process->browser_policy_connector() |
| ->machine_level_user_cloud_policy_manager(); |
| if (!manager) |
| return std::string(); |
| |
| policy::CloudPolicyStore* store = manager->store(); |
| return (store && store->has_policy()) |
| ? gaia::ExtractDomainName(store->policy()->username()) |
| : std::string(); |
| #endif |
| } |
| |
| std::string ConnectorsService::GetRealTimeUrlCheckIdentifier() const { |
| auto dm_token = GetDmToken(kEnterpriseRealTimeUrlCheckScope); |
| if (!dm_token) { |
| return std::string(); |
| } |
| |
| Profile* profile = Profile::FromBrowserContext(context_); |
| if (dm_token->scope == policy::POLICY_SCOPE_MACHINE) { |
| return GetClientId(profile); |
| } |
| |
| #if BUILDFLAG(SAFE_BROWSING_AVAILABLE) |
| auto* identity_manager = IdentityManagerFactory::GetForProfile(profile); |
| if (!identity_manager) { |
| return std::string(); |
| } |
| |
| return GetProfileEmail(identity_manager); |
| #else |
| return std::string(); |
| #endif |
| } |
| |
| ConnectorsManager* ConnectorsService::ConnectorsManagerForTesting() { |
| return connectors_manager_.get(); |
| } |
| |
| void ConnectorsService::ObserveTelemetryReporting( |
| base::RepeatingCallback<void()> callback) { |
| connectors_manager_->SetTelemetryObserverCallback(callback); |
| } |
| |
| std::optional<ConnectorsService::DmToken> ConnectorsService::GetDmToken( |
| const char* scope_pref) const { |
| #if BUILDFLAG(IS_CHROMEOS) |
| // On CrOS the settings from primary profile applies to all profiles. |
| auto dm_token = GetBrowserDmToken(); |
| return dm_token ? std::make_optional<DmToken>(*dm_token, |
| policy::POLICY_SCOPE_MACHINE) |
| : std::nullopt; |
| #else |
| auto browser_dm_token = GetBrowserDmToken(); |
| policy::PolicyScope scope = GetPolicyScope(scope_pref); |
| std::string token_string = scope == policy::POLICY_SCOPE_USER |
| ? GetProfileDmToken().value_or("") |
| : browser_dm_token.value_or(""); |
| if (token_string.empty()) { |
| return std::nullopt; |
| } |
| return DmToken(token_string, scope); |
| #endif |
| } |
| |
| std::optional<std::string> ConnectorsService::GetBrowserDmToken() const { |
| policy::DMToken dm_token = |
| policy::GetDMToken(Profile::FromBrowserContext(context_)); |
| |
| if (!dm_token.is_valid()) |
| return std::nullopt; |
| |
| return dm_token.value(); |
| } |
| |
| policy::PolicyScope ConnectorsService::GetPolicyScope( |
| const char* scope_pref) const { |
| #if BUILDFLAG(IS_CHROMEOS) |
| // CrOS always uses a browser DM throughout connectors code, so its policy |
| // scope should always be POLICY_SCOPE_MACHINE. |
| return policy::PolicyScope::POLICY_SCOPE_MACHINE; |
| #else |
| return static_cast<policy::PolicyScope>(GetPrefs()->GetInteger(scope_pref)); |
| #endif |
| } |
| |
| bool ConnectorsService::ConnectorsEnabled() const { |
| Profile* profile = Profile::FromBrowserContext(context_); |
| |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) |
| // On desktop, the guest profile is actually the primary OTR profile of |
| // the "regular" guest profile. The regular guest profile is never used |
| // directly by users. Also, user are not able to create child OTR profiles |
| // from guest profiles, the menu item "New incognito window" is not |
| // available. So, if this is a guest session, allow it only if it is a |
| // child OTR profile as well. |
| if (profile->IsGuestSession()) |
| return profile->GetOriginalProfile() != profile; |
| |
| // Never allow system profiles. |
| if (profile->IsSystemProfile()) |
| return false; |
| #endif |
| |
| return !profile->IsOffTheRecord() || profile->IsGuestSession(); |
| } |
| |
| PrefService* ConnectorsService::GetPrefs() { |
| return Profile::FromBrowserContext(context_)->GetPrefs(); |
| } |
| |
| const PrefService* ConnectorsService::GetPrefs() const { |
| return Profile::FromBrowserContext(context_)->GetPrefs(); |
| } |
| |
| ConnectorsManagerBase* ConnectorsService::GetConnectorsManagerBase() { |
| return connectors_manager_.get(); |
| } |
| |
| const ConnectorsManagerBase* ConnectorsService::GetConnectorsManagerBase() |
| const { |
| return connectors_manager_.get(); |
| } |
| |
| policy::CloudPolicyManager* |
| ConnectorsService::GetManagedUserCloudPolicyManager() const { |
| return Profile::FromBrowserContext(context_)->GetCloudPolicyManager(); |
| } |
| |
| std::unique_ptr<ClientMetadata> ConnectorsService::BuildClientMetadata( |
| bool is_cloud) { |
| auto reporting_settings = GetReportingSettings(); |
| |
| Profile* profile = Profile::FromBrowserContext(context_); |
| if (is_cloud && !reporting_settings.has_value()) { |
| return GetBasicClientMetadata(profile); |
| } |
| |
| auto metadata = std::make_unique<ClientMetadata>( |
| reporting::GetContextAsClientMetadata(profile)); |
| |
| // Device info is only useful for cloud service providers since local |
| // providers can already determine all this info themselves. For this reason, |
| // we only include browser metadata. |
| if (!is_cloud) { |
| PopulateBrowserMetadata(/*include_device_info=*/true, |
| metadata->mutable_browser()); |
| return metadata; |
| } |
| |
| metadata->set_is_chrome_os_managed_guest_session(IsManagedGuestSession()); |
| |
| #if BUILDFLAG(SAFE_BROWSING_AVAILABLE) |
| bool include_device_info = |
| IncludeDeviceInfo(profile, reporting_settings.value().per_profile); |
| |
| PopulateBrowserMetadata(include_device_info, metadata->mutable_browser()); |
| |
| if (include_device_info) { |
| PopulateDeviceMetadata(reporting_settings.value(), GetClientId(profile), |
| metadata->mutable_device()); |
| } |
| #endif |
| |
| return metadata; |
| } |
| |
| // --------------------------------------- |
| // ConnectorsServiceFactory implementation |
| // --------------------------------------- |
| |
| // static |
| ConnectorsServiceFactory* ConnectorsServiceFactory::GetInstance() { |
| return base::Singleton<ConnectorsServiceFactory>::get(); |
| } |
| |
| ConnectorsService* ConnectorsServiceFactory::GetForBrowserContext( |
| content::BrowserContext* context) { |
| return static_cast<ConnectorsService*>( |
| GetInstance()->GetServiceForBrowserContext(context, true)); |
| } |
| |
| ConnectorsServiceFactory::ConnectorsServiceFactory() |
| : BrowserContextKeyedServiceFactory( |
| "ConnectorsService", |
| BrowserContextDependencyManager::GetInstance()) { |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| DependsOn(extensions::ExtensionRegistryFactory::GetInstance()); |
| #endif |
| } |
| |
| ConnectorsServiceFactory::~ConnectorsServiceFactory() = default; |
| |
| std::unique_ptr<KeyedService> |
| ConnectorsServiceFactory::BuildServiceInstanceForBrowserContext( |
| content::BrowserContext* context) const { |
| return std::make_unique<ConnectorsService>( |
| context, |
| std::make_unique<ConnectorsManager>(user_prefs::UserPrefs::Get(context), |
| GetServiceProviderConfig())); |
| } |
| |
| content::BrowserContext* ConnectorsServiceFactory::GetBrowserContextToUse( |
| content::BrowserContext* context) const { |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| // Do not construct the connectors service if the extensions are disabled for |
| // the given context. |
| if (extensions::ChromeContentBrowserClientExtensionsPart:: |
| AreExtensionsDisabledForProfile(context)) { |
| return nullptr; |
| } |
| #endif |
| |
| // On Chrome OS, settings from the primary/main profile apply to all |
| // profiles, besides incognito. |
| // However, the primary/main profile might not exist in tests - then the |
| // provided |context| is still used. |
| if (context && !context->IsOffTheRecord() && |
| !Profile::FromBrowserContext(context)->AsTestingProfile() && |
| !context->ShutdownStarted()) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| auto* user_manager = user_manager::UserManager::Get(); |
| if (auto* primary_user = user_manager->GetPrimaryUser()) { |
| if (auto* primary_browser_context = |
| ash::BrowserContextHelper::Get()->GetBrowserContextByUser( |
| primary_user)) { |
| return primary_browser_context; |
| } |
| } |
| #endif |
| } |
| return context; |
| } |
| |
| } // namespace enterprise_connectors |