TinyAuthorize.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <?php
  2. App::uses('Inflector', 'Utility');
  3. App::uses('Hash', 'Utility');
  4. App::uses('BaseAuthorize', 'Controller/Component/Auth');
  5. if (!defined('CLASS_USER')) {
  6. define('CLASS_USER', 'User'); // override if you have it in a plugin: PluginName.User etc
  7. }
  8. if (!defined('AUTH_CACHE')) {
  9. define('AUTH_CACHE', '_cake_core_'); // use the most persistent cache by default
  10. }
  11. if (!defined('ACL_FILE')) {
  12. define('ACL_FILE', 'acl.ini'); // stored in /app/Config/
  13. }
  14. /**
  15. * Probably the most simple and fastest Acl out there.
  16. * Only one config file `acl.ini` necessary
  17. * Doesn't even need a Role Model / roles table
  18. * Uses most persistent _cake_core_ cache by default
  19. * @link http://www.dereuromark.de/2011/12/18/tinyauth-the-fastest-and-easiest-authorization-for-cake2
  20. *
  21. * Usage:
  22. * Include it in your beforeFilter() method of the AppController
  23. * $this->Auth->authorize = array('Tools.Tiny');
  24. *
  25. * Or with admin prefix protection only
  26. * $this->Auth->authorize = array('Tools.Tiny'=>array('allowUser'=>true));
  27. *
  28. * @version 1.2 - now allows other parent model relations besides Role/role_id
  29. * @author Mark Scherer
  30. * @license http://opensource.org/licenses/mit-license.php MIT
  31. */
  32. class TinyAuthorize extends BaseAuthorize {
  33. protected $_acl = null;
  34. protected $_defaultConfig = [
  35. 'superadminRole' => null, // quick way to allow access to every action
  36. 'allowUser' => false, // quick way to allow user access to non prefixed urls
  37. 'allowAdmin' => false, // quick way to allow admin access to admin prefixed urls
  38. 'adminPrefix' => 'admin_',
  39. 'adminRole' => null, // needed together with adminPrefix if allowAdmin is enabled
  40. 'cache' => AUTH_CACHE,
  41. 'cacheKey' => 'tiny_auth_acl',
  42. 'autoClearCache' => false, // usually done by Cache automatically in debug mode,
  43. 'aclModel' => 'Role', // only for multiple roles per user (HABTM)
  44. 'aclKey' => 'role_id', // only for single roles per user (BT)
  45. ];
  46. /**
  47. * TinyAuthorize::__construct()
  48. *
  49. * @param ComponentCollection $Collection
  50. * @param array $config
  51. */
  52. public function __construct(ComponentCollection $Collection, $config = []) {
  53. $config += $this->_defaultConfig;
  54. parent::__construct($Collection, $config);
  55. if (Cache::config($config['cache']) === false) {
  56. throw new CakeException(sprintf('TinyAuth could not find `%s` cache - expects at least a `default` cache', $config['cache']));
  57. }
  58. }
  59. /**
  60. * Authorize a user using the AclComponent.
  61. * allows single or multi role based authorization
  62. *
  63. * Examples:
  64. * - User HABTM Roles (Role array in User array)
  65. * - User belongsTo Roles (role_id in User array)
  66. *
  67. * @param array $user The user to authorize
  68. * @param CakeRequest $request The request needing authorization.
  69. * @return bool Success
  70. */
  71. public function authorize($user, CakeRequest $request) {
  72. if (isset($user[$this->settings['aclModel']])) {
  73. if (isset($user[$this->settings['aclModel']][0]['id'])) {
  74. $roles = Hash::extract($user[$this->settings['aclModel']], '{n}.id');
  75. } elseif (isset($user[$this->settings['aclModel']]['id'])) {
  76. $roles = [$user[$this->settings['aclModel']]['id']];
  77. } else {
  78. $roles = (array)$user[$this->settings['aclModel']];
  79. }
  80. } elseif (isset($user[$this->settings['aclKey']])) {
  81. $roles = [$user[$this->settings['aclKey']]];
  82. } else {
  83. $acl = $this->settings['aclModel'] . '/' . $this->settings['aclKey'];
  84. trigger_error(sprintf('Missing acl information (%s) in user session', $acl));
  85. $roles = [];
  86. }
  87. return $this->validate($roles, $request->params['plugin'], $request->params['controller'], $request->params['action']);
  88. }
  89. /**
  90. * Validate the url to the role(s)
  91. * allows single or multi role based authorization
  92. *
  93. * @param array $roles
  94. * @param string $plugin
  95. * @param string $controller
  96. * @param string $action
  97. * @return bool Success
  98. */
  99. public function validate($roles, $plugin, $controller, $action) {
  100. $action = Inflector::underscore($action);
  101. $controller = Inflector::underscore($controller);
  102. $plugin = Inflector::underscore($plugin);
  103. if (!empty($this->settings['allowUser'])) {
  104. // all user actions are accessable for logged in users
  105. if (mb_strpos($action, $this->settings['adminPrefix']) !== 0) {
  106. return true;
  107. }
  108. }
  109. if (!empty($this->settings['allowAdmin']) && !empty($this->settings['adminRole'])) {
  110. // all admin actions are accessable for logged in admins
  111. if (mb_strpos($action, $this->settings['adminPrefix']) === 0) {
  112. if (in_array((string)$this->settings['adminRole'], $roles)) {
  113. return true;
  114. }
  115. }
  116. }
  117. if ($this->_acl === null) {
  118. $this->_acl = $this->_getAcl();
  119. }
  120. // allow_all check
  121. if (!empty($this->settings['superadminRole'])) {
  122. foreach ($roles as $role) {
  123. if ($role == $this->settings['superadminRole']) {
  124. return true;
  125. }
  126. }
  127. }
  128. // controller wildcard
  129. if (isset($this->_acl[$controller]['*'])) {
  130. $matchArray = $this->_acl[$controller]['*'];
  131. if (in_array('-1', $matchArray)) {
  132. return true;
  133. }
  134. foreach ($roles as $role) {
  135. if (in_array((string)$role, $matchArray)) {
  136. return true;
  137. }
  138. }
  139. }
  140. // specific controller/action
  141. if (!empty($controller) && !empty($action)) {
  142. if (array_key_exists($controller, $this->_acl) && !empty($this->_acl[$controller][$action])) {
  143. $matchArray = $this->_acl[$controller][$action];
  144. // direct access? (even if he has no roles = GUEST)
  145. if (in_array('-1', $matchArray)) {
  146. return true;
  147. }
  148. // normal access (rolebased)
  149. foreach ($roles as $role) {
  150. if (in_array((string)$role, $matchArray)) {
  151. return true;
  152. }
  153. }
  154. }
  155. }
  156. return false;
  157. }
  158. /**
  159. * @return object The User model
  160. */
  161. public function getModel() {
  162. return ClassRegistry::init(CLASS_USER);
  163. }
  164. /**
  165. * Parse ini file and returns the allowed roles per action
  166. * - uses cache for maximum performance
  167. * improved speed by several actions before caching:
  168. * - resolves role slugs to their primary key / identifier
  169. * - resolves wildcards to their verbose translation
  170. *
  171. * @param string $path
  172. * @return array Roles
  173. */
  174. protected function _getAcl($path = null) {
  175. if ($path === null) {
  176. $path = APP . 'Config' . DS;
  177. }
  178. $res = [];
  179. if ($this->settings['autoClearCache'] && Configure::read('debug') > 0) {
  180. Cache::delete($this->settings['cacheKey'], $this->settings['cache']);
  181. }
  182. if (($roles = Cache::read($this->settings['cacheKey'], $this->settings['cache'])) !== false) {
  183. return $roles;
  184. }
  185. if (!file_exists($path . ACL_FILE)) {
  186. touch($path . ACL_FILE);
  187. }
  188. if (function_exists('parse_ini_file')) {
  189. $iniArray = parse_ini_file($path . ACL_FILE, true);
  190. } else {
  191. $iniArray = parse_ini_string(file_get_contents($path . ACL_FILE), true);
  192. }
  193. $availableRoles = Configure::read($this->settings['aclModel']);
  194. if (!is_array($availableRoles)) {
  195. $Model = $this->getModel();
  196. if (!isset($Model->{$this->settings['aclModel']})) {
  197. throw new CakeException('Missing relationship between User and Role.');
  198. }
  199. $availableRoles = $Model->{$this->settings['aclModel']}->find('list', ['fields' => ['alias', 'id']]);
  200. Configure::write($this->settings['aclModel'], $availableRoles);
  201. }
  202. if (!is_array($availableRoles) || !is_array($iniArray)) {
  203. trigger_error('Invalid Role Setup for TinyAuthorize (no roles found)');
  204. return [];
  205. }
  206. foreach ($iniArray as $key => $array) {
  207. list($plugin, $controllerName) = pluginSplit($key);
  208. $controllerName = Inflector::underscore($controllerName);
  209. foreach ($array as $actions => $roles) {
  210. $actions = explode(',', $actions);
  211. $roles = explode(',', $roles);
  212. foreach ($roles as $key => $role) {
  213. if (!($role = trim($role))) {
  214. continue;
  215. }
  216. if ($role === '*') {
  217. unset($roles[$key]);
  218. $roles = array_merge($roles, array_keys(Configure::read($this->settings['aclModel'])));
  219. }
  220. }
  221. foreach ($actions as $action) {
  222. if (!($action = trim($action))) {
  223. continue;
  224. }
  225. $actionName = Inflector::underscore($action);
  226. foreach ($roles as $role) {
  227. if (!($role = trim($role)) || $role === '*') {
  228. continue;
  229. }
  230. $newRole = Configure::read($this->settings['aclModel'] . '.' . strtolower($role));
  231. if (!empty($res[$controllerName][$actionName]) && in_array((string)$newRole, $res[$controllerName][$actionName])) {
  232. continue;
  233. }
  234. $res[$controllerName][$actionName][] = $newRole;
  235. }
  236. }
  237. }
  238. }
  239. Cache::write($this->settings['cacheKey'], $res, $this->settings['cache']);
  240. return $res;
  241. }
  242. }