composer package updates
[openemr.git] / vendor / zendframework / zend-permissions-acl / src / Assertion / ExpressionAssertion.php
blobdf1899789603ef630da8f2c32143ba2f5d0185ba
1 <?php
2 /**
3 * @see https://github.com/zendframework/zend-permissions-acl for the canonical source repository
4 * @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
5 * @license https://github.com/zendframework/zend-permissions-acl/blob/master/LICENSE.md New BSD License
6 */
8 namespace Zend\Permissions\Acl\Assertion;
10 use ReflectionProperty;
11 use Zend\Permissions\Acl\Acl;
12 use Zend\Permissions\Acl\Role\RoleInterface;
13 use Zend\Permissions\Acl\Resource\ResourceInterface;
14 use Zend\Permissions\Acl\Assertion\Exception\InvalidAssertionException;
15 use Zend\Permissions\Acl\Exception\RuntimeException;
17 /**
18 * Create an assertion based on expression rules.
20 * Each of the constructor, fromProperties, and fromArray methods allow you to
21 * define expression rules, and these include the left hand side, operator, and
22 * right hand side of the expression.
24 * The left and right hand sides of the expression are the values to compare.
25 * These values can be either an exact value to match, or an array with the key
26 * OPERAND_CONTEXT_PROPERTY pointing to one of two value types.
28 * First, it can be a string value matching one of "acl", "privilege", "role",
29 * or "resource", with the latter two values being the most common. In those
30 * cases, the matching value passed to `assert()` will be used in the
31 * comparison.
33 * Second, it can be a dot-separated property path string of the format
34 * "object.property", representing the associated object (role, resource, acl,
35 * or privilege) and its property to test against. The property may refer to a
36 * public property, or a public `get` or `is` method (following
37 * canonicalization of the property by replacing underscore separated values
38 * with camelCase values).
40 final class ExpressionAssertion implements AssertionInterface
42 const OPERAND_CONTEXT_PROPERTY = '__context';
44 const OPERATOR_EQ = '=';
45 const OPERATOR_NEQ = '!=';
46 const OPERATOR_LT = '<';
47 const OPERATOR_LTE = '<=';
48 const OPERATOR_GT = '>';
49 const OPERATOR_GTE = '>=';
50 const OPERATOR_IN = 'in';
51 const OPERATOR_NIN = '!in';
52 const OPERATOR_REGEX = 'regex';
53 const OPERATOR_NREGEX = '!regex';
54 const OPERATOR_SAME = '===';
55 const OPERATOR_NSAME = '!==';
57 /**
58 * @var array
60 private static $validOperators = [
61 self::OPERATOR_EQ,
62 self::OPERATOR_NEQ,
63 self::OPERATOR_LT,
64 self::OPERATOR_LTE,
65 self::OPERATOR_GT,
66 self::OPERATOR_GTE,
67 self::OPERATOR_IN,
68 self::OPERATOR_NIN,
69 self::OPERATOR_REGEX,
70 self::OPERATOR_NREGEX,
71 self::OPERATOR_SAME,
72 self::OPERATOR_NSAME,
75 /**
76 * @var mixed
78 private $left;
80 /**
81 * @var string
83 private $operator;
85 /**
86 * @var mixed
88 private $right;
90 /**
91 * Constructor
93 * Note that the constructor is marked private; use `fromProperties()` or
94 * `fromArray()` to create an instance.
96 * @param mixed|array $left See the class description for valid values.
97 * @param string $operator One of the OPERATOR constants (or their values)
98 * @param mixed|array $right See the class description for valid values.
100 private function __construct($left, $operator, $right)
102 $this->left = $left;
103 $this->operator = $operator;
104 $this->right = $right;
108 * @param mixed|array $left See the class description for valid values.
109 * @param string $operator One of the OPERATOR constants (or their values)
110 * @param mixed|array $right See the class description for valid values.
111 * @return self
112 * @throws InvalidAssertionException if either operand is invalid.
113 * @throws InvalidAssertionException if the operator is not supported.
115 public static function fromProperties($left, $operator, $right)
117 $operator = strtolower($operator);
119 self::validateOperand($left);
120 self::validateOperator($operator);
121 self::validateOperand($right);
123 return new self($left, $operator, $right);
127 * @param array $expression Must contain the following keys:
128 * - left: the left-hand side of the expression
129 * - operator: the operator to use for the comparison
130 * - right: the right-hand side of the expression
131 * See the class description for valid values for the left and right
132 * hand side values.
133 * @return self
134 * @throws InvalidAssertionException if missing one of the required keys.
135 * @throws InvalidAssertionException if either operand is invalid.
136 * @throws InvalidAssertionException if the operator is not supported.
138 public static function fromArray(array $expression)
140 $required = ['left', 'operator', 'right'];
142 if (count(array_intersect_key($expression, array_flip($required))) < count($required)) {
143 throw new InvalidAssertionException(
144 "Expression assertion requires 'left', 'operator' and 'right' to be supplied"
148 return self::fromProperties(
149 $expression['left'],
150 $expression['operator'],
151 $expression['right']
156 * @param mixed|array $operand
157 * @throws InvalidAssertionException if the operand is invalid.
159 private static function validateOperand($operand)
161 if (is_array($operand) && isset($operand[self::OPERAND_CONTEXT_PROPERTY])) {
162 if (! is_string($operand[self::OPERAND_CONTEXT_PROPERTY])) {
163 throw new InvalidAssertionException('Expression assertion context operand must be string');
169 * @param string $operand
170 * @throws InvalidAssertionException if the operator is not supported.
172 private static function validateOperator($operator)
174 if (! in_array($operator, self::$validOperators, true)) {
175 throw new InvalidAssertionException('Provided expression assertion operator is not supported');
180 * {@inheritDoc}
182 public function assert(Acl $acl, RoleInterface $role = null, ResourceInterface $resource = null, $privilege = null)
184 return $this->evaluate([
185 'acl' => $acl,
186 'role' => $role,
187 'resource' => $resource,
188 'privilege' => $privilege,
193 * @param array $context Contains the acl, privilege, role, and resource
194 * being tested currently.
195 * @return bool
197 private function evaluate(array $context)
199 $left = $this->getLeftValue($context);
200 $right = $this->getRightValue($context);
202 return static::evaluateExpression($left, $this->operator, $right);
206 * @param array $context Contains the acl, privilege, role, and resource
207 * being tested currently.
208 * @return mixed
210 private function getLeftValue(array $context)
212 return $this->resolveOperandValue($this->left, $context);
216 * @param array $context Contains the acl, privilege, role, and resource
217 * being tested currently.
218 * @return mixed
220 private function getRightValue(array $context)
222 return $this->resolveOperandValue($this->right, $context);
226 * @param mixed|array
227 * @param array $context Contains the acl, privilege, role, and resource
228 * being tested currently.
229 * @return mixed
230 * @throws RuntimeException if object cannot be resolved in context.
231 * @throws RuntimeException if property cannot be resolved.
233 private function resolveOperandValue($operand, array $context)
235 if (! is_array($operand) || ! isset($operand[self::OPERAND_CONTEXT_PROPERTY])) {
236 return $operand;
239 $contextProperty = $operand[self::OPERAND_CONTEXT_PROPERTY];
241 if (strpos($contextProperty, '.') !== false) { // property path?
242 list($objectName, $objectField) = explode('.', $contextProperty, 2);
243 return $this->getObjectFieldValue($context, $objectName, $objectField);
246 if (! isset($context[$contextProperty])) {
247 throw new RuntimeException(sprintf(
248 "'%s' is not available in the assertion context",
249 $contextProperty
253 return $context[$contextProperty];
257 * @param array $context Contains the acl, privilege, role, and resource
258 * being tested currently.
259 * @param string $objectName Name of object in context to use.
260 * @param string $field
261 * @return mixed
262 * @throws RuntimeException if object cannot be resolved in context.
263 * @throws RuntimeException if property cannot be resolved.
265 private function getObjectFieldValue(array $context, $objectName, $field)
267 if (! isset($context[$objectName])) {
268 throw new RuntimeException(sprintf(
269 "'%s' is not available in the assertion context",
270 $objectName
274 $object = $context[$objectName];
275 $accessors = ['get', 'is'];
276 $fieldAccessor = false === strpos($field, '_')
277 ? $field
278 : str_replace(' ', '', ucwords(str_replace('_', ' ', $field)));
280 foreach ($accessors as $accessor) {
281 $accessor .= $fieldAccessor;
283 if (method_exists($object, $accessor)) {
284 return $object->$accessor();
288 if (! $this->propertyExists($object, $field)) {
289 throw new RuntimeException(sprintf(
290 "'%s' property cannot be resolved on the '%s' object",
291 $field,
292 $objectName
296 return $object->$field;
300 * @param mixed $left
301 * @param string $right
302 * @param mixed $right
303 * @throws RuntimeException if operand is not supported.
305 private static function evaluateExpression($left, $operator, $right)
307 switch ($operator) {
308 case self::OPERATOR_EQ:
309 return $left == $right;
310 case self::OPERATOR_NEQ:
311 return $left != $right;
312 case self::OPERATOR_LT:
313 return $left < $right;
314 case self::OPERATOR_LTE:
315 return $left <= $right;
316 case self::OPERATOR_GT:
317 return $left > $right;
318 case self::OPERATOR_GTE:
319 return $left >= $right;
320 case self::OPERATOR_IN:
321 return in_array($left, $right);
322 case self::OPERATOR_NIN:
323 return ! in_array($left, $right);
324 case self::OPERATOR_REGEX:
325 return (bool) preg_match($right, $left);
326 case self::OPERATOR_NREGEX:
327 return ! (bool) preg_match($right, $left);
328 case self::OPERATOR_SAME:
329 return $left === $right;
330 case self::OPERATOR_NSAME:
331 return $left !== $right;
336 * @param object $object
337 * @param string $field
338 * @return mixed
340 private function propertyExists($object, $property)
342 if (! property_exists($object, $property)) {
343 return false;
346 $r = new ReflectionProperty($object, $property);
347 return $r->isPublic();