1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/metrics/extensions_metrics_provider.h"
6
7#include <algorithm>
8#include <set>
9#include <vector>
10
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/strings/stringprintf.h"
14#include "chrome/browser/browser_process.h"
15#include "chrome/browser/extensions/install_verifier.h"
16#include "chrome/browser/profiles/profile_manager.h"
17#include "components/metrics/metrics_log.h"
18#include "components/metrics/metrics_state_manager.h"
19#include "components/metrics/proto/system_profile.pb.h"
20#include "extensions/browser/extension_registry.h"
21#include "extensions/browser/extension_system.h"
22#include "extensions/common/extension_set.h"
23#include "third_party/smhasher/src/City.h"
24
25namespace {
26
27// The number of possible hash keys that a client may use.  The UMA client_id
28// value is reduced modulo this value to produce the key used by that
29// particular client.
30const size_t kExtensionListClientKeys = 4096;
31
32// The number of hash buckets into which extension IDs are mapped.  This sets
33// the possible output range of the HashExtension function.
34const size_t kExtensionListBuckets = 1024;
35
36// Possible states for extensions. The order of these enum values is important,
37// and is used when combining the state of multiple extensions and multiple
38// profiles. Combining two states should always result in the higher state.
39// Ex: One profile is in state FROM_STORE_VERIFIED, and another is in
40// FROM_STORE_UNVERIFIED. The state of the two profiles together will be
41// FROM_STORE_UNVERIFIED.
42// This enum should be kept in sync with the corresponding enum in
43// components/metrics/proto/system_profile.proto
44enum ExtensionState {
45  NO_EXTENSIONS,
46  FROM_STORE_VERIFIED,
47  FROM_STORE_UNVERIFIED,
48  OFF_STORE
49};
50
51metrics::SystemProfileProto::ExtensionsState ExtensionStateAsProto(
52    ExtensionState value) {
53  switch (value) {
54    case NO_EXTENSIONS:
55      return metrics::SystemProfileProto::NO_EXTENSIONS;
56    case FROM_STORE_VERIFIED:
57      return metrics::SystemProfileProto::NO_OFFSTORE_VERIFIED;
58    case FROM_STORE_UNVERIFIED:
59      return metrics::SystemProfileProto::NO_OFFSTORE_UNVERIFIED;
60    case OFF_STORE:
61      return metrics::SystemProfileProto::HAS_OFFSTORE;
62  }
63  NOTREACHED();
64  return metrics::SystemProfileProto::NO_EXTENSIONS;
65}
66
67// Determines if the |extension| is an extension (can use extension APIs) and is
68// not from the webstore. If local information claims the extension is from the
69// webstore, we attempt to verify with |verifier| by checking if it has been
70// explicitly deemed invalid. If |verifier| is inactive or if the extension is
71// unknown to |verifier|, the local information is trusted.
72ExtensionState IsOffStoreExtension(
73    const extensions::Extension& extension,
74    const extensions::InstallVerifier& verifier) {
75  if (!extension.is_extension() && !extension.is_legacy_packaged_app())
76    return NO_EXTENSIONS;
77
78  // Component extensions are considered safe.
79  if (extensions::Manifest::IsComponentLocation(extension.location()))
80    return NO_EXTENSIONS;
81
82  if (verifier.AllowedByEnterprisePolicy(extension.id()))
83    return NO_EXTENSIONS;
84
85  if (!extensions::InstallVerifier::IsFromStore(extension))
86    return OFF_STORE;
87
88  // Local information about the extension implies it is from the store. We try
89  // to use the install verifier to verify this.
90  if (!verifier.IsKnownId(extension.id()))
91    return FROM_STORE_UNVERIFIED;
92
93  if (verifier.IsInvalid(extension.id()))
94    return OFF_STORE;
95
96  return FROM_STORE_VERIFIED;
97}
98
99// Finds the ExtensionState of |extensions|. The return value will be the
100// highest (as defined by the order of ExtensionState) value of each extension
101// in |extensions|.
102ExtensionState CheckForOffStore(const extensions::ExtensionSet& extensions,
103                                const extensions::InstallVerifier& verifier) {
104  ExtensionState state = NO_EXTENSIONS;
105  for (extensions::ExtensionSet::const_iterator it = extensions.begin();
106       it != extensions.end() && state < OFF_STORE;
107       ++it) {
108    // Combine the state of each extension, always favoring the higher state as
109    // defined by the order of ExtensionState.
110    state = std::max(state, IsOffStoreExtension(**it, verifier));
111  }
112  return state;
113}
114
115}  // namespace
116
117ExtensionsMetricsProvider::ExtensionsMetricsProvider(
118    metrics::MetricsStateManager* metrics_state_manager)
119    : metrics_state_manager_(metrics_state_manager), cached_profile_(NULL) {
120  DCHECK(metrics_state_manager_);
121}
122
123ExtensionsMetricsProvider::~ExtensionsMetricsProvider() {
124}
125
126// static
127int ExtensionsMetricsProvider::HashExtension(const std::string& extension_id,
128                                             uint32 client_key) {
129  DCHECK_LE(client_key, kExtensionListClientKeys);
130  std::string message =
131      base::StringPrintf("%u:%s", client_key, extension_id.c_str());
132  uint64 output = CityHash64(message.data(), message.size());
133  return output % kExtensionListBuckets;
134}
135
136Profile* ExtensionsMetricsProvider::GetMetricsProfile() {
137  ProfileManager* profile_manager = g_browser_process->profile_manager();
138  if (!profile_manager)
139    return NULL;
140
141  // If there is a cached profile, reuse that.  However, check that it is still
142  // valid first.
143  if (cached_profile_ && profile_manager->IsValidProfile(cached_profile_))
144    return cached_profile_;
145
146  // Find a suitable profile to use, and cache it so that we continue to report
147  // statistics on the same profile.  We would simply use
148  // ProfileManager::GetLastUsedProfile(), except that that has the side effect
149  // of creating a profile if it does not yet exist.
150  cached_profile_ = profile_manager->GetProfileByPath(
151      profile_manager->GetLastUsedProfileDir(profile_manager->user_data_dir()));
152  if (cached_profile_) {
153    // Ensure that the returned profile is not an incognito profile.
154    cached_profile_ = cached_profile_->GetOriginalProfile();
155  }
156  return cached_profile_;
157}
158
159scoped_ptr<extensions::ExtensionSet>
160ExtensionsMetricsProvider::GetInstalledExtensions(Profile* profile) {
161  if (profile) {
162    return extensions::ExtensionRegistry::Get(profile)
163        ->GenerateInstalledExtensionsSet();
164  }
165  return scoped_ptr<extensions::ExtensionSet>();
166}
167
168uint64 ExtensionsMetricsProvider::GetClientID() {
169  // TODO(blundell): Create a MetricsLog::ClientIDAsInt() API and call it
170  // here as well as in MetricsLog's population of the client_id field of
171  // the uma_proto.
172  return metrics::MetricsLog::Hash(metrics_state_manager_->client_id());
173}
174
175void ExtensionsMetricsProvider::ProvideSystemProfileMetrics(
176    metrics::SystemProfileProto* system_profile) {
177  ProvideOffStoreMetric(system_profile);
178  ProvideOccupiedBucketMetric(system_profile);
179}
180
181void ExtensionsMetricsProvider::ProvideOffStoreMetric(
182    metrics::SystemProfileProto* system_profile) {
183  ProfileManager* profile_manager = g_browser_process->profile_manager();
184  if (!profile_manager)
185    return;
186
187  ExtensionState state = NO_EXTENSIONS;
188
189  // The off-store metric includes information from all loaded profiles at the
190  // time when this metric is generated.
191  std::vector<Profile*> profiles = profile_manager->GetLoadedProfiles();
192  for (size_t i = 0u; i < profiles.size() && state < OFF_STORE; ++i) {
193    extensions::InstallVerifier* verifier =
194        extensions::ExtensionSystem::Get(profiles[i])->install_verifier();
195
196    scoped_ptr<extensions::ExtensionSet> extensions(
197        GetInstalledExtensions(profiles[i]));
198    if (!extensions)
199      continue;
200
201    // Combine the state from each profile, always favoring the higher state as
202    // defined by the order of ExtensionState.
203    state = std::max(state, CheckForOffStore(*extensions.get(), *verifier));
204  }
205
206  system_profile->set_offstore_extensions_state(ExtensionStateAsProto(state));
207}
208
209void ExtensionsMetricsProvider::ProvideOccupiedBucketMetric(
210    metrics::SystemProfileProto* system_profile) {
211  // UMA reports do not support multiple profiles, but extensions are installed
212  // per-profile.  We return the extensions installed in the primary profile.
213  // In the future, we might consider reporting data about extensions in all
214  // profiles.
215  Profile* profile = GetMetricsProfile();
216
217  scoped_ptr<extensions::ExtensionSet> extensions(
218      GetInstalledExtensions(profile));
219  if (!extensions)
220    return;
221
222  const int client_key = GetClientID() % kExtensionListClientKeys;
223
224  std::set<int> buckets;
225  for (extensions::ExtensionSet::const_iterator it = extensions->begin();
226       it != extensions->end();
227       ++it) {
228    buckets.insert(HashExtension((*it)->id(), client_key));
229  }
230
231  for (std::set<int>::const_iterator it = buckets.begin(); it != buckets.end();
232       ++it) {
233    system_profile->add_occupied_extension_bucket(*it);
234  }
235}
236