diff --git a/authorization.php b/authorization.php index fc233a4..62dc513 100644 --- a/authorization.php +++ b/authorization.php @@ -10,6 +10,11 @@ class authorization /** Separator between differents modules/objects */ private $separator = "/"; + /** The user ID of the authenticated user */ + public $authiduser = ""; // user id + /** The groups ID of the authenticated user */ + public $authgroups = array (); // List of user's groups id + /** Establish a connexion to the authorization database */ public function connect () { diff --git a/authorizationdb.php b/authorizationdb.php new file mode 100644 index 0000000..75e7f13 --- /dev/null +++ b/authorizationdb.php @@ -0,0 +1,571 @@ + */ + +error_reporting (E_ALL); +require_once ("domframework/dblayer.php"); +require_once ("domframework/authorization.php"); + +/** 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; + + /** 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->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 (_("Database to authorize is not connected"), 500); + $tables = $this->db->listTables (); + if (!in_array ($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 an array with READ, WRITE, EXECUTE */ + public function validate ($object) + { + if ($this->db === null) + throw new Exception (_("Database to authorize is not connected"), 500); + if (substr ($object, -1) === "/") + $object = substr ($object, 0, -1); + if (substr ($object, 0, 1) !== "/") + throw new Exception (_("Object don't start by slash"), 412); + $object = preg_replace ("#//+#", "/", $object); + if ($this->authiduser === "") + throw new Exception (_("Not authenticated"), 401); + try + { + $this->treecheckExecute ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + // 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 (_("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 TRUE or an exception with the error message */ + public function add ($object, $ownerid, $groupid, $modbits) + { + if ($this->db === null) + throw new Exception (_("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 (_("Object don't start by slash"), 412); + $object = preg_replace ("#//+#", "/", $object); + if ($this->authiduser === "") + throw new Exception (_("Not authenticated !!"), 401); + if ($this->authiduser !== 0 && $this->authiduser !== $ownerid) + throw new Exception (_("Can't create object not owned by myself"), 412); + if ($this->authiduser !== 0 && !in_array ($groupid, $this->authgroups)) + throw new Exception (_("Can't create object with not owned group"), 412); + try + { + $this->treecheckExecute ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + // 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 (_("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(), 401); + } + + $this->db->create (array ("object"=>$object, + "ownerid"=>$ownerid, + "groupid"=>$groupid, + "modbits"=>$modbits)); + return TRUE; + } + + /** Remove the informations about an object + @param string $object Object path to drop + @return TRUE or an exception with the error message */ + public function drop ($object) + { + if ($this->db === null) + throw new Exception (_("Database to authorize is not connected"), 500); + if (substr ($object, -1) === "/") + $object = substr ($object, 0, -1); + if (substr ($object, 0, 1) !== "/") + throw new Exception (_("Object don't start by slash"), 412); + $object = preg_replace ("#//+#", "/", $object); + if ($this->authiduser === "") + throw new Exception (_("Not authenticated"), 401); + if ($object === "/") + throw new Exception (_("The root can not be removed"), 412); + try + { + $this->treecheckExecute ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + // 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 (_("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 (_("Removing more than one object"), 412); + if ($rc == 0) + throw new Exception (_("No object removed"), 412); + return TRUE; + } + + try + { + $this->treecheckWrite ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + $rc = $this->db->delete ($object); + if ($rc > 1) + throw new Exception (_("Removing more than one object"), 412); + if ($rc == 0) + throw new Exception (_("No object removed"), 412); + 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 TRUE or an exception with the error message */ + public function chown ($object, $ownerid) + { + if ($this->db === null) + throw new Exception (_("Database to authorize is not connected"), 500); + if (substr ($object, -1) === "/") + $object = substr ($object, 0, -1); + if (substr ($object, 0, 1) !== "/") + throw new Exception (_("Object don't start by slash"), 412); + $object = preg_replace ("#//+#", "/", $object); + if ($this->authiduser === "") + throw new Exception (_("Not authenticated !!"), 401); + if ($this->authiduser !== 0) + throw new Exception (_("The chown is reserved to root user"), 401); + try + { + $this->treecheckExecute ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + // 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 (_("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 TRUE or an exception with the error message */ + public function chgrp ($object, $groupid) + { + if ($this->db === null) + throw new Exception (_("Database to authorize is not connected"), 500); + if (substr ($object, -1) === "/") + $object = substr ($object, 0, -1); + if (substr ($object, 0, 1) !== "/") + throw new Exception (_("Object don't start by slash"), 412); + $object = preg_replace ("#//+#", "/", $object); + if ($this->authiduser === "") + throw new Exception (_("Not authenticated"), 401); + if ($this->authiduser !== 0 && !in_array ($groupid, $this->authgroups)) + throw new Exception (_("The user must be in the wanted group"), 401); + if (!in_array ("WRITE", $this->validate ($object))) + throw new Exception (sprintf (_("%s is write protected"), $object), 401); + try + { + $this->treecheckExecute ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + // 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 (_("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 TRUE or an exception with the error message */ + public function chmod ($object, $mod) + { + if ($this->db === null) + throw new Exception (_("Database to authorize is not connected"), 500); + if (substr ($object, -1) === "/") + $object = substr ($object, 0, -1); + if (substr ($object, 0, 1) !== "/") + throw new Exception (_("Object don't start by slash"), 412); + $object = preg_replace ("#//+#", "/", $object); + if ($this->authiduser === "") + throw new Exception (_("Not authenticated"), 401); + if (!in_array ("WRITE", $this->validate ($object))) + throw new Exception (sprintf (_("%s is write protected"), $object), 401); + try + { + $this->treecheckExecute ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + // 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 (_("Object %s doesn't exists"), $object), + 400); + $search = reset ($search); + $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 the modbits or an exception with the error message */ + public function lsmod ($object) + { + if ($this->db === null) + throw new Exception (_("Database to authorize is not connected"), 500); + if (substr ($object, -1) === "/") + $object = substr ($object, 0, -1); + if (substr ($object, 0, 1) !== "/") + throw new Exception (_("Object don't start by slash"), 412); + $object = preg_replace ("#//+#", "/", $object); + if ($this->authiduser === "") + throw new Exception (_("Not authenticated"), 401); + try + { + $this->treecheckExecute ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + // 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 (_("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 the ownerid or an exception with the error message */ + public function lsown ($object) + { + if ($this->db === null) + throw new Exception (_("Database to authorize is not connected"), 500); + if (substr ($object, -1) === "/") + $object = substr ($object, 0, -1); + if (substr ($object, 0, 1) !== "/") + throw new Exception (_("Object don't start by slash"), 412); + $object = preg_replace ("#//+#", "/", $object); + if ($this->authiduser === "") + throw new Exception (_("Not authenticated !!"), 401); + try + { + $this->treecheckExecute ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + // 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 (_("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 the groupid or an exception with the error message */ + public function lsgrp ($object) + { + if ($this->db === null) + throw new Exception (_("Database to authorize is not connected"), 500); + if (substr ($object, -1) === "/") + $object = substr ($object, 0, -1); + if (substr ($object, 0, 1) !== "/") + throw new Exception (_("Object don't start by slash"), 412); + $object = preg_replace ("#//+#", "/", $object); + if ($this->authiduser === "") + throw new Exception (_("Not authenticated !!"), 401); + try + { + $this->treecheckExecute ($object); + } + catch (Exception $e) + { + throw new Exception ($e->getMessage(), 401); + } + + // 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 (_("Object %s doesn't exists"), $object), + 400); + $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 TRUE or an exception in case of error */ + private function treecheckExecute ($object) + { + if ($this->db === null) + throw new Exception (_("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 (_("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 (_("No execute rights on %s"), $p), 401); + } + } + return TRUE; + } + + /** Check if the parent of object is writeable + @param string $object The object to found + @return 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 (_("No write rights on %s"), $parent), 401); + } +}