Skip to content

Commit 404551b

Browse files
committed
merge changes from 9.next, fix unit tests
2 parents 03907b6 + 5e4e9a6 commit 404551b

File tree

17 files changed

+1946
-48
lines changed

17 files changed

+1946
-48
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,4 @@ jobs:
124124
- name: Run phpstan
125125
if: success() || failure()
126126
run: composer stan
127-
continue-on-error: true
127+
continue-on-error: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/.idea
44
composer.lock
55
.php_cs.cache
6+
.phpunit.result.cache

Docs/Documentation/Social.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Social Layer
22
============
33
The social layer provide a easier way to handle social provider authentication
4-
with provides using OAuth1 or OAuth2. The idea is to provide a base
4+
with provides using OAuth1 or OAuth2. The idea is to provide a base
55
interface for both OAuth and OAuth2.
66

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

2021
You must define 'options.redirectUri', 'options.clientId' and
@@ -57,7 +58,7 @@ use CakeDC\Auth\Social\Service\ServiceFactory;
5758
->getAuthorizationUrl($this->request)
5859
);
5960
}
60-
61+
6162
/**
6263
* Callback to get user information from provider
6364
*
@@ -80,7 +81,7 @@ use CakeDC\Auth\Social\Service\ServiceFactory;
8081
}
8182
$data = $server->getUser($this->request);
8283
$data = (new MapUser())($server, $data);
83-
84+
8485
//your code
8586
} catch (\Exception $e) {
8687
$this->log($log);
@@ -92,4 +93,4 @@ Working with cakephp/authentication
9293
If you're using the new cakephp/authentication we recommend you to use
9394
the SocialAuthenticator and SocialMiddleware provided in this plugin. For more
9495
details of how to handle social authentication with cakephp/authentication, please check
95-
how we implemented at CakeDC/Users plugins.
96+
how we implemented at CakeDC/Users plugins.

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
"cakephp/authorization": "^3.0",
4444
"cakephp/cakephp-codesniffer": "^5.0",
4545
"cakephp/authentication": "^3.0",
46-
"yubico/u2flib-server": "^1.0"
46+
"yubico/u2flib-server": "^1.0",
47+
"firebase/php-jwt": "^v6.8"
4748
},
4849
"suggest": {
4950
},

config/auth.php

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111

1212
use Cake\Routing\Router;
13+
1314
return [
1415
'OAuth.path' => ['plugin' => 'CakeDC/Users', 'controller' => 'Users', 'action' => 'socialLogin', 'prefix' => null],
1516
'OAuth.providers' => [
@@ -23,7 +24,7 @@
2324
'redirectUri' => Router::fullBaseUrl() . '/auth/facebook',
2425
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/facebook',
2526
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/facebook',
26-
]
27+
],
2728
],
2829
'twitter' => [
2930
'service' => 'CakeDC\Auth\Social\Service\OAuth1Service',
@@ -33,8 +34,9 @@
3334
'redirectUri' => Router::fullBaseUrl() . '/auth/twitter',
3435
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/twitter',
3536
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/twitter',
36-
]
37+
],
3738
],
39+
// Deprecated, LinkedIn switched to OpenID-Connect and OAuth2 is no longer working properly
3840
'linkedIn' => [
3941
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
4042
'className' => 'League\OAuth2\Client\Provider\LinkedIn',
@@ -43,7 +45,18 @@
4345
'redirectUri' => Router::fullBaseUrl() . '/auth/linkedIn',
4446
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/linkedIn',
4547
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/linkedIn',
46-
]
48+
],
49+
],
50+
'linkedInOpenIDConnect' => [
51+
'service' => 'CakeDC\Auth\Social\Service\OpenIDConnectService',
52+
'className' => 'League\OAuth2\Client\Provider\LinkedIn',
53+
'mapper' => 'CakeDC\Auth\Social\Mapper\LinkedInOpenIDConnect',
54+
'options' => [
55+
'redirectUri' => Router::fullBaseUrl() . '/auth/linkedInOpenIDConnect',
56+
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/linkedInOpenIDConnect',
57+
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/linkedInOpenIDConnect',
58+
'defaultScopes' => ['email', 'openid', 'profile'],
59+
],
4760
],
4861
'instagram' => [
4962
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
@@ -53,7 +66,7 @@
5366
'redirectUri' => Router::fullBaseUrl() . '/auth/instagram',
5467
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/instagram',
5568
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/instagram',
56-
]
69+
],
5770
],
5871
'google' => [
5972
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
@@ -64,7 +77,7 @@
6477
'redirectUri' => Router::fullBaseUrl() . '/auth/google',
6578
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/google',
6679
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/google',
67-
]
80+
],
6881
],
6982
'amazon' => [
7083
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
@@ -74,7 +87,7 @@
7487
'redirectUri' => Router::fullBaseUrl() . '/auth/amazon',
7588
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/amazon',
7689
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/amazon',
77-
]
90+
],
7891
],
7992
'azure' => [
8093
'service' => 'CakeDC\Auth\Social\Service\OAuth2Service',
@@ -84,7 +97,7 @@
8497
'redirectUri' => Router::fullBaseUrl() . '/auth/azure',
8598
'linkSocialUri' => Router::fullBaseUrl() . '/link-social/azure',
8699
'callbackLinkSocialUri' => Router::fullBaseUrl() . '/callback-link-social/azure',
87-
]
100+
],
88101
],
89102
],
90103
'TwoFactorProcessors' => [
@@ -110,7 +123,7 @@
110123
// QR-code provider (more on this later)
111124
'qrcodeprovider' => new \RobThree\Auth\Providers\Qr\EndroidQrCodeProvider(),
112125
// Random Number Generator provider (more on this later)
113-
'rngprovider' => null
126+
'rngprovider' => null,
114127
],
115128
'Webauthn2fa' => [
116129
'enabled' => false,
@@ -122,6 +135,6 @@
122135
'controller' => 'Users',
123136
'action' => 'webauthn2fa',
124137
'prefix' => false,
125-
]
126-
]
138+
],
139+
],
127140
];

config/permissions.php

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,10 @@
105105
'plugin' => 'CakeDC/Users',
106106
'controller' => 'Users',
107107
'action' => 'resetOneTimePasswordAuthenticator',
108-
'allowed' => function (array $user, $role, \Cake\Http\ServerRequest $request) {
109-
$userId = \Cake\Utility\Hash::get($request->getAttribute('params'), 'pass.0');
110-
if (!empty($userId) && !empty($user)) {
111-
return $userId === $user['id'];
112-
}
113-
114-
return false;
115-
}
108+
'allowed' => [
109+
'className' => \CakeDC\Auth\Rbac\Rules\OTPRule::class,
110+
'option' => []
111+
],
116112
],
117113
//all roles allowed to Pages/display
118114
[

src/Rbac/CachedRbac.php

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
6+
*
7+
* Licensed under The MIT License
8+
* Redistributions of files must retain the above copyright notice.
9+
*
10+
* @copyright Copyright 2010 - 2019, Cake Development Corporation (https://www.cakedc.com)
11+
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
12+
*/
13+
14+
namespace CakeDC\Auth\Rbac;
15+
16+
use Cake\Cache\Cache;
17+
use Cake\Core\Configure;
18+
use Cake\Core\InstanceConfigTrait;
19+
use Cake\Log\LogTrait;
20+
use Cake\Utility\Hash;
21+
use Cake\Utility\Inflector;
22+
use CakeDC\Auth\Rbac\Permissions\AbstractProvider;
23+
use CakeDC\Auth\Rbac\PermissionMatchResult;
24+
use CakeDC\Auth\Rbac\Rules\Rule;
25+
use CakeDC\Auth\Rbac\Rules\RuleRegistry;
26+
use CakeDC\Auth\Rbac\RbacInterface;
27+
use Psr\Http\Message\ServerRequestInterface;
28+
use Psr\Log\LogLevel;
29+
30+
/**
31+
* Class CachedRbac, determine if a request matches any of the given rbac rules
32+
*
33+
* @package Rbac
34+
*/
35+
class CachedRbac extends Rbac
36+
{
37+
/**
38+
* A map of rules
39+
*
40+
* @var array[] rules array
41+
*/
42+
protected $permissionsMap = [];
43+
44+
/**
45+
* An undasherize flag
46+
*
47+
* @var bool undasherize flag
48+
*/
49+
protected bool $undasherize = false;
50+
51+
/**
52+
* Rbac constructor.
53+
*
54+
* @param array $config Class configuration
55+
*/
56+
public function __construct($config = [])
57+
{
58+
parent::__construct($config);
59+
if (isset($config['undasherize'])) {
60+
$this->undasherize = $config['undasherize'];
61+
}
62+
$this->permissionsMap = Cache::remember('auth_permissions', function () {
63+
return $this->buildPermissionsMap();
64+
}, '_cakedc_auth_');
65+
}
66+
67+
/**
68+
* @return array
69+
*/
70+
public function getPermissions(): array
71+
{
72+
return $this->permissions;
73+
}
74+
75+
/**
76+
* @return array
77+
*/
78+
public function buildPermissionsMap()
79+
{
80+
$asArray = function ($permission, $key, $default = null) {
81+
if ($default !== null && !array_key_exists($key, $permission)) {
82+
return [$default, '_'];
83+
}
84+
if (!array_key_exists($key, $permission) || $permission[$key] == false || $permission[$key] == null) {
85+
return ['_'];
86+
}
87+
$item = $permission[$key];
88+
if (is_string($item)) {
89+
return [$item];
90+
}
91+
return (array)$item;
92+
};
93+
$map = [];
94+
foreach ($this->permissions as $permission) {
95+
if (isset($permission['role'])) {
96+
$role = $permission['role'];
97+
} else {
98+
$role = '*';
99+
}
100+
$roles = (array)$role;
101+
foreach ($roles as $role) {
102+
$prefixes = $asArray($permission, 'prefix', '*');
103+
$plugins = $asArray($permission, 'plugin', '*');
104+
$controllers = $asArray($permission, 'controller', '*');
105+
foreach ($prefixes as $prefix) {
106+
foreach ($plugins as $plugin) {
107+
foreach ($controllers as $controller) {
108+
$key = "$prefix|$plugin|$controller";
109+
$map[$role][$key][] = $permission;
110+
}
111+
}
112+
}
113+
}
114+
}
115+
116+
return $map;
117+
}
118+
119+
/**
120+
* @param array $permissions permissions
121+
* @return void
122+
*/
123+
public function setPermissions($permissions): void
124+
{
125+
$this->permissions = $permissions;
126+
}
127+
128+
/**
129+
* Match against permissions, return if matched
130+
* Permissions are processed based on the 'permissions' config values
131+
*
132+
* @param array|\ArrayAccess $user current user array
133+
* @param \Psr\Http\Message\ServerRequestInterface $request request
134+
* @return bool true if there is a match in permissions
135+
*/
136+
public function checkPermissions($user, ServerRequestInterface $request): bool
137+
{
138+
$roleField = $this->getConfig('role_field');
139+
$defaultRole = $this->getConfig('default_role');
140+
$role = Hash::get($user, $roleField, $defaultRole);
141+
142+
$keys = $this->permissionKeys($request);
143+
foreach ([$role, '*'] as $checkRole) {
144+
if (!array_key_exists($checkRole, $this->permissionsMap)) {
145+
continue;
146+
}
147+
foreach ($keys as $key) {
148+
if (!array_key_exists($key, $this->permissionsMap[$checkRole])) {
149+
continue;
150+
}
151+
$permissions = $this->permissionsMap[$checkRole][$key];
152+
foreach ($permissions as $permission) {
153+
$matchResult = $this->_matchPermission($permission, $user, $role, $request);
154+
if ($matchResult !== null) {
155+
if ($this->getConfig('log')) {
156+
$this->log($matchResult->getReason(), LogLevel::DEBUG);
157+
}
158+
159+
return $matchResult->isAllowed();
160+
}
161+
}
162+
}
163+
}
164+
165+
return false;
166+
}
167+
168+
/**
169+
* Build list of permission keys to search.
170+
*
171+
* @param \Psr\Http\Message\ServerRequestInterface $request request
172+
* @return array list of key to search based on request.
173+
*/
174+
protected function permissionKeys(ServerRequestInterface $request)
175+
{
176+
$params = $request->getAttribute('params');
177+
$permission = [
178+
'prefix' => $params['prefix'] ?? null,
179+
'plugin' => $params['plugin'] ?? null,
180+
'controller' => $params['controller'] ?? null,
181+
];
182+
$keys = [];
183+
$getKeys = function ($permission, $key) {
184+
if ($permission[$key] == false || $permission[$key] == null) {
185+
return ['_', '*'];
186+
}
187+
$item = $permission[$key];
188+
if ($this->undasherize) {
189+
$item = Inflector::camelize((string)$item, '-');
190+
}
191+
return [$item, '*'];
192+
};
193+
$prefixes = $getKeys($permission, 'prefix');
194+
$plugins = $getKeys($permission, 'plugin');
195+
$controllers = $getKeys($permission, 'controller');
196+
foreach ($prefixes as $prefix) {
197+
foreach ($plugins as $plugin) {
198+
foreach ($controllers as $controller) {
199+
$keys[] = "$prefix|$plugin|$controller";
200+
}
201+
}
202+
}
203+
204+
return $keys;
205+
}
206+
207+
}

0 commit comments

Comments
 (0)