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