HPCloud-PHP  1.2.0
PHP bindings for HPCloud and OpenStack services.
 All Classes Namespaces Files Functions Variables Pages
ACL.php
Go to the documentation of this file.
1 <?php
2 /* ============================================================================
3 (c) Copyright 2012 Hewlett-Packard Development Company, L.P.
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights to
7 use, copy, modify, merge,publish, distribute, sublicense, and/or sell copies of
8 the Software, and to permit persons to whom the Software is furnished to do so,
9 subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21 ============================================================================ */
22 /**
23  * @file
24  *
25  * Contains the class for manipulating ObjectStorage ACL strings.
26  */
27 
29 
30 /**
31  * Access control list for object storage.
32  *
33  * @b EXPERIMENTAL: This is bassed on a feature of Swift that is likely to
34  * change. Most of this is based on undocmented features of the API
35  * discovered both in the Python docs and in discussions by various
36  * members of the OpenStack community.
37  *
38  * Swift access control rules are broken into two permissions: READ and
39  * WRITE. Read permissions grant the user the ability to access the file
40  * (using verbs like GET and HEAD), while WRITE permissions allow any
41  * modification operation. WRITE does not imply READ.
42  *
43  * In the current implementation of Swift, access can be assigned based
44  * on two different factors:
45  *
46  * - @b Accounts: Access can be granted to specific accounts, and within
47  * those accounts, can be further specified to specific users. See the
48  * addAccount() method for details on this.
49  * - @b Referrers: Access can be granted based on host names or host name
50  * patterns. For example, only subdomains of <tt>*.example.com</tt> may be
51  * granted READ access to a particular object.
52  *
53  * ACLs are transmitted within the HTTP headers for an object or
54  * container. Two headers are used: @c X-Container-Read for READ rules, and
55  * @c X-Container-Write for WRITE rules. Each header may have a chain of
56  * rules.
57  *
58  * @b Examples
59  *
60  * For most casual cases, only the static constructor functions are
61  * used. For example, an ACL that does not grant any public access can
62  * be created with a single call:
63  *
64  * @code
65  * <?php
66  * $acl = ACL::makeNonPublic();
67  * ?>
68  * @endcode
69  *
70  * Public read access is granted like this:
71  *
72  * @code
73  * <?php
74  * $acl = ACL::makePublic();
75  * ?>
76  * @endcode
77  *
78  * (Note that in both cases, what is returned is an instance of an ACL with
79  * all of the necessary configuration done.)
80  *
81  * Sometimes you will need more sophisticated access control rules. The
82  * following grants READ access to anyone coming from an @c example.com
83  * domain, but grants WRITE access only to the account @c admins:
84  *
85  * @code
86  * <?php
87  * $acl = new ACL();
88  *
89  * // Grant READ to example.com users.
90  * $acl->addReferrer(ACL::READ, '*.example.com');
91  *
92  * // Allow only people in the account 'admins' access to
93  * // write.
94  * $acl->addAccount(ACL::WRITE, 'admins');
95  *
96  * // Allow example.com users to view the container
97  * // listings:
98  * $acl->allowListings();
99  *
100  * ?>
101  * @endcode
102  *
103  *
104  * Notes
105  *
106  * - The current implementation does not do any validation of rules.
107  * This will likely change in the future.
108  * - There is discussion in OpenStack about providing a different or
109  * drastically improved ACL mechanism. This class would then be
110  * replaced by a new mechanism.
111  *
112  * For a detailed description of the rules for ACL creation,
113  * see http://swift.openstack.org/misc.html#acls
114  */
115 class ACL {
116 
117  /**
118  * Read flag.
119  *
120  * This is for an ACL of the READ type.
121  */
122  const READ = 1;
123  /**
124  * Write flag.
125  *
126  * This is for an ACL of the WRITE type.
127  */
128  const WRITE = 2;
129  /**
130  * Flag for READ and WRITE.
131  *
132  * This is equivalent to <tt>ACL::READ | ACL::WRITE</tt>
133  */
134  const READ_WRITE = 3; // self::READ | self::WRITE;
135 
136  /**
137  * Header string for a read flag.
138  */
139  const HEADER_READ = 'X-Container-Read';
140  /**
141  * Header string for a write flag.
142  */
143  const HEADER_WRITE = 'X-Container-Write';
144 
145  protected $rules = array();
146 
147  /**
148  * Allow READ access to the public.
149  *
150  * This grants the following:
151  *
152  * - READ to any host, with container listings.
153  *
154  * @retval HPCloud::Storage::ObjectStorage::ACL
155  * @return \HPCloud\Storage\ObjectStorage\ACL
156  * an ACL object with the appopriate permissions set.
157  */
158  public static function makePublic() {
159  $acl = new ACL();
160  $acl->addReferrer(self::READ, '*');
161  $acl->allowListings();
162 
163  return $acl;
164  }
165 
166  /**
167  * Disallow all public access.
168  *
169  * Non-public is the same as private. Private, however, is a reserved
170  * word in PHP.
171  *
172  * This does not grant any permissions. OpenStack interprets an object
173  * with no permissions as a private object.
174  *
175  * @retval HPCloud::Storage::ObjectStorage::ACL
176  * @return \HPCloud\Storage\ObjectStorage\ACL
177  * an ACL object with the appopriate permissions set.
178  */
179  public static function makeNonPublic() {
180  // Default ACL is private.
181  return new ACL();
182  }
183 
184  /**
185  * Alias of ACL::makeNonPublic().
186  */
187  public static function makePrivate() {
188  return self::makeNonPublic();
189  }
190 
191  /**
192  * Given a list of headers, get the ACL info.
193  *
194  * This is a utility for processing headers and discovering any ACLs embedded
195  * inside the headers.
196  *
197  * @param array $headers
198  * An associative array of headers.
199  * @retval HPCloud::Storage::ObjectStorage::ACL
200  * @return \HPCloud\Storage\ObjectStorage\ACL
201  * A new ACL.
202  */
203  public static function newFromHeaders($headers) {
204  $acl = new ACL();
205 
206  // READ rules.
207  $rules = array();
208  if (!empty($headers[self::HEADER_READ])) {
209  $read = $headers[self::HEADER_READ];
210  $rules = explode(',', $read);
211  foreach ($rules as $rule) {
212  $ruleArray = self::parseRule(self::READ, $rule);
213  if (!empty($ruleArray)) {
214  $acl->rules[] = $ruleArray;
215  }
216  }
217  }
218 
219  // WRITE rules.
220  $rules = array();
221  if (!empty($headers[self::HEADER_WRITE])) {
222  $write = $headers[self::HEADER_WRITE];
223  $rules = explode(',', $write);
224  foreach ($rules as $rule) {
225  $ruleArray = self::parseRule(self::WRITE, $rule);
226  if (!empty($ruleArray)) {
227  $acl->rules[] = $ruleArray;
228  }
229  }
230  }
231 
232  //throw new \Exception(print_r($acl->rules(), TRUE));
233 
234  return $acl;
235  }
236 
237  /**
238  * Parse a rule.
239  *
240  * This attempts to parse an ACL rule. It is not particularly
241  * fault-tolerant.
242  *
243  * @param int $perm
244  * The permission (ACL::READ, ACL::WRITE).
245  * @param string $rule
246  * The string rule to parse.
247  * @retval array
248  * @return array
249  * The rule as an array.
250  */
251  public static function parseRule($perm, $rule) {
252  // This regular expression generates the following:
253  //
254  // array(
255  // 0 => ENTIRE RULE
256  // 1 => WHOLE EXPRESSION, no whitespace
257  // 2 => domain compontent
258  // 3 => 'rlistings', set if .rincludes is the directive
259  // 4 => account name
260  // 5 => :username
261  // 6 => username
262  // );
263  $exp = '/^\s*(.r:([a-zA-Z0-9\*\-\.]+)|\.(rlistings)|([a-zA-Z0-9]+)(\:([a-zA-Z0-9]+))?)\s*$/';
264 
265  $matches = array();
266  preg_match($exp, $rule, $matches);
267 
268  $entry = array('mask' => $perm);
269  if (!empty($matches[2])) {
270  $entry['host'] = $matches[2];
271  }
272  elseif (!empty($matches[3])) {
273  $entry['rlistings'] = TRUE;
274  }
275  elseif (!empty($matches[4])) {
276  $entry['account'] = $matches[4];
277  if (!empty($matches[6])) {
278  $entry['user'] = $matches[6];
279  }
280  }
281 
282  return $entry;
283  }
284 
285  /**
286  * Create a new ACL.
287  *
288  * This creates an empty ACL with no permissions granted. When no
289  * permissions are granted, the file is effectively private
290  * (nonPublic()).
291  *
292  * Use add* methods to add permissions.
293  */
294  public function __construct() {}
295 
296  /**
297  * Grant ACL access to an account.
298  *
299  * Optionally, a user may be given to further limit access.
300  *
301  * This is used to restrict access to a particular account and, if so
302  * specified, a specific user on that account.
303  *
304  * If just an account is given, any user on that account will be
305  * automatically granted access.
306  *
307  * If an account and a user is given, only that user of the account is
308  * granted access.
309  *
310  * If $user is an array, every user in the array will be granted
311  * access under the provided account. That is, for each user in the
312  * array, an entry of the form \c account:user will be generated in the
313  * final ACL.
314  *
315  * At this time there does not seem to be a way to grant global write
316  * access to an object.
317  *
318  * @param int $perm
319  * ACL::READ, ACL::WRITE or ACL::READ_WRITE (which is the same as
320  * ACL::READ|ACL::WRITE).
321  * @param string $account
322  * The name of the account.
323  * @param mixed $user
324  * The name of the user, or optionally an indexed array of user
325  * names.
326  *
327  * @retval HPCloud::Storage::ObjectStorage::ACL
328  * @return \HPCloud\Storage\ObjectStorage\ACL
329  * $this for current object so the method can be used in chaining.
330  */
331  public function addAccount($perm, $account, $user = NULL) {
332  $rule = array('account' => $account);
333 
334  if (!empty($user)) {
335  $rule['user'] = $user;
336  }
337 
338  $this->addRule($perm, $rule);
339 
340  return $this;
341  }
342 
343  /**
344  * Allow (or deny) a hostname or host pattern.
345  *
346  * In current Swift implementations, only READ rules can have host
347  * patterns. WRITE permissions cannot be granted to hostnames.
348  *
349  * Formats:
350  * - Allow any host: '*'
351  * - Allow exact host: 'www.example.com'
352  * - Allow hosts in domain: '.example.com'
353  * - Disallow exact host: '-www.example.com'
354  * - Disallow hosts in domain: '-.example.com'
355  *
356  * Note that a simple minus sign ('-') is illegal, though it seems it
357  * should be "disallow all hosts."
358  *
359  * @param string $perm
360  * The permission being granted. One of ACL:READ, ACL::WRITE, or ACL::READ_WRITE.
361  * @param string $host
362  * A host specification string as described above.
363  *
364  * @retval HPCloud::Storage::ObjectStorage::ACL
365  * @return \HPCloud\Storage\ObjectStorage\ACL
366  * $this for current object so the method can be used in chaining.
367  */
368  public function addReferrer($perm, $host = '*') {
369  $this->addRule($perm, array('host' => $host));
370 
371  return $this;
372  }
373 
374  /**
375  * Add a rule to the appropriate stack of rules.
376  *
377  * @param int $perm
378  * One of the predefined permission constants.
379  * @param array $rule
380  * A rule array.
381  *
382  * @retval HPCloud::Storage::ObjectStorage::ACL
383  * @return \HPCloud\Storage\ObjectStorage\ACL
384  * $this for current object so the method can be used in chaining.
385  */
386  protected function addRule($perm, $rule) {
387  $rule['mask'] = $perm;
388 
389  $this->rules[] = $rule;
390 
391  return $this;
392  }
393 
394  /**
395  * Allow hosts with READ permissions to list a container's content.
396  *
397  * By default, granting READ permission on a container does not grant
398  * permission to list the contents of a container. Setting the
399  * ACL::allowListings() permission will allow matching hosts to also list
400  * the contents of a container.
401  *
402  * In the current Swift implementation, there is no mechanism for
403  * allowing some hosts to get listings, while denying others.
404  *
405  * @retval HPCloud::Storage::ObjectStorage::ACL
406  * @return \HPCloud\Storage\ObjectStorage\ACL
407  * $this for current object so the method can be used in chaining.
408  */
409  public function allowListings() {
410 
411  $this->rules[] = array(
412  'mask' => self::READ,
413  'rlistings' => TRUE,
414  );
415 
416  return $this;
417  }
418 
419  /**
420  * Get the rules array for this ACL.
421  *
422  * @retval array
423  * @return array
424  * An array of associative arrays of rules.
425  */
426  public function rules() {
427  return $this->rules;
428  }
429 
430  /**
431  * Generate HTTP headers for this ACL.
432  *
433  * If this is called on an empty object, an empty set of headers is
434  * returned.
435  */
436  public function headers() {
437  $headers = array();
438  $readers = array();
439  $writers = array();
440 
441  // Create the rule strings. We need two copies, one for READ and
442  // one for WRITE.
443  foreach ($this->rules as $rule) {
444  // We generate read and write rules separately so that the
445  // generation logic has a chance to respond to the differences
446  // allowances for READ and WRITE ACLs.
447  if (self::READ & $rule['mask']) {
448  $ruleStr = $this->ruleToString(self::READ, $rule);
449  if (!empty($ruleStr)) {
450  $readers[] = $ruleStr;
451  }
452  }
453  if (self::WRITE & $rule['mask']) {
454  $ruleStr = $this->ruleToString(self::WRITE, $rule);
455  if (!empty($ruleStr)) {
456  $writers[] = $ruleStr;
457  }
458  }
459  }
460 
461  // Create the HTTP headers.
462  if (!empty($readers)) {
463  $headers[self::HEADER_READ] = implode(',', $readers);
464  }
465  if (!empty($writers)) {
466  $headers[self::HEADER_WRITE] = implode(',', $writers);
467  }
468 
469  return $headers;
470  }
471 
472  /**
473  * Convert a rule to a string.
474  *
475  * @param int $perm
476  * The permission for which to generate the rule.
477  * @param array $rule
478  * A rule array.
479  */
480  protected function ruleToString($perm, $rule) {
481 
482  // Some rules only apply to READ.
483  if (self::READ & $perm) {
484 
485  // Host rule.
486  if (!empty($rule['host'])) {
487  return '.r:' . $rule['host'];
488  }
489 
490  // Listing rule.
491  if (!empty($rule['rlistings'])) {
492  return '.rlistings';
493  }
494  }
495 
496  // READ and WRITE both allow account/user rules.
497  if (!empty($rule['account'])) {
498 
499  // Just an account name.
500  if (empty($rule['user'])) {
501  return $rule['account'];
502  }
503 
504  // Account + multiple users.
505  elseif (is_array($rule['user'])) {
506  $buffer = array();
507  foreach ($rule['user'] as $user) {
508  $buffer[] = $rule['account'] . ':' . $user;
509  }
510  return implode(',', $buffer);
511 
512  }
513 
514  // Account + one user.
515  else {
516  return $rule['account'] . ':' . $rule['user'];
517  }
518  }
519  }
520 
521  /**
522  * Check if the ACL marks this private.
523  *
524  * This returns TRUE only if this ACL does not grant any permissions
525  * at all.
526  *
527  * @retval boolean
528  * @return boolean
529  * TRUE if this is private (non-public), FALSE if
530  * any permissions are granted via this ACL.
531  */
532  public function isNonPublic() {
533  return empty($this->rules);
534  }
535 
536  /**
537  * Alias of isNonPublic().
538  */
539  public function isPrivate() {
540  return $this->isNonPublic();
541  }
542 
543  /**
544  * Check whether this object allows public reading.
545  *
546  * This will return TRUE the ACL allows (a) any host to access
547  * the item, and (b) it allows container listings.
548  *
549  * This checks whether the object allows public reading,
550  * not whether it is ONLY allowing public reads.
551  *
552  * See ACL::makePublic().
553  */
554  public function isPublic() {
555  $allowsAllHosts = FALSE;
556  $allowsRListings = FALSE;
557  foreach ($this->rules as $rule) {
558  if (self::READ & $rule['mask']) {
559  if (!empty($rule['rlistings'])) {
560  $allowsRListings = TRUE;
561  }
562  elseif(!empty($rule['host']) && trim($rule['host']) == '*') {
563  $allowsAllHosts = TRUE;
564  }
565  }
566  }
567  return $allowsAllHosts && $allowsRListings;
568  }
569 
570  /**
571  * Implements the magic __toString() PHP function.
572  *
573  * This allows you to <tt>print $acl</tt> and get back
574  * a pretty string.
575  *
576  * @retval string
577  * @return string
578  * The ACL represented as a string.
579  */
580  public function __toString() {
581  $headers = $this->headers();
582 
583  $buffer = array();
584  foreach ($headers as $k => $v) {
585  $buffer[] = $k . ': ' . $v;
586  }
587 
588  return implode("\t", $buffer);
589  }
590 
591 }