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: Bucket()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. DeductToken()125 bool DeductToken() { return num_tokens_-- > 0; } 126 127 // Returns true if this bucket has tokens to deduct. has_tokens()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. expiration()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: ~BucketMapper()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: SingletonBucketMapper()165 SingletonBucketMapper() {} ~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: config()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: TimedLimit(const Config & config,BucketMapper * map,const std::string & name)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