From 4c74b9c41471f0b5d9ec7424abffc10465edad66 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 20 Aug 2018 13:45:50 +0200 Subject: [PATCH] Setup API requests limit system. --- RestControllers/accountController.php | 41 ++++---- classes/APILimits.php | 135 ++++++++++++++++++++++++++ db_struct.sql | 8 ++ helpers/APILimits.php | 16 +++ index.php | 5 + 5 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 classes/APILimits.php create mode 100644 helpers/APILimits.php diff --git a/RestControllers/accountController.php b/RestControllers/accountController.php index 761906c..4e37726 100644 --- a/RestControllers/accountController.php +++ b/RestControllers/accountController.php @@ -17,31 +17,38 @@ class accountController { * @url POST /account/login */ public function connectUSER(){ + //Check variables sent in request if(!isset($_POST['userMail']) OR !isset($_POST['userPassword'])) throw new RestException(400, "Missing data !"); - //Retrieve database connection - $db = CS::get()->db;; + //API limit + api_limit_query(APILimits::ACTION_LOGIN_FAILED, false); - //Extract data - $userMail = $_POST["userMail"]; - $userPassword = $_POST['userPassword']; + //Retrieve database connection + $db = CS::get()->db;; - //Try to perform login - $loginTokens = CS::get()->components->account->generateUserLoginTokens($userMail, $userPassword, APIServiceID, $db); + //Extract data + $userMail = $_POST["userMail"]; + $userPassword = $_POST['userPassword']; - if(count($loginTokens) == 0) - throw new RestException(401, "Invalid e-mail address / password !"); + //Try to perform login + $loginTokens = CS::get()->components->account->generateUserLoginTokens($userMail, $userPassword, APIServiceID, $db); - //Return result with tokens - return array( - "success" => "User logged in !", - "tokens" => array( - "token1" => $loginTokens[0], - "token2" => $loginTokens[1], - ), - ); + if(count($loginTokens) == 0){ + api_limit_query(APILimits::ACTION_LOGIN_FAILED, true); + throw new RestException(401, "Invalid e-mail address / password !"); + } + + + //Return result with tokens + return array( + "success" => "User logged in !", + "tokens" => array( + "token1" => $loginTokens[0], + "token2" => $loginTokens[1], + ), + ); } /** diff --git a/classes/APILimits.php b/classes/APILimits.php new file mode 100644 index 0000000..8207518 --- /dev/null +++ b/classes/APILimits.php @@ -0,0 +1,135 @@ + array( + "limit" => 10 + ) + ); + + /** + * Limit the number of time a client can perform a query over the API + * + * @param string $action The name of the action to limit + * @param bool $trigger Specify whether this call of the method must be + * considered as a call of the client or not + */ + public function limit_query(string $action, bool $trigger){ + + //First, clean old entries + $this->clean(); + + $ip = $_SERVER["REMOTE_ADDR"]; + + //If required, increase action by one + if($trigger) + $this->trigger($action, $ip); + + //Count the number of time the action occurred + if($this->count($action, $ip) > self::ACTIONS[$action]["limit"]) + Rest_fatal_error(429, "Too many request. Please try again later."); + } + + /** + * Clean old entries + */ + public function clean(){ + db()->deleteEntry( + self::TABLE_NAME, + "time_start < ?", + array(time() - self::KEEP_DATA_FOR) + ); + } + + /** + * Increase by one the number of the time a client performed + * an action + * + * @param string $action The action to trigger + * @param string $ip The target IP address + * @return bool TRUE for a success else FALSE + */ + private function trigger(string $action, string $ip) : bool { + + if(!$this->exists($action, $ip)){ + return db()->addLine(self::TABLE_NAME, array( + "ip" => $ip, + "time_start" => time(), + "action" => $action, + "count" => 1 + )); + } + + else { + + $number = $this->count($action, $ip); + $number++; + + return db()->updateDB(self::TABLE_NAME, + "ip = ? AND action = ?", + array("count" => $number), + array($ip, $action)); + } + + } + + /** + * Check wether an action has been referenced at least once in + * the database + * + * @param string $action The action to check + * @param string $ip The target IP address + * @return bool TRUE if the entry has been found at least once / FALSE else + */ + private function exists(string $action, string $ip) : bool { + return db()->count(self::TABLE_NAME, + "WHERE ip = ? AND action = ?", + array($ip, $action)) > 0; + } + + /** + * Count the number of time an IP address has performed an action + * + * @param string $action The target action + * @param string $ip Target IP address + * @return int The number of time the action has been done + */ + private function count(string $action, string $ip) : int { + $data = db()->select(self::TABLE_NAME, + "WHERE ip = ? AND action = ?", + array($ip, $action), + array("count")); + + if(count($data) < 1) + return 1; + + else + return $data[0]["count"]; + } +} \ No newline at end of file diff --git a/db_struct.sql b/db_struct.sql index 3c18a51..5c35c14 100644 --- a/db_struct.sql +++ b/db_struct.sql @@ -72,6 +72,14 @@ CREATE TABLE `commentaires` ( PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; +DROP TABLE IF EXISTS `comunic_api_limit_count`; +CREATE TABLE `comunic_api_limit_count` ( + `ip` varchar(15) NOT NULL, + `time_start` int(11) DEFAULT NULL, + `action` varchar(45) DEFAULT NULL, + `count` int(11) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + DROP TABLE IF EXISTS `comunic_API_ServicesToken`; CREATE TABLE `comunic_API_ServicesToken` ( diff --git a/helpers/APILimits.php b/helpers/APILimits.php new file mode 100644 index 0000000..f2fc7bf --- /dev/null +++ b/helpers/APILimits.php @@ -0,0 +1,16 @@ +limit->limit_query($name, $trigger); +} \ No newline at end of file diff --git a/index.php b/index.php index 4f41ecd..437d784 100644 --- a/index.php +++ b/index.php @@ -70,6 +70,11 @@ else { define("userID", 0); } +//Setup API limits +require_once "classes/APILimits.php"; +$api_limits = new APILimits(); +cs()->register("limit", $api_limits); + /** * Handle Rest requests */