├── README.md ├── RestapiModule.php ├── components ├── CommonRest.php ├── RestApiAccessControl.php ├── RestApiUserIdentity.php └── RestUrlRule.php ├── controllers └── ApiController.php ├── migrations └── m111118_024648_create_api_users.php └── models └── ApiUsers.php /README.md: -------------------------------------------------------------------------------- 1 | How to use this module. 2 | ======================= 3 | This module is only for Yii version 1.1.8 and above. 4 | 5 | You need to load this module into the Yii modules configuration and use the RestUrlRule as one of the rules in Url 6 | Manager. 7 | 8 | In modules configuration, you must specify the modelMap configuration. If you need the module to output valid Origin for 9 | cross platform capability, include validOrigin configuration as well. 10 | 11 | e.g 12 |

13 |     'modules' => array(
14 |       'restapi' => array(
15 |         'modelMap'=>'application.restmodel',
16 |         'validOrigin'=>array(
17 |           '/preg*expression/',
18 |         ),
19 |       ),
20 |     ),
21 | 
22 | 23 | - modelMap need to be in application alias format. It should point to a file that contain the mapping configuration for 24 | models. 25 | 26 | - validOrigin need to an array, it should contain a list of domain (in Regular Expression) that is allow to access this 27 | Rest API through AJAX. Check out Cross-Site HTTP request on (https://developer.mozilla.org/En/HTTP_Access_Control) 28 | 29 | Sample of the model map configuration file. 30 |

31 |     array(
32 |       'ModelRestName' => array(
33 |         'class' => 'application.path.alias.ModelRestName',
34 |         'excludeAll' => true,
35 |         'exclude' => array( 'field_to_exclude', 'field_to_exclude', ),
36 |         'include' => array( 'addition_field_to_include', ),
37 |         'attributeAccessControl' => true,
38 |         'defaultCriteria' => array for CDbCriteria,
39 |       ),
40 |     );
41 | 
42 | 43 | Add URL routing rule class in URL Manager. 44 | 45 |

46 |     'urlManager' => array(
47 |       'class'=>'CUrlManager',
48 |       'urlFormat' => 'path',
49 |       'rules' => array(
50 |         array(
51 |           'class' => 'restapi.components.RestUrlRule'
52 |         ),
53 |       ),
54 |     ),
55 | 
56 | 57 | Token Access to REST API 58 | ------------------------ 59 | If you are going to use Access Control in this module, you will need run migration tool from Yii. 60 | 61 | e.g 62 |

63 |     php yiic migrate --migrationPath=application.modules.restapi.migrations
64 | 
65 | 66 | This will create the needed table in the database to keep track of API access token, key and secret. 67 | 68 | In controller outside of restapi module that need Rest Api Access Control just add 69 | restapi.components.RestApiAccessControl as one of its filter. 70 | 71 | e.g 72 |

73 |     public function filters() {
74 |       return array(
75 |         array(
76 |           'restapi.components.RestApiAccessControl'
77 |         ),
78 |         'accessControl',
79 |       );
80 |     }
81 | 
82 | 83 | API user will need to get temporary token from Api controller in the restapi module using their api key and secret key. -------------------------------------------------------------------------------- /RestapiModule.php: -------------------------------------------------------------------------------- 1 | 8 | * array( 9 | * 'ModelRestName' => array( 10 | * 'class' => 'application.path.alias.ModelRestName', 11 | * 'excludeAll' => true, 12 | * 'exclude' => array( 'field_to_exclude', 'field_to_exclude', ), 13 | * 'include' => array( 'addition_field_to_include', ), 14 | * 'attributeAccessControl' => true, 15 | * 'defaultCriteria' => array for CDbCriteria, 16 | * ), 17 | * ); 18 | * 19 | */ 20 | public $modelMap; 21 | public $accessControl = false; 22 | public $validOrigin = array(); 23 | 24 | public function checkModel($model) { 25 | if (!isset($this->modelMap[$model])) return false; 26 | return true; 27 | } 28 | 29 | public function includeModel($model) { 30 | $classname = Yii::import($this->modelMap[$model]['class']); 31 | $this->modelMap[$classname] = $this->modelMap[$model]; 32 | return $classname; 33 | } 34 | 35 | public function getDefaultCriteria($model) { 36 | if (isset($this->modelMap[$model]['defaultCriteria'])) { 37 | return $this->modelMap[$model]['defaultCriteria']; 38 | } else return array(); 39 | } 40 | 41 | public function getExcludedAttribute($model) { 42 | if (isset($this->modelMap[$model]['excludeAll']) && $this->modelMap[$model]['excludeAll'] === true) { 43 | $instance = new $model(); 44 | $attributes = array_keys($instance->getAttributes()); 45 | if (isset($this->modelMap[$model]['include'])) { 46 | return array_intersect($attributes, array_diff($attributes, $this->modelMap[$model]['include'])); 47 | } else { 48 | return $attributes; 49 | } 50 | } else return isset($this->modelMap[$model]['exclude'])?$this->modelMap[$model]['exclude']:array(); 51 | } 52 | 53 | public function getIncludedAttribute($model) { 54 | return isset($this->modelMap[$model]['include'])?$this->modelMap[$model]['include']:array(); 55 | } 56 | 57 | public function getCheckAttributeAccessControl($model) { 58 | return (isset($this->modelMap[$model]) && isset($this->modelMap[$model]['attributeAccessControl']) && $this->modelMap[$model]['attributeAccessControl']); 59 | } 60 | 61 | protected function init() { 62 | Yii::import($this->getId().".components.*"); 63 | Yii::import($this->getId().".models.*"); 64 | 65 | if (empty($this->modelMap)) { 66 | Yii::log("Model Map is not set. No Model will rest.", CLogger::LEVEL_ERROR, "restapi"); 67 | } 68 | if (is_string($this->modelMap)) { 69 | $this->modelMap = require(Yii::getPathOfAlias($this->modelMap).".php"); 70 | } 71 | parent::init(); 72 | } 73 | 74 | public function validOrigin($origin) { 75 | foreach ($this->validOrigin as $valid) { 76 | if (preg_match($valid, $origin) > 0) return true; 77 | } 78 | return false; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /components/CommonRest.php: -------------------------------------------------------------------------------- 1 | getModule("restapi")->getCheckAttributeAccessControl($model)) { 27 | $row = $list[0]; 28 | /** @var CWebUser $user */ 29 | $user = Yii::app()->user; 30 | foreach ($list as $rowId=>$row) { 31 | foreach ($row as $field=>$value) { 32 | if (!$user->checkAccess("view/".$_GET['model']."/".$field, array('model'=>$row), true)) { 33 | unset($list[$rowId][$field]); 34 | } 35 | } 36 | } 37 | } 38 | return $list; 39 | } 40 | 41 | /** 42 | * Get attribute of the record. Return in array 43 | * 44 | * @param CActiveRecord $record 45 | * @param string $model Model Name 46 | * 47 | * @return array of attributes 48 | */ 49 | public static function getRecordAttribute($record, $model, $checkPermission = true) { 50 | /** @var $record CActiveRecord */ 51 | $attributes = $record->getAttributes(); 52 | if ($checkPermission) { 53 | $excludeArray = array_flip(Yii::app()->getModule("restapi")->getExcludedAttribute($model)); 54 | $attributes = array_intersect_key($attributes, array_diff_key($attributes, $excludeArray)); 55 | } 56 | foreach (Yii::app()->getModule("restapi")->getIncludedAttribute($model) as $includedAttribute) { 57 | try { 58 | $attributes[$includedAttribute] = $record->$includedAttribute; 59 | } catch (CException $e) { 60 | Yii::log($e->getMessage(), CLogger::LEVEL_INFO, "restapi"); 61 | } 62 | } 63 | if (method_exists($record, 'attributeRestMapping')) { 64 | foreach ($record->attributeRestMapping() as $field=>$map) { 65 | if (isset($record->$field)) { 66 | $attributes[$map] = $record->$field; 67 | unset($attributes[$field]); 68 | } 69 | } 70 | } 71 | if (method_exists($record, "relations")) { 72 | foreach ($record->relations() as $name=>$relation) { 73 | try { 74 | if ($relation[0] == CActiveRecord::HAS_ONE || $relation[0] == CActiveRecord::BELONGS_TO) { 75 | if (!@class_exists($relation[1], true)) continue; 76 | /** @var $related CActiveRecord */ 77 | $related = $record->$name; 78 | if ($related == null) continue; 79 | if ($related->hasAttribute("name")) $attributes[$name] = $related->name; 80 | else if ($related->hasAttribute("title")) $attributes[$name] = $related->title; 81 | else if (method_exists($related, "toString")) $attributes[$name] = $related->toString(); 82 | else $attributes[$name] = $related->getPrimaryKey(); 83 | } 84 | } catch (Exception $e) { 85 | Yii::log($e->getMessage(), CLogger::LEVEL_INFO, "restapi"); 86 | } 87 | } 88 | } 89 | return $attributes; 90 | } 91 | } -------------------------------------------------------------------------------- /components/RestApiAccessControl.php: -------------------------------------------------------------------------------- 1 | setToken($_GET['token']); 8 | if ($identity->authenticate()) { 9 | Yii::app()->user->login($identity); 10 | } 11 | } 12 | if (isset($_SERVER['HTTP_ORIGIN'])) { 13 | $restapi = Yii::app()->getModule("restapi"); 14 | if ($restapi->validOrigin($_SERVER['HTTP_ORIGIN'])) { 15 | header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']); 16 | } 17 | } 18 | if ($_SERVER['REQUEST_METHOD'] == "OPTIONS") { 19 | return false; 20 | } 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /components/RestApiUserIdentity.php: -------------------------------------------------------------------------------- 1 | key = $key; 13 | $this->secret = $secret; 14 | } 15 | 16 | public function setToken($token) { 17 | $this->token = $token; 18 | } 19 | 20 | /** 21 | * Authenticates the user. 22 | * The information needed to authenticate the user 23 | * are usually provided in the constructor. 24 | * @return boolean whether authentication succeeds. 25 | */ 26 | public function authenticate() { 27 | if ($this->token) { 28 | $this->user = ApiUsers::model()->findByAttributes(array("token"=>$this->token)); 29 | if ($this->user != null && !$this->user->tokenExpired()) { 30 | $this->errorCode = self::ERROR_NONE; 31 | return true; 32 | } 33 | $this->errorMessage = "Invalid Token"; 34 | } else if ($this->key && $this->secret) { 35 | /** @var $user ApiUsers */ 36 | $this->user = ApiUsers::model()->findByAttributes(array("key"=>$this->key, "secret"=>$this->secret, "active"=>1)); 37 | if ($this->user != null) { 38 | $this->errorCode = self::ERROR_NONE; 39 | return true; 40 | } 41 | $this->errorMessage = "Invalid Key or/and Secret"; 42 | } 43 | $this->errorCode = self::ERROR_UNKNOWN_IDENTITY; 44 | return false; 45 | } 46 | 47 | /** 48 | * @return ApiUsers 49 | */ 50 | public function getApiUser() { 51 | return $this->user; 52 | } 53 | 54 | public function getId() { 55 | if ($this->user) return $this->user->getPrimaryKey(); 56 | else return ''; 57 | } 58 | 59 | public function getName() { 60 | if ($this->user) return $this->user->username; 61 | else return ''; 62 | } 63 | } -------------------------------------------------------------------------------- /components/RestUrlRule.php: -------------------------------------------------------------------------------- 1 | value) associated with the route 10 | * @param string $ampersand the token separating name-value pairs in the URL. 11 | * @return mixed the constructed URL. False if this rule does not apply. 12 | */ 13 | public function createUrl($manager, $route, $params, $ampersand) { 14 | return false; 15 | } 16 | 17 | /** 18 | * Parses a URL based on this rule. 19 | * @param CUrlManager $manager the URL manager 20 | * @param CHttpRequest $request the request object 21 | * @param string $pathInfo path info part of the URL (URL suffix is already removed based on {@link CUrlManager::urlSuffix}) 22 | * @param string $rawPathInfo path info that contains the potential URL suffix 23 | * @return mixed the route that consists of the controller ID and action ID. False if this rule does not apply. 24 | */ 25 | public function parseUrl($manager, $request, $pathInfo, $rawPathInfo) { 26 | $validRule = array( 27 | array('/^api\/token\/([^\/]+)\/([^\/]+)$/', 'GET', 'restapi/api/getToken', 28 | 'parameters'=>array('api'=>1, 'secret'=>2) 29 | ), 30 | array('/^api\/([a-zA-Z][a-z0-9A-Z\._\-]+)\/(.*)$/', 'GET', 'restapi/api/view', 31 | 'parameters'=>array("model"=>1, "id"=>2), 32 | ), 33 | array('/^api\/([a-zA-Z][a-z0-9A-Z\._\-]+)$/', 'GET', 'restapi/api/list', 34 | 'parameters'=>array("model"=>1) 35 | ), 36 | array('/^api\/([a-zA-Z][a-z0-9A-Z\._\-]+)\/(.*)$/', 'PUT', 'restapi/api/update', 37 | 'parameters'=>array("model"=>1, "id"=>2) 38 | ), 39 | array('/^api\/([a-zA-Z][a-z0-9A-Z\._\-]+)\/(.*)$/', 'DELETE', 'restapi/api/delete', 40 | 'parameters'=>array("model"=>1, "id"=>2) 41 | ), 42 | array('/^api\/([a-zA-Z][a-z0-9A-Z\._\-]+)$/', 'POST', 'restapi/api/create', 43 | 'parameters'=>array("model"=>1) 44 | ), 45 | ); 46 | foreach ($validRule as $rule) { 47 | if ($request->getRequestType() == $rule[1]) { 48 | $matches = array(); 49 | if (preg_match($rule[0], $pathInfo, $matches) >= 1) { 50 | if (isset($rule['parameters'])) { 51 | foreach ($rule['parameters'] as $param=>$index) { 52 | $_GET[$param] = $matches[$index]; 53 | } 54 | return $rule[2]; 55 | } 56 | } 57 | } 58 | } 59 | return false; // this rule does not apply 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /controllers/ApiController.php: -------------------------------------------------------------------------------- 1 | layout = false; 23 | Yii::app()->getErrorHandler()->errorAction = 'restapi/api/error'; 24 | if (isset($_GET['model'])) { 25 | if (!$this->getModule()->checkModel($_GET['model'])) { 26 | throw new CHttpException(501, $_GET['model'].' is not implemented'); 27 | } else { 28 | $_GET['model'] = $this->getModule()->includeModel($_GET['model']); 29 | } 30 | } else { 31 | if (in_array($action->getId(), array('list', 'view', 'update', 'delete', 'create'))) { 32 | throw new CHttpException(501, 'Model is not specify'); 33 | } 34 | } 35 | return parent::beforeAction($action); 36 | } 37 | 38 | public function actionError() { 39 | $error = Yii::app()->getErrorHandler()->getError(); 40 | Yii::log($error['trace'], CLogger::LEVEL_ERROR, "restapi"); 41 | $this->renderText($error['message']); 42 | Yii::app()->end(1); 43 | } 44 | 45 | /** 46 | * Get Token for API Access. 47 | */ 48 | public function actionGetToken($api, $secret) { 49 | $identity = new RestApiUserIdentity(); 50 | $identity->setKeySecret($api, $secret); 51 | if ($identity->authenticate()) { 52 | $apiuser = $identity->getApiUser(); 53 | if ($apiuser->tokenExpired()) { 54 | $apiuser->generateNewToken(); 55 | if ($apiuser->save()) { 56 | $result = array('success'=>true, 'token'=>$apiuser->token, 'expiry'=>strtotime($apiuser->token_expire)); 57 | } else { 58 | $result = array('success'=>false, 'message'=>"Can not save the generated token"); 59 | } 60 | } else { 61 | $result = array('success'=>true, 'token'=>$apiuser->token, 'expiry'=>strtotime($apiuser->token_expire)); 62 | } 63 | } else { 64 | $result = array('success'=>false, 'message'=>"Access Denied"); 65 | } 66 | echo json_encode($result); 67 | Yii::app()->end(); 68 | } 69 | 70 | /** 71 | * This method is invoked right after an action is executed. 72 | * You may override this method to do some postprocessing for the action. 73 | * @param CAction $action the action just executed. 74 | */ 75 | protected function afterAction($action) { 76 | if (empty($this->result) && !is_array($this->result)) throw new CHttpException(404, "Not found with ID:".$_GET['id']); 77 | $list = CommonRest::buildModelJsonReply($this->result, $_GET['model']); 78 | header('content-type: application/json'); 79 | $this->renderText(CJSON::encode(array("root"=>$list, "success"=>true))); 80 | } 81 | 82 | public function actionList($model) { 83 | if ($this->getModule()->accessControl) { 84 | /** @var CWebUser $user */ 85 | $user = Yii::app()->user; 86 | if (!$user->checkAccess("list/$model", array(), true)) { 87 | throw new CHttpException(403, "Read access on $model denied."); 88 | } 89 | } 90 | $modelInstance = new $model(); 91 | $limit = isset($_GET['limit'])?$_GET['limit']:50; 92 | $start = isset($_GET['start'])?$_GET['start']:0; 93 | if (isset($_GET['page'])) { 94 | $start += (($_GET['page']-1) * $limit); 95 | } 96 | if ($modelInstance instanceof CActiveRecord) { 97 | $c = new CDbCriteria($this->getModule()->getDefaultCriteria($model)); 98 | $c->offset = $start; 99 | $c->limit = $limit; 100 | if (isset($_GET['filter'])) { 101 | $filter = CJSON::decode($_GET['filter']); 102 | foreach ($filter as $field=>$condition) { 103 | if (is_array($condition)) { 104 | if (is_int($field)) { 105 | $c->addCondition($condition['property'] . " = " . $condition["value"]); 106 | } else $c->addCondition("$field $condition[0] $condition[1]"); 107 | } else $c->addCondition("$field = $condition"); 108 | } 109 | } 110 | if (isset($_GET['sort'])) { 111 | $sort = CJSON::decode($_GET['sort']); 112 | if (is_array($sort)) { 113 | foreach ($sort as $s) { 114 | $c->order .= $s['property'] . ' ' . $s['direction'] . ','; 115 | } 116 | $c->order = substr($c->order, 0, -1); 117 | } else { 118 | $c->order = $_GET['sort']; 119 | } 120 | } 121 | if (isset($_GET['group'])) { 122 | $group = CJSON::decode($_GET['group']); 123 | if (is_array($group)) { 124 | foreach ($group as $s) { 125 | $c->group .= $s['property'] . ','; 126 | } 127 | $c->group = substr($c->group, 0, -1); 128 | } else { 129 | $c->group = $_GET['group']; 130 | } 131 | } 132 | $this->result = CActiveRecord::model($model)->findAll($c); 133 | } else if (@class_exists('EActiveResource') && $modelInstance instanceof EActiveResource) { 134 | /** @var $list EActiveResource[] */ 135 | $this->result = EActiveResource::model($model)->findAll(); 136 | } else if (@class_exists('EMongoDocument') && $modelInstance instanceof EMongoDocument) { 137 | $mc = new EMongoCriteria(); 138 | $mc->setLimit($limit); 139 | $mc->setOffset($start); 140 | if (isset($_GET['filter'])) { 141 | $filter = CJSON::decode($_GET['filter']); 142 | foreach ($filter as $field=>$condition) { 143 | if (is_array($condition)) { 144 | if (is_int($field)) { 145 | $field = $condition['property']; 146 | $mc->$field = $condition["value"]; 147 | } else $mc->$field($condition[0], $condition[1]); 148 | } else $mc->$field = $condition; 149 | } 150 | } 151 | if (isset($_GET['sort'])) $mc->setSort($_GET['sort']); 152 | $this->result = EMongoDocument::model($model)->findAll($mc); 153 | } 154 | if ($this->getModule()->accessControl) { 155 | /** @var CWebUser $user */ 156 | $user = Yii::app()->user; 157 | foreach ($this->result as $key=>$item) { 158 | if (!$user->checkAccess("read/$model", array('model'=>$item), true)) { 159 | Yii::log("DENIED: Try to list model $model ID: ".$item->getPrimaryKey(), CLogger::LEVEL_INFO, "restapi"); 160 | unset($this->result[$key]); 161 | } 162 | } 163 | $this->result = array_values($this->result); 164 | } 165 | } 166 | 167 | public function actionView($model, $id) { 168 | $modelInstance = new $model(); 169 | if ($modelInstance instanceof CActiveRecord) { 170 | $this->result = CActiveRecord::model($model)->findByPk($id); 171 | } else if (@class_exists('EMongoDocument') && $modelInstance instanceof EMongoDocument) { 172 | $this->result = EMongoDocument::model($model)->findByPk(new MongoId($id)); 173 | } else if ($modelInstance instanceof EActiveResource) { 174 | $this->result = EActiveResource::model($model)->findById($id); 175 | } 176 | if ($this->getModule()->accessControl) { 177 | /** @var CWebUser $user */ 178 | $user = Yii::app()->user; 179 | if (!$user->checkAccess("view/$model", array('model'=>$this->result), true)) { 180 | throw new CHttpException(403, "Read access on $model denied."); 181 | } 182 | } 183 | } 184 | 185 | public function actionUpdate($model, $id, $type = 'update') { 186 | $this->actionView($model, $id); 187 | if (is_null($this->result)) { 188 | throw new CHttpException(400, "Did not find any model with ID: " . $id); 189 | } 190 | $modelInstance = $this->result; 191 | $modelInstance->setScenario("update"); 192 | if ($this->getModule()->accessControl) { 193 | $modelInstance->setScenario($type); 194 | /** @var CWebUser $user */ 195 | $user = Yii::app()->user; 196 | if ($type !== "update") $type = "update.".$type; 197 | if (!$user->checkAccess("$type/$model", array('model'=>$modelInstance, 'scenario'=>$type), true)) { 198 | throw new CHttpException(403, "Write access on $model denied."); 199 | } 200 | } 201 | $vars = CJSON::decode(file_get_contents('php://input')); 202 | if (!is_array($vars)) { 203 | Yii::log("Input need to be Json: ".var_export($vars, true), CLogger::LEVEL_ERROR, "restapi"); 204 | throw new CHttpException(500, "Input need to be JSON"); 205 | } 206 | if ($this->getModule()->getCheckAttributeAccessControl($_GET['model'])) { 207 | /** @var CWebUser $user */ 208 | $user = Yii::app()->user; 209 | foreach ($vars as $field=>$value) { 210 | if (!$user->checkAccess("update/".$_GET['model']."/".$field, array('model'=>$modelInstance), true)) { 211 | unset($vars[$field]); 212 | Yii::log("DENIED: Try to set attribute: $field with $value", CLogger::LEVEL_INFO, "restapi"); 213 | } 214 | } 215 | } 216 | $modelInstance->setAttributes($vars); 217 | if ($modelInstance->save()) { 218 | $modelInstance->refresh(); 219 | } else { 220 | Yii::log(implode("\n", $modelInstance->getErrors()), CLogger::LEVEL_ERROR, "restapi"); 221 | throw new CHttpException(500, "Can not save model ID: " . $id); 222 | } 223 | } 224 | 225 | public function actionDelete($model, $id) { 226 | $this->actionView($model, $id); 227 | if (is_null($this->result)) { 228 | throw new CHttpException(400, "Did not find any model with ID: " . $id); 229 | } 230 | $modelInstance = $this->result; 231 | if ($this->getModule()->accessControl) { 232 | /** @var CWebUser $user */ 233 | $user = Yii::app()->user; 234 | if (!$user->checkAccess("delete/$model", array('model'=>$modelInstance), true)) { 235 | throw new CHttpException(403, "Write access on $model denied."); 236 | } 237 | } 238 | if (!$modelInstance->delete()) { 239 | Yii::log(implode("\n", $modelInstance->getErrors()), CLogger::LEVEL_ERROR, "restapi"); 240 | throw new CHttpException(500, "Can not delete model ID: " . $id); 241 | } else { 242 | Yii::app()->end(); 243 | } 244 | } 245 | 246 | public function actionCreate($model) { 247 | $modelInstance = new $model(); 248 | $vars = CJSON::decode(file_get_contents('php://input')); 249 | if (!is_array($vars)) { 250 | Yii::log("Input need to be Json: ".var_export($vars, true), CLogger::LEVEL_ERROR, "restapi"); 251 | throw new CHttpException(500, "Input need to be JSON"); 252 | } 253 | if ($this->getModule()->accessControl) { 254 | /** @var CWebUser $user */ 255 | $user = Yii::app()->user; 256 | if (!$user->checkAccess("create/$model", array('model'=>$modelInstance), true)) { 257 | throw new CHttpException(403, "Write access on $model denied."); 258 | } 259 | } 260 | if ($this->getModule()->getCheckAttributeAccessControl($_GET['model'])) { 261 | /** @var CWebUser $user */ 262 | $user = Yii::app()->user; 263 | foreach ($vars as $field=>$value) { 264 | if (!$user->checkAccess("update/".$_GET['model']."/".$field, array('model'=>$modelInstance), true)) { 265 | unset($vars[$field]); 266 | Yii::log("DENIED: Try to set attribute: $field with $value", CLogger::LEVEL_INFO, "restapi"); 267 | } 268 | } 269 | } 270 | $modelInstance->setAttributes($vars); 271 | if ($modelInstance->save()) { 272 | $modelInstance->refresh(); 273 | $this->result = $modelInstance; 274 | } else { 275 | Yii::log(implode("\n", $modelInstance->getErrors()), CLogger::LEVEL_ERROR, "restapi"); 276 | throw new CHttpException(500, "Can not save model: " . $model); 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /migrations/m111118_024648_create_api_users.php: -------------------------------------------------------------------------------- 1 | createTable("{{api_users}}", array( 8 | 'id'=>'pk', 9 | 'username'=>'string NOT NULL', 10 | 'password'=>'string NOT NULL', 11 | 'key'=>'string NOT NULL', 12 | 'secret'=>'string NOT NULL', 13 | 'token'=>'string', 14 | 'token_expire'=>'datetime', 15 | 'created'=>'datetime NOT NULL', 16 | 'active'=>'boolean DEFAULT "1"' 17 | )); 18 | $this->createIndex("api_users_token_unq", "{{api_users}}", "token", true); 19 | if (strpos($this->getDbConnection()->getDriverName(), 'mysql') !== false) { 20 | $this->getDbConnection()->createCommand("ALTER TABLE {{api_users}} ENGINE=InnoDB")->execute(); 21 | } 22 | $this->insert("{{api_users}}", array( 23 | "username"=>"Api Tester", 24 | "password"=>"pHuFum8tewuPregesWaWuSw9", 25 | "key"=>"hewr65ufruwaXUw5EspezA8e", 26 | "secret"=>"pRaxUBrAxEfrAwubrEsE6ARa", 27 | "token"=>"pravesTEBRUjeYecHuc4busTmUKamePapAtaWrewAzu2uPacSWUGacafadRAtRebe2RuTRav", 28 | "token_expire"=>date('Y-m-d H:i:s', time()+(60*60*24*365)), 29 | "created"=>date('Y-m-d H:i:s'), 30 | )); 31 | } 32 | 33 | public function down() 34 | { 35 | $this->dropTable("{{api_users}}"); 36 | } 37 | } -------------------------------------------------------------------------------- /models/ApiUsers.php: -------------------------------------------------------------------------------- 1 | true), 46 | array('username, password, key, secret, token', 'length', 'max'=>255), 47 | array('token_expire', 'safe'), 48 | // The following rule is used by search(). 49 | // Please remove those attributes that should not be searched. 50 | array('id, username, password, key, secret, token, token_expire, created, active', 'safe', 'on'=>'search'), 51 | ); 52 | } 53 | 54 | /** 55 | * @return array relational rules. 56 | */ 57 | public function relations() 58 | { 59 | // NOTE: you may need to adjust the relation name and the related 60 | // class name for the relations automatically generated below. 61 | return array( 62 | ); 63 | } 64 | 65 | /** 66 | * @return array customized attribute labels (name=>label) 67 | */ 68 | public function attributeLabels() 69 | { 70 | return array( 71 | 'id' => 'Id', 72 | 'username' => 'Username', 73 | 'password' => 'Password', 74 | 'key' => 'Key', 75 | 'secret' => 'Secret', 76 | 'token' => 'Token', 77 | 'token_expire' => 'Token Expire', 78 | 'created' => 'Created', 79 | 'active' => 'Active', 80 | ); 81 | } 82 | 83 | /** 84 | * Retrieves a list of models based on the current search/filter conditions. 85 | * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions. 86 | */ 87 | public function search() 88 | { 89 | // Warning: Please modify the following code to remove attributes that 90 | // should not be searched. 91 | 92 | $criteria=new CDbCriteria; 93 | 94 | $criteria->compare('id',$this->id); 95 | 96 | $criteria->compare('username',$this->username,true); 97 | 98 | $criteria->compare('password',$this->password,true); 99 | 100 | $criteria->compare('key',$this->key,true); 101 | 102 | $criteria->compare('secret',$this->secret,true); 103 | 104 | $criteria->compare('token',$this->token,true); 105 | 106 | $criteria->compare('token_expire',$this->token_expire,true); 107 | 108 | $criteria->compare('created',$this->created,true); 109 | 110 | $criteria->compare('active',$this->active); 111 | 112 | return new CActiveDataProvider('ApiUsers', array( 113 | 'criteria'=>$criteria, 114 | )); 115 | } 116 | 117 | public function tokenExpired() { 118 | if ($this->token == "") return true; 119 | if (strtotime($this->token_expire) < time()) return true; 120 | return false; 121 | } 122 | 123 | public function generateNewToken() { 124 | $this->token = $this->genToken(); 125 | $this->token_expire = date('Y-m-d H:i:s', strtotime("+24 hours")); 126 | } 127 | 128 | protected function genToken($len = 32) { 129 | mt_srand((double)microtime()*1000000 + time()); 130 | $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnm-_.!*)('; 131 | $numChars = strlen($chars) - 1; $token = ''; 132 | for ( $i=0; $i<$len; $i++ ) { 133 | $token .= $chars[ mt_rand(0, $numChars) ]; 134 | } 135 | return $token; 136 | } 137 | } --------------------------------------------------------------------------------