diff options
Diffstat (limited to 'main/app/sprinkles/account/src/Authorize')
4 files changed, 504 insertions, 504 deletions
diff --git a/main/app/sprinkles/account/src/Authorize/AccessConditionExpression.php b/main/app/sprinkles/account/src/Authorize/AccessConditionExpression.php index e36f4f4..8a8225e 100644 --- a/main/app/sprinkles/account/src/Authorize/AccessConditionExpression.php +++ b/main/app/sprinkles/account/src/Authorize/AccessConditionExpression.php @@ -1,138 +1,138 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authorize; - -use Monolog\Logger; -use PhpParser\Lexer\Emulative as EmulativeLexer; -use PhpParser\Node; -use PhpParser\NodeTraverser; -use PhpParser\Parser as Parser; -use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter; -use PhpParser\Error as PhpParserException; -use Psr\Http\Message\ServerRequestInterface as Request; -use UserFrosting\Sprinkle\Account\Database\Models\User; - -/** - * AccessConditionExpression class - * - * This class models the evaluation of an authorization condition expression, as associated with permissions. - * A condition is built as a boolean expression composed of AccessCondition method calls. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AccessConditionExpression -{ - /** - * @var User A user object, which for convenience can be referenced as 'self' in access conditions. - */ - protected $user; - - /** - * @var ParserNodeFunctionEvaluator The node visitor, which evaluates access condition callbacks used in a permission condition. - */ - protected $nodeVisitor; - - /** - * @var \PhpParser\Parser The PhpParser object to use (initialized in the ctor) - */ - protected $parser; - - /** - * @var NodeTraverser The NodeTraverser object to use (initialized in the ctor) - */ - protected $traverser; - - /** - * @var StandardPrettyPrinter The PrettyPrinter object to use (initialized in the ctor) - */ - protected $prettyPrinter; - - /** - * @var Logger - */ - protected $logger; - - /** - * @var bool Set to true if you want debugging information printed to the auth log. - */ - protected $debug; - - /** - * Create a new AccessConditionExpression object. - * - * @param User $user A user object, which for convenience can be referenced as 'self' in access conditions. - * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations. - * @param bool $debug Set to true if you want debugging information printed to the auth log. - */ - public function __construct(ParserNodeFunctionEvaluator $nodeVisitor, User $user, Logger $logger, $debug = FALSE) { - $this->nodeVisitor = $nodeVisitor; - $this->user = $user; - $this->parser = new Parser(new EmulativeLexer); - $this->traverser = new NodeTraverser; - $this->traverser->addVisitor($nodeVisitor); - $this->prettyPrinter = new StandardPrettyPrinter; - $this->logger = $logger; - $this->debug = $debug; - } - - /** - * Evaluates a condition expression, based on the given parameters. - * - * The special parameter `self` is an array of the current user's data. - * This get included automatically, and so does not need to be passed in. - * @param string $condition a boolean expression composed of calls to AccessCondition functions. - * @param array[mixed] $params the parameters to be used when evaluating the expression. - * @return bool true if the condition is passed for the given parameters, otherwise returns false. - */ - public function evaluateCondition($condition, $params) { - // Set the reserved `self` parameters. - // This replaces any values of `self` specified in the arguments, thus preventing them from being overridden in malicious user input. - // (For example, from an unfiltered request body). - $params['self'] = $this->user->export(); - - $this->nodeVisitor->setParams($params); - - $code = "<?php $condition;"; - - if ($this->debug) { - $this->logger->debug("Evaluating access condition '$condition' with parameters:", $params); - } - - // Traverse the parse tree, and execute any callbacks found using the supplied parameters. - // Replace the function node with the return value of the callback. - try { - // parse - $stmts = $this->parser->parse($code); - - // traverse - $stmts = $this->traverser->traverse($stmts); - - // Evaluate boolean statement. It is safe to use eval() here, because our expression has been reduced entirely to a boolean expression. - $expr = $this->prettyPrinter->prettyPrintExpr($stmts[0]); - $expr_eval = "return " . $expr . ";\n"; - $result = eval($expr_eval); - - if ($this->debug) { - $this->logger->debug("Expression '$expr' evaluates to " . ($result == TRUE ? "true" : "false")); - } - - return $result; - } catch (PhpParserException $e) { - if ($this->debug) { - $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage()); - } - return FALSE; // Access fails if the access condition can't be parsed. - } catch (AuthorizationException $e) { - if ($this->debug) { - $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage()); - } - return FALSE; - } - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authorize;
+
+use Monolog\Logger;
+use PhpParser\Lexer\Emulative as EmulativeLexer;
+use PhpParser\Node;
+use PhpParser\NodeTraverser;
+use PhpParser\Parser as Parser;
+use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;
+use PhpParser\Error as PhpParserException;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+
+/**
+ * AccessConditionExpression class
+ *
+ * This class models the evaluation of an authorization condition expression, as associated with permissions.
+ * A condition is built as a boolean expression composed of AccessCondition method calls.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AccessConditionExpression
+{
+ /**
+ * @var User A user object, which for convenience can be referenced as 'self' in access conditions.
+ */
+ protected $user;
+
+ /**
+ * @var ParserNodeFunctionEvaluator The node visitor, which evaluates access condition callbacks used in a permission condition.
+ */
+ protected $nodeVisitor;
+
+ /**
+ * @var \PhpParser\Parser The PhpParser object to use (initialized in the ctor)
+ */
+ protected $parser;
+
+ /**
+ * @var NodeTraverser The NodeTraverser object to use (initialized in the ctor)
+ */
+ protected $traverser;
+
+ /**
+ * @var StandardPrettyPrinter The PrettyPrinter object to use (initialized in the ctor)
+ */
+ protected $prettyPrinter;
+
+ /**
+ * @var Logger
+ */
+ protected $logger;
+
+ /**
+ * @var bool Set to true if you want debugging information printed to the auth log.
+ */
+ protected $debug;
+
+ /**
+ * Create a new AccessConditionExpression object.
+ *
+ * @param User $user A user object, which for convenience can be referenced as 'self' in access conditions.
+ * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations.
+ * @param bool $debug Set to true if you want debugging information printed to the auth log.
+ */
+ public function __construct(ParserNodeFunctionEvaluator $nodeVisitor, User $user, Logger $logger, $debug = FALSE) {
+ $this->nodeVisitor = $nodeVisitor;
+ $this->user = $user;
+ $this->parser = new Parser(new EmulativeLexer);
+ $this->traverser = new NodeTraverser;
+ $this->traverser->addVisitor($nodeVisitor);
+ $this->prettyPrinter = new StandardPrettyPrinter;
+ $this->logger = $logger;
+ $this->debug = $debug;
+ }
+
+ /**
+ * Evaluates a condition expression, based on the given parameters.
+ *
+ * The special parameter `self` is an array of the current user's data.
+ * This get included automatically, and so does not need to be passed in.
+ * @param string $condition a boolean expression composed of calls to AccessCondition functions.
+ * @param array[mixed] $params the parameters to be used when evaluating the expression.
+ * @return bool true if the condition is passed for the given parameters, otherwise returns false.
+ */
+ public function evaluateCondition($condition, $params) {
+ // Set the reserved `self` parameters.
+ // This replaces any values of `self` specified in the arguments, thus preventing them from being overridden in malicious user input.
+ // (For example, from an unfiltered request body).
+ $params['self'] = $this->user->export();
+
+ $this->nodeVisitor->setParams($params);
+
+ $code = "<?php $condition;";
+
+ if ($this->debug) {
+ $this->logger->debug("Evaluating access condition '$condition' with parameters:", $params);
+ }
+
+ // Traverse the parse tree, and execute any callbacks found using the supplied parameters.
+ // Replace the function node with the return value of the callback.
+ try {
+ // parse
+ $stmts = $this->parser->parse($code);
+
+ // traverse
+ $stmts = $this->traverser->traverse($stmts);
+
+ // Evaluate boolean statement. It is safe to use eval() here, because our expression has been reduced entirely to a boolean expression.
+ $expr = $this->prettyPrinter->prettyPrintExpr($stmts[0]);
+ $expr_eval = "return " . $expr . ";\n";
+ $result = eval($expr_eval);
+
+ if ($this->debug) {
+ $this->logger->debug("Expression '$expr' evaluates to " . ($result == TRUE ? "true" : "false"));
+ }
+
+ return $result;
+ } catch (PhpParserException $e) {
+ if ($this->debug) {
+ $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage());
+ }
+ return FALSE; // Access fails if the access condition can't be parsed.
+ } catch (AuthorizationException $e) {
+ if ($this->debug) {
+ $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage());
+ }
+ return FALSE;
+ }
+ }
+}
diff --git a/main/app/sprinkles/account/src/Authorize/AuthorizationException.php b/main/app/sprinkles/account/src/Authorize/AuthorizationException.php index 33f3d35..f93e847 100644 --- a/main/app/sprinkles/account/src/Authorize/AuthorizationException.php +++ b/main/app/sprinkles/account/src/Authorize/AuthorizationException.php @@ -1,24 +1,24 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authorize; - -use UserFrosting\Support\Exception\ForbiddenException; - -/** - * AuthorizationException class - * - * Exception for AccessConditionExpression. - * - * @author Alex Weissman (https://alexanderweissman.com) - * @see http://www.userfrosting.com/components/#authorization - */ -class AuthorizationException extends ForbiddenException -{ - -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authorize;
+
+use UserFrosting\Support\Exception\ForbiddenException;
+
+/**
+ * AuthorizationException class
+ *
+ * Exception for AccessConditionExpression.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see http://www.userfrosting.com/components/#authorization
+ */
+class AuthorizationException extends ForbiddenException
+{
+
+}
diff --git a/main/app/sprinkles/account/src/Authorize/AuthorizationManager.php b/main/app/sprinkles/account/src/Authorize/AuthorizationManager.php index f9fb196..487881f 100644 --- a/main/app/sprinkles/account/src/Authorize/AuthorizationManager.php +++ b/main/app/sprinkles/account/src/Authorize/AuthorizationManager.php @@ -1,153 +1,153 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authorize; - -use Interop\Container\ContainerInterface; -use UserFrosting\Sprinkle\Account\Database\Models\User; - -/** - * AuthorizationManager class. - * - * Manages a collection of access condition callbacks, and uses them to perform access control checks on user objects. - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AuthorizationManager -{ - /** - * @var ContainerInterface The global container object, which holds all your services. - */ - protected $ci; - - /** - * @var array[callable] An array of callbacks that accept some parameters and evaluate to true or false. - */ - protected $callbacks = []; - - /** - * Create a new AuthorizationManager object. - * - * @param ContainerInterface $ci The global container object, which holds all your services. - */ - public function __construct(ContainerInterface $ci, array $callbacks = []) { - $this->ci = $ci; - $this->callbacks = $callbacks; - } - - /** - * Register an authorization callback, which can then be used in permission conditions. - * - * To add additional callbacks, simply extend the `authorizer` service in your Sprinkle's service provider. - * @param string $name - * @param callable $callback - */ - public function addCallback($name, $callback) { - $this->callbacks[$name] = $callback; - return $this; - } - - /** - * Get all authorization callbacks. - * - * @return callable[] - */ - public function getCallbacks() { - return $this->callbacks; - } - - /** - * Checks whether or not a user has access on a particular permission slug. - * - * Determine if this user has access to the given $slug under the given $params. - * - * @param UserFrosting\Sprinkle\Account\Database\Models\User $user - * @param string $slug The permission slug to check for access. - * @param array $params [optional] An array of field names => values, specifying any additional data to provide the authorization module - * when determining whether or not this user has access. - * @return boolean True if the user has access, false otherwise. - */ - public function checkAccess(User $user, $slug, array $params = []) { - $debug = $this->ci->config['debug.auth']; - - if ($debug) { - $trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1); - $this->ci->authLogger->debug("Authorization check requested at: ", $trace); - $this->ci->authLogger->debug("Checking authorization for user {$user->id} ('{$user->user_name}') on permission '$slug'..."); - } - - if ($this->ci->authenticator->guest()) { - if ($debug) { - $this->ci->authLogger->debug("User is not logged in. Access denied."); - } - return FALSE; - } - - // The master (root) account has access to everything. - // Need to use loose comparison for now, because some DBs return `id` as a string. - - if ($user->id == $this->ci->config['reserved_user_ids.master']) { - if ($debug) { - $this->ci->authLogger->debug("User is the master (root) user. Access granted."); - } - return TRUE; - } - - // Find all permissions that apply to this user (via roles), and check if any evaluate to true. - $permissions = $user->getCachedPermissions(); - - if (empty($permissions) || !isset($permissions[$slug])) { - if ($debug) { - $this->ci->authLogger->debug("No matching permissions found. Access denied."); - } - return FALSE; - } - - $permissions = $permissions[$slug]; - - if ($debug) { - $this->ci->authLogger->debug("Found matching permissions: \n" . print_r($this->getPermissionsArrayDebugInfo($permissions), TRUE)); - } - - $nodeVisitor = new ParserNodeFunctionEvaluator($this->callbacks, $this->ci->authLogger, $debug); - $ace = new AccessConditionExpression($nodeVisitor, $user, $this->ci->authLogger, $debug); - - foreach ($permissions as $permission) { - $pass = $ace->evaluateCondition($permission->conditions, $params); - if ($pass) { - if ($debug) { - $this->ci->authLogger->debug("User passed conditions '{$permission->conditions}' . Access granted."); - } - return TRUE; - } - } - - if ($debug) { - $this->ci->authLogger->debug("User failed to pass any of the matched permissions. Access denied."); - } - - return FALSE; - } - - /** - * Remove extraneous information from the permission to reduce verbosity. - * - * @param array - * @return array - */ - protected function getPermissionsArrayDebugInfo($permissions) { - $permissionsInfo = []; - foreach ($permissions as $permission) { - $permissionData = array_only($permission->toArray(), ['id', 'slug', 'name', 'conditions', 'description']); - // Remove this until we can find an efficient way to only load these once during debugging - //$permissionData['roles_via'] = $permission->roles_via->pluck('id')->all(); - $permissionsInfo[] = $permissionData; - } - - return $permissionsInfo; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authorize;
+
+use Interop\Container\ContainerInterface;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+
+/**
+ * AuthorizationManager class.
+ *
+ * Manages a collection of access condition callbacks, and uses them to perform access control checks on user objects.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AuthorizationManager
+{
+ /**
+ * @var ContainerInterface The global container object, which holds all your services.
+ */
+ protected $ci;
+
+ /**
+ * @var array[callable] An array of callbacks that accept some parameters and evaluate to true or false.
+ */
+ protected $callbacks = [];
+
+ /**
+ * Create a new AuthorizationManager object.
+ *
+ * @param ContainerInterface $ci The global container object, which holds all your services.
+ */
+ public function __construct(ContainerInterface $ci, array $callbacks = []) {
+ $this->ci = $ci;
+ $this->callbacks = $callbacks;
+ }
+
+ /**
+ * Register an authorization callback, which can then be used in permission conditions.
+ *
+ * To add additional callbacks, simply extend the `authorizer` service in your Sprinkle's service provider.
+ * @param string $name
+ * @param callable $callback
+ */
+ public function addCallback($name, $callback) {
+ $this->callbacks[$name] = $callback;
+ return $this;
+ }
+
+ /**
+ * Get all authorization callbacks.
+ *
+ * @return callable[]
+ */
+ public function getCallbacks() {
+ return $this->callbacks;
+ }
+
+ /**
+ * Checks whether or not a user has access on a particular permission slug.
+ *
+ * Determine if this user has access to the given $slug under the given $params.
+ *
+ * @param UserFrosting\Sprinkle\Account\Database\Models\User $user
+ * @param string $slug The permission slug to check for access.
+ * @param array $params [optional] An array of field names => values, specifying any additional data to provide the authorization module
+ * when determining whether or not this user has access.
+ * @return boolean True if the user has access, false otherwise.
+ */
+ public function checkAccess(User $user, $slug, array $params = []) {
+ $debug = $this->ci->config['debug.auth'];
+
+ if ($debug) {
+ $trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1);
+ $this->ci->authLogger->debug("Authorization check requested at: ", $trace);
+ $this->ci->authLogger->debug("Checking authorization for user {$user->id} ('{$user->user_name}') on permission '$slug'...");
+ }
+
+ if ($this->ci->authenticator->guest()) {
+ if ($debug) {
+ $this->ci->authLogger->debug("User is not logged in. Access denied.");
+ }
+ return FALSE;
+ }
+
+ // The master (root) account has access to everything.
+ // Need to use loose comparison for now, because some DBs return `id` as a string.
+
+ if ($user->id == $this->ci->config['reserved_user_ids.master']) {
+ if ($debug) {
+ $this->ci->authLogger->debug("User is the master (root) user. Access granted.");
+ }
+ return TRUE;
+ }
+
+ // Find all permissions that apply to this user (via roles), and check if any evaluate to true.
+ $permissions = $user->getCachedPermissions();
+
+ if (empty($permissions) || !isset($permissions[$slug])) {
+ if ($debug) {
+ $this->ci->authLogger->debug("No matching permissions found. Access denied.");
+ }
+ return FALSE;
+ }
+
+ $permissions = $permissions[$slug];
+
+ if ($debug) {
+ $this->ci->authLogger->debug("Found matching permissions: \n" . print_r($this->getPermissionsArrayDebugInfo($permissions), TRUE));
+ }
+
+ $nodeVisitor = new ParserNodeFunctionEvaluator($this->callbacks, $this->ci->authLogger, $debug);
+ $ace = new AccessConditionExpression($nodeVisitor, $user, $this->ci->authLogger, $debug);
+
+ foreach ($permissions as $permission) {
+ $pass = $ace->evaluateCondition($permission->conditions, $params);
+ if ($pass) {
+ if ($debug) {
+ $this->ci->authLogger->debug("User passed conditions '{$permission->conditions}' . Access granted.");
+ }
+ return TRUE;
+ }
+ }
+
+ if ($debug) {
+ $this->ci->authLogger->debug("User failed to pass any of the matched permissions. Access denied.");
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Remove extraneous information from the permission to reduce verbosity.
+ *
+ * @param array
+ * @return array
+ */
+ protected function getPermissionsArrayDebugInfo($permissions) {
+ $permissionsInfo = [];
+ foreach ($permissions as $permission) {
+ $permissionData = array_only($permission->toArray(), ['id', 'slug', 'name', 'conditions', 'description']);
+ // Remove this until we can find an efficient way to only load these once during debugging
+ //$permissionData['roles_via'] = $permission->roles_via->pluck('id')->all();
+ $permissionsInfo[] = $permissionData;
+ }
+
+ return $permissionsInfo;
+ }
+}
diff --git a/main/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php b/main/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php index e0db07d..af26d9a 100644 --- a/main/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php +++ b/main/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php @@ -1,189 +1,189 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authorize; - -use Monolog\Logger; -use PhpParser\Node; -use PhpParser\NodeVisitorAbstract; -use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter; - -/** - * ParserNodeFunctionEvaluator class - * - * This class parses access control condition expressions. - * - * @author Alex Weissman (https://alexanderweissman.com) - * @see http://www.userfrosting.com/components/#authorization - */ -class ParserNodeFunctionEvaluator extends NodeVisitorAbstract -{ - - /** - * @var array[callable] An array of callback functions to be used when evaluating a condition expression. - */ - protected $callbacks; - /** - * @var \PhpParser\PrettyPrinter\Standard The PrettyPrinter object to use (initialized in the ctor) - */ - protected $prettyPrinter; - /** - * @var array The parameters to be used when evaluating the methods in the condition expression, as an array. - */ - protected $params = []; - - /** - * @var Logger - */ - protected $logger; - - /** - * @var bool Set to true if you want debugging information printed to the auth log. - */ - protected $debug; - - /** - * Create a new ParserNodeFunctionEvaluator object. - * - * @param array $params The parameters to be used when evaluating the methods in the condition expression, as an array. - * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations. - * @param bool $debug Set to true if you want debugging information printed to the auth log. - */ - public function __construct($callbacks, $logger, $debug = FALSE) { - $this->callbacks = $callbacks; - $this->prettyPrinter = new StandardPrettyPrinter; - $this->logger = $logger; - $this->debug = $debug; - $this->params = []; - } - - public function leaveNode(Node $node) { - // Look for function calls - if ($node instanceof \PhpParser\Node\Expr\FuncCall) { - $eval = new \PhpParser\Node\Scalar\LNumber; - - // Get the method name - $callbackName = $node->name->toString(); - // Get the method arguments - $argNodes = $node->args; - - $args = []; - $argsInfo = []; - foreach ($argNodes as $arg) { - $argString = $this->prettyPrinter->prettyPrintExpr($arg->value); - - // Debugger info - $currentArgInfo = [ - 'expression' => $argString - ]; - // Resolve parameter placeholders ('variable' names (either single-word or array-dot identifiers)) - if (($arg->value instanceof \PhpParser\Node\Expr\BinaryOp\Concat) || ($arg->value instanceof \PhpParser\Node\Expr\ConstFetch)) { - $value = $this->resolveParamPath($argString); - $currentArgInfo['type'] = "parameter"; - $currentArgInfo['resolved_value'] = $value; - // Resolve arrays - } else if ($arg->value instanceof \PhpParser\Node\Expr\Array_) { - $value = $this->resolveArray($arg); - $currentArgInfo['type'] = "array"; - $currentArgInfo['resolved_value'] = print_r($value, TRUE); - // Resolve strings - } else if ($arg->value instanceof \PhpParser\Node\Scalar\String_) { - $value = $arg->value->value; - $currentArgInfo['type'] = "string"; - $currentArgInfo['resolved_value'] = $value; - // Resolve numbers - } else if ($arg->value instanceof \PhpParser\Node\Scalar\DNumber) { - $value = $arg->value->value; - $currentArgInfo['type'] = "float"; - $currentArgInfo['resolved_value'] = $value; - } else if ($arg->value instanceof \PhpParser\Node\Scalar\LNumber) { - $value = $arg->value->value; - $currentArgInfo['type'] = "integer"; - $currentArgInfo['resolved_value'] = $value; - // Anything else is simply interpreted as its literal string value - } else { - $value = $argString; - $currentArgInfo['type'] = "unknown"; - $currentArgInfo['resolved_value'] = $value; - } - - $args[] = $value; - $argsInfo[] = $currentArgInfo; - } - - if ($this->debug) { - if (count($args)) { - $this->logger->debug("Evaluating callback '$callbackName' on: ", $argsInfo); - } else { - $this->logger->debug("Evaluating callback '$callbackName'..."); - } - } - - // Call the specified access condition callback with the specified arguments. - if (isset($this->callbacks[$callbackName]) && is_callable($this->callbacks[$callbackName])) { - $result = call_user_func_array($this->callbacks[$callbackName], $args); - } else { - throw new AuthorizationException("Authorization failed: Access condition method '$callbackName' does not exist."); - } - - if ($this->debug) { - $this->logger->debug("Result: " . ($result ? "1" : "0")); - } - - return new \PhpParser\Node\Scalar\LNumber($result ? "1" : "0"); - } - } - - public function setParams($params) { - $this->params = $params; - } - - /** - * Resolve an array expression in a condition expression into an actual array. - * - * @param string $arg the array, represented as a string. - * @return array[mixed] the array, as a plain ol' PHP array. - */ - private function resolveArray($arg) { - $arr = []; - $items = (array)$arg->value->items; - foreach ($items as $item) { - if ($item->key) { - $arr[$item->key] = $item->value->value; - } else { - $arr[] = $item->value->value; - } - } - return $arr; - } - - /** - * Resolve a parameter path (e.g. "user.id", "post", etc) into its value. - * - * @param string $path the name of the parameter to resolve, based on the parameters set in this object. - * @throws Exception the path could not be resolved. Path is malformed or key does not exist. - * @return mixed the value of the specified parameter. - */ - private function resolveParamPath($path) { - $pathTokens = explode(".", $path); - $value = $this->params; - foreach ($pathTokens as $token) { - $token = trim($token); - if (is_array($value) && isset($value[$token])) { - $value = $value[$token]; - continue; - } else if (is_object($value) && isset($value->$token)) { - $value = $value->$token; - continue; - } else { - throw new AuthorizationException("Cannot resolve the path \"$path\". Error at token \"$token\"."); - } - } - return $value; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authorize;
+
+use Monolog\Logger;
+use PhpParser\Node;
+use PhpParser\NodeVisitorAbstract;
+use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;
+
+/**
+ * ParserNodeFunctionEvaluator class
+ *
+ * This class parses access control condition expressions.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see http://www.userfrosting.com/components/#authorization
+ */
+class ParserNodeFunctionEvaluator extends NodeVisitorAbstract
+{
+
+ /**
+ * @var array[callable] An array of callback functions to be used when evaluating a condition expression.
+ */
+ protected $callbacks;
+ /**
+ * @var \PhpParser\PrettyPrinter\Standard The PrettyPrinter object to use (initialized in the ctor)
+ */
+ protected $prettyPrinter;
+ /**
+ * @var array The parameters to be used when evaluating the methods in the condition expression, as an array.
+ */
+ protected $params = [];
+
+ /**
+ * @var Logger
+ */
+ protected $logger;
+
+ /**
+ * @var bool Set to true if you want debugging information printed to the auth log.
+ */
+ protected $debug;
+
+ /**
+ * Create a new ParserNodeFunctionEvaluator object.
+ *
+ * @param array $params The parameters to be used when evaluating the methods in the condition expression, as an array.
+ * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations.
+ * @param bool $debug Set to true if you want debugging information printed to the auth log.
+ */
+ public function __construct($callbacks, $logger, $debug = FALSE) {
+ $this->callbacks = $callbacks;
+ $this->prettyPrinter = new StandardPrettyPrinter;
+ $this->logger = $logger;
+ $this->debug = $debug;
+ $this->params = [];
+ }
+
+ public function leaveNode(Node $node) {
+ // Look for function calls
+ if ($node instanceof \PhpParser\Node\Expr\FuncCall) {
+ $eval = new \PhpParser\Node\Scalar\LNumber;
+
+ // Get the method name
+ $callbackName = $node->name->toString();
+ // Get the method arguments
+ $argNodes = $node->args;
+
+ $args = [];
+ $argsInfo = [];
+ foreach ($argNodes as $arg) {
+ $argString = $this->prettyPrinter->prettyPrintExpr($arg->value);
+
+ // Debugger info
+ $currentArgInfo = [
+ 'expression' => $argString
+ ];
+ // Resolve parameter placeholders ('variable' names (either single-word or array-dot identifiers))
+ if (($arg->value instanceof \PhpParser\Node\Expr\BinaryOp\Concat) || ($arg->value instanceof \PhpParser\Node\Expr\ConstFetch)) {
+ $value = $this->resolveParamPath($argString);
+ $currentArgInfo['type'] = "parameter";
+ $currentArgInfo['resolved_value'] = $value;
+ // Resolve arrays
+ } else if ($arg->value instanceof \PhpParser\Node\Expr\Array_) {
+ $value = $this->resolveArray($arg);
+ $currentArgInfo['type'] = "array";
+ $currentArgInfo['resolved_value'] = print_r($value, TRUE);
+ // Resolve strings
+ } else if ($arg->value instanceof \PhpParser\Node\Scalar\String_) {
+ $value = $arg->value->value;
+ $currentArgInfo['type'] = "string";
+ $currentArgInfo['resolved_value'] = $value;
+ // Resolve numbers
+ } else if ($arg->value instanceof \PhpParser\Node\Scalar\DNumber) {
+ $value = $arg->value->value;
+ $currentArgInfo['type'] = "float";
+ $currentArgInfo['resolved_value'] = $value;
+ } else if ($arg->value instanceof \PhpParser\Node\Scalar\LNumber) {
+ $value = $arg->value->value;
+ $currentArgInfo['type'] = "integer";
+ $currentArgInfo['resolved_value'] = $value;
+ // Anything else is simply interpreted as its literal string value
+ } else {
+ $value = $argString;
+ $currentArgInfo['type'] = "unknown";
+ $currentArgInfo['resolved_value'] = $value;
+ }
+
+ $args[] = $value;
+ $argsInfo[] = $currentArgInfo;
+ }
+
+ if ($this->debug) {
+ if (count($args)) {
+ $this->logger->debug("Evaluating callback '$callbackName' on: ", $argsInfo);
+ } else {
+ $this->logger->debug("Evaluating callback '$callbackName'...");
+ }
+ }
+
+ // Call the specified access condition callback with the specified arguments.
+ if (isset($this->callbacks[$callbackName]) && is_callable($this->callbacks[$callbackName])) {
+ $result = call_user_func_array($this->callbacks[$callbackName], $args);
+ } else {
+ throw new AuthorizationException("Authorization failed: Access condition method '$callbackName' does not exist.");
+ }
+
+ if ($this->debug) {
+ $this->logger->debug("Result: " . ($result ? "1" : "0"));
+ }
+
+ return new \PhpParser\Node\Scalar\LNumber($result ? "1" : "0");
+ }
+ }
+
+ public function setParams($params) {
+ $this->params = $params;
+ }
+
+ /**
+ * Resolve an array expression in a condition expression into an actual array.
+ *
+ * @param string $arg the array, represented as a string.
+ * @return array[mixed] the array, as a plain ol' PHP array.
+ */
+ private function resolveArray($arg) {
+ $arr = [];
+ $items = (array)$arg->value->items;
+ foreach ($items as $item) {
+ if ($item->key) {
+ $arr[$item->key] = $item->value->value;
+ } else {
+ $arr[] = $item->value->value;
+ }
+ }
+ return $arr;
+ }
+
+ /**
+ * Resolve a parameter path (e.g. "user.id", "post", etc) into its value.
+ *
+ * @param string $path the name of the parameter to resolve, based on the parameters set in this object.
+ * @throws Exception the path could not be resolved. Path is malformed or key does not exist.
+ * @return mixed the value of the specified parameter.
+ */
+ private function resolveParamPath($path) {
+ $pathTokens = explode(".", $path);
+ $value = $this->params;
+ foreach ($pathTokens as $token) {
+ $token = trim($token);
+ if (is_array($value) && isset($value[$token])) {
+ $value = $value[$token];
+ continue;
+ } else if (is_object($value) && isset($value->$token)) {
+ $value = $value->$token;
+ continue;
+ } else {
+ throw new AuthorizationException("Cannot resolve the path \"$path\". Error at token \"$token\".");
+ }
+ }
+ return $value;
+ }
+}
|