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