Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
steinkel committed Dec 19, 2016
2 parents c768163 + 964fb74 commit 25d6a1c
Show file tree
Hide file tree
Showing 22 changed files with 1,153 additions and 162 deletions.
6 changes: 3 additions & 3 deletions .semver
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
:major: 3
:minor: 2
:patch: 5
:major: 4
:minor: 0
:patch: 0
:special: ''
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ Changelog

Releases for CakePHP 3
-------------
* 4.0.0
* Add Google Authenticator
* Add improvements to SimpleRbac, like star to invert rules and `user.` prefix to match values from the user array
* Add `allowed` to manage the AuthLinkHelper when action is allowed
* Add option to configure the api table and finder in ApiKeyAuthenticate

* 3.2.5
* Fixed RegisterBehavior api, make getRegisterValidators public.

Expand Down
12 changes: 12 additions & 0 deletions Docs/Documentation/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ composer require google/recaptcha:@stable
NOTE: you'll need to configure the reCaptcha key and secret, check the [Configuration](Configuration.md)
page for more details.

If you want to use Google Authenticator features...

```
composer require robthree/twofactorauth:"^1.5.2"
```

NOTE: you'll need to enable `Users.GoogleAuthenticator.login`

```
Configure::write('Users.GoogleAuthenticator.login', true);
```

Creating Required Tables
------------------------
If you want to use the Users tables to store your users and social accounts:
Expand Down
95 changes: 68 additions & 27 deletions Docs/Documentation/SimpleRbacAuthorize.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,49 +52,90 @@ The ```default_role``` will be used to set the role of the registered users by d
Permission rules syntax
-----------------

* Rules are evaluated top-down, first matching rule will apply
* Each rule is defined:
```php
[
'role' => 'REQUIRED_NAME_OF_THE_ROLE_OR_[]_OR_*',
'prefix' => 'OPTIONAL_PREFIX_USED_OR_[]_OR_*_DEFAULT_NULL',
'extension' => 'OPTIONAL_PREFIX_USED_OR_[]_OR_*_DEFAULT_NULL',
'plugin' => 'OPTIONAL_NAME_OF_THE_PLUGIN_OR_[]_OR_*_DEFAULT_NULL',
'controller' => 'REQUIRED_NAME_OF_THE_CONTROLLER_OR_[]_OR_*'
'action' => 'REQUIRED_NAME_OF_ACTION_OR_[]_OR_*',
'allowed' => 'OPTIONAL_BOOLEAN_OR_CALLABLE_OR_INSTANCE_OF_RULE_DEFAULT_TRUE'
]
```
* If no rule allowed = true is matched for a given user role and url, default return value will be false
* Note for Superadmin access (permission to access ALL THE THINGS in your app) there is a specific Authorize Object provided
* Permissions are evaluated top-down, first matching permission will apply
* Each permission is an associative array of rules with following structure: `'value_to_check' => 'expected_value'`
* `value_to_check` can be any key from user array or one of special keys:
* Routing params:
* `prefix`
* `plugin`
* `extension`
* `controller`
* `action`
* `role` - Alias/shortcut to field defined in `role_field` config value
* `allowed` - see below
* If you have a user field that overlaps with special keys (eg. `$user->allowed`) you can prepend `user.` to key to force matching from user array (eg. `user.allowed`)
* The keys can be placed in any order with exception of `allowed` which must be last one (see below)
* `value_to_check` can be prepended with `*` to match everything except `expected_value`
* `expected_value` can be one of following things:
* `*` will match absolutely everything
* A _string_/_integer_/_boolean_/etc - will match only the specified value
* An _array_ of strings/integers/booleans/etc (can be mixed). The rule will match if real value is equal to any of expected ones
* A callable/object (see below)
* If any of rules fail, the permission is discarded and the next one is evaluated
* A very special key `allowed` exists which has completely different behaviour:
* If `expected_value` is a callable/object then it's executed and the result is casted to boolean
* If `expected_value` is **not** a callable/object then it's simply casted to boolean
* The `*` is checked and if found the result is inverted
* The final boolean value is **the result of permission** checker. This means if it is `false` then no other permissions are checked and the user is denied access.
For this reason the `allowed` key must be placed at the end of permission since no other rules are executed after it

Permission Callbacks
-----------------
You could use a callback in your 'allowed' to process complex authentication, like
**Notes**:

