1// Copyright (c) 2012 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// The ExtensionsQuotaService uses heuristics to limit abusive requests
6// made by extensions.  In this model 'items' (e.g individual bookmarks) are
7// represented by a 'Bucket' that holds state for that item for one single
8// interval of time.  The interval of time is defined as 'how long we need to
9// watch an item (for a particular heuristic) before making a decision about
10// quota violations'.  A heuristic is two functions: one mapping input
11// arguments to a unique Bucket (the BucketMapper), and another to determine
12// if a new request involving such an item at a given time is a violation.
13
14#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSIONS_QUOTA_SERVICE_H_
15#define CHROME_BROWSER_EXTENSIONS_EXTENSIONS_QUOTA_SERVICE_H_
16
17#include <list>
18#include <map>
19#include <string>
20
21#include "base/compiler_specific.h"
22#include "base/containers/hash_tables.h"
23#include "base/memory/scoped_ptr.h"
24#include "base/threading/non_thread_safe.h"
25#include "base/time/time.h"
26#include "base/timer/timer.h"
27#include "base/values.h"
28
29class ExtensionFunction;
30class QuotaLimitHeuristic;
31typedef std::list<QuotaLimitHeuristic*> QuotaLimitHeuristics;
32
33namespace extensions {
34class TestResetQuotaFunction;
35}
36
37// The ExtensionsQuotaService takes care that calls to certain extension
38// functions do not exceed predefined quotas.
39//
40// The ExtensionsQuotaService needs to live entirely on one thread, i.e.
41// be created, called and destroyed on the same thread, due to its use
42// of a RepeatingTimer.
43class ExtensionsQuotaService : public base::NonThreadSafe {
44 public:
45  // Some concrete heuristics (declared below) that ExtensionFunctions can
46  // use to help the service make decisions about quota violations.
47  class TimedLimit;
48  class SustainedLimit;
49
50  ExtensionsQuotaService();
51  virtual ~ExtensionsQuotaService();
52
53  // Decide whether the invocation of |function| with argument |args| by the
54  // extension specified by |extension_id| results in a quota limit violation.
55  // Returns an error message representing the failure if quota was exceeded,
56  // or empty-string if the request is fine and can proceed.
57  std::string Assess(const std::string& extension_id,
58                     ExtensionFunction* function,
59                     const base::ListValue* args,
60                     const base::TimeTicks& event_time);
61
62 private:
63  friend class extensions::TestResetQuotaFunction;
64  typedef std::string ExtensionId;
65  typedef std::string FunctionName;
66  // All QuotaLimitHeuristic instances in this map are owned by us.
67  typedef std::map<FunctionName, QuotaLimitHeuristics> FunctionHeuristicsMap;
68
69  // Purge resets all accumulated data (except |violation_errors_|) as if the
70  // service was just created. Called periodically so we don't consume an
71  // unbounded amount of memory while tracking quota.  Yes, this could mean an
72  // extension gets away with murder if it is timed right, but the extensions
73  // we are trying to limit are ones that consistently violate, so we'll
74  // converge to the correct set.
75  void Purge();
76  void PurgeFunctionHeuristicsMap(FunctionHeuristicsMap* map);
77  base::RepeatingTimer<ExtensionsQuotaService> purge_timer_;
78
79  // Our quota tracking state for extensions that have invoked quota limited
80  // functions.  Each extension is treated separately, so extension ids are the
81  // key for the mapping.  As an extension invokes functions, the map keeps
82  // track of which functions it has invoked and the heuristics for each one.
83  // Each heuristic will be evaluated and ANDed together to get a final answer.
84  std::map<ExtensionId, FunctionHeuristicsMap> function_heuristics_;
85
86  // For now, as soon as an extension violates quota, we don't allow it to
87  // make any more requests to quota limited functions.  This provides a quick
88  // lookup for these extensions that is only stored in memory.
89  typedef std::map<std::string, std::string> ViolationErrorMap;
90  ViolationErrorMap violation_errors_;
91
92  DISALLOW_COPY_AND_ASSIGN(ExtensionsQuotaService);
93};
94
95// A QuotaLimitHeuristic is two things: 1, A heuristic to map extension
96// function arguments to corresponding Buckets for each input arg, and 2) a
97// heuristic for determining if a new event involving a particular item
98// (represented by its Bucket) constitutes a quota violation.
99class QuotaLimitHeuristic {
100 public:
101  // Parameters to configure the amount of tokens allotted to individual
102  // Bucket objects (see Below) and how often they are replenished.
103  struct Config {
104    // The maximum number of tokens a bucket can contain, and is refilled to
105    // every epoch.
106    int64 refill_token_count;
107
108    // Specifies how frequently the bucket is logically refilled with tokens.
109    base::TimeDelta refill_interval;
110  };
111
112  // A Bucket is how the heuristic portrays an individual item (since quota
113  // limits are per item) and all associated state for an item that needs to
114  // carry through multiple calls to Apply.  It "holds" tokens, which are
115  // debited and credited in response to new events involving the item being
116  // being represented.  For convenience, instead of actually periodically
117  // refilling buckets they are just 'Reset' on-demand (e.g. when new events
118  // come in). So, a bucket has an expiration to denote it has becomes stale.
119  class Bucket {
120   public:
121    Bucket() : num_tokens_(0) {}
122    // Removes a token from this bucket, and returns true if the bucket had
123    // any tokens in the first place.
124    bool DeductToken() { return num_tokens_-- > 0; }
125
126    // Returns true if this bucket has tokens to deduct.
127    bool has_tokens() const { return num_tokens_ > 0; }
128
129    // Reset this bucket to specification (from internal configuration), to be
130    // valid from |start| until the first refill interval elapses and it needs
131    // to be reset again.
132    void Reset(const Config& config, const base::TimeTicks& start);
133
134    // The time at which the token count and next expiration should be reset,
135    // via a call to Reset.
136    const base::TimeTicks& expiration() { return expiration_; }
137   private:
138    base::TimeTicks expiration_;
139    int64 num_tokens_;
140    DISALLOW_COPY_AND_ASSIGN(Bucket);
141  };
142  typedef std::list<Bucket*> BucketList;
143
144  // A helper interface to retrieve the bucket corresponding to |args| from
145  // the set of buckets (which is typically stored in the BucketMapper itself)
146  // for this QuotaLimitHeuristic.
147  class BucketMapper {
148   public:
149    virtual ~BucketMapper() {}
150    // In most cases, this should simply extract item IDs from the arguments
151    // (e.g for bookmark operations involving an existing item). If a problem
152    // occurs while parsing |args|, the function aborts - buckets may be non-
153    // empty). The expectation is that invalid args and associated errors are
154    // handled by the ExtensionFunction itself so we don't concern ourselves.
155    virtual void GetBucketsForArgs(const base::ListValue* args,
156                                   BucketList* buckets) = 0;
157  };
158
159  // Maps all calls to the same bucket, regardless of |args|, for this
160  // QuotaLimitHeuristic.
161  class SingletonBucketMapper : public BucketMapper {
162   public:
163    SingletonBucketMapper() {}
164    virtual ~SingletonBucketMapper() {}
165    virtual void GetBucketsForArgs(const base::ListValue* args,
166                                   BucketList* buckets) OVERRIDE;
167
168   private:
169    Bucket bucket_;
170    DISALLOW_COPY_AND_ASSIGN(SingletonBucketMapper);
171  };
172
173  // Ownership of |map| is given to the new QuotaLimitHeuristic.
174  QuotaLimitHeuristic(const Config& config,
175                      BucketMapper* map,
176                      const std::string& name);
177  virtual ~QuotaLimitHeuristic();
178
179  // Determines if sufficient quota exists (according to the Apply
180  // implementation of a derived class) to perform an operation with |args|,
181  // based on the history of similar operations with similar arguments (which
182  // is retrieved using the BucketMapper).
183  bool ApplyToArgs(const base::ListValue* args,
184                   const base::TimeTicks& event_time);
185
186  // Returns an error formatted according to this heuristic.
187  std::string GetError() const;
188
189 protected:
190  const Config& config() { return config_; }
191
192  // Determine if the new event occurring at |event_time| involving |bucket|
193  // constitutes a quota violation according to this heuristic.
194  virtual bool Apply(Bucket* bucket, const base::TimeTicks& event_time) = 0;
195
196 private:
197  friend class QuotaLimitHeuristicTest;
198
199  const Config config_;
200
201  // The mapper used in Map. Cannot be NULL.
202  scoped_ptr<BucketMapper> bucket_mapper_;
203
204  // The name of the heuristic for formatting error messages.
205  std::string name_;
206
207  DISALLOW_COPY_AND_ASSIGN(QuotaLimitHeuristic);
208};
209
210// A simple per-item heuristic to limit the number of events that can occur in
211// a given period of time; e.g "no more than 100 events in an hour".
212class ExtensionsQuotaService::TimedLimit : public QuotaLimitHeuristic {
213 public:
214  TimedLimit(const Config& config, BucketMapper* map, const std::string& name)
215      : QuotaLimitHeuristic(config, map, name) {}
216  virtual bool Apply(Bucket* bucket,
217                     const base::TimeTicks& event_time) OVERRIDE;
218};
219
220// A per-item heuristic to limit the number of events that can occur in a
221// period of time over a sustained longer interval. E.g "no more than two
222// events per minute, sustained over 10 minutes".
223class ExtensionsQuotaService::SustainedLimit : public QuotaLimitHeuristic {
224 public:
225  SustainedLimit(const base::TimeDelta& sustain,
226                 const Config& config,
227                 BucketMapper* map,
228                 const std::string& name);
229  virtual bool Apply(Bucket* bucket,
230                     const base::TimeTicks& event_time) OVERRIDE;
231 private:
232  // Specifies how long exhaustion of buckets is allowed to continue before
233  // denying requests.
234  const int64 repeat_exhaustion_allowance_;
235  int64 num_available_repeat_exhaustions_;
236};
237
238#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSIONS_QUOTA_SERVICE_H_
239