_rootPolicySet = new PolicySet(PolicySet::COMBINING_DENY_OVERRIDES); } // // Setters and Getters // /** * Set the default decision if none of the * policies in the root policy set applies. * * @param int $decisionIfNoPolicyApplies */ public function setDecisionIfNoPolicyApplies($decisionIfNoPolicyApplies) { $this->_rootPolicySet->setEffectIfNoPolicyApplies($decisionIfNoPolicyApplies); } /** * Add an authorization policy or a policy set. * * @param AuthorizationPolicy|PolicySet $policyOrPolicySet * @param bool $addToTop whether to insert the new policy * to the top of the list. */ public function addPolicy($policyOrPolicySet, $addToTop = false) { $this->_rootPolicySet->addPolicy($policyOrPolicySet, $addToTop); } /** * Add an authorization message * * @param string $message */ public function addAuthorizationMessage($message) { $this->_authorizationMessages[] = $message; } /** * Return all authorization messages * * @return array */ public function getAuthorizationMessages() { return $this->_authorizationMessages; } /** * Retrieve an object from the authorized context * * @param int $assocType * * @return mixed will return null if the context * for the given assoc type does not exist. */ public function &getAuthorizedContextObject($assocType) { if (isset($this->_authorizedContext[$assocType])) { return $this->_authorizedContext[$assocType]; } else { $nullVar = null; return $nullVar; } } /** * Get the authorized context. * * @return array */ public function &getAuthorizedContext() { return $this->_authorizedContext; } // // Public methods // /** * Take an authorization decision. * * @return int one of AUTHORIZATION_PERMIT or * AUTHORIZATION_DENY. */ public function decide() { // Decide the root policy set which will recursively decide // all nested policy sets and return a single decision. $callOnDeny = null; $decision = $this->_decidePolicySet($this->_rootPolicySet, $callOnDeny); assert($decision !== self::AUTHORIZATION_NOT_APPLICABLE); // Call the "call on deny" advice if ($decision === AuthorizationPolicy::AUTHORIZATION_DENY && !is_null($callOnDeny)) { assert(is_array($callOnDeny) && count($callOnDeny) == 3); [$classOrObject, $method, $parameters] = $callOnDeny; $methodCall = [$classOrObject, $method]; assert(is_callable($methodCall)); call_user_func_array($methodCall, $parameters); } return $decision; } // // Private helper methods // /** * Recursively decide the given policy set. * * @param PolicySet $policySet * @param int $callOnDeny A "call-on-deny" advice will be passed * back by reference if found. * * @return int one of the AUTHORIZATION_* values. */ public function _decidePolicySet(&$policySet, &$callOnDeny) { // Configure the decision algorithm. $combiningAlgorithm = $policySet->getCombiningAlgorithm(); switch ($combiningAlgorithm) { case PolicySet::COMBINING_DENY_OVERRIDES: $dominantEffect = AuthorizationPolicy::AUTHORIZATION_DENY; $overriddenEffect = AuthorizationPolicy::AUTHORIZATION_PERMIT; break; case PolicySet::COMBINING_PERMIT_OVERRIDES: $dominantEffect = AuthorizationPolicy::AUTHORIZATION_PERMIT; $overriddenEffect = AuthorizationPolicy::AUTHORIZATION_DENY; break; default: assert(false); } // Set the default decision. $decision = $policySet->getEffectIfNoPolicyApplies(); // The following flag will record when the // overridden decision state is returned by // at least one policy. $decidedByOverriddenEffect = false; // Separated from below for bug #6821. $context = & $this->getAuthorizedContext(); // Go through all policies within the policy set // and combine them with the configured algorithm. foreach ($policySet->getPolicies() as $policy) { // Treat policies and policy sets differently. switch (true) { case $policy instanceof AuthorizationPolicy: // Make sure that the policy can access the latest authorized context. // NB: The authorized context is set by reference. This means that it // will change globally if changed by the policy which is intended // behavior so that policies can access authorized objects provided // by policies called earlier in the authorization process. $policy->setAuthorizedContext($context); // Check whether the policy applies. if ($policy->applies()) { // If the policy applies then retrieve its effect. $effect = $policy->effect(); } else { $effect = self::AUTHORIZATION_NOT_APPLICABLE; } break; case $policy instanceof PolicySet: // We found a nested policy set. $effect = $this->_decidePolicySet($policy, $callOnDeny); break; default: assert(false); } // Try the next policy if this policy didn't apply. if ($effect === self::AUTHORIZATION_NOT_APPLICABLE) { continue; } assert($effect === AuthorizationPolicy::AUTHORIZATION_PERMIT || $effect === AuthorizationPolicy::AUTHORIZATION_DENY); // "Deny" decision may cause a message to the end user. if ($policy instanceof AuthorizationPolicy && $effect == AuthorizationPolicy::AUTHORIZATION_DENY && $policy->hasAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE)) { $this->addAuthorizationMessage($policy->getAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_DENY_MESSAGE)); } // Process the effect. if ($effect === $overriddenEffect) { $decidedByOverriddenEffect = true; } else { // In case of a "deny overrides" we allow a "call-on-deny" advice. if ($policy instanceof AuthorizationPolicy && $dominantEffect == AuthorizationPolicy::AUTHORIZATION_DENY && $policy->hasAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_CALL_ON_DENY)) { $callOnDeny = $policy->getAdvice(AuthorizationPolicy::AUTHORIZATION_ADVICE_CALL_ON_DENY); } // Only one dominant effect overrides all other effects // so we don't even have to evaluate other policies. return $dominantEffect; } } // Only return an overridden effect if at least one // policy returned that effect and none returned the // dominant effect. if ($decidedByOverriddenEffect) { $decision = $overriddenEffect; } return $decision; } } if (!PKP_STRICT_MODE) { class_alias('\PKP\security\authorization\AuthorizationDecisionManager', '\AuthorizationDecisionManager'); define('AUTHORIZATION_NOT_APPLICABLE', AuthorizationDecisionManager::AUTHORIZATION_NOT_APPLICABLE); }