| // Copyright 2020 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. |
| |
| #include "chrome/browser/ui/webui/settings/recent_site_settings_helper.h" |
| |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/webui/settings/site_settings_helper.h" |
| #include "components/content_settings/core/common/content_settings_utils.h" |
| #include "components/permissions/permission_decision_auto_blocker.h" |
| |
| namespace site_settings { |
| |
| namespace { |
| |
| // The priority for surfacing a permission to the user. |
| constexpr int GetPriorityForType(ContentSettingsType type) { |
| switch (type) { |
| case ContentSettingsType::GEOLOCATION: |
| return 0; |
| case ContentSettingsType::MEDIASTREAM_CAMERA: |
| return 1; |
| case ContentSettingsType::MEDIASTREAM_MIC: |
| return 2; |
| case ContentSettingsType::NOTIFICATIONS: |
| return 3; |
| case ContentSettingsType::BACKGROUND_SYNC: |
| return 4; |
| default: |
| // Every other |content_type| is considered of lower but equal priority |
| return 5; |
| } |
| } |
| |
| // Return the most recent timestamp from the settings contained in a |
| // RecentSitePermissions struct. Returns base::Time() if no settings exist. |
| base::Time GetMostRecentTimestamp(const RecentSitePermissions& x) { |
| auto most_recent = base::Time(); |
| for (const auto& setting : x.settings) { |
| if (setting.timestamp > most_recent) |
| most_recent = setting.timestamp; |
| } |
| return most_recent; |
| } |
| |
| // Return a map keyed on URLs of all TimestampedSettings which currently apply |
| // to the provided profile for the provided content types. |
| std::map<GURL, std::vector<TimestampedSetting>> GetAllSettingsForProfile( |
| Profile* profile, |
| std::vector<ContentSettingsType> content_types) { |
| auto* auto_blocker = |
| PermissionDecisionAutoBlockerFactory::GetForProfile(profile); |
| HostContentSettingsMap* content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| std::map<GURL, std::vector<TimestampedSetting>> results; |
| for (auto content_type : content_types) { |
| auto exceptions_for_type = site_settings::GetSiteExceptionsForContentType( |
| content_settings_map, content_type); |
| for (const auto& e : exceptions_for_type) { |
| auto last_modified = content_settings_map->GetSettingLastModifiedDate( |
| e.primary_pattern, e.secondary_pattern, content_type); |
| if (last_modified.is_null()) { |
| continue; |
| } |
| GURL origin = GURL(e.primary_pattern.ToString()).GetOrigin(); |
| results[origin].emplace_back( |
| last_modified, content_type, |
| content_settings::ValueToContentSetting(&e.setting_value), |
| site_settings::SiteSettingSource::kPreference); |
| } |
| |
| // Get sites under embargo. |
| auto embargoed_sites = auto_blocker->GetEmbargoedOrigins(content_type); |
| for (auto& url : embargoed_sites) { |
| auto last_modified = |
| PermissionDecisionAutoBlockerFactory::GetForProfile(profile) |
| ->GetEmbargoStartTime(url, content_type); |
| results[url.GetOrigin()].emplace_back( |
| last_modified, content_type, ContentSetting::CONTENT_SETTING_BLOCK, |
| site_settings::SiteSettingSource::kEmbargo); |
| } |
| } |
| |
| // Keep only the first entry for any content type. This will be the enforced |
| // user set permission appropriate for the profile. |
| for (auto& source_settings : results) { |
| std::stable_sort( |
| source_settings.second.begin(), source_settings.second.end(), |
| [](const TimestampedSetting& x, const TimestampedSetting& y) { |
| return x.content_type < y.content_type; |
| }); |
| source_settings.second.erase( |
| std::unique( |
| source_settings.second.begin(), source_settings.second.end(), |
| [](const TimestampedSetting& x, const TimestampedSetting& y) { |
| return x.content_type == y.content_type; |
| }), |
| source_settings.second.end()); |
| } |
| return results; |
| } |
| |
| } // namespace |
| |
| TimestampedSetting::TimestampedSetting() |
| : timestamp(base::Time()), |
| content_type(ContentSettingsType::DEFAULT), |
| content_setting(ContentSetting::CONTENT_SETTING_DEFAULT), |
| setting_source(site_settings::SiteSettingSource::kDefault) {} |
| TimestampedSetting::TimestampedSetting(const TimestampedSetting& other) = |
| default; |
| TimestampedSetting::TimestampedSetting( |
| base::Time timestamp, |
| ContentSettingsType content_type, |
| ContentSetting content_setting, |
| site_settings::SiteSettingSource setting_source) |
| : timestamp(timestamp), |
| content_type(content_type), |
| content_setting(content_setting), |
| setting_source(setting_source) {} |
| TimestampedSetting::~TimestampedSetting() = default; |
| |
| RecentSitePermissions::RecentSitePermissions() |
| : origin(GURL()), |
| incognito(false), |
| settings(std::vector<TimestampedSetting>()) {} |
| RecentSitePermissions::RecentSitePermissions( |
| const RecentSitePermissions& other) = default; |
| RecentSitePermissions::RecentSitePermissions(RecentSitePermissions&& other) = |
| default; |
| RecentSitePermissions::RecentSitePermissions( |
| GURL origin, |
| bool incognito, |
| std::vector<TimestampedSetting> settings) |
| : origin(origin), incognito(incognito), settings(settings) {} |
| RecentSitePermissions::~RecentSitePermissions() = default; |
| |
| std::vector<RecentSitePermissions> GetRecentSitePermissions( |
| Profile* profile, |
| std::vector<ContentSettingsType> content_types, |
| size_t max_sources) { |
| std::map<GURL, std::vector<TimestampedSetting>> regular_settings = |
| GetAllSettingsForProfile(profile, content_types); |
| std::map<GURL, std::vector<TimestampedSetting>> incognito_settings; |
| |
| if (profile->HasPrimaryOTRProfile()) { |
| incognito_settings = GetAllSettingsForProfile( |
| profile->GetPrimaryOTRProfile(/*create_if_needed=*/true), |
| content_types); |
| |
| // Remove all permission entries in the incognito map which also have |
| // an entry in the regular settings. This may result in an empty setting |
| // vector, in which case we remove the map entry. |
| // TODO(https://crbug.com/1049531): Make determining actual source of |
| // active permission simpler. |
| for (auto incognito_iter = incognito_settings.begin(); |
| incognito_iter != incognito_settings.end();) { |
| auto regular_iter = regular_settings.find(incognito_iter->first); |
| if (regular_iter == regular_settings.end()) { |
| ++incognito_iter; |
| continue; |
| } |
| |
| // Define an arbitrary ordering on TimestampedSetting's so we can sort |
| // and make use of set difference |
| auto arbitrary_strict_weak_ordering = [](const TimestampedSetting& x, |
| const TimestampedSetting& y) { |
| return std::tie(x.timestamp, x.content_type, x.content_setting, |
| x.setting_source) < |
| std::tie(y.timestamp, y.content_type, y.content_setting, |
| y.setting_source); |
| }; |
| |
| std::vector<TimestampedSetting> incognito_only_settings; |
| std::sort(regular_iter->second.begin(), regular_iter->second.end(), |
| arbitrary_strict_weak_ordering); |
| std::sort(incognito_iter->second.begin(), incognito_iter->second.end(), |
| arbitrary_strict_weak_ordering); |
| std::set_difference( |
| incognito_iter->second.begin(), incognito_iter->second.end(), |
| regular_iter->second.begin(), regular_iter->second.end(), |
| std::back_inserter(incognito_only_settings), |
| arbitrary_strict_weak_ordering); |
| |
| if (incognito_only_settings.empty()) { |
| incognito_iter = incognito_settings.erase(incognito_iter); |
| } else { |
| incognito_iter->second = incognito_only_settings; |
| ++incognito_iter; |
| } |
| } |
| } |
| |
| // Combine incognito and regular permissions and sort based on the most |
| // recent setting for each source. |
| std::vector<RecentSitePermissions> all_site_permissions; |
| for (auto& url_settings_pair : regular_settings) { |
| all_site_permissions.push_back({url_settings_pair.first, |
| false /*incognito*/, |
| std::move(url_settings_pair.second)}); |
| } |
| for (auto& url_settings_pair : incognito_settings) { |
| all_site_permissions.push_back({url_settings_pair.first, true /*incognito*/, |
| std::move(url_settings_pair.second)}); |
| } |
| std::sort(all_site_permissions.begin(), all_site_permissions.end(), |
| [](const RecentSitePermissions& x, const RecentSitePermissions& y) { |
| return GetMostRecentTimestamp(x) > GetMostRecentTimestamp(y); |
| }); |
| |
| // Sort source permissions based on their priority for surfacing to the user |
| for (auto& site_permissions : all_site_permissions) { |
| std::sort(site_permissions.settings.begin(), |
| site_permissions.settings.end(), |
| [](const TimestampedSetting& x, const TimestampedSetting& y) { |
| if (GetPriorityForType(x.content_type) != |
| GetPriorityForType(y.content_type)) { |
| return GetPriorityForType(x.content_type) < |
| GetPriorityForType(y.content_type); |
| } |
| return x.timestamp > y.timestamp; |
| }); |
| } |
| |
| if (all_site_permissions.size() <= max_sources) { |
| return all_site_permissions; |
| } |
| |
| // We only want to surface the recent permissions for sites. Explicitly no |
| // settings older than the newest setting for the (max_sources + 1)th source. |
| auto min_timestamp = |
| GetMostRecentTimestamp(all_site_permissions[max_sources]); |
| all_site_permissions.erase(all_site_permissions.begin() + max_sources, |
| all_site_permissions.end()); |
| |
| for (auto& site_permissions : all_site_permissions) { |
| site_permissions.settings.erase( |
| std::remove_if(site_permissions.settings.begin(), |
| site_permissions.settings.end(), |
| [min_timestamp](const TimestampedSetting& x) { |
| return x.timestamp < min_timestamp; |
| }), |
| site_permissions.settings.end()); |
| } |
| return all_site_permissions; |
| } |
| |
| } // namespace site_settings |