
./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