| // Copyright 2016 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/safe_browsing/permission_reporter.h" |
| |
| #include <functional> |
| |
| #include "base/containers/queue.h" |
| #include "base/hash.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_piece.h" |
| #include "base/time/default_clock.h" |
| #include "chrome/browser/permissions/permission_request.h" |
| #include "chrome/common/safe_browsing/permission_report.pb.h" |
| #include "components/variations/active_field_trials.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "net/url_request/report_sender.h" |
| |
| namespace safe_browsing { |
| |
| namespace { |
| // URL to upload permission action reports. |
| const char kPermissionActionReportingUploadUrl[] = |
| "https://safebrowsing.google.com/safebrowsing/clientreport/" |
| "chrome-permissions"; |
| |
| const int kMaximumReportsPerOriginPerPermissionPerMinute = 5; |
| |
| PermissionReport::PermissionType PermissionTypeForReport( |
| ContentSettingsType permission) { |
| switch (permission) { |
| case CONTENT_SETTINGS_TYPE_MIDI_SYSEX: |
| return PermissionReport::MIDI_SYSEX; |
| case CONTENT_SETTINGS_TYPE_NOTIFICATIONS: |
| return PermissionReport::NOTIFICATIONS; |
| case CONTENT_SETTINGS_TYPE_GEOLOCATION: |
| return PermissionReport::GEOLOCATION; |
| case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER: |
| return PermissionReport::PROTECTED_MEDIA_IDENTIFIER; |
| case CONTENT_SETTINGS_TYPE_DURABLE_STORAGE: |
| return PermissionReport::DURABLE_STORAGE; |
| case CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC: |
| return PermissionReport::AUDIO_CAPTURE; |
| case CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA: |
| return PermissionReport::VIDEO_CAPTURE; |
| case CONTENT_SETTINGS_TYPE_BACKGROUND_SYNC: |
| return PermissionReport::BACKGROUND_SYNC; |
| case CONTENT_SETTINGS_TYPE_PLUGINS: |
| return PermissionReport::FLASH; |
| default: |
| break; |
| } |
| |
| NOTREACHED(); |
| return PermissionReport::UNKNOWN_PERMISSION; |
| } |
| |
| PermissionReport::Action PermissionActionForReport(PermissionAction action) { |
| switch (action) { |
| case PermissionAction::GRANTED: |
| return PermissionReport::GRANTED; |
| case PermissionAction::DENIED: |
| return PermissionReport::DENIED; |
| case PermissionAction::DISMISSED: |
| return PermissionReport::DISMISSED; |
| case PermissionAction::IGNORED: |
| return PermissionReport::IGNORED; |
| case PermissionAction::REVOKED: |
| return PermissionReport::REVOKED; |
| case PermissionAction::REENABLED: |
| case PermissionAction::REQUESTED: |
| return PermissionReport::ACTION_UNSPECIFIED; |
| case PermissionAction::NUM: |
| break; |
| } |
| |
| NOTREACHED(); |
| return PermissionReport::ACTION_UNSPECIFIED; |
| } |
| |
| PermissionReport::SourceUI SourceUIForReport(PermissionSourceUI source_ui) { |
| switch (source_ui) { |
| case PermissionSourceUI::PROMPT: |
| return PermissionReport::PROMPT; |
| case PermissionSourceUI::OIB: |
| return PermissionReport::OIB; |
| case PermissionSourceUI::SITE_SETTINGS: |
| return PermissionReport::SITE_SETTINGS; |
| case PermissionSourceUI::PAGE_ACTION: |
| return PermissionReport::PAGE_ACTION; |
| case PermissionSourceUI::NUM: |
| break; |
| } |
| |
| NOTREACHED(); |
| return PermissionReport::SOURCE_UI_UNSPECIFIED; |
| } |
| |
| PermissionReport::GestureType GestureTypeForReport( |
| PermissionRequestGestureType gesture_type) { |
| switch (gesture_type) { |
| case PermissionRequestGestureType::UNKNOWN: |
| return PermissionReport::GESTURE_TYPE_UNSPECIFIED; |
| case PermissionRequestGestureType::GESTURE: |
| return PermissionReport::GESTURE; |
| case PermissionRequestGestureType::NO_GESTURE: |
| return PermissionReport::NO_GESTURE; |
| case PermissionRequestGestureType::NUM: |
| break; |
| } |
| |
| NOTREACHED(); |
| return PermissionReport::GESTURE_TYPE_UNSPECIFIED; |
| } |
| |
| constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation = |
| net::DefineNetworkTrafficAnnotation("permission_reporting", R"( |
| semantics { |
| sender: "Safe Browsing" |
| description: |
| "When a website prompts for a permission request, the origin and " |
| "the action taken are reported to Google Safe Browsing for a " |
| "subset of users. This is to find sites that are abusing " |
| "permissions or asking much too often." |
| trigger: |
| "When a permission prompt closes for any reason, and the user is " |
| "opted into safe browsing, metrics reporting, and history sync. " |
| "These are throttled to less than five (permission, origin) pairs " |
| "per minute." |
| data: |
| "The type of permission, the action taken on it, and the origin." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "Users can control this feature via either of the 'Protect you and " |
| "your device from dangerous sites' setting under 'Privacy', or " |
| "'Automatically send usage statistics and crash reports to Google' " |
| "setting under 'Privacy' or 'History sync' setting under 'Sign in, " |
| "Advanced sync settings'." |
| chrome_policy { |
| MetricsReportingEnabled { |
| policy_options {mode: MANDATORY} |
| MetricsReportingEnabled: false |
| } |
| } |
| chrome_policy { |
| SyncDisabled { |
| policy_options {mode: MANDATORY} |
| SyncDisabled: true |
| } |
| } |
| chrome_policy { |
| SafeBrowsingEnabled { |
| policy_options {mode: MANDATORY} |
| SafeBrowsingEnabled: false |
| } |
| } |
| })"); |
| |
| } // namespace |
| |
| bool PermissionAndOrigin::operator==(const PermissionAndOrigin& other) const { |
| return (permission == other.permission && origin == other.origin); |
| } |
| |
| std::size_t PermissionAndOriginHash::operator()( |
| const PermissionAndOrigin& permission_and_origin) const { |
| std::size_t permission_hash = |
| static_cast<std::size_t>(permission_and_origin.permission); |
| std::size_t origin_hash = |
| std::hash<std::string>()(permission_and_origin.origin.spec()); |
| return base::HashInts(permission_hash, origin_hash); |
| } |
| |
| PermissionReporter::PermissionReporter(net::URLRequestContext* request_context) |
| : PermissionReporter( |
| base::MakeUnique<net::ReportSender>(request_context, |
| kTrafficAnnotation), |
| base::WrapUnique(new base::DefaultClock)) {} |
| |
| PermissionReporter::PermissionReporter( |
| std::unique_ptr<net::ReportSender> report_sender, |
| std::unique_ptr<base::Clock> clock) |
| : permission_report_sender_(std::move(report_sender)), |
| clock_(std::move(clock)) {} |
| |
| PermissionReporter::~PermissionReporter() {} |
| |
| void PermissionReporter::SendReport(const PermissionReportInfo& report_info) { |
| if (IsReportThresholdExceeded(report_info.permission, report_info.origin)) |
| return; |
| |
| std::string serialized_report; |
| BuildReport(report_info, &serialized_report); |
| permission_report_sender_->Send( |
| GURL(kPermissionActionReportingUploadUrl), "application/octet-stream", |
| serialized_report, base::Callback<void()>(), |
| base::Callback<void(const GURL&, int, int)>()); |
| } |
| |
| // static |
| bool PermissionReporter::BuildReport(const PermissionReportInfo& report_info, |
| std::string* output) { |
| PermissionReport report; |
| report.set_origin(report_info.origin.spec()); |
| report.set_permission(PermissionTypeForReport(report_info.permission)); |
| report.set_action(PermissionActionForReport(report_info.action)); |
| report.set_source_ui(SourceUIForReport(report_info.source_ui)); |
| report.set_gesture(GestureTypeForReport(report_info.gesture_type)); |
| // The persistence experiment was removed in M64. |
| report.set_persisted(PermissionReport::PERSIST_DECISION_UNSPECIFIED); |
| report.set_num_prior_dismissals(report_info.num_prior_dismissals); |
| report.set_num_prior_ignores(report_info.num_prior_ignores); |
| |
| // Collect platform data. |
| #if defined(OS_ANDROID) |
| report.set_platform_type(PermissionReport::ANDROID_PLATFORM); |
| #elif defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_CHROMEOS) || \ |
| defined(OS_LINUX) |
| report.set_platform_type(PermissionReport::DESKTOP_PLATFORM); |
| #else |
| #error Unsupported platform. |
| #endif |
| |
| // Collect field trial data. |
| std::vector<variations::ActiveGroupId> active_group_ids; |
| variations::GetFieldTrialActiveGroupIds(base::StringPiece(), |
| &active_group_ids); |
| for (auto active_group_id : active_group_ids) { |
| PermissionReport::FieldTrial* field_trial = report.add_field_trials(); |
| field_trial->set_name_id(active_group_id.name); |
| field_trial->set_group_id(active_group_id.group); |
| } |
| return report.SerializeToString(output); |
| } |
| |
| bool PermissionReporter::IsReportThresholdExceeded( |
| ContentSettingsType permission, |
| const GURL& origin) { |
| base::queue<base::Time>& log = report_logs_[{permission, origin}]; |
| base::Time current_time = clock_->Now(); |
| // Remove entries that are sent more than one minute ago. |
| while (!log.empty() && |
| current_time - log.front() > base::TimeDelta::FromMinutes(1)) { |
| log.pop(); |
| } |
| if (log.size() < kMaximumReportsPerOriginPerPermissionPerMinute) { |
| log.push(current_time); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| } // namespace safe_browsing |