Setup API requests limit system.

This commit is contained in:
Pierre HUBERT 2018-08-20 13:45:50 +02:00
parent fba6c796a8
commit 4c74b9c414
5 changed files with 188 additions and 17 deletions

View File

@ -17,31 +17,38 @@ class accountController {
* @url POST /account/login * @url POST /account/login
*/ */
public function connectUSER(){ public function connectUSER(){
//Check variables sent in request //Check variables sent in request
if(!isset($_POST['userMail']) OR !isset($_POST['userPassword'])) if(!isset($_POST['userMail']) OR !isset($_POST['userPassword']))
throw new RestException(400, "Missing data !"); throw new RestException(400, "Missing data !");
//Retrieve database connection //API limit
$db = CS::get()->db;; api_limit_query(APILimits::ACTION_LOGIN_FAILED, false);
//Extract data //Retrieve database connection
$userMail = $_POST["userMail"]; $db = CS::get()->db;;
$userPassword = $_POST['userPassword'];
//Try to perform login //Extract data
$loginTokens = CS::get()->components->account->generateUserLoginTokens($userMail, $userPassword, APIServiceID, $db); $userMail = $_POST["userMail"];
$userPassword = $_POST['userPassword'];
if(count($loginTokens) == 0) //Try to perform login
throw new RestException(401, "Invalid e-mail address / password !"); $loginTokens = CS::get()->components->account->generateUserLoginTokens($userMail, $userPassword, APIServiceID, $db);
//Return result with tokens if(count($loginTokens) == 0){
return array( api_limit_query(APILimits::ACTION_LOGIN_FAILED, true);
"success" => "User logged in !", throw new RestException(401, "Invalid e-mail address / password !");
"tokens" => array( }
"token1" => $loginTokens[0],
"token2" => $loginTokens[1],
), //Return result with tokens
); return array(
"success" => "User logged in !",
"tokens" => array(
"token1" => $loginTokens[0],
"token2" => $loginTokens[1],
),
);
} }
/** /**

135
classes/APILimits.php Normal file
View File

@ -0,0 +1,135 @@
<?php
/**
* API Actions limits count
*
* @author Pierre HUBERT
*/
class APILimits {
/**
* Table name
*/
const TABLE_NAME = DBprefix."api_limit_count";
/**
* Entries live time
*/
const KEEP_DATA_FOR = 3600; // 1 hour
/**
* Actions list
*/
const ACTION_LOGIN_FAILED = "failed_login";
/**
* Actions configruation
*/
const ACTIONS = array(
//Login failed
self::ACTION_LOGIN_FAILED => 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"];
}
}

View File

@ -72,6 +72,14 @@ CREATE TABLE `commentaires` (
PRIMARY KEY (`ID`) PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1; ) 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`; DROP TABLE IF EXISTS `comunic_API_ServicesToken`;
CREATE TABLE `comunic_API_ServicesToken` ( CREATE TABLE `comunic_API_ServicesToken` (

16
helpers/APILimits.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/**
* API Limits helper
*
* @author Pierre HUBERT
*/
/**
* Limit the number of time a query can be performed by a client
*
* @param string $name The name of the action to limit
* @param bool $trigger Count this as an action of the user or not
*/
function api_limit_query(string $name, bool $trigger){
cs()->limit->limit_query($name, $trigger);
}

View File

@ -70,6 +70,11 @@ else {
define("userID", 0); define("userID", 0);
} }
//Setup API limits
require_once "classes/APILimits.php";
$api_limits = new APILimits();
cs()->register("limit", $api_limits);
/** /**
* Handle Rest requests * Handle Rest requests
*/ */