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. 41 // be created, called and destroyed on the same thread, due to its use 42 // of a RepeatingTimer. 43 class QuotaService : 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 QuotaService(); 51 virtual ~QuotaService(); 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<QuotaService> 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(QuotaService); 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. 99 class 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 138 private: 139 base::TimeTicks expiration_; 140 int64 num_tokens_; 141 DISALLOW_COPY_AND_ASSIGN(Bucket); 142 }; 143 typedef std::list<Bucket*> BucketList; 144 145 // A helper interface to retrieve the bucket corresponding to |args| from 146 // the set of buckets (which is typically stored in the BucketMapper itself) 147 // for this QuotaLimitHeuristic. 148 class BucketMapper { 149 public: 150 virtual ~BucketMapper() {} 151 // In most cases, this should simply extract item IDs from the arguments 152 // (e.g for bookmark operations involving an existing item). If a problem 153 // occurs while parsing |args|, the function aborts - buckets may be non- 154 // empty). The expectation is that invalid args and associated errors are 155 // handled by the ExtensionFunction itself so we don't concern ourselves. 156 virtual void GetBucketsForArgs(const base::ListValue* args, 157 BucketList* buckets) = 0; 158 }; 159 160 // Maps all calls to the same bucket, regardless of |args|, for this 161 // QuotaLimitHeuristic. 162 class SingletonBucketMapper : public BucketMapper { 163 public: 164 SingletonBucketMapper() {} 165 virtual ~SingletonBucketMapper() {} 166 virtual void GetBucketsForArgs(const base::ListValue* args, 167 BucketList* buckets) OVERRIDE; 168 169 private: 170 Bucket bucket_; 171 DISALLOW_COPY_AND_ASSIGN(SingletonBucketMapper); 172 }; 173 174 // Ownership of |map| is given to the new QuotaLimitHeuristic. 175 QuotaLimitHeuristic(const Config& config, 176 BucketMapper* map, 177 const std::string& name); 178 virtual ~QuotaLimitHeuristic(); 179 180 // Determines if sufficient quota exists (according to the Apply 181 // implementation of a derived class) to perform an operation with |args|, 182 // based on the history of similar operations with similar arguments (which 183 // is retrieved using the BucketMapper). 184 bool ApplyToArgs(const base::ListValue* args, 185 const base::TimeTicks& event_time); 186 187 // Returns an error formatted according to this heuristic. 188 std::string GetError() const; 189 190 protected: 191 const Config& config() { return config_; } 192 193 // Determine if the new event occurring at |event_time| involving |bucket| 194 // constitutes a quota violation according to this heuristic. 195 virtual bool Apply(Bucket* bucket, const base::TimeTicks& event_time) = 0; 196 197 private: 198 friend class QuotaLimitHeuristicTest; 199 200 const Config config_; 201 202 // The mapper used in Map. Cannot be NULL. 203 scoped_ptr<BucketMapper> bucket_mapper_; 204 205 // The name of the heuristic for formatting error messages. 206 std::string name_; 207 208 DISALLOW_COPY_AND_ASSIGN(QuotaLimitHeuristic); 209 }; 210 211 // A simple per-item heuristic to limit the number of events that can occur in 212 // a given period of time; e.g "no more than 100 events in an hour". 213 class QuotaService::TimedLimit : public QuotaLimitHeuristic { 214 public: 215 TimedLimit(const Config& config, BucketMapper* map, const std::string& name) 216 : QuotaLimitHeuristic(config, map, name) {} 217 virtual bool Apply(Bucket* bucket, 218 const base::TimeTicks& event_time) OVERRIDE; 219 }; 220 221 // A per-item heuristic to limit the number of events that can occur in a 222 // period of time over a sustained longer interval. E.g "no more than two 223 // events per minute, sustained over 10 minutes". 224 class QuotaService::SustainedLimit : public QuotaLimitHeuristic { 225 public: 226 SustainedLimit(const base::TimeDelta& sustain, 227 const Config& config, 228 BucketMapper* map, 229 const std::string& name); 230 virtual bool Apply(Bucket* bucket, 231 const base::TimeTicks& event_time) OVERRIDE; 232 233 private: 234 // Specifies how long exhaustion of buckets is allowed to continue before 235 // denying requests. 236 const int64 repeat_exhaustion_allowance_; 237 int64 num_available_repeat_exhaustions_; 238 }; 239 240 } // namespace extensions 241 242 #endif // EXTENSIONS_BROWSER_QUOTA_SERVICE_H_ 243