Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a55cbad
Fixes loading available roles from database
bravo-kernel Feb 27, 2015
90e9799
Rephrase
bravo-kernel Feb 27, 2015
29875f1
Fixes fetching associated (multi) roles
bravo-kernel Feb 27, 2015
72848f5
Moves comment
bravo-kernel Feb 27, 2015
f8f2971
Removes obsolete function declaration
bravo-kernel Feb 27, 2015
c061b48
Fixes condition + removes obsolete variable
bravo-kernel Feb 27, 2015
5fffd51
Adds multiRole configuration option
bravo-kernel Feb 27, 2015
cf47653
Improves comments
bravo-kernel Feb 28, 2015
1951bdc
Renames aclKey and aclTable to roleColumn and rolesTable
bravo-kernel Feb 28, 2015
b16cbc5
Renames roles variable to userRoles
bravo-kernel Feb 28, 2015
fc5e8e4
Refactors getting user roles
bravo-kernel Feb 28, 2015
bf02732
Refactors getting available roles
bravo-kernel Feb 28, 2015
2649db6
Removes comments
bravo-kernel Feb 28, 2015
d8fd245
Fixes logic for fetching available roles
bravo-kernel Feb 28, 2015
2823477
Refactors condition
bravo-kernel Feb 28, 2015
e372ea3
Renames super admin role to allowAll for consistency
bravo-kernel Mar 1, 2015
463246e
Reverts superAdminRole
bravo-kernel Mar 1, 2015
64981f4
Adds useDatabaseRoles boolean + refactors available roles and ini par…
bravo-kernel Mar 1, 2015
929b680
Removes unused var
bravo-kernel Mar 1, 2015
a4df046
Removes user dependency for available roles + hardening
bravo-kernel Mar 2, 2015
3b79ade
Removing log
bravo-kernel Mar 3, 2015
9965525
Adds tests for available roles
bravo-kernel Mar 3, 2015
9762636
Adds tests for ini parsing
bravo-kernel Mar 3, 2015
8cd8130
Renames RolesFixture and aligns role definitions with testcase
bravo-kernel Mar 3, 2015
60f2520
Adds configure/database multirole + tests
bravo-kernel Mar 3, 2015
51c71aa
Removes obsolete magic -1
bravo-kernel Mar 3, 2015
d1f2928
Removes obsolete var
bravo-kernel Mar 3, 2015
c831b50
Removes obsolete method
bravo-kernel Mar 3, 2015
ef54f8c
Fixes incorrect cache condition
bravo-kernel Mar 3, 2015
e1fc162
Updates docs
bravo-kernel Mar 3, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,12 @@ Authorize supports the following configuration options.