* For Superadmin access (permission to access ALL THE THINGS in your app) there is a specific Authorize Object provided
* Permissions that do not have `controller` and/or `action` keys (or the inverted versions) are automatically discarded in order to prevent errors.
If you need to match all controllers/actions you can explicitly do `'contoller' => '*'`
* Key `user` (or the inverted version) is illegal (as it's impossible to match an array) and any permission containing it will be discarded
* If the permission is discarded for the reasons stated above, a debug message will be logged

**Permission Callbacks**: you could use a callback in your 'allowed' to process complex authentication, like
- ownership
- permissions stored in your database
- permission based on an external service API call

Example *ownership* callback, to allow users to edit their own Posts:

```php
'allowed' => function (array $user, $role, Request $request) {
$postId = Hash::get($request->params, 'pass.0');
$post = TableRegistry::get('Posts')->get($postId);
$userId = Hash::get($user, 'id');
'allowed' => function (array $user, $role, \Cake\Network\Request $request) {
$postId = \Cake\Utility\Hash::get($request->params, 'pass.0');
$post = \Cake\ORM\TableRegistry::get('Posts')->get($postId);
$userId = $user['id'];
if (!empty($post->user_id) && !empty($userId)) {
return $post->user_id === $userId;
}
return false;
}
```

Permission Rules
----------------
You could use an instance of the \CakeDC\Users\Auth\Rules\Rule interface to reuse your custom rules
Examples:
**Permission Rules**: If you see that you are duplicating logic in your callables, you can create rule class to re-use the logic.
For example, the above ownership callback is included in CakeDC\Users as `Owner` rule
```php
'allowed' => new Owner() //will pick by default the post id from the first pass param
'allowed' => new \CakeDC\Users\Auth\Rules\Owner() //will pick by default the post id from the first pass param
```
Check the [Owner Rule](OwnerRule.md) documentation for more details

Creating rule classes
---------------------

The only requirement is to implement `\CakeDC\Users\Auth\Rules\Rule` interface which has one method:

```php
class YourRule implements \CakeDC\Users\Auth\Rules\Rule
{
/**
* Check the current entity is owned by the logged in user
*
* @param array $user Auth array with the logged in data
* @param string $role role of the user
* @param Request $request current request, used to get a default table if not provided
* @return bool
*/
public function allowed(array $user, $role, Request $request)
{
// Your logic here
}
}
```

This logic can be anything: database, external auth, etc.

Also, if you are using DB, you can choose to extend `\CakeDC\Users\Auth\Rules\AbstractRule` since it provides convenience methods for reading from DB
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,17 @@
"league/oauth2-instagram": "@stable",
"league/oauth2-google": "@stable",
"league/oauth2-linkedin": "@stable",
"google/recaptcha": "@stable"
"google/recaptcha": "@stable",
"robthree/twofactorauth": "^1.5.2"
},
"suggest": {
"league/oauth1-client": "Provides Social Authentication with Twitter",
"league/oauth2-facebook": "Provides Social Authentication with Facebook",
"league/oauth2-instagram": "Provides Social Authentication with Instagram",
"league/oauth2-google": "Provides Social Authentication with Google+",
"league/oauth2-linkedin": "Provides Social Authentication with LinkedIn",
"google/recaptcha": "Provides reCAPTCHA validation for registration form"
"google/recaptcha": "Provides reCAPTCHA validation for registration form",
"robthree/twofactorauth": "Provides Google Authenticator functionality"
},
"autoload": {
"psr-4": {
Expand Down
33 changes: 33 additions & 0 deletions config/Migrations/20161031101316_AddSecretToUsers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
use Migrations\AbstractMigration;

class AddSecretToUsers extends AbstractMigration
{
/**
* Change Method.
*
* More information on this method is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-change-method
* @return void
*/
public function change()
{
$table = $this->table('users');
/**
* Limiting secret field to 32 chars
* @see https://en.wikipedia.org/wiki/Google_Authenticator#Technical_description
*/
$table->addColumn('secret', 'string', [
'after' => 'activation_date',
'default' => null,
'limit' => 32,
'null' => true,
]);
$table->addColumn('secret_verified', 'boolean', [
'after' => 'secret',
'default' => null,
'null' => true,
]);
$table->update();
}
}
2 changes: 2 additions & 0 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@
Router::connect('/profile/*', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'profile']);
Router::connect('/login', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'login']);
Router::connect('/logout', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'logout']);
Router::connect('/verify', ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'verify']);

