PermissionGrantingStrategy.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Security\Acl\Domain;
  11. use Symfony\Component\Security\Acl\Exception\NoAceFoundException;
  12. use Symfony\Component\Security\Acl\Model\AclInterface;
  13. use Symfony\Component\Security\Acl\Model\AuditLoggerInterface;
  14. use Symfony\Component\Security\Acl\Model\EntryInterface;
  15. use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
  16. use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface;
  17. /**
  18. * The permission granting strategy to apply to the access control list.
  19. *
  20. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  21. */
  22. class PermissionGrantingStrategy implements PermissionGrantingStrategyInterface
  23. {
  24. const EQUAL = 'equal';
  25. const ALL = 'all';
  26. const ANY = 'any';
  27. private $auditLogger;
  28. /**
  29. * Sets the audit logger.
  30. *
  31. * @param AuditLoggerInterface $auditLogger
  32. */
  33. public function setAuditLogger(AuditLoggerInterface $auditLogger)
  34. {
  35. $this->auditLogger = $auditLogger;
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. public function isGranted(AclInterface $acl, array $masks, array $sids, $administrativeMode = false)
  41. {
  42. try {
  43. try {
  44. $aces = $acl->getObjectAces();
  45. if (!$aces) {
  46. throw new NoAceFoundException();
  47. }
  48. return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode);
  49. } catch (NoAceFoundException $e) {
  50. $aces = $acl->getClassAces();
  51. if (!$aces) {
  52. throw $e;
  53. }
  54. return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode);
  55. }
  56. } catch (NoAceFoundException $e) {
  57. if ($acl->isEntriesInheriting() && null !== $parentAcl = $acl->getParentAcl()) {
  58. return $parentAcl->isGranted($masks, $sids, $administrativeMode);
  59. }
  60. throw $e;
  61. }
  62. }
  63. /**
  64. * {@inheritdoc}
  65. */
  66. public function isFieldGranted(AclInterface $acl, $field, array $masks, array $sids, $administrativeMode = false)
  67. {
  68. try {
  69. try {
  70. $aces = $acl->getObjectFieldAces($field);
  71. if (!$aces) {
  72. throw new NoAceFoundException();
  73. }
  74. return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode);
  75. } catch (NoAceFoundException $e) {
  76. $aces = $acl->getClassFieldAces($field);
  77. if (!$aces) {
  78. throw $e;
  79. }
  80. return $this->hasSufficientPermissions($acl, $aces, $masks, $sids, $administrativeMode);
  81. }
  82. } catch (NoAceFoundException $e) {
  83. if ($acl->isEntriesInheriting() && null !== $parentAcl = $acl->getParentAcl()) {
  84. return $parentAcl->isFieldGranted($field, $masks, $sids, $administrativeMode);
  85. }
  86. throw $e;
  87. }
  88. }
  89. /**
  90. * Makes an authorization decision.
  91. *
  92. * The order of ACEs, and SIDs is significant; the order of permission masks
  93. * not so much. It is important to note that the more specific security
  94. * identities should be at the beginning of the SIDs array in order for this
  95. * strategy to produce intuitive authorization decisions.
  96. *
  97. * First, we will iterate over permissions, then over security identities.
  98. * For each combination of permission, and identity we will test the
  99. * available ACEs until we find one which is applicable.
  100. *
  101. * The first applicable ACE will make the ultimate decision for the
  102. * permission/identity combination. If it is granting, this method will return
  103. * true, if it is denying, the method will continue to check the next
  104. * permission/identity combination.
  105. *
  106. * This process is repeated until either a granting ACE is found, or no
  107. * permission/identity combinations are left. Finally, we will either throw
  108. * an NoAceFoundException, or deny access.
  109. *
  110. * @param AclInterface $acl
  111. * @param EntryInterface[] $aces An array of ACE to check against
  112. * @param array $masks An array of permission masks
  113. * @param SecurityIdentityInterface[] $sids An array of SecurityIdentityInterface implementations
  114. * @param bool $administrativeMode True turns off audit logging
  115. *
  116. * @return bool true, or false; either granting, or denying access respectively.
  117. *
  118. * @throws NoAceFoundException
  119. */
  120. private function hasSufficientPermissions(AclInterface $acl, array $aces, array $masks, array $sids, $administrativeMode)
  121. {
  122. $firstRejectedAce = null;
  123. foreach ($masks as $requiredMask) {
  124. foreach ($sids as $sid) {
  125. foreach ($aces as $ace) {
  126. if ($sid->equals($ace->getSecurityIdentity()) && $this->isAceApplicable($requiredMask, $ace)) {
  127. if ($ace->isGranting()) {
  128. if (!$administrativeMode && null !== $this->auditLogger) {
  129. $this->auditLogger->logIfNeeded(true, $ace);
  130. }
  131. return true;
  132. }
  133. if (null === $firstRejectedAce) {
  134. $firstRejectedAce = $ace;
  135. }
  136. break 2;
  137. }
  138. }
  139. }
  140. }
  141. if (null !== $firstRejectedAce) {
  142. if (!$administrativeMode && null !== $this->auditLogger) {
  143. $this->auditLogger->logIfNeeded(false, $firstRejectedAce);
  144. }
  145. return false;
  146. }
  147. throw new NoAceFoundException();
  148. }
  149. /**
  150. * Determines whether the ACE is applicable to the given permission/security
  151. * identity combination.
  152. *
  153. * Per default, we support three different comparison strategies.
  154. *
  155. * Strategy ALL:
  156. * The ACE will be considered applicable when all the turned-on bits in the
  157. * required mask are also turned-on in the ACE mask.
  158. *
  159. * Strategy ANY:
  160. * The ACE will be considered applicable when any of the turned-on bits in
  161. * the required mask is also turned-on the in the ACE mask.
  162. *
  163. * Strategy EQUAL:
  164. * The ACE will be considered applicable when the bitmasks are equal.
  165. *
  166. * @param int $requiredMask
  167. * @param EntryInterface $ace
  168. *
  169. * @return bool
  170. *
  171. * @throws \RuntimeException if the ACE strategy is not supported
  172. */
  173. private function isAceApplicable($requiredMask, EntryInterface $ace)
  174. {
  175. $strategy = $ace->getStrategy();
  176. if (self::ALL === $strategy) {
  177. return $requiredMask === ($ace->getMask() & $requiredMask);
  178. } elseif (self::ANY === $strategy) {
  179. return 0 !== ($ace->getMask() & $requiredMask);
  180. } elseif (self::EQUAL === $strategy) {
  181. return $requiredMask === $ace->getMask();
  182. }
  183. throw new \RuntimeException(sprintf('The strategy "%s" is not supported.', $strategy));
  184. }
  185. }