Option | Type | Description
:----- | :--- | :----------
allowUser|boolean|True will give authenticated users access to all resources except those using the `adminPrefix`
allowAdmin|boolean|True will give users with a role id matching `adminRole` access to all resources using the `adminPrefix`
roleColumn|string|Name of column in user table holding role id (only used for single-role per user/BT)
rolesTable|string|Name of Configure key holding all available roles OR class name of roles database table
multiRole|boolean|True will enable multi-role/HABTM authorization (requires a valid join table)
adminRole|int|Id of the role you will use as admins. Users with this role are granted access to all actions using `adminPrefix` but only when `allowAdmin` is enabled
superAdminRole|int|Id of the super admin role. Users with this role will have access to ALL resources.
adminPrefix|string|Name of the prefix used for admin pages. Defaults to admin.
allowAdmin|boolean|True will give users with a role id matching `adminRole` access to all resources using the `adminPrefix`
allowUser|boolean|True will give authenticated users access to all resources except those using the `adminPrefix`
autoClearCache|Boolean|True will generate a new acl cache file every time.
aclKey|string|Name of the column holding your user role id (only for single role per user/BT)
aclTable|string|Name of the table holding your user roles (only for multiple roles per user/HABTM)
197 changes: 125 additions & 72 deletions src/Auth/TinyAuthorize.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
use Cake\Cache\Cache;
use Cake\Controller\ComponentRegistry;
use Cake\Core\Configure;
use Cake\Core\Exception\Exception;
use Cake\Database\Schema\Collection;
use Cake\Datasource\ConnectionManager;
use Cake\Network\Request;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
Expand Down Expand Up @@ -42,30 +45,34 @@ class TinyAuthorize extends BaseAuthorize {
protected $_acl = null;

protected $_defaultConfig = [
'adminRole' => null, // id of the admin role used by allowAdmin
'roleColumn' => 'role_id', // name of column in user table holding role id (used for single role/BT only)
'rolesTable' => 'Roles', // name of Configure key holding available roles OR class name of roles table
'multiRole' => false, // true to enables multirole/HABTM authorization (requires a valid join table)

'adminRole' => null, // id of the admin role (used to give access to all /admin prefixed resources when allowAdmin is enabled)
'superAdminRole' => null, // id of super admin role granted access to ALL resources
'adminPrefix' => 'admin', // name of the admin prefix route (only used when allowAdmin is enabled)
'allowAdmin' => false, // boolean, true to allow admin role access to all 'adminPrefix' prefixed urls
'allowUser' => false, // enable to allow ALL roles access to all actions except prefixed with 'adminPrefix'
'allowAdmin' => false, // enable to allow admin role access to all 'adminPrefix' prefixed urls
'adminPrefix' => 'admin', // admin prefix used by allowAdmin

'cache' => AUTH_CACHE,
'cacheKey' => 'tiny_auth_acl',
'autoClearCache' => false, // usually done by Cache automatically in debug mode,
'aclTable' => 'Roles', // only for multiple roles per user (HABTM)
'aclKey' => 'role_id', // only for single roles per user (BT)
];

/**
* TinyAuthorize::__construct()
*
* @param ComponentRegistry $registry
* @param array $config
* @throws Cake\Core\Exception\Exception
*/
public function __construct(ComponentRegistry $registry, array $config = []) {
$config += $this->_defaultConfig;
parent::__construct($registry, $config);

if (Cache::config($config['cache']) === false) {
throw new \Exception(sprintf('TinyAuth could not find `%s` cache - expects at least a `default` cache', $config['cache']));
if (!in_array($config['cache'], Cache::configured())) {
throw new Exception(sprintf('Invalid TinyAuthorization cache `%s`', $config['cache']));
}
}

Expand All @@ -82,40 +89,20 @@ public function __construct(ComponentRegistry $registry, array $config = []) {
* @return bool Success
*/
public function authorize($user, Request $request) {
if (isset($user[$this->_config['aclTable']])) {
if (isset($user[$this->_config['aclTable']][0]['id'])) {
$roles = Hash::extract($user[$this->_config['aclTable']], '{n}.id');
} elseif (isset($user[$this->_config['aclTable']]['id'])) {
$roles = [$user[$this->_config['aclTable']]['id']];
} else {
$roles = (array)$user[$this->_config['aclTable']];
}
} elseif (isset($user[$this->_config['aclKey']])) {
$roles = [$user[$this->_config['aclKey']]];
} else {
$acl = $this->_config['aclTable'] . '/' . $this->_config['aclKey'];
trigger_error(sprintf('Missing acl information (%s) in user session', $acl));
$roles = [];
}
return $this->validate($roles, $request);
return $this->validate($this->_getUserRoles($user), $request);
}

/**
* Validate the url to the role(s)
* allows single or multi role based authorization
*
* @param array $roles
* @param array $userRoles
* @param string $plugin
* @param string $controller
* @param string $action
* @return bool Success
*/
//public function validate($roles, $plugin, $controller, $action) {
public function validate($roles, Request $request) {
// construct the iniKey and iniMap for easy lookups
$iniKey = $this->_constructIniKey($request);
$availableRoles = Configure::read($this->_config['aclTable']);

public function validate($userRoles, Request $request) {
// Give any logged in user access to ALL actions when `allowUser` is
// enabled except when the `adminPrefix` is being used.
if (!empty($this->_config['allowUser'])) {
Expand All @@ -131,16 +118,16 @@ public function validate($roles, Request $request) {
// the specified adminRole id.
if (!empty($this->_config['allowAdmin']) && !empty($this->_config['adminRole'])) {
if (!empty($request->params['prefix']) && $request->params['prefix'] === $this->_config['adminPrefix']) {
if (in_array($this->_config['adminRole'], $roles)) {
if (in_array($this->_config['adminRole'], $userRoles)) {
return true;
}
}
}

// allow logged in super admins access to all resources
if (!empty($this->_config['superAdminRole'])) {
foreach ($roles as $role) {
if ($role === $this->_config['superAdminRole']) {
foreach ($userRoles as $userRole) {
if ($userRole === $this->_config['superAdminRole']) {
return true;
}
}
Expand All @@ -152,10 +139,11 @@ public function validate($roles, Request $request) {
}

// allow access if user has a role with wildcard access to the resource
$iniKey = $this->_constructIniKey($request);
if (isset($this->_acl[$iniKey]['actions']['*'])) {
$matchArray = $this->_acl[$iniKey]['actions']['*'];
foreach ($roles as $role) {
if (in_array((string)$role, $matchArray)) {
foreach ($userRoles as $userRole) {
if (in_array((string)$userRole, $matchArray)) {
return true;
}
}
Expand All @@ -165,8 +153,8 @@ public function validate($roles, Request $request) {
if (isset($this->_acl[$iniKey]['actions'])) {
if(array_key_exists($request->action, $this->_acl[$iniKey]['actions']) && !empty($this->_acl[$iniKey]['actions'][$request->action])) {
$matchArray = $this->_acl[$iniKey]['actions'][$request->action];
foreach ($roles as $role) {
if (in_array((string)$role, $matchArray)) {
foreach ($userRoles as $userRole) {
if (in_array((string)$userRole, $matchArray)) {
return true;
}
}
Expand All @@ -175,13 +163,6 @@ public function validate($roles, Request $request) {
return false;
}

/**
* @return Cake\ORM\Table The User table
*/
public function getTable() {
return TableRegistry::get(CLASS_USER);
}

/**
* Parse ini file and returns the allowed roles per action
* - uses cache for maximum performance
Expand All @@ -204,32 +185,8 @@ protected function _getAcl($path = null) {
return $roles;
}

if (!file_exists($path . ACL_FILE)) {
touch($path . ACL_FILE);
}

if (function_exists('parse_ini_file')) {
$iniArray = parse_ini_file($path . ACL_FILE, true);
} else {
$iniArray = parse_ini_string(file_get_contents($path . ACL_FILE), true);
}

$availableRoles = Configure::read($this->_config['aclTable']);
if (!is_array($availableRoles)) {
$Table = $this->getTable();
if (!isset($Table->{$this->_config['aclTable']})) {
throw new \Exception('Missing relationship between Users and Roles. TinyAuthorize needs either a Configure or DB setup.');
}

$availableRoles = $Table->{$this->_config['aclTable']}->find('all')->formatResults(function ($results) {
return $results->combine('alias', 'id');
})->toArray();
Configure::write($this->_config['aclTable'], $availableRoles);
}
if (!is_array($availableRoles) || !is_array($iniArray)) {
trigger_error('Invalid Role Setup for TinyAuthorize (no roles found)');
return [];
}
$iniArray = $this->_parseAclIni($path . ACL_FILE);
$availableRoles = $this->_getAvailableRoles();

$res = [];
foreach ($iniArray as $key => $array) {
Expand Down Expand Up @@ -264,9 +221,9 @@ protected function _getAcl($path = null) {
if (!($role = trim($role)) || $role === '*') {
continue;
}
$newRole = Configure::read($this->_config['aclTable'] . '.' . strtolower($role));
// lookup role id by name in roles array
$newRole = $availableRoles[strtolower($role)];
$res[$key]['actions'][$action][] = $newRole;

}
}
}
Expand All @@ -275,6 +232,28 @@ protected function _getAcl($path = null) {
return $res;
}

/**
* Returns the acl.ini file as an array.
*
* @return array List with all available roles
* @throws Cake\Core\Exception\Exception
*/
protected function _parseAclIni($ini) {
if (!file_exists($ini)) {
throw new Exception(sprintf('Missing TinyAuthorize ACL file (%s)', $ini));
}

if (function_exists('parse_ini_file')) {
$iniArray = parse_ini_file($ini, true);
} else {
$iniArray = parse_ini_string(file_get_contents($ini), true);
}
if (!count($iniArray)) {
throw new Exception('Invalid TinyAuthorize ACL file');
}
return $iniArray;
}

/**
* Deconstructs an ACL ini section key into a named array with ACL parts
*
Expand Down Expand Up @@ -314,4 +293,78 @@ protected function _constructIniKey(Request $request) {
return $res;
}

/**
* Returns a list of all available roles. Will look for a roles array in
* Configure first, tries database roles table next.
*
* @return array List with all available roles
* @throws Cake\Core\Exception\Exception
*/
protected function _getAvailableRoles() {
$roles = Configure::read($this->_config['rolesTable']);
if (is_array($roles)) {
return $roles;
}

// no roles in Configure AND rolesTable does not exist
$tables = ConnectionManager::get('default')->schemaCollection()->listTables();
if (!in_array(Inflector::tableize($this->_config['rolesTable']), $tables)) {
throw new Exception('Invalid TinyAuthorize Role Setup (no roles found in Configure or database)');
}

// fetch roles from database
$rolesTable = TableRegistry::get($this->_config['rolesTable']);
$roles = $rolesTable->find('all')->formatResults(function ($results) {
return $results->combine('alias', 'id');
})->toArray();

if (!count($roles)) {
throw new Exception('Invalid TinyAuthorize Role Setup (rolesTable has no roles)');
}
return $roles;
}

/**
* Returns a list of all roles belonging to the authenticated user in the
* following order:
* - single role id using the roleColumn in single-role mode
* - direct lookup in the pivot table (to support both Configure and Model
* in multi-role mode)
*
* @param array $user The user to get the roles for
* @return array List with all role ids belonging to the user
* @throws Cake\Core\Exception\Exception
*/
protected function _getUserRoles($user) {
// single-role
if (!$this->_config['multiRole']) {
if (isset($user[$this->_config['roleColumn']])) {
return [$user[$this->_config['roleColumn']]];
}
throw new Exception (sprintf('Missing TinyAuthorize role id (%s) in user session', $this->_config['roleColumn']));
}

// multi-role: reverse engineer name of the pivot table
$rolesTableName = Inflector::tableize($this->_config['rolesTable']);
$tables = [
Inflector::tableize(CLASS_USER),
$rolesTableName
];
asort($tables);
$pivotTableName = implode('_', $tables);

// fetch roles directly from the pivot table
$pivotTable = TableRegistry::get($pivotTableName);
$roleColumn = Inflector::singularize($rolesTableName) . '_id';
$roles = $pivotTable->find('all', [
'conditions' => ['user_id =' => $user['id']],
'fields' => $roleColumn
])->extract($roleColumn)->toArray();

if (!count($roles)) {
throw new Exception ('Missing TinyAuthorize roles for user in pivot table');
}
return $roles;
}

}
Loading