diff --git a/controllers/TestController.php b/controllers/TestController.php index 1b40f7b..1fd1ab5 100644 --- a/controllers/TestController.php +++ b/controllers/TestController.php @@ -1,23 +1,48 @@ namespace = 'test/v1'; + $this->auth = new WPUser(); + $this->limiter = new RestEndpointTimedRateLimit( + self::RATE_FIELD."_".$this->auth->getPk(), + time(), + self::RATE_LIMIT, + new WPTransientStorage(), + self::RATE_TIME + ); } public function register_routes() { - register_rest_route( + \register_rest_route( "{$this->namespace}", "/date", [ [ - 'methods' => WP_REST_Server::READABLE, + 'methods' => \WP_REST_Server::READABLE, 'callback' => [$this, 'index'], 'permission_callback' => [$this, 'index_permissions_check'], 'args' => [], @@ -26,21 +51,32 @@ public function register_routes() { ); } - public function index(WP_REST_Request $request) { - $data = ['now'=> date('Y-m-d H:i:s')]; - return new WP_REST_Response( $data, 200); + public function index(\WP_REST_Request $request) { + $creditData = $this->limiter->getCreditData(); + $refresh = $this->limiter->formatCreditReload($creditData); + if(!$this->limiter->checkCreditData($creditData)) { + $left = $creditData->getBalance(); + $limit = self::RATE_LIMIT; + return new \WP_REST_Response( + "API Limit reached {$left}/{$limit} calls refresh in {$refresh}", + 429 + ); + } + + $data = [ + 'now'=> date('Y-m-d H:i:s'), + 'time_to_refresh' => $refresh + ]; + + $creditData->deductBalance(1); + $this->limiter->setCreditData($creditData); + + return new \WP_REST_Response( $data, 200); } public function index_permissions_check(WP_REST_Request $request) { // Really basic example for permissions checking // (https://codex.wordpress.org/Roles_and_Capabilities#read) - return $this->user_permission_check(get_current_user_id(), 'read'); - } - - public function user_permission_check($user_id='', $cap='admin') { - if(!empty($user_id)) { - return user_can($user_id, $cap); - } - return false; + return $this->auth->hasCapability('read'); } } diff --git a/index.php b/index.php index db2119a..676c31f 100644 --- a/index.php +++ b/index.php @@ -8,13 +8,31 @@ Author URI: https://www.codesign2.co.uk */ -add_action('rest_api_init', function(){ +namespace lewiscowles; + +\add_action('rest_api_init', function() { + error_reporting(E_ALL); + ini_set('display_errors', true); foreach([ 'Test' ] as $endpoint) { require_once( __DIR__."/controllers/{$endpoint}Controller.php"); - $controller_class = "{$endpoint}Controller"; + $controller_class = __NAMESPACE__."\\{$endpoint}Controller"; $controller = new $controller_class(); $controller->register_routes(); } }); + +\add_action('wp_ajax_apinonce', function() { + die(\wp_create_nonce( 'wp_rest' )); +}); + +\spl_autoload_register( function($classname) { + $file = sprintf( + '%s/vendor/%s.php', + __DIR__, str_replace('\\', DIRECTORY_SEPARATOR, $classname) + ); + if(file_exists($file)) { + require $file; + } +}); diff --git a/swagger/description.yaml b/swagger/description.yaml index a0057e5..4d720b3 100644 --- a/swagger/description.yaml +++ b/swagger/description.yaml @@ -22,7 +22,11 @@ paths: 403: description: "Unauthorised request rejection" schema: - $ref: "#/definitions/inline_response_403" + $ref: "#/definitions/inline_response_err" + 421: + description: "Too many requests rejection" + schema: + $ref: "#/definitions/inline_response_err" definitions: inline_response_200: properties: @@ -30,7 +34,7 @@ definitions: type: "string" format: "datetime" description: "Curent server datetime value" - inline_response_403: + inline_response_err: properties: code: type: string @@ -44,4 +48,4 @@ definitions: status: type: integer description: "HTTP Error Code" - + diff --git a/vendor/lewiscowles/CreditDataInterface.php b/vendor/lewiscowles/CreditDataInterface.php new file mode 100644 index 0000000..391ceb5 --- /dev/null +++ b/vendor/lewiscowles/CreditDataInterface.php @@ -0,0 +1,10 @@ +Created = $time; + $this->Balance = $balance; + } + + public function getCreated() : int { + return $this->Created; + } + + public function getBalance() : int { + return $this->Balance; + } + + public function deductBalance(int $value) { + $this->Balance -= abs($value); + } + + public function topUpBalance(int $value) { + $this->Balance += abs($value); + } +} diff --git a/vendor/lewiscowles/HTTP/REST/RestEndpointTimedRateLimit.php b/vendor/lewiscowles/HTTP/REST/RestEndpointTimedRateLimit.php new file mode 100644 index 0000000..4cac5d6 --- /dev/null +++ b/vendor/lewiscowles/HTTP/REST/RestEndpointTimedRateLimit.php @@ -0,0 +1,55 @@ +HashId = $hash_id; + $this->Time = $time; + $this->Credits = $credits; + $this->Storage = $ttsi; + $this->Ttl = $ttl; + } + + public function getCreditData() : CreditDataInterface { + return $this->Storage->get($this->HashId, new RestEndpointCreditData( + $this->Time, $this->Credits + )); + } + + public function checkCreditData(CreditDataInterface $data) : bool { + return ($data->getBalance() > 0); + } + + public function setCreditData(CreditDataInterface $data) { + $this->Storage->set($this->HashId, $data, $this->getRefreshTtl($data)); + } + + public function formatCreditReload(CreditDataInterface $data) : string { + $refresh = $this->getRefreshTtl($data); + $out = date('s', $refresh).' seconds'; + if($refresh > 60) { + $out = date('i', $refresh).' minutes, ' . $out; + } + if($refresh > 3600) { + $out = intval($refresh / 3600).' hours, '.$out; + } + return $out; + } + + protected function getRefreshTtl(CreditDataInterface $data) { + return ($this->Ttl - ($this->Time - $data->getCreated())); + } +} diff --git a/vendor/lewiscowles/auth/SystemUserInterface.php b/vendor/lewiscowles/auth/SystemUserInterface.php new file mode 100644 index 0000000..d5792d7 --- /dev/null +++ b/vendor/lewiscowles/auth/SystemUserInterface.php @@ -0,0 +1,11 @@ +Pk = \get_current_user_id(); + } + + public function hasCapability(string $capability) : bool { + return \current_user_can($capability); + } + + public function isLoggedIn() : bool { + return \is_user_logged_in(); + } + + public function getPK() : string { + return $this->Pk; + } + + public function login(string $username, string $password) { + \wp_authenticate($username, $password); + } + + public function logout() { + \wp_logout(); + } +} diff --git a/vendor/lewiscowles/wordpress/storage/WPTransientStorage.php b/vendor/lewiscowles/wordpress/storage/WPTransientStorage.php new file mode 100644 index 0000000..969a63d --- /dev/null +++ b/vendor/lewiscowles/wordpress/storage/WPTransientStorage.php @@ -0,0 +1,26 @@ +