./api
to see what we now have. And we have:./api/extensions/filters/EApiAccessControlFilter.php
is a filter class for checking API access rules../api/extensions/components/EApiAccessRule.php
is a class that represents an API access rule../api/extensions/components/EApiActiveRecord.php
- class for auxiliary methods of working with AR-models with API../api/extensions/components/EApiController.php
- controller class for processing requests to API../api/extensions/components/EApiError.php
is an API error class that exists for the convenience of reading logs../api/extensions/components/EApiErrorHandler.php
is a class for handling API errors. If we decide to log errors to the database, then we will use this particular class../api/models/ApiUser.php
is an example of a model with which we will manage external users of our API../common/lib/YiiRestTools/
- helper classes for the functioning of the REST API../api/config/api.php
): 'rules' => array( // REST patterns array('<controller>/index', 'pattern' => 'api/<controller:\w+>', 'verb' => 'POST'), array('<controller>/view', 'pattern' => 'api/<controller:\w+>/view', 'verb' => 'POST'), array('<controller>/update', 'pattern' => 'api/<controller:\w+>/update', 'verb' => 'PUT'), array('<controller>/delete', 'pattern' => 'api/<controller:\w+>/delete', 'verb' => 'DELETE'), array('<controller>/create', 'pattern' => 'api/<controller:\w+>/create', 'verb' => 'POST'), ),
Address | HTTP method | Action caused |
---|---|---|
api.yiinitializr.dev/test/ | Get | TestController \ actionIndex () |
api.yiinitializr.dev/test/1/ | Get | TestController \ actionView (1) |
api.yiinitializr.dev/test/ | POST | TestController \ actionCreate () |
api.yiinitializr.dev/test/1/ | PUT | TestController \ actionUpdate (1) |
api.yiinitializr.dev/test/1/ | DELETE | TestController \ actionDelete (1) |
'rules' => array( // REST patterns array('<controller>/index', 'pattern' => '<controller:\w+>', 'verb' => 'GET'), array('<controller>/view', 'pattern' => '<controller:\w+>/<id:\d+>', 'verb' => 'GET'), array('<controller>/update', 'pattern' => '<controller:\w+>/<id:\d+>', 'verb' => 'PUT'), array('<controller>/delete', 'pattern' => '<controller:\w+>/<id:\d+>', 'verb' => 'DELETE'), array('<controller>/create', 'pattern' => '<controller:\w+>', 'verb' => 'POST'), ),
use YiiRestTools\Helpers\RequestData; use Yiinitializr\Helpers\ArrayX;
EApiAccessControlFilter.php
in EApiAccessRule.php
, EApiAccessRule.php
these classes are used in the second file../api/extensions/components/EApiAccessRule.php
): public function isRequestAllowed($user, $controller, $action, $ip, $verb) { if ($this->isActionMatched($action) && $this->isUserMatched(Yii::app()->user) && $this->isRoleMatched(Yii::app()->user) && $this->isSignatureMatched($user) && $this->isIpMatched($ip) && $this->isVerbMatched($verb) && $this->isControllerMatched($controller) ) { return $this->allow ? 1 : -1; } else { return 0; } }
isRequestAllowed
method checks the compliance of the request with the rules. If the chain of checks in the if block is true, then this rule is applied, returning 1 or -1, depending on what this rule does - allows or denies. Otherwise, this rule is not applicable to a specific request and the method returns 0. To make it clearer, I remind you what the rules for filters look like: public function filters() { return array( array( 'EApiAccessControlFilter -error', 'rules' => array( array('allow', 'users' => array('@')), ) ) ); }
$this->isSignatureMatched($user)
in this thread. Obtaining an incorrect signature, the system decides that this rule is not applicable and accordingly passes the user (or hacker) inside. Most likely, verification of the signature should be made at the correct request after, and according to the result, let or not let us into the system. Therefore, it is necessary to slightly change this method: public function isRequestAllowed($user, $controller, $action, $ip, $verb) { if ($this->isActionMatched($action) && $this->isUserMatched(Yii::app()->user) && $this->isRoleMatched(Yii::app()->user) && $this->isIpMatched($ip) && $this->isVerbMatched($verb) && $this->isControllerMatched($controller) ) { return ($this->allow && $this->isSignatureMatched($user)) ? 1 : -1; } else { return 0; } }
./api/config/api.php
): 'params' => array( 'api.key.name' => 'APIKEY', )
common/config/main.php
): 'params' => array( ... 'php.timezone' => 'Europe/Moscow', ),
> yiic migrate create create_api_user_table
up()
and down()
methods to the following form: public function up() { $this->createTable('{{api_user}}', array( 'id' => 'pk', 'username' => 'varchar(32) NOT NULL', 'api_key' => 'varchar(32) NOT NULL', 'api_secret' => 'varchar(32) NOT NULL', )); $this->insert('{{api_user}}', array( 'username' => 'test_user', 'api_key' => 'e4afe26b5b57083f74b2d01c7066379c', // md5('public_key') 'api_secret' => '156a17333e77a3c504018cae5ada8c3b', // md5('private_key') )); } public function down() { $this->dropTable('{{api_user}}'); }
ApiUser
model. class ApiUser extends EApiActiveRecord { ... public function tableName() { return '{{api_user}}'; } ... }
generateSignature()
method for generating a signature is based on the prepareData($secretKey)
method of the prepareData($secretKey)
class of the RequestData
library. /** * Class SimpleClient * * Simple REST-client for Yiinitializr Advanced API. */ class SimpleClient { private $baseUrl; private $apiPublic; private $apiSecret; private $expiration; public function __construct($url, $publicKey, $secretKey, $expiration = '+1 hour') { $this->baseUrl = $url; $this->apiPublic = $publicKey; $this->apiSecret = $secretKey; $this->expiration = $expiration; } public function makeRequest($verb, $controller, $params = array()) { $ch = curl_init(); $signature = $this->generateSignature(); $url = $this->makeUrl($controller); if (!empty($params) && isset($params['id'])) { $url .= $params['id']; } curl_setopt_array($ch, array( CURLOPT_URL => $url, CURLOPT_CUSTOMREQUEST => $verb, CURLOPT_HTTPHEADER => array('APIKEY: ' . $this->apiPublic), CURLOPT_POSTFIELDS => json_encode(array( 'signature' => $signature, 'expiration' => $this->relativeTimeToAbsolute($this->expiration), )), )); $result = curl_exec($ch); curl_close($ch); return $result; } private function generateSignature() { $ttdInt = strtotime($this->expiration); $raw = json_encode(array('expiration' => gmdate('Ymd\TH:i:s\Z', $ttdInt))); $jsonPolicy64 = base64_encode($raw); $signature = base64_encode(hash_hmac( 'sha1', $jsonPolicy64, $this->apiSecret, true )); return $signature; } private function makeUrl($controller) { return 'http://' . rtrim($this->baseUrl, '/') . '/' . $controller . '/'; } private function relativeTimeToAbsolute ($relativeTime) { return date('M d Y, H:i:s', strtotime($relativeTime)); } }
$api = new SimpleClient('api.yiinitializr.dev', 'e4afe26b5b57083f74b2d01c7066379c', '156a17333e77a3c504018cae5ada8c3b'); $api->makeRequest('GET', 'test'); $api->makeRequest('GET', 'test', array('id' => 1)); $api->makeRequest('POST', 'test'); $api->makeRequest('PUT', 'test', array('id' => 1)); $api->makeRequest('DELETE', 'test', array('id' => 1));
class TestController extends EApiController { public function actionIndex() { // just drop API request :) $this->renderJson(array('response' => 'index')); } public function actionView($id) { $this->renderJson(array('response' => 'view#' . $id)); } public function actionCreate() { $this->renderJson(array('response' => 'created')); } public function actionUpdate($id) { $this->renderJson(array('response' => 'updated#' . $id)); } public function actionDelete($id) { $this->renderJson(array('response' => 'deleted#' . $id)); } }
./api/www/.htaccess
file: <Limit GET POST PUT DELETE> order deny,allow allow from all </Limit>
Source: https://habr.com/ru/post/208376/
All Articles