blob: 4613757e72a1a6dace8711d0314b596a6b18f35a [file] [log] [blame]
// 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/util/managed_browser_utils.h"
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/image_fetcher/image_fetcher_service_factory.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/profile_key.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/certificate_matching/certificate_principal_pattern.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/device_signals/core/browser/pref_names.h"
#include "components/image_fetcher/core/image_fetcher.h"
#include "components/image_fetcher/core/image_fetcher_service.h"
#include "components/image_fetcher/core/image_fetcher_types.h"
#include "components/image_fetcher/core/request_metadata.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/account_managed_status_finder.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "net/base/host_port_pair.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/client_cert_identity.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/text_elider.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_ANDROID)
#include <jni.h>
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "chrome/browser/enterprise/connectors/connectors_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/managed_ui.h"
#include "components/enterprise/browser/reporting/common_pref_names.h"
#include "components/enterprise/connectors/core/features.h"
#include "components/safe_browsing/core/common/features.h"
// Must come after other includes, because FromJniType() uses Profile.
#include "chrome/browser/enterprise/util/jni_headers/ManagedBrowserUtils_jni.h"
#endif // BUILDFLAG(IS_ANDROID)
namespace enterprise_util {
// Enterprise custom labels have a limmit of 16 characters, so they will be cut
// at the 17th characters.
constexpr int kMaximumEnterpriseCustomLabelLengthCutOff = 17;
namespace {
// Returns client certificate auto-selection filters configured for the given
// URL in |ContentSettingsType::AUTO_SELECT_CERTIFICATE| content setting. The
// format of the returned filters corresponds to the "filter" property of the
// AutoSelectCertificateForUrls policy as documented at policy_templates.json.
base::Value::List GetCertAutoSelectionFilters(Profile* profile,
const GURL& requesting_url) {
HostContentSettingsMap* host_content_settings_map =
HostContentSettingsMapFactory::GetForProfile(profile);
base::Value setting = host_content_settings_map->GetWebsiteSetting(
requesting_url, requesting_url,
ContentSettingsType::AUTO_SELECT_CERTIFICATE, nullptr);
if (!setting.is_dict())
return {};
base::Value::List* filters = setting.GetDict().FindList("filters");
if (!filters) {
// |setting_dict| has the wrong format (e.g. single filter instead of a
// list of filters). This content setting is only provided by
// the |PolicyProvider|, which should always set it to a valid format.
// Therefore, delete the invalid value.
host_content_settings_map->SetWebsiteSettingDefaultScope(
requesting_url, requesting_url,
ContentSettingsType::AUTO_SELECT_CERTIFICATE, base::Value());
return {};
}
return std::move(*filters);
}
// Returns whether the client certificate matches any of the auto-selection
// filters. Returns false when there's no valid filter.
bool CertMatchesSelectionFilters(
const net::ClientCertIdentity& client_cert,
const base::Value::List& auto_selection_filters) {
for (const auto& filter : auto_selection_filters) {
if (!filter.is_dict()) {
// The filter has a wrong format, so ignore it. Note that reporting of
// schema violations, like this, to UI is already implemented in the
// policy handler - see configuration_policy_handler_list_factory.cc.
continue;
}
auto issuer_pattern = certificate_matching::CertificatePrincipalPattern::
ParseFromOptionalDict(filter.GetDict().FindDict("ISSUER"), "CN", "L",
"O", "OU");
auto subject_pattern = certificate_matching::CertificatePrincipalPattern::
ParseFromOptionalDict(filter.GetDict().FindDict("SUBJECT"), "CN", "L",
"O", "OU");
if (issuer_pattern.Matches(client_cert.certificate()->issuer()) &&
subject_pattern.Matches(client_cert.certificate()->subject())) {
return true;
}
}
return false;
}
void OnManagementIconReceived(
base::OnceCallback<void(const gfx::Image&)> callback,
const gfx::Image& icon,
const image_fetcher::RequestMetadata& metadata) {
if (icon.IsEmpty()) {
LOG(WARNING) << "EnterpriseLogoUrl fetch failed with error code "
<< metadata.http_response_code << " and MIME type "
<< metadata.mime_type;
}
std::move(callback).Run(icon);
}
// Expected to be called when Management is set and enterprise badging is
// enabled. Returns:
// - true for Work.
// - false for School.
bool IsManagementWork(Profile* profile) {
CHECK(enterprise_util::CanShowEnterpriseBadgingForAvatar(profile));
auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
auto management_environment = enterprise_util::GetManagementEnvironment(
profile, identity_manager->FindExtendedAccountInfoByAccountId(
identity_manager->GetPrimaryAccountId(
signin::ConsentLevel::kSignin)));
CHECK_NE(management_environment,
enterprise_util::ManagementEnvironment::kNone);
return management_environment ==
enterprise_util::ManagementEnvironment::kWork;
}
} // namespace
bool IsBrowserManaged(Profile* profile) {
DCHECK(profile);
return policy::ManagementServiceFactory::GetForProfile(profile)->IsManaged();
}
std::string GetDomainFromEmail(const std::string& email) {
size_t email_separator_pos = email.find('@');
bool is_email = email_separator_pos != std::string::npos &&
email_separator_pos < email.length() - 1;
if (!is_email)
return std::string();
return gaia::ExtractDomainName(email);
}
GURL GetRequestingUrl(const net::HostPortPair host_port_pair) {
return GURL("https://" + host_port_pair.ToString());
}
void AutoSelectCertificates(
Profile* profile,
const GURL& requesting_url,
net::ClientCertIdentityList client_certs,
net::ClientCertIdentityList* matching_client_certs,
net::ClientCertIdentityList* nonmatching_client_certs) {
matching_client_certs->clear();
nonmatching_client_certs->clear();
const base::Value::List auto_selection_filters =
GetCertAutoSelectionFilters(profile, requesting_url);
for (auto& client_cert : client_certs) {
if (CertMatchesSelectionFilters(*client_cert, auto_selection_filters))
matching_client_certs->push_back(std::move(client_cert));
else
nonmatching_client_certs->push_back(std::move(client_cert));
}
}
bool IsMachinePolicyPref(const std::string& pref_name) {
const PrefService::Preference* pref =
g_browser_process->local_state()->FindPreference(pref_name);
return pref && pref->IsManaged();
}
void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
registry->RegisterListPref(prefs::kManagedAutoSelectCertificateForUrls);
}
void SetUserAcceptedAccountManagement(Profile* profile, bool accepted) {
// Some tests do not have a profile manager.
if (!g_browser_process->profile_manager())
return;
// The updated consent screen also ask the user for consent to share device
// signals.
if (accepted && base::FeatureList::IsEnabled(
features::kEnterpriseUpdatedProfileCreationScreen)) {
profile->GetPrefs()->SetBoolean(
device_signals::prefs::kDeviceSignalsPermanentConsentReceived, true);
}
ProfileManager* profile_manager = g_browser_process->profile_manager();
ProfileAttributesEntry* entry =
profile_manager->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile->GetPath());
if (entry) {
entry->SetUserAcceptedAccountManagement(accepted);
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
entry->SetEnterpriseProfileLabel(GetEnterpriseLabel(profile));
#endif
}
}
bool UserAcceptedAccountManagement(Profile* profile) {
// Some tests do not have a profile manager.
if (!g_browser_process->profile_manager())
return false;
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile->GetPath());
return entry && entry->UserAcceptedAccountManagement();
}
bool ProfileCanBeManaged(Profile* profile) {
// Some tests do not have a profile manager.
if (!g_browser_process->profile_manager() || !profile) {
return false;
}
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile->GetPath());
return entry && entry->CanBeManaged();
}
ManagementEnvironment GetManagementEnvironment(
Profile* profile,
const AccountInfo& account_info) {
if (!UserAcceptedAccountManagement(profile)) {
return ManagementEnvironment::kNone;
}
return account_info.IsEduAccount() ? ManagementEnvironment::kSchool
: ManagementEnvironment::kWork;
}
bool IsEnterpriseBadgingEnabledForToolbar(Profile* profile) {
return profile->GetPrefs()->GetInteger(
prefs::kEnterpriseProfileBadgeToolbarSettings) == 0;
}
bool CanShowEnterpriseBadgingForMenu(Profile* profile) {
if (profile->IsGuestSession() || profile->IsOffTheRecord()) {
return false;
}
if (!UserAcceptedAccountManagement(profile) && !profile->IsChild()) {
return false;
}
if (base::FeatureList::IsEnabled(
features::kEnterpriseProfileBadgingForMenu)) {
return true;
}
if (!base::FeatureList::IsEnabled(
features::kEnterpriseProfileBadgingPolicies)) {
return false;
}
// The check for supervised users is here as a precacution since the
// kEnterpriseLogoUrlForProfile should be set by policy.
return !profile->GetPrefs()
->GetString(prefs::kEnterpriseLogoUrlForProfile)
.empty() &&
!profile->IsChild();
}
bool CanShowEnterpriseBadgingForAvatar(Profile* profile) {
if (profile->IsGuestSession() || profile->IsOffTheRecord()) {
return false;
}
if (!UserAcceptedAccountManagement(profile)) {
return false;
}
if (!IsEnterpriseBadgingEnabledForToolbar(profile)) {
return false;
}
if (base::FeatureList::IsEnabled(
features::kEnterpriseProfileBadgingForAvatar)) {
return true;
}
if (!base::FeatureList::IsEnabled(
features::kEnterpriseProfileBadgingPolicies)) {
return false;
}
return !profile->GetPrefs()
->GetString(prefs::kEnterpriseCustomLabelForProfile)
.empty();
}
bool IsKnownConsumerDomain(const std::string& email_domain) {
return !signin::AccountManagedStatusFinder::MayBeEnterpriseDomain(
email_domain);
}
#if BUILDFLAG(IS_ANDROID)
// static
jboolean JNI_ManagedBrowserUtils_IsBrowserManaged(JNIEnv* env,
Profile* profile) {
return policy::ManagementServiceFactory::GetForProfile(profile)
->IsBrowserManaged();
}
// static
jboolean JNI_ManagedBrowserUtils_IsProfileManaged(JNIEnv* env,
Profile* profile) {
return policy::ManagementServiceFactory::GetForProfile(profile)
->IsAccountManaged();
}
// static
std::u16string JNI_ManagedBrowserUtils_GetTitle(JNIEnv* env, Profile* profile) {
return GetManagementPageSubtitle(profile);
}
// static
jboolean JNI_ManagedBrowserUtils_IsBrowserReportingEnabled(JNIEnv* env) {
return g_browser_process->local_state()->GetBoolean(
enterprise_reporting::kCloudReportingEnabled);
}
// static
jboolean JNI_ManagedBrowserUtils_IsProfileReportingEnabled(JNIEnv* env,
Profile* profile) {
return profile->GetPrefs()->GetBoolean(
enterprise_reporting::kCloudProfileReportingEnabled);
}
// static
jboolean JNI_ManagedBrowserUtils_IsOnSecurityEventEnterpriseConnectorEnabled(
JNIEnv* env,
Profile* profile) {
DCHECK(profile);
if (!base::FeatureList::IsEnabled(
enterprise_connectors::kEnterpriseSecurityEventReportingOnAndroid) &&
!base::FeatureList::IsEnabled(
enterprise_connectors::
kEnterpriseUrlFilteringEventReportingOnAndroid)) {
return false;
}
auto* service =
enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
profile);
if (!service) {
return false;
}
return !service->GetReportingServiceProviderNames().empty();
}
// static
jboolean JNI_ManagedBrowserUtils_IsEnterpriseRealTimeUrlCheckModeEnabled(
JNIEnv* env,
Profile* profile) {
DCHECK(profile);
if (!base::FeatureList::IsEnabled(
safe_browsing::kEnterpriseRealTimeUrlCheckOnAndroid)) {
return false;
}
auto* service =
enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
profile);
if (!service) {
return false;
}
return service->GetAppliedRealTimeUrlCheck() !=
enterprise_connectors::REAL_TIME_CHECK_DISABLED;
}
#endif // BUILDFLAG(IS_ANDROID)
void GetManagementIcon(const GURL& url,
Profile* profile,
base::OnceCallback<void(const gfx::Image&)> callback) {
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("enterprise_logo_fetcher",
R"(
semantics {
sender: "Chrome Profiles"
description:
"Retrieves an image set by the admin as the enterprise logo. This "
"is used to show the user which organization manages their browser "
"in the profile menu."
trigger:
"When the user launches the browser and the EnterpriseLogoUrl "
"policy is set."
data:
"An admin-controlled URL for an image on the profile menu."
destination: OTHER
internal {
contacts {
email: "cbe-magic@google.com"
}
}
user_data {
type: SENSITIVE_URL
}
last_reviewed: "2024-07-22"
}
policy {
cookies_allowed: NO
setting:
"There is no setting. This fetch is enabled for any managed user "
"with the EnterpriseLogoUrl policy set."
chrome_policy {
EnterpriseLogoUrl {
EnterpriseLogoUrl: ""
}
}
})");
if (!url.is_valid()) {
std::move(callback).Run(gfx::Image());
return;
}
image_fetcher::ImageFetcher* fetcher =
ImageFetcherServiceFactory::GetForKey(profile->GetProfileKey())
->GetImageFetcher(image_fetcher::ImageFetcherConfig::kDiskCacheOnly);
fetcher->FetchImage(
url, base::BindOnce(&OnManagementIconReceived, std::move(callback)),
image_fetcher::ImageFetcherParams(
kTrafficAnnotation,
/*uma_client_name=*/"BrowserManagementMetadata"));
}
std::u16string GetEnterpriseLabel(Profile* profile, bool truncated) {
if (!CanShowEnterpriseBadgingForAvatar(profile)) {
return std::u16string();
}
const std::string enterprise_custom_label =
profile->GetPrefs()->GetString(prefs::kEnterpriseCustomLabelForProfile);
if (!enterprise_custom_label.empty()) {
return truncated
? gfx::TruncateString(base::UTF8ToUTF16(enterprise_custom_label),
kMaximumEnterpriseCustomLabelLengthCutOff,
gfx::CHARACTER_BREAK)
: base::UTF8ToUTF16(enterprise_custom_label);
} else if (IsManagementWork(profile)) {
return l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_WORK);
} else {
// School.
return l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_SCHOOL);
}
}
} // namespace enterprise_util