PluginProbe ʕ •ᴥ•ʔ
Advanced Access Manager – Access Governance for WordPress / 5.11
Advanced Access Manager – Access Governance for WordPress v5.11
6.8.4 6.8.5 6.9.0 6.9.1 6.9.10 6.9.11 6.9.12 6.9.13 6.9.14 6.9.15 6.9.16 6.9.17 6.9.18 6.9.19 6.9.2 6.9.20 6.9.21 6.9.22 6.9.23 6.9.24 6.9.25 6.9.26 6.9.27 6.9.28 6.9.29 6.9.3 6.9.30 6.9.31 6.9.32 6.9.33 6.9.34 6.9.35 6.9.36 6.9.37 6.9.38 6.9.39 6.9.4 6.9.41 6.9.42 6.9.43 6.9.44 6.9.45 6.9.46 6.9.47 6.9.48 6.9.49 6.9.5 6.9.51 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.0-alpha.6 7.0.0-alpha.7 7.0.0-beta.1 7.0.0-rc1 7.0.0-rc2 7.0.0-rc3 7.0.1 7.0.10 7.0.11 7.0.2 7.0.3 7.0.4 7.0.5 7.0.6 7.0.7 7.0.8 7.0.9 7.1.0 7.1.1 trunk 3.0 4.0 4.0.1 4.1 4.2 4.3 4.4 4.4.1 4.5 4.6 4.6.1 4.6.2 4.7 4.7.1 4.7.2 4.7.5 4.7.6 4.8 4.8.1 4.9 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 4.9.5.1 4.9.5.2 5.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1 5.1.1 5.10 5.11 5.2 5.2.1 5.2.5 5.2.6 5.2.7 5.3 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.4 5.4.1 5.4.2 5.4.3 5.4.3.1 5.4.3.2 5.5 5.5.1 5.5.2 5.6 5.6.1 5.6.1.1 5.7 5.7.1 5.7.2 5.7.3 5.8 5.8.1 5.8.2 5.8.3 5.9 5.9.1 5.9.1.1 5.9.2 5.9.2.1 5.9.3 5.9.4 5.9.5 5.9.6 5.9.6.1 5.9.6.2 5.9.6.3 5.9.7 5.9.7.1 5.9.7.2 5.9.7.3 5.9.8 5.9.8.1 5.9.9 5.9.9.1 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.1.0 6.1.1 6.2.0 6.2.1 6.2.2 6.3.0 6.3.1 6.3.2 6.3.3 6.4.0 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.6.0 6.6.1 6.6.2 6.6.3 6.6.4 6.7.0 6.7.1 6.7.2 6.7.3 6.7.4 6.7.5 6.7.6 6.7.7 6.7.8 6.7.9 6.8.0 6.8.1 6.8.2 6.8.3
advanced-access-manager / application / Core / Policy / Manager.php
advanced-access-manager / application / Core / Policy Last commit date
Condition.php 6 years ago Factory.php 6 years ago Manager.php 6 years ago Token.php 6 years ago Validator.php 6 years ago
Manager.php
428 lines
1 <?php
2
3 /**
4 * ======================================================================
5 * LICENSE: This file is subject to the terms and conditions defined in *
6 * file 'license.txt', which is part of this source code package. *
7 * ======================================================================
8 */
9
10 /**
11 * AAM core policy manager
12 *
13 * @package AAM
14 * @author Vasyl Martyniuk <vasyl@vasyltech.com>
15 * @since AAM v5.7.2
16 */
17 final class AAM_Core_Policy_Manager {
18
19 /**
20 * Policy core object
21 *
22 * @var AAM_Core_Object_Policy
23 *
24 * @access protected
25 */
26 protected $policyObject;
27
28 /**
29 * Current subject
30 *
31 * @var AAM_Core_Subject
32 *
33 * @access protected
34 */
35 protected $subject;
36
37 /**
38 * Parsed policy tree
39 *
40 * @var array
41 *
42 * @access protected
43 */
44 protected $tree = null;
45
46 /**
47 * Constructor
48 *
49 * @access protected
50 *
51 * @return void
52 */
53 public function __construct(AAM_Core_Subject $subject) {
54 $this->policyObject = $subject->getObject('policy');
55 $this->subject = $subject;
56 }
57
58 /**
59 * Undocumented function
60 *
61 * @return void
62 */
63 public function initializePolicyTree() {
64 $this->preparePolicyTree();
65 }
66
67 /**
68 * Call policy object public methods
69 *
70 * @param string $name
71 * @param array $args
72 *
73 * @return mixed
74 *
75 * @access public
76 */
77 public function __call($name, $args) {
78 $result = null;
79
80 if (method_exists($this->policyObject, $name)) {
81 $result = call_user_func_array(array($this->policyObject, $name), $args);
82 }
83
84 return $result;
85 }
86
87 /**
88 * Find all the matching policies
89 *
90 * @param string $s RegEx
91 * @param array $args Inline arguments
92 * @param bool $single Single record only - the last record
93 *
94 * @return array
95 *
96 * @access public
97 */
98 public function find($s, $args = array(), $single = false) {
99 $statements = array();
100 $tree = $this->preparePolicyTree();
101
102 foreach($tree['Statement'] as $key => $stm) {
103 if (preg_match($s, $key) && $this->isApplicable($stm, $args)) {
104 $statements[$this->strToLower($key)] = $stm;
105 }
106 }
107
108 return ($single ? end($statements) : $statements);
109 }
110
111 /**
112 * Check if specified action is allowed for resource
113 *
114 * This method is working with "Statement" array.
115 *
116 * @param string $resource Resource name
117 * @param array $args Args that will be injected during condition evaluation
118 *
119 * @return boolean|null
120 *
121 * @access public
122 */
123 public function isAllowed($resource, $args = array()) {
124 $allowed = null;
125 $tree = $this->preparePolicyTree();
126 $id = $this->strToLower($resource);
127
128 if (isset($tree['Statement'][$id])) {
129 $stm = $tree['Statement'][$id];
130
131 if ($this->isApplicable($stm, $args)) {
132 $effect = strtolower($stm['Effect']);
133 $allowed = ($effect === 'allow');
134 }
135 }
136
137 return $allowed;
138 }
139
140 /**
141 * Convert string to lowercase
142 *
143 * @param string $str
144 *
145 * @return string
146 *
147 * @access protected
148 */
149 protected function strToLower($str) {
150 if (function_exists('mb_strtolower')) {
151 $result = mb_strtolower($str);
152 } else {
153 $result = strtolower($str);
154 }
155
156 return $result;
157 }
158
159 /**
160 * Determine if resource is the boundary
161 *
162 * The Boundary is type of resource that is denied and is enforced so no other
163 * statements can override it. For example edit_posts capability can be boundary
164 * for any statement that user Role resource
165 *
166 * @param string $resource
167 * @param array $args
168 *
169 * @return boolean
170 *
171 * @access public
172 */
173 public function isBoundary($resource, $args = array()) {
174 $denied = false;
175 $tree = $this->preparePolicyTree();
176 $id = $this->strToLower($resource);
177
178 if (isset($tree['Statement'][$id])) {
179 $stm = $tree['Statement'][$id];
180
181 if ($this->isApplicable($stm, $args)) {
182 $effect = strtolower($stm['Effect']);
183 $denied = ($effect === 'deny' && !empty($stm['Enforce']));
184 }
185 }
186
187 return $denied;
188 }
189
190 /**
191 * Get Policy Param
192 *
193 * @param string $name
194 * @param array $args
195 *
196 * @return mixed
197 *
198 * @access public
199 */
200 public function getParam($id, $args = array()) {
201 $value = null;
202
203 if (isset($this->tree['Param'][$id])) {
204 $param = $this->tree['Param'][$id];
205
206 if ($this->isApplicable($param, $args)) {
207 if (preg_match_all('/(\$\{[^}]+\})/', $param['Value'], $match)) {
208 $value = AAM_Core_Policy_Token::evaluate($param['Value'], $match[1]);
209 } else {
210 $value = $param['Value'];
211 }
212 }
213 }
214
215 return $value;
216 }
217
218 /**
219 * Check if current subject can toggle specific policy
220 *
221 * Verify that policy can be attached/detached
222 *
223 * @param int $id Policy ID
224 * @param string $action Either "attach" or "detach"
225 *
226 * @return bool
227 *
228 * @access public
229 * @since v5.9
230 */
231 public function canTogglePolicy($id, $action) {
232 $post = get_post($id);
233
234 // Verify that current user can perform following action
235 $stm = $this->find(
236 "/^post:{$post->post_type}:({$post->post_name}|{$post->ID}):{$action}/i",
237 array('post' => $post),
238 true
239 );
240
241 return (empty($stm['Effect']) || $stm['Effect'] === 'allow');
242 }
243
244 /**
245 * Check if policy block is applicable
246 *
247 * @param array $block
248 * @param array $args
249 *
250 * @return boolean
251 *
252 * @access protected
253 */
254 protected function isApplicable($block, $args = array()) {
255 $result = true;
256
257 if (!empty($block['Condition']) && !is_scalar($block['Condition'])) {
258 $result = AAM_Core_Policy_Condition::getInstance()->evaluate(
259 $block['Condition'], $args
260 );
261 }
262
263 return $result;
264 }
265
266 /**
267 * Prepare policy tree
268 *
269 * This is the lazy load for the policy tree. If tree has not been initialized,
270 * trigger the process of parsing and merging statements and settings.
271 *
272 * @return array
273 *
274 * @access protected
275 */
276 protected function preparePolicyTree() {
277 if (is_null($this->tree)) {
278 $this->tree = array(
279 'Statement' => array(),
280 'Param' => array()
281 );
282
283 $ids = array_filter(
284 $this->policyObject->getOption(),
285 function($state) {
286 return !empty($state);
287 }
288 );
289
290 if (count($ids)) {
291 $policies = get_posts(array(
292 'include' => array_keys($ids),
293 'post_status' => 'publish',
294 'post_type' => 'aam_policy'
295 ));
296
297 foreach($policies as $policy) {
298 $this->extendTree(
299 $this->tree, $this->parsePolicy($policy->post_content)
300 );
301 }
302 }
303 }
304
305 return $this->tree;
306 }
307
308 /**
309 * Parse policy post and extract Statements and Params
310 *
311 * @param string $policy
312 *
313 * @return array
314 *
315 * @access protected
316 */
317 protected function parsePolicy($policy) {
318 $val = json_decode($policy, true);
319
320 // Do not load the policy if any errors
321 if (json_last_error() === JSON_ERROR_NONE) {
322 $tree = array(
323 'Statement' => isset($val['Statement']) ? (array) $val['Statement'] : array(),
324 'Param' => isset($val['Param']) ? (array) $val['Param'] : array(),
325 );
326 } else {
327 $tree = array('Statement' => array(), 'Param' => array());
328 }
329
330 return $tree;
331 }
332
333 /**
334 * Extend tree with additional statements and params
335 *
336 * @param array &$tree
337 * @param array $addition
338 *
339 * @return array
340 *
341 * @access protected
342 */
343 protected function extendTree(&$tree, $addition) {
344 // Step #1. If there are any statements, let's index them by resource:action
345 // and insert into the list of statements
346 foreach($addition['Statement'] as $stm) {
347 $list = (isset($stm['Resource']) ? (array) $stm['Resource'] : array());
348 $acts = (isset($stm['Action']) ? (array) $stm['Action'] : array(''));
349
350 foreach($list as $res) {
351 // Allow to build resource name dynamically.
352 // e.g. "Term:category:${USERMETA.region}:posts"
353 if (preg_match_all('/(\$\{[^}]+\})/', $res, $match)) {
354 $res = AAM_Core_Policy_Token::evaluate($res, $match[1]);
355 }
356 foreach($acts as $act) {
357 $id = $this->strToLower($res . (!empty($act) ? ":{$act}" : ''));
358
359 if (!isset($tree['Statement'][$id]) || empty($tree['Statement'][$id]['Enforce'])) {
360 $tree['Statement'][$id] = $this->removeKeys($stm, array('Resource', 'Action'));
361 }
362 }
363 }
364 }
365
366 // Step #2. If there are any params, let's index them and insert into the list
367 foreach($addition['Param'] as $param) {
368 if (!empty($param['Key'])) {
369 $id = $param['Key'];
370
371 if (!isset($tree['Param'][$id]) || empty($tree['Param'][$id]['Enforce'])) {
372 $tree['Param'][$id] = $this->removeKeys($param, array('Key'));
373
374 if (strpos($id, 'option:') === 0) {
375 add_filter('option_' . substr($id, 7), function($res, $option) {
376 $param = $this->tree['Param']["option:{$option}"];
377
378 if ($this->isApplicable($param)) {
379 if (is_array($res) && is_array($param['Value'])) {
380 $res = array_merge($res, $param['Value']);
381 } else {
382 $res = $param['Value'];
383 }
384 }
385
386 return $res;
387 }, 1, 2);
388 } elseif (strpos($id, 'site_option:') === 0) {
389 add_filter('site_option_' . substr($id, 12), function($res, $option) {
390 $param = $this->tree['Param']["site_option:{$option}"];
391
392 if ($this->isApplicable($param)) {
393 if (is_array($res) && is_array($param['Value'])) {
394 $res = array_merge($res, $param['Value']);
395 } else {
396 $res = $param['Value'];
397 }
398 }
399
400 return $res;
401 }, 1, 2);
402 }
403 }
404 }
405 }
406 }
407
408 /**
409 * Remove unnecessary keys from array
410 *
411 * @param array $arr
412 * @param array $keys
413 *
414 * @return array
415 *
416 * @access private
417 */
418 private function removeKeys($arr, $keys) {
419 foreach($keys as $key) {
420 if (isset($arr[$key])) {
421 unset($arr[$key]);
422 }
423 }
424
425 return $arr;
426 }
427
428 }