Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,4 @@ jobs:
- name: Run phpstan
if: success() || failure()
run: composer stan
continue-on-error: true
continue-on-error: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/.idea
composer.lock
.php_cs.cache
.phpunit.result.cache
13 changes: 7 additions & 6 deletions Docs/Documentation/Social.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Social Layer
============
The social layer provide a easier way to handle social provider authentication
with provides using OAuth1 or OAuth2. The idea is to provide a base
with provides using OAuth1 or OAuth2. The idea is to provide a base
interface for both OAuth and OAuth2.

***Make sure to load the bootstap.php file of this plugin!***
Expand All @@ -12,9 +12,10 @@ We have mappers to allow you a quick start with these providers:
- Facebook
- Google
- Instagram
- LinkedIn
- LinkedIn (Deprecated, they switched to OpenID-Connect)
- LinkedInOpenIDConnect (New, OIDC based authentication)
- Pinterest
- Tumblr
- Tumblr
- Twitter

You must define 'options.redirectUri', 'options.clientId' and
Expand Down Expand Up @@ -57,7 +58,7 @@ use CakeDC\Auth\Social\Service\ServiceFactory;
->getAuthorizationUrl($this->request)
);
}

/**
* Callback to get user information from provider
*
Expand All @@ -80,7 +81,7 @@ use CakeDC\Auth\Social\Service\ServiceFactory;
}
$data = $server->getUser($this->request);
$data = (new MapUser())($server, $data);

//your code
} catch (\Exception $e) {
$this->log($log);
Expand All @@ -92,4 +93,4 @@ Working with cakephp/authentication
If you're using the new cakephp/authentication we recommend you to use
the SocialAuthenticator and SocialMiddleware provided in this plugin. For more
details of how to handle social authentication with cakephp/authentication, please check
how we implemented at CakeDC/Users plugins.
how we implemented at CakeDC/Users plugins.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"cakephp/authorization": "^3.0",
"cakephp/cakephp-codesniffer": "^5.0",
"cakephp/authentication": "^3.0",
"yubico/u2flib-server": "^1.0"
"yubico/u2flib-server": "^1.0",
"firebase/php-jwt": "^v6.8"
},
"suggest": {
},
Expand Down
33 changes: 23 additions & 10 deletions config/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

use Cake\Routing\Router;

return [
'OAuth.path' => ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialLogin', 'prefix' => null],
'OAuth.providers' => [
Expand All @@ -23,7 +24,7 @@
'redirectUri' => Router::fullBaseUrl() . '/auth/facebook',
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/facebook',
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/facebook',
]
],
],
'twitter' => [
'service' => 'CakeDC\Auth\Social\Service\OAuth1Service',
Expand All @@ -33,8 +34,9 @@
'redirectUri' => Router::fullBaseUrl() . '/auth/twitter',
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/twitter',
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/twitter',
]
],
],
// Deprecated, LinkedIn switched to OpenID-Connect and OAuth2 is no longer working properly
'linkedIn' => [
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
'className' => 'League\OAuth2\Client\Provider\LinkedIn',
Expand All @@ -43,7 +45,18 @@
'redirectUri' => Router::fullBaseUrl() . '/auth/linkedIn',
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/linkedIn',
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/linkedIn',
]
],
],
'linkedInOpenIDConnect' => [
'service' => 'CakeDC\Auth\Social\Service\OpenIDConnectService',
'className' => 'League\OAuth2\Client\Provider\LinkedIn',
'mapper' => 'CakeDC\Auth\Social\Mapper\LinkedInOpenIDConnect',
'options' => [
'redirectUri' => Router::fullBaseUrl() . '/auth/linkedInOpenIDConnect',
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/linkedInOpenIDConnect',
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/linkedInOpenIDConnect',
'defaultScopes' => ['email', 'openid', 'profile'],
],
],
'instagram' => [
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
Expand All @@ -53,7 +66,7 @@
'redirectUri' => Router::fullBaseUrl() . '/auth/instagram',
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/instagram',
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/instagram',
]
],
],
'google' => [
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
Expand All @@ -64,7 +77,7 @@
'redirectUri' => Router::fullBaseUrl() . '/auth/google',
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/google',
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/google',
]
],
],
'amazon' => [
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
Expand All @@ -74,7 +87,7 @@
'redirectUri' => Router::fullBaseUrl() . '/auth/amazon',
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/amazon',
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/amazon',
]
],
],
'azure' => [
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
Expand All @@ -84,7 +97,7 @@
'redirectUri' => Router::fullBaseUrl() . '/auth/azure',
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/azure',
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/azure',
]
],
],
],
'TwoFactorProcessors' => [
Expand All @@ -110,7 +123,7 @@
// QR-code provider (more on this later)
'qrcodeprovider' => new \RobThree\Auth\Providers\Qr\EndroidQrCodeProvider(),
// Random Number Generator provider (more on this later)
'rngprovider' => null
'rngprovider' => null,
],
'Webauthn2fa' => [
'enabled' => false,
Expand All @@ -122,6 +135,6 @@
'controller' => 'Users',
'action' => 'webauthn2fa',
'prefix' => false,
]
]
],
],
];
12 changes: 4 additions & 8 deletions config/permissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,10 @@
'plugin' => 'CakeDC/Users',
'controller' => 'Users',
'action' => 'resetOneTimePasswordAuthenticator',
'allowed' => function (array $user, $role, \Cake\Http\ServerRequest $request) {
$userId = \Cake\Utility\Hash::get($request->getAttribute('params'), 'pass.0');
if (!empty($userId) && !empty($user)) {
return $userId === $user['id'];
}

return false;
}
'allowed' => [
'className' => \CakeDC\Auth\Rbac\Rules\OTPRule::class,
'option' => []
],
],
//all roles allowed to Pages/display
[
Expand Down
207 changes: 207 additions & 0 deletions src/Rbac/CachedRbac.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<?php
declare(strict_types=1);

/**
* Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/

namespace CakeDC\Auth\Rbac;

use Cake\Cache\Cache;
use Cake\Core\Configure;
use Cake\Core\InstanceConfigTrait;
use Cake\Log\LogTrait;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use CakeDC\Auth\Rbac\Permissions\AbstractProvider;
use CakeDC\Auth\Rbac\PermissionMatchResult;
use CakeDC\Auth\Rbac\Rules\Rule;
use CakeDC\Auth\Rbac\Rules\RuleRegistry;
use CakeDC\Auth\Rbac\RbacInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LogLevel;

/**
* Class CachedRbac, determine if a request matches any of the given rbac rules
*
* @package Rbac
*/
class CachedRbac extends Rbac
{
/**
* A map of rules
*
* @var array[] rules array
*/
protected $permissionsMap = [];

/**
* An undasherize flag
*
* @var bool undasherize flag
*/
protected bool $undasherize = false;

/**
* Rbac constructor.
*
* @param array $config Class configuration
*/
public function __construct($config = [])
{
parent::__construct($config);
if (isset($config['undasherize'])) {
$this->undasherize = $config['undasherize'];
}
$this->permissionsMap = Cache::remember('auth_permissions', function () {
return $this->buildPermissionsMap();
}, '_cakedc_auth_');
}

/**
* @return array
*/
public function getPermissions(): array
{
return $this->permissions;
}

/**
* @return array
*/
public function buildPermissionsMap()
{
$asArray = function ($permission, $key, $default = null) {
if ($default !== null && !array_key_exists($key, $permission)) {
return [$default, '_'];
}
if (!array_key_exists($key, $permission) || $permission[$key] == false || $permission[$key] == null) {
return ['_'];
}
$item = $permission[$key];
if (is_string($item)) {
return [$item];
}
return (array)$item;
};
$map = [];
foreach ($this->permissions as $permission) {
if (isset($permission['role'])) {
$role = $permission['role'];
} else {
$role = '*';
}
$roles = (array)$role;
foreach ($roles as $role) {
$prefixes = $asArray($permission, 'prefix', '*');
$plugins = $asArray($permission, 'plugin', '*');
$controllers = $asArray($permission, 'controller', '*');
foreach ($prefixes as $prefix) {
foreach ($plugins as $plugin) {
foreach ($controllers as $controller) {
$key = "$prefix|$plugin|$controller";
$map[$role][$key][] = $permission;
}
}
}
}
}

return $map;
}

/**
* @param array $permissions permissions
* @return void
*/
public function setPermissions($permissions): void
{
$this->permissions = $permissions;
}

/**
* Match against permissions, return if matched
* Permissions are processed based on the 'permissions' config values
*
* @param array|\ArrayAccess $user current user array
* @param \Psr\Http\Message\ServerRequestInterface $request request
* @return bool true if there is a match in permissions
*/
public function checkPermissions($user, ServerRequestInterface $request): bool
{
$roleField = $this->getConfig('role_field');
$defaultRole = $this->getConfig('default_role');
$role = Hash::get($user, $roleField, $defaultRole);

$keys = $this->permissionKeys($request);
foreach ([$role, '*'] as $checkRole) {
if (!array_key_exists($checkRole, $this->permissionsMap)) {
continue;
}
foreach ($keys as $key) {
if (!array_key_exists($key, $this->permissionsMap[$checkRole])) {
continue;
}
$permissions = $this->permissionsMap[$checkRole][$key];
foreach ($permissions as $permission) {
$matchResult = $this->_matchPermission($permission, $user, $role, $request);
if ($matchResult !== null) {
if ($this->getConfig('log')) {
$this->log($matchResult->getReason(), LogLevel::DEBUG);
}

return $matchResult->isAllowed();
}
}
}
}

return false;
}

/**
* Build list of permission keys to search.
*
* @param \Psr\Http\Message\ServerRequestInterface $request request
* @return array list of key to search based on request.
*/
protected function permissionKeys(ServerRequestInterface $request)
{
$params = $request->getAttribute('params');
$permission = [
'prefix' => $params['prefix'] ?? null,
'plugin' => $params['plugin'] ?? null,
'controller' => $params['controller'] ?? null,
];
$keys = [];
$getKeys = function ($permission, $key) {
if ($permission[$key] == false || $permission[$key] == null) {
return ['_', '*'];
}
$item = $permission[$key];
if ($this->undasherize) {
$item = Inflector::camelize((string)$item, '-');
}
return [$item, '*'];
};
$prefixes = $getKeys($permission, 'prefix');
$plugins = $getKeys($permission, 'plugin');
$controllers = $getKeys($permission, 'controller');
foreach ($prefixes as $prefix) {
foreach ($plugins as $plugin) {
foreach ($controllers as $controller) {
$keys[] = "$prefix|$plugin|$controller";
}
}
}

return $keys;
}

}
Loading
Loading