// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The ExtensionsQuotaService uses heuristics to limit abusive requests
// made by extensions. In this model 'items' (e.g individual bookmarks) are
// represented by a 'Bucket' that holds state for that item for one single
// interval of time. The interval of time is defined as 'how long we need to
// watch an item (for a particular heuristic) before making a decision about
// quota violations'. A heuristic is two functions: one mapping input
// arguments to a unique Bucket (the BucketMapper), and another to determine
// if a new request involving such an item at a given time is a violation.
#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSIONS_QUOTA_SERVICE_H_
#define CHROME_BROWSER_EXTENSIONS_EXTENSIONS_QUOTA_SERVICE_H_
#pragma once
#include <list>
#include <map>
#include <string>
#include "base/hash_tables.h"
#include "base/memory/scoped_ptr.h"
#include "base/time.h"
#include "base/timer.h"
#include "base/values.h"
class ExtensionFunction;
class QuotaLimitHeuristic;
typedef std::list<QuotaLimitHeuristic*> QuotaLimitHeuristics;
class ExtensionsQuotaService {
public:
// Some concrete heuristics (declared below) that ExtensionFunctions can
// use to help the service make decisions about quota violations.
class TimedLimit;
class SustainedLimit;
ExtensionsQuotaService();
~ExtensionsQuotaService();
// Decide whether the invocation of |function| with argument |args| by the
// extension specified by |extension_id| results in a quota limit violation.
// Returns true if the request is fine and can proceed, false if the request
// should be throttled and an error returned to the extension.
bool Assess(const std::string& extension_id, ExtensionFunction* function,
const ListValue* args, const base::TimeTicks& event_time);
private:
friend class ExtensionTestQuotaResetFunction;
typedef std::map<std::string, QuotaLimitHeuristics> FunctionHeuristicsMap;
// Purge resets all accumulated data (except |violators_|) as if the service
// was just created. Called periodically so we don't consume an unbounded
// amount of memory while tracking quota. Yes, this could mean an extension
// gets away with murder if it is timed right, but the extensions we are
// trying to limit are ones that consistently violate, so we'll converge
// to the correct set.
void Purge();
void PurgeFunctionHeuristicsMap(FunctionHeuristicsMap* map);
base::RepeatingTimer<ExtensionsQuotaService> purge_timer_;
// Our quota tracking state for extensions that have invoked quota limited
// functions. Each extension is treated separately, so extension ids are the
// key for the mapping. As an extension invokes functions, the map keeps
// track of which functions it has invoked and the heuristics for each one.
// Each heuristic will be evaluated and ANDed together to get a final answer.
std::map<std::string, FunctionHeuristicsMap> function_heuristics_;
// For now, as soon as an extension violates quota, we don't allow it to
// make any more requests to quota limited functions. This provides a quick
// lookup for these extensions that is only stored in memory.
base::hash_set<std::string> violators_;
DISALLOW_COPY_AND_ASSIGN(ExtensionsQuotaService);
};
// A QuotaLimitHeuristic is two things: 1, A heuristic to map extension
// function arguments to corresponding Buckets for each input arg, and 2) a
// heuristic for determining if a new event involving a particular item
// (represented by its Bucket) constitutes a quota violation.
class QuotaLimitHeuristic {
public:
// Parameters to configure the amount of tokens allotted to individual
// Bucket objects (see Below) and how often they are replenished.
struct Config {
// The maximum number of tokens a bucket can contain, and is refilled to
// every epoch.
int64 refill_token_count;
// Specifies how frequently the bucket is logically refilled with tokens.
base::TimeDelta refill_interval;
};
// A Bucket is how the heuristic portrays an individual item (since quota
// limits are per item) and all associated state for an item that needs to
// carry through multiple calls to Apply. It "holds" tokens, which are
// debited and credited in response to new events involving the item being
// being represented. For convenience, instead of actually periodically
// refilling buckets they are just 'Reset' on-demand (e.g. when new events
// come in). So, a bucket has an expiration to denote it has becomes stale.
class Bucket {
public:
Bucket() : num_tokens_(0) {}
// Removes a token from this bucket, and returns true if the bucket had
// any tokens in the first place.
bool DeductToken() { return num_tokens_-- > 0; }
// Returns true if this bucket has tokens to deduct.
bool has_tokens() const { return num_tokens_ > 0; }
// Reset this bucket to specification (from internal configuration), to be
// valid from |start| until the first refill interval elapses and it needs
// to be reset again.
void Reset(const Config& config, const base::TimeTicks& start);
// The time at which the token count and next expiration should be reset,
// via a call to Reset.
const base::TimeTicks& expiration() { return expiration_; }
private:
base::TimeTicks expiration_;
int64 num_tokens_;
DISALLOW_COPY_AND_ASSIGN(Bucket);
};
typedef std::list<Bucket*> BucketList;
// A generic error message for quota violating requests.
static const char kGenericOverQuotaError[];
// A helper interface to retrieve the bucket corresponding to |args| from
// the set of buckets (which is typically stored in the BucketMapper itself)
// for this QuotaLimitHeuristic.
class BucketMapper {
public:
virtual ~BucketMapper() {}
// In most cases, this should simply extract item IDs from the arguments
// (e.g for bookmark operations involving an existing item). If a problem
// occurs while parsing |args|, the function aborts - buckets may be non-
// empty). The expectation is that invalid args and associated errors are
// handled by the ExtensionFunction itself so we don't concern ourselves.
virtual void GetBucketsForArgs(const ListValue* args,
BucketList* buckets) = 0;
};
// Ownership of |mapper| is given to the new QuotaLimitHeuristic.
explicit QuotaLimitHeuristic(const Config& config, BucketMapper* map);
virtual ~QuotaLimitHeuristic();
// Determines if sufficient quota exists (according to the Apply
// implementation of a derived class) to perform an operation with |args|,
// based on the history of similar operations with similar arguments (which
// is retrieved using the BucketMapper).
bool ApplyToArgs(const ListValue* args, const base::TimeTicks& event_time);
protected:
const Config& config() { return config_; }
// Determine if the new event occurring at |event_time| involving |bucket|
// constitutes a quota violation according to this heuristic.
virtual bool Apply(Bucket* bucket, const base::TimeTicks& event_time) = 0;
private:
friend class QuotaLimitHeuristicTest;
const Config config_;
// The mapper used in Map. Cannot be NULL.
scoped_ptr<BucketMapper> bucket_mapper_;
DISALLOW_COPY_AND_ASSIGN(QuotaLimitHeuristic);
};
// A simple per-item heuristic to limit the number of events that can occur in
// a given period of time; e.g "no more than 100 events in an hour".
class ExtensionsQuotaService::TimedLimit : public QuotaLimitHeuristic {
public:
explicit TimedLimit(const Config& config, BucketMapper* map)
: QuotaLimitHeuristic(config, map) {}
virtual bool Apply(Bucket* bucket, const base::TimeTicks& event_time);
};
// A per-item heuristic to limit the number of events that can occur in a
// period of time over a sustained longer interval. E.g "no more than two
// events per minute, sustained over 10 minutes".
class ExtensionsQuotaService::SustainedLimit : public QuotaLimitHeuristic {
public:
SustainedLimit(const base::TimeDelta& sustain,
const Config& config,
BucketMapper* map);
virtual bool Apply(Bucket* bucket, const base::TimeTicks& event_time);
private:
// Specifies how long exhaustion of buckets is allowed to continue before
// denying requests.
const int64 repeat_exhaustion_allowance_;
int64 num_available_repeat_exhaustions_;
};
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSIONS_QUOTA_SERVICE_H_