Files
DomFramework/authorizationdb.php
2016-02-21 19:25:55 +00:00

631 lines
22 KiB
PHP

<?php
/** DomFramework
@package domframework
@author Dominique Fournier <dominique@fournier38.fr> */
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;
/** 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 an 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 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 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 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 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 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 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 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 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 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 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);
}
}