1  <?php
  2  /* Copyright (c) 2012, Geert Bergman (geert@scrivo.nl)
  3   * All rights reserved.
  4   *
  5   * Redistribution and use in source and binary forms, with or without
  6   * modification, are permitted provided that the following conditions are met:
  7   *
  8   * 1. Redistributions of source code must retain the above copyright notice,
  9   *    this list of conditions and the following disclaimer.
 10   * 2. Redistributions in binary form must reproduce the above copyright notice,
 11   *    this list of conditions and the following disclaimer in the documentation
 12   *    and/or other materials provided with the distribution.
 13   * 3. Neither the name of "Scrivo" nor the names of its contributors may be
 14   *    used to endorse or promote products derived from this software without
 15   *    specific prior written permission.
 16   *
 17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 18   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 19   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 20   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 21   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 22   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 23   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 24   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 25   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 26   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 27   * POSSIBILITY OF SUCH DAMAGE.
 28   *
 29   * $Id: AccessController.php 866 2013-08-25 16:22:35Z geert $
 30   */
 31  
 32  /**
 33   * Implementation of the \Scrivo\AccessController class.
 34   */
 35  
 36  namespace Scrivo;
 37  
 38  /**
 39   * Class that deals with the user permissions on objects.
 40   *
 41   * The AccessController class implements the fuctionality to determine
 42   * user access level to objects like pages and assets.
 43   *
 44   * Access rights are expressed as bit flags. There are three different access
 45   * right defined:
 46   *
 47   * * AccessController::READ_ACCESS: first bit set (=1)
 48   * * AccessController::WRITE_ACCESS: second bit set (=2)
 49   * * AccessController::PUBLISH_ACCESS: third bit set (=4)
 50   *
 51   * It is important to note that Scrivo uses access levels internally. This
 52   * means that although there are different permissions (READ_ACCESS,
 53   * WRITE_ACCESS, etc.) defined they are not used indivudally:
 54   * WRITE_ACCESS always implies READ_ACCESS permission (WRITE_ACCESS
 55   * always means READ_ACCESS+WRITE_ACCESS, PUBLISH_ACCESS always means
 56   * READ_ACCESS+WRITE_ACCESS+PUBLISH_ACCESS).
 57   *
 58   * This means that when you retrieve a permission you are guaranteed to
 59   * retrieve one of the values (0, 1, 3 or 7). Using bit flags might suggest
 60   * otherwise, but note that other combinations are not possible.
 61   *
 62   * This also means that you can use either bitwise operations or comparison
 63   * when checking a permission:
 64   *
 65   * if (AccessController::getPermission($aContext, $anObjectId) >=
 66   *         AccessController::ACCESS_LEVEL_READ) { ... }
 67   *
 68   * is equivalent to:
 69   *
 70   * if (AccessController::getPermission($aContext, $anObjectId) &
 71   *         AccessController::ACCESS_LEVEL_READ) { ... }
 72   *
 73   * Note that AccessController::checkPermission(...) is probably easier
 74   * to use.
 75   *
 76   * Another feature is that Scrivo users are differentiated into three
 77   * catagories which limits the range of atainable permissions:
 78   *
 79   * * Members (Users::STATUS_MEMBER), these users represent the group of users
 80   *   that visit the actual site: Their users access levels can only be one
 81   *   of NO_ACCESS or READ_ACCESS.
 82   * * Editors (Users::STATUS_EDITOR), the users that login in to do editing
 83   *   work Scrivo: These users have access level can range from READ_ACCESS to
 84   *   PUBLISH_ACCESS.
 85   * * Super users (Users::STATUS_ADMIN), users that can access everything. These
 86   *   users always have PUBLISH_ACCESS.
 87   *
 88   * In other words members can read what they are allowed to but never write,
 89   * editors can write (and possibly publish) what they are allowed to and always
 90   * read and admins can do everthing.
 91   *
 92   * For a description of Scrivo user see the Scrivo::User class and to see
 93   * how access rights are granted to users see the Scrivo::Role class.
 94   */
 95  class AccessController {
 96  
 97      /**
 98       * Bit flag that indicates that the user has read access.
 99       */
100      const READ_ACCESS 1;
101  
102      /**
103       * Bit flag that indicates that the user has write access.
104       */
105      const WRITE_ACCESS 2;
106  
107      /**
108       * Bit flag that indicates that the user has publiser rights.
109       */
110      const PUBLISH_ACCESS 4;
111  
112      /**
113       * Check the permission of a user on an object (page or asset).
114       *
115       * Note that a valid user and object id are assumed. Invalid user ids
116       * will raise an exception but invalid object ids are accepted and
117       * the given permission will then be the checked against the minimum
118       * access permission for the given user.
119       *
120       * @param Context $context A connection to a Scrivo database.
121       * @param int $perm The permission to test (READ_ACCESS || WRITE_ACCESS
122       *    || PUBLISH_ACCESS)
123       * @param int $objectId A valid object id of a page or asset.
124       *
125       * @return boolean True if the user has the specified permission on the
126       *    object.
127       */
128      public static function checkPermission(
129              Context $context$perm$objectId=null) {
130          \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array(
131              null,
132              array(\Scrivo\ArgumentCheck::TYPE_INTEGER, array(0,1,2,3,4,5,6,7)),
133              array(\Scrivo\ArgumentCheck::TYPE_INTEGER)
134          ), 2);
135  
136          return self::getPermission($context$objectId) >= $perm;
137      }
138  
139      /**
140       * Get the permission of a user on an object (page or asset).
141       *
142       * Note that a valid user and object id are assumed. Invalid user ids
143       * will raise an exception but invalid object ids are accepted and
144       * assigned the minimum access permission for the given user.
145       *
146       * @param Context $context A connection to a Scrivo database.
147       * @param int $objectId A valid object id of a page or asset.
148       *
149       * @return int The user's permission on the object (A bitwise combination
150       *   of READ_ACCESS, WRITE_ACCESS and PUBLISH_ACCESS).
151       */
152      public static function getPermission(Context $context$objectId=null) {
153          \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array(
154              null,
155              array(\Scrivo\ArgumentCheck::TYPE_INTEGER)
156          ), 1);
157          try {
158  
159              // Status (admin/editor/member) of given user.
160              $us $context->principal->status;
161  
162              // Determine minimum access permission for given user.
163              $p 0;
164  
165              if ($us == User::STATUS_EDITOR) {
166                  $p self::READ_ACCESS;
167              }
168              if ($us == User::STATUS_ADMIN) {
169                  $p self::READ_ACCESS self::WRITE_ACCESS
170                      self::PUBLISH_ACCESS;
171              }
172  
173              // For admin users we're finished, more work for others.
174              if ($us != User::STATUS_ADMIN) {
175  
176                  // Check if there are object roles match the user roles.
177                  $sth $context->connection->prepare(
178                      "SELECT MAX(is_publisher)
179                      FROM user_role UR, object_role DR, role R
180                      WHERE UR.instance_id = :instId AND DR.instance_id = :instId
181                        AND R.instance_id = :instId AND R.type = :type
182                        AND R.role_id = UR.role_id AND R.role_id = DR.role_id
183                        AND UR.user_id = :userId AND DR.page_id = :objectId");
184  
185                  $context->connection->bindInstance($sth);
186                  $sth->bindValue(":userId", \Scrivo\User::patchId(
187                      $context->principal->id), \PDO::PARAM_INT);
188                  $sth->bindValue(":type"$us == User::STATUS_MEMBER ?
189                      Role::PUBLIC_ROLE Role::EDITOR_ROLE, \PDO::PARAM_INT);
190                  $sth->bindValue(":objectId"$objectId, \PDO::PARAM_INT);
191  
192                  $sth->execute();
193  
194                  // Add additional permissions depending on the values retrieved
195                  // from the query.
196                  if (null !== ($publ $sth->fetchColumn(0))) {
197                      if ($us == User::STATUS_EDITOR) {
198                          $p |= self::WRITE_ACCESS;
199                          if ($publ) {
200                              $p |= self::PUBLISH_ACCESS;
201                          }
202                      } else if ($us User::STATUS_MEMBER) {
203                          $p |= self::READ_ACCESS;
204                      }
205                  }
206              }
207  
208              return $p;
209  
210          } catch(\PDOException $e) {
211              throw new ResourceException($e);
212          }
213      }
214  
215      /**
216       * Get the permissions on a series of objects for a given user.
217       *
218       * @deprecated Access to pages and assets should be checked through role
219       *   mapping.
220       *
221       * @param Context $context A connection to a Scrivo database.
222       * @param string[] $queryParts An array that contains SQL fragments to
223       *    do the proper select statments for the given case.
224       * @param int $parentId An optional parent id to use in the selection of
225       *    the objects.
226       *
227       * @return int[] Array in which the keys are the object ids and the
228       *    values the user's permissions (A bitwise combination of READ_ACCESS,
229       *    WRITE_ACCESS and PUBLISH_ACCESS) on the objects.
230       */
231      private static function getPermissionsOnObjects(
232              Context $context$queryParts$parentId=-1) {
233          try {
234              // Array for the return values.
235              $ret = array();
236  
237              // Prepare the parent clause to us in queries.
238              $pc $parentId != -$queryParts["parentClause"] : "";
239  
240              // Status (admin/editor/member) of given user.
241              $us $context->principal->status;
242  
243              // Determine minimum access permission for given user.
244              $p 0;
245              if ($us == User::STATUS_EDITOR) {
246                  $p self::READ_ACCESS;
247              }
248              if ($us == User::STATUS_ADMIN) {
249                  $p self::READ_ACCESS self::WRITE_ACCESS
250                      self::PUBLISH_ACCESS;
251              }
252  
253              // Get the object ids from the database.
254              $sth $context->connection->prepare(
255                  $queryParts["listIds"] . $pc);
256  
257              $context->connection->bindInstance($sth);
258              if ($parentId != -1) {
259                  $sth->bindValue(":parentId"$parentId, \PDO::PARAM_INT);
260              }
261  
262              $sth->execute();
263  
264              // Fill the result array with the object id as keys and the
265              // minimum access permission for the values.
266              while ($id $sth->fetchColumn()) {
267                  $ret[$id] = $p;
268              }
269  
270              // For admin users we're finished, more work for others.
271              if ($us != User::STATUS_ADMIN) {
272  
273                  // Select the object ids of objects with object roles that
274                  // match the user roles.
275                  $sth $context->connection->prepare(
276                      $queryParts["listIdsForRoles"] . $pc .
277                          " GROUP BY id");
278  
279                  $context->connection->bindInstance($sth);
280                  $sth->bindValue(":userId", \Scrivo\User::patchId(
281                      $context->principal->id), \PDO::PARAM_INT);
282                  $sth->bindValue(":type"$us == User::STATUS_MEMBER ?
283                      Role::PUBLIC_ROLE Role::EDITOR_ROLE, \PDO::PARAM_INT);
284                  if ($parentId != -1) {
285                      $sth->bindValue(":parentId"$parentId, \PDO::PARAM_INT);
286                  }
287  
288                  $sth->execute();
289  
290                  // Add additional permissions depending on the values retrieved
291                  // from the query.
292                  while ($rd $sth->fetch(\PDO::FETCH_ASSOC)) {
293                      $p2 $p $ret[$rd["id"]] : 0;
294                      if ($us == User::STATUS_EDITOR) {
295                          $p2 |= self::WRITE_ACCESS;
296                          if ($rd["is_publisher"]) {
297                              $p2 |= self::PUBLISH_ACCESS;
298                          }
299                      } else if ($us User::STATUS_MEMBER) {
300                          $p2 |= self::READ_ACCESS;
301                      }
302                      $ret[$rd["id"]] = $p2;
303                  }
304              }
305  
306              return $ret;
307  
308          } catch(\PDOException $e) {
309              throw new ResourceException($e);
310          }
311      }
312  
313      /**
314       * Get the permissions of a user on a set of pages.
315       *
316       * You can either get the permissions of the user on all pages or retrieve
317       * them for all pages directly underneath (not recursive) the page that is
318       * identified by the optional parent id.
319       *
320       * @deprecated Access to pages and assets should be checked through role
321       *   mapping.
322       *
323       * @param Context $context A connection to a Scrivo database.
324       * @param int $parentId An optional parent id to make a subselection of
325       *    pages.
326       *
327       * @return int[] Array in which the keys are the object ids and the
328       *    values the user's permissions (A bitwise combination of READ_ACCESS,
329       *    WRITE_ACCESS and PUBLISH_ACCESS) on the objects.
330       */
331      public static function getPermissionsOnPages(Context $context$parentId=-1) {
332          \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array(
333              null,
334              array(\Scrivo\ArgumentCheck::TYPE_INTEGER)
335          ), 1);
336  
337          $sql = array(
338              "listIds" =>
339                  "SELECT DISTINCT page_id id
340                  FROM page D WHERE D.instance_id = :instId AND version <= 0",
341              "parentClause" =>
342                  " AND D.parent_id = :parentId",
343              "listIdsForRoles" =>
344                  "SELECT DR.page_id id, MAX(is_publisher) is_publisher
345                  FROM role R, user_role UR, object_role DR, page D
346                  WHERE D.instance_id = :instId AND UR.instance_id = :instId
347                  AND DR.instance_id = :instId AND R.instance_id = :instId
348                  AND R.role_id = UR.role_id AND R.role_id = DR.role_id
349                  AND DR.page_id = D.page_id AND R.type = :type
350                  AND UR.user_id = :userId AND D.version <= 0"
351          );
352  
353          return self::getPermissionsOnObjects($context$sql$parentId);
354      }
355  
356      /**
357       * Get the permissions of a user on a set of assets.
358       *
359       * You can either get the permissions of the user on all assets or retrieve
360       * them for all assets in a folder (not recursive) that is identified by
361       * the optional parent id.
362       *
363       * @deprecated Access to pages and assets should be checked through role
364       *   mapping.
365       *
366       * @param Context $context A connection to a Scrivo database.
367       * @param int $parentId An optional parent id to make a subselection of
368       *    pages.
369       *
370       * @return int[] Array in which the keys are the object ids and the
371       *    values the user's permissions (A bitwise combination of READ_ACCESS,
372       *    WRITE_ACCESS and PUBLISH_ACCESS) on the objects.
373       */
374      public static function getPermissionsOnAssets(Context $context$parentId=-1) {
375          \Scrivo\ArgumentCheck::assertArgs(func_get_args(), array(
376              null,
377              array(\Scrivo\ArgumentCheck::TYPE_INTEGER)
378          ), 1);
379  
380          $sql = array(
381              "listIds" =>
382                  "SELECT DISTINCT asset_id id
383                  FROM asset D WHERE D.instance_id = :instId",
384              "parentClause" =>
385                  " AND D.parent_id = :parentId",
386              "listIdsForRoles" =>
387                  "SELECT DR.page_id id, MAX(is_publisher) is_publisher
388                  FROM role R, user_role UR, object_role DR, asset D
389                  WHERE UR.instance_id = :instId AND DR.instance_id = :instId
390                  AND R.instance_id = :instId AND D.instance_id = :instId
391                  AND R.role_id = UR.role_id AND R.role_id = DR.role_id
392                  AND DR.page_id = D.asset_id AND R.type = :type
393                  AND UR.user_id = :userId"
394          );
395  
396          return self::getPermissionsOnObjects($context$sql$parentId);
397      }
398  
399  }
400  
401  ?>

Documentation generated by phpDocumentor 2.0.0a12 and ScrivoDocumentor on August 29, 2013