27 changes: 26 additions & 1 deletion config/users.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@
//enable social login
'login' => false,
],
'GoogleAuthenticator' => [
//enable Google Authenticator
'login' => false,
'issuer' => null,
// The number of digits the resulting codes will be
'digits' => 6,
// The number of seconds a code will be valid
'period' => 30,
// The algorithm used
'algorithm' => 'sha1',
// QR-code provider (more on this later)
'qrcodeprovider' => null,
// Random Number Generator provider (more on this later)
'rngprovider' => null,
// Key used for encrypting the user credentials, leave this false to use Security.salt
'encryptionKey' => false
],
'Profile' => [
//Allow view other users profiles
'viewOthers' => true,
Expand Down Expand Up @@ -95,13 +112,21 @@
]
],
],
'GoogleAuthenticator' => [
'verifyAction' => [
'plugin' => 'CakeDC/Users',
'controller' => 'Users',
'action' => 'verify',
'prefix' => false,
],
],
//default configuration used to auto-load the Auth Component, override to change the way Auth works
'Auth' => [
'loginAction' => [
'plugin' => 'CakeDC/Users',
'controller' => 'Users',
'action' => 'login',
'prefix' => null
'prefix' => false
],
'authenticate' => [
'all' => [
Expand Down
8 changes: 6 additions & 2 deletions src/Auth/ApiKeyAuthenticate.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class ApiKeyAuthenticate extends BaseAuthenticate
'field' => 'api_token',
//require SSL to pass the token. You should always require SSL to use tokens for Auth
'require_ssl' => true,
//set a specific table for API auth, set as null to use Users.table
'table' => null,
//set a specific finder for API auth, set as null to use Auth.authenticate.all.finder
'finder' => null,
];

/**
Expand Down Expand Up @@ -87,8 +91,8 @@ public function getUser(Request $request)
}

$this->_config['fields']['username'] = $this->config('field');
$this->_config['userModel'] = Configure::read('Users.table');
$this->_config['finder'] = 'all';
$this->_config['userModel'] = $this->config('table') ?: Configure::read('Users.table');
$this->_config['finder'] = $this->config('finder') ?: Configure::read('Auth.authenticate.all.finder') ?: 'all';
$result = $this->_query($apiKey)->first();

if (empty($result)) {
Expand Down
15 changes: 9 additions & 6 deletions src/Auth/Rules/AbstractRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
*/
namespace CakeDC\Users\Auth\Rules;

use Cake\Core\InstanceConfigTrait;
use Cake\Datasource\ModelAwareTrait;
use Cake\Network\Request;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
Expand All @@ -22,9 +25,9 @@
*/
abstract class AbstractRule implements Rule
{
use \Cake\Core\InstanceConfigTrait;
use \Cake\Datasource\ModelAwareTrait;
use \Cake\ORM\Locator\LocatorAwareTrait;
use InstanceConfigTrait;
use LocatorAwareTrait;
use ModelAwareTrait;

/**
* @var array default config
Expand All @@ -46,7 +49,7 @@ public function __construct($config = [])
* @param Request $request request
* @param mixed $table table
* @return \Cake\ORM\Table
* @throw OutOfBoundsException if table alias is empty
* @throws \OutOfBoundsException if table alias is empty
*/
protected function _getTable(Request $request, $table = null)
{
Expand All @@ -65,7 +68,7 @@ protected function _getTable(Request $request, $table = null)
*
* @param Request $request request
* @return Table
* @throws OutOfBoundsException if table alias can't be extracted from request
* @throws \OutOfBoundsException if table alias can't be extracted from request
*/
protected function _getTableFromRequest(Request $request)
{
Expand All @@ -88,7 +91,7 @@ protected function _getTableFromRequest(Request $request)
* @param string $role role of the user
* @param Request $request current request, used to get a default table if not provided
* @return bool
* @throws OutOfBoundsException if table is not found or it doesn't have the expected fields
* @throws \OutOfBoundsException if table is not found or it doesn't have the expected fields
*/
abstract public function allowed(array $user, $role, Request $request);
}
1 change: 0 additions & 1 deletion src/Auth/Rules/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ interface Rule
* @param string $role role of the user
* @param Request $request current request, used to get a default table if not provided
* @return bool
* @throws \OutOfBoundsException if table is not found or it doesn't have the expected fields
*/
public function allowed(array $user, $role, Request $request);
}
Loading

0 comments on commit 25d6a1c

Please sign in to comment.