* @license BSD */ namespace Domframework; /** All the needed functions to authorize or deny access to an authenticated user */ class Authorizationdb extends Authorization { /** Separator between differents modules/objects */ private $separator = "/"; /** Database PDO connector */ private $db = null; /** The prefix for the table */ public $tableprefix = ""; /** Establish a connexion to the authorization database @param string $dsn The DSN needed to connect to database @param string|null $username The username needed to connect to database @param string|null $password The password needed to connect to database @param string|null $driver_options The driver options need to connect to database */ public function connectdb( $dsn, $username = null, $password = null, $driver_options = null ) { // Define the structure of the table in the database $this->db = new Dblayer($dsn, $username, $password, $driver_options); $this->db->table = "domframework_authorization"; $this->db->tableprefix = $this->tableprefix; $this->db->fields = array( "object" => array("varchar", "255", "not null"), "ownerid" => array("integer", "not null"), "groupid" => array("integer", "not null"), "modbits" => array("integer", "not null")); $this->db->primary = "object"; $this->db->unique = array("object"); } /** Create the structure to store the authorization database */ public function initialize() { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } $tables = $this->db->listTables(); if (!in_array($this->db->tableprefix . $this->db->table, $tables)) { $this->db->createTable(); $first = array("object" => "/", "ownerid" => 0, "groupid" => 0, "modbits" => 755); $this->db->create($first); } } /** Return if the user right is NONE, READ, WRITE, EXECUTE if the object doesn't exists, or is not readable, throw an exception @param string $object The object path to examine @return array with READ, WRITE, EXECUTE */ public function validate($object) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } if (substr($object, -1) === "/") { $object = substr($object, 0, -1); } if (substr($object, 0, 1) !== "/") { throw new \Exception(dgettext( "domframework", "Object don't start by slash" ), 406); } $object = preg_replace("#//+#", "/", $object); if ($this->authiduser === "") { throw new \Exception(dgettext( "domframework", "Not authenticated" ), 401); } try { $this->treecheckExecute($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } // All the folder structure is accessible. Check if the object already // exists $search = $this->db->read(array(array("object", $object))); if (count($search) === 0) { throw new \Exception( sprintf( dgettext( "domframework", "Object %s doesn't exists" ), $object ), 404 ); } $search = reset($search); $ownerid = intval($search["ownerid"]); $groupid = intval($search["groupid"]); $modbits = octdec($search["modbits"]); if ($this->authiduser === 0) { return array("READ", "WRITE", "EXECUTE"); } $res = array(); if ($this->authiduser === $ownerid) { if (($modbits & 0100) === 0100) { $res[] = "EXECUTE"; } if (($modbits & 0200) === 0200) { $res[] = "WRITE"; } if (($modbits & 0400) === 0400) { $res[] = "READ"; } } foreach ($this->authgroups as $group) { if ($group !== $groupid) { continue; } if (($modbits & 0010) === 0010) { $res[] = "EXECUTE"; } if (($modbits & 0020) === 0020) { $res[] = "WRITE"; } if (($modbits & 0040) === 0040) { $res[] = "READ"; } } if (($modbits & 0001) === 0001) { $res[] = "EXECUTE"; } if (($modbits & 0002) === 0002) { $res[] = "WRITE"; } if (($modbits & 0004) === 0004) { $res[] = "READ"; } return array_unique($res); } /** Add a new object, with ownerid and groupid, and mode bits @param string $object Object path to add @param integer $ownerid Owner ID of the object @param integer $groupid Group ID of the object @param integer $modbits Bits of authorization @return boolean TRUE or an exception with the error message */ public function add($object, $ownerid, $groupid, $modbits) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } // The modbits are stored in octal to be more readable $modbits = decoct($modbits); if (substr($object, -1) === "/") { $object = substr($object, 0, -1); } if (substr($object, 0, 1) !== "/") { throw new \Exception(dgettext( "domframework", "Object don't start by slash" ), 406); } $object = preg_replace("#//+#", "/", $object); if ($this->authiduser === "") { throw new \Exception(dgettext( "domframework", "Not authenticated" ), 401); } if ($this->authiduser !== 0 && $this->authiduser !== $ownerid) { throw new \Exception(dgettext( "domframework", "Can't create object not owned by myself" ), 406); } if ($this->authiduser !== 0 && !in_array($groupid, $this->authgroups)) { throw new \Exception(dgettext( "domframework", "Can't create object with not owned group" ), 406); } try { $this->treecheckExecute($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } // All the folder structure is accessible. Check if the object already // exists $search = $this->db->read(array(array("object", $object))); if (count($search)) { throw new \Exception( sprintf(dgettext( "domframework", "Object %s already defined" ), $object), 400 ); } // All the folder structure is accessible. Check if we can add the file in // the last folder (Write modbit). Root can always write. if ($this->authiduser === 0) { $this->db->create(array("object" => $object, "ownerid" => $ownerid, "groupid" => $groupid, "modbits" => $modbits)); return true; } try { $this->treecheckWrite($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } $this->db->create(array("object" => $object, "ownerid" => $ownerid, "groupid" => $groupid, "modbits" => $modbits)); return true; } /** Remove the information about an object and all its sub-objects @param string $object Object path to drop @return boolean TRUE or an exception with the error message */ public function drop($object) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } if (substr($object, -1) === "/") { $object = substr($object, 0, -1); } if (substr($object, 0, 1) !== "/") { throw new \Exception(dgettext( "domframework", "Object don't start by slash" ), 406); } $object = preg_replace("#//+#", "/", $object); if ($this->authiduser === "") { throw new \Exception(dgettext( "domframework", "Not authenticated" ), 401); } if ($object === "/") { throw new \Exception(dgettext( "domframework", "The root can not be removed" ), 406); } try { $this->treecheckExecute($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } // All the folder structure is accessible. Check if the object already // exists $search = $this->db->read(array(array("object", $object))); if (count($search) === 0) { throw new \Exception(sprintf( dgettext( "domframework", "Object %s doesn't exists" ), $object ), 400); } // All the folder structure is accessible. Check if we can remove the file // in the last folder (Write modbit). Root can always write. if ($this->authiduser === 0) { $rc = $this->db->delete($object); if ($rc > 1) { throw new \Exception(dgettext( "domframework", "Removing more than one object" ), 406); } if ($rc == 0) { throw new \Exception(dgettext( "domframework", "No object removed" ), 406); } $rc = $this->db->delete("$object$this->separator%"); return true; } try { $this->treecheckWrite($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } $rc = $this->db->delete($object); if ($rc > 1) { throw new \Exception(dgettext( "domframework", "Removing more than one object" ), 406); } if ($rc == 0) { throw new \Exception(dgettext( "domframework", "No object removed" ), 406); } $rc = $this->db->delete("$object$this->separator%"); return true; } /** Change the owner of an object Need to be the root administrator @param string $object Object path to add @param integer $ownerid Owner ID of the object @return boolean TRUE or an exception with the error message */ public function chown($object, $ownerid) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } if (substr($object, -1) === "/") { $object = substr($object, 0, -1); } if (substr($object, 0, 1) !== "/") { throw new \Exception(dgettext( "domframework", "Object don't start by slash" ), 406); } $object = preg_replace("#//+#", "/", $object); if ($this->authiduser === "") { throw new \Exception(dgettext( "domframework", "Not authenticated" ), 401); } if ($this->authiduser !== 0) { throw new \Exception(dgettext( "domframework", "The chown is reserved to root user" ), 405); } try { $this->treecheckExecute($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } // All the folder structure is accessible. Check if the object already // exists $search = $this->db->read(array(array("object", $object))); if (count($search) === 0) { throw new \Exception( sprintf(dgettext( "domframework", "Object %s doesn't exists" ), $object), 400 ); } $search = reset($search); $this->db->update($object, array("ownerid" => $ownerid)); return true; } /** Change the group of an object Need to be the ownerid of the object or the root administrator @param string $object Object path to add @param integer $groupid Group ID of the object @return boolean TRUE or an exception with the error message */ public function chgrp($object, $groupid) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } if (substr($object, -1) === "/") { $object = substr($object, 0, -1); } if (substr($object, 0, 1) !== "/") { throw new \Exception(dgettext( "domframework", "Object don't start by slash" ), 406); } $object = preg_replace("#//+#", "/", $object); if ($this->authiduser === "") { throw new \Exception(dgettext( "domframework", "Not authenticated" ), 401); } if ($this->authiduser !== 0 && !in_array($groupid, $this->authgroups)) { throw new \Exception(dgettext( "domframework", "The user must be in the wanted group" ), 405); } if (!in_array("WRITE", $this->validate($object))) { throw new \Exception(sprintf(dgettext( "domframework", "%s is write protected" ), $object), 405); } try { $this->treecheckExecute($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } // All the folder structure is accessible. Check if the object already // exists $search = $this->db->read(array(array("object", $object))); if (count($search) === 0) { throw new \Exception( sprintf(dgettext( "domframework", "Object %s doesn't exists" ), $object), 400 ); } $search = reset($search); $this->db->update($object, array("groupid" => $groupid)); return true; } /** Change mode bits for an object Need to be the owner of the object or the root administrator @param string $object Object path to change @param integer $mod Bits of authorization @return boolean TRUE or an exception with the error message */ public function chmod($object, $mod) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } if (substr($object, -1) === "/") { $object = substr($object, 0, -1); } if (substr($object, 0, 1) !== "/") { throw new \Exception(dgettext( "domframework", "Object don't start by slash" ), 406); } $object = preg_replace("#//+#", "/", $object); if ($this->authiduser === "") { throw new \Exception(dgettext( "domframework", "Not authenticated" ), 401); } if (!in_array("WRITE", $this->validate($object))) { throw new \Exception(sprintf(dgettext( "domframework", "%s is write protected" ), $object), 405); } try { $this->treecheckExecute($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } // All the folder structure is accessible. Check if the object already // exists $search = $this->db->read(array(array("object", $object))); if (count($search) === 0) { throw new \Exception( sprintf(dgettext( "domframework", "Object %s doesn't exists" ), $object), 400 ); } $search = reset($search); // TODO : Don't update if the user is not the owner of the file $this->db->update($object, array("modbits" => $modbits)); return true; } /** Return the mode bits for an object if all his parents are readable for the user @param string $object Object path to examine @return int|float the modbits or an exception with the error message */ public function lsmod($object) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } if (substr($object, -1) === "/") { $object = substr($object, 0, -1); } if (substr($object, 0, 1) !== "/") { throw new \Exception(dgettext( "domframework", "Object don't start by slash" ), 406); } $object = preg_replace("#//+#", "/", $object); if ($this->authiduser === "") { throw new \Exception(dgettext( "domframework", "Not authenticated" ), 401); } try { $this->treecheckExecute($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } // All the folder structure is accessible. Check if the object already // exists $search = $this->db->read(array(array("object", $object))); if (count($search) === 0) { throw new \Exception( sprintf(dgettext( "domframework", "Object %s doesn't exists" ), $object), 400 ); } $search = reset($search); return octdec($search["modbits"]); } /** Return the ownerid for an object if all his parents are readable for the user @param string $object Object path to examine @return integer the ownerid or an exception with the error message */ public function lsown($object) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } if (substr($object, -1) === "/") { $object = substr($object, 0, -1); } if (substr($object, 0, 1) !== "/") { throw new \Exception(dgettext( "domframework", "Object don't start by slash" ), 406); } $object = preg_replace("#//+#", "/", $object); if ($this->authiduser === "") { throw new \Exception(dgettext( "domframework", "Not authenticated" ), 401); } try { $this->treecheckExecute($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } // All the folder structure is accessible. Check if the object already // exists $search = $this->db->read(array(array("object", $object))); if (count($search) === 0) { throw new \Exception( sprintf(dgettext( "domframework", "Object %s doesn't exists" ), $object), 400 ); } $search = reset($search); return intval($search["ownerid"]); } /** Return the groupid for an object if all his parents are readable for the user @param string $object Object path to examine @return integer the groupid or an exception with the error message */ public function lsgrp($object) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } if (substr($object, -1) === "/") { $object = substr($object, 0, -1); } if (substr($object, 0, 1) !== "/") { throw new \Exception(dgettext( "domframework", "Object don't start by slash" ), 406); } $object = preg_replace("#//+#", "/", $object); if ($this->authiduser === "") { throw new \Exception(dgettext( "domframework", "Not authenticated" ), 401); } try { $this->treecheckExecute($object); } catch (\Exception $e) { throw new \Exception($e->getMessage(), 405); } // All the folder structure is accessible. Check if the object already // exists $search = $this->db->read(array(array("object", $object))); if (count($search) === 0) { throw new \Exception( sprintf(dgettext( "domframework", "Object %s doesn't exists" ), $object), 404 ); } $search = reset($search); return intval($search["groupid"]); } //////////////////// // TREE CHECK // //////////////////// /** Check if all the parent objects are executable @param string $object The object to test @return boolean TRUE or an exception in case of error */ private function treecheckExecute($object) { if ($this->db === null) { throw new \Exception(dgettext( "domframework", "Database to authorize is not connected" ), 500); } // Search all the parents in an array $parents = array(); $par = $object; while ($par !== "/") { $par = dirname($par); $parents[] = $par; } // For each parent (folder), check if there is the right to cross (Execute // modbit). Look at the database in one read request for all the parents. $parents = array_reverse($parents); $req = "SELECT object,ownerid,groupid,modbits FROM domframework_authorization WHERE "; foreach ($parents as $i => $p) { if ($i > 0) { $req .= " OR "; } $req .= "object='" . addslashes($p) . "'"; } $res = $this->db->directRead($req); foreach ($parents as $i => $p) { $found = false; foreach ($res as $r) { if ($r["object"] === $p) { $found = true; break; } } if (!$found) { throw new \Exception(sprintf( dgettext( "domframework", "The path %s is not found in database" ), $p ), 404); } else { $parentOwner = intval($r["ownerid"]); $parentGroup = intval($r["groupid"]); $parentModbits = octdec($r["modbits"]); if ($this->authiduser === 0) { continue; } if ( (($parentModbits & 0100) === 64) && $parentOwner === $this->authiduser ) { continue; } if ((($parentModbits & 0010) === 8)) { foreach ($this->authgroups as $tmpgrp) { if ($parentGroup === $tmpgrp || $tmpgrp === 0) { continue 2; } } } if (($parentModbits & 0001) === 1) { continue; } throw new \Exception(sprintf(dgettext( "domframework", "No execute rights on %s" ), $p), 405); } } return true; } /** Check if the parent of object is writeable @param string $object The object to found @return boolean TRUE or an exception */ private function treecheckWrite($object) { $parent = dirname($object); $search = $this->db->read(array(array("object", $parent))); $search = reset($search); $parentOwner = intval($search["ownerid"]); $parentGroup = intval($search["groupid"]); $parentModbits = octdec($search["modbits"]); if ((($parentModbits & 0200) === 128) && $parentOwner === $this->authiduser) { return true; } if ((($parentModbits & 0020) === 16)) { if ( in_array($parentGroup, $this->authgroups) || in_array(0, $this->authgroups) ) { return true; } } if (($parentModbits & 0002) === 2) { return true; } throw new \Exception(sprintf(dgettext( "domframework", "No write rights on %s" ), $parent), 405); } }