1 | <?php |
||
2 | /** |
||
3 | * BEdita, API-first content management framework |
||
4 | * Copyright 2019 ChannelWeb Srl, Chialab Srl |
||
5 | * |
||
6 | * This file is part of BEdita: you can redistribute it and/or modify |
||
7 | * it under the terms of the GNU Lesser General Public License as published |
||
8 | * by the Free Software Foundation, either version 3 of the License, or |
||
9 | * (at your option) any later version. |
||
10 | * |
||
11 | * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details. |
||
12 | */ |
||
13 | |||
14 | namespace BEdita\API\Auth; |
||
15 | |||
16 | use BEdita\Core\Model\Entity\EndpointPermission; |
||
17 | use BEdita\Core\Model\Table\RolesTable; |
||
18 | use Cake\Auth\BaseAuthorize; |
||
19 | use Cake\Datasource\ModelAwareTrait; |
||
20 | use Cake\Http\ServerRequest; |
||
21 | use Cake\Utility\Hash; |
||
22 | |||
23 | /** |
||
24 | * Provide authorization on a per-endpoint basis. |
||
25 | * |
||
26 | * @since 4.0.0 |
||
27 | * @property \BEdita\Core\Model\Table\EndpointsTable $Endpoints |
||
28 | * @property \BEdita\Core\Model\Table\EndpointPermissionsTable $EndpointPermissions |
||
29 | */ |
||
30 | class EndpointAuthorize extends BaseAuthorize |
||
31 | { |
||
32 | use ModelAwareTrait; |
||
33 | |||
34 | /** |
||
35 | * {@inheritDoc} |
||
36 | * |
||
37 | * If 'blockAnonymousUsers' is true no access will be granted |
||
38 | * to unauthenticated users otherwise authorization check is performed |
||
39 | * If 'defaultAuthorized' is set current request is authorized |
||
40 | * unless a specific permission is set. |
||
41 | */ |
||
42 | protected $_defaultConfig = [ |
||
43 | 'blockAnonymousUsers' => true, |
||
44 | 'defaultAuthorized' => false, |
||
45 | ]; |
||
46 | |||
47 | /** |
||
48 | * Cache result of `authorized()` method call. |
||
49 | * |
||
50 | * This is required for controller to know whether authorization was granted on all contents, |
||
51 | * or only on those that belong to the current user. Whatever that means, it is controller's |
||
52 | * responsibility to interpret, as it may vary. Some controller may also decide to ignore this |
||
53 | * fine-grained authorization level. |
||
54 | * |
||
55 | * @var bool|string |
||
56 | */ |
||
57 | protected $authorized; |
||
58 | |||
59 | /** |
||
60 | * @inheritDoc |
||
61 | */ |
||
62 | public function authorize($user, ServerRequest $request): bool |
||
63 | { |
||
64 | // if 'blockAnonymousUsers' configuration is true and user unlogged authorization is denied |
||
65 | if ( |
||
66 | !$this->getConfig('defaultAuthorized') && |
||
67 | $this->isAnonymous($user) && |
||
68 | $this->getConfig('blockAnonymousUsers') |
||
69 | ) { |
||
70 | $this->unauthenticated(); |
||
71 | } |
||
72 | $this->loadModel('Endpoints'); |
||
73 | $this->loadModel('EndpointPermissions'); |
||
74 | |||
75 | // For anonymous users performing write operations, use strict mode. |
||
76 | $readRequest = $request->is(['get', 'head']); |
||
77 | $strict = ($this->isAnonymous($user) && !$readRequest); |
||
78 | |||
79 | $endpointId = $this->Endpoints->fetchId($request->getPath()); |
||
80 | $permsCount = $this->EndpointPermissions->fetchCount($endpointId); |
||
81 | |||
82 | // If request si authorized and no permission is set on it then it is authorized for anyone |
||
83 | if ($this->getConfig('defaultAuthorized') && ($endpointId === null || $permsCount === 0)) { |
||
84 | return $this->authorized = true; |
||
85 | } |
||
86 | |||
87 | $permissions = $this->EndpointPermissions->fetchPermissions($endpointId, $user, $strict); |
||
88 | $this->authorized = $this->checkPermissions($permissions, $readRequest); |
||
89 | |||
90 | if (empty($permissions) && ($endpointId === null || $permsCount === 0)) { |
||
91 | // If no permissions are set for an endpoint, assume the least restrictive permissions possible. |
||
92 | // This does not apply to write operations for anonymous users: those **MUST** be explicitly allowed. |
||
93 | $this->authorized = !$strict; |
||
94 | } |
||
95 | |||
96 | // if 'administratorOnly' configuration is true logged user must have administrator role |
||
97 | if ($this->authorized && $this->getConfig('administratorOnly')) { |
||
98 | $this->authorized = in_array(RolesTable::ADMIN_ROLE, Hash::extract($user, 'roles.{n}.id')); |
||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
99 | } |
||
100 | |||
101 | if ($this->isAnonymous($user) && $this->authorized !== true) { |
||
102 | // Anonymous user should not get a 403. Thus, we invoke authentication provider's |
||
103 | // `unauthenticated()` method. Furthermore, for anonymous users, `mine` doesn't make any sense, |
||
104 | // so we treat that as a non-authorized request. |
||
105 | $this->unauthenticated(); |
||
106 | } |
||
107 | |||
108 | // Authorization is granted for both `true` and `'mine'` values. |
||
109 | return !empty($this->authorized); |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * Perform user unauthentication to return 401 Unauthorized |
||
114 | * instead of 403 Forbidden |
||
115 | * |
||
116 | * @return void |
||
117 | */ |
||
118 | protected function unauthenticated() |
||
119 | { |
||
120 | $controller = $this->_registry->getController(); |
||
121 | $controller |
||
122 | ->Auth->getAuthenticate('BEdita/API.Jwt') |
||
123 | ->unauthenticated($controller->getRequest(), $controller->getResponse()); |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * Check if user is anonymous. |
||
128 | * |
||
129 | * @param array|\ArrayAccess $user User data. |
||
130 | * @return bool |
||
131 | */ |
||
132 | public function isAnonymous($user) |
||
133 | { |
||
134 | return !empty($user['_anonymous']); |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Checks if request can be authorized basing on a set of applicable permissions. |
||
139 | * |
||
140 | * @param \BEdita\Core\Model\Entity\EndpointPermission[] $permissions Set of applicable permissions. |
||
141 | * @param bool $readRequest Read request flag. |
||
142 | * @return bool|string |
||
143 | */ |
||
144 | protected function checkPermissions(array $permissions, bool $readRequest) |
||
145 | { |
||
146 | $shift = EndpointPermission::PERM_READ; |
||
147 | if (!$readRequest) { |
||
148 | $shift = EndpointPermission::PERM_WRITE; |
||
149 | } |
||
150 | |||
151 | $result = EndpointPermission::PERM_NO; |
||
152 | foreach ($permissions as $permission) { |
||
153 | $permission = $permission->permission >> $shift & EndpointPermission::PERM_YES; |
||
154 | $result = $result | $permission; |
||
155 | |||
156 | if ($permission === EndpointPermission::PERM_BLOCK) { |
||
157 | $result = EndpointPermission::PERM_NO; |
||
158 | |||
159 | break; |
||
160 | } |
||
161 | } |
||
162 | |||
163 | return EndpointPermission::decode($result); |
||
164 | } |
||
165 | } |
||
166 |