Files
DomFramework/src/Authorizationdb.php
2022-11-25 21:21:30 +01:00

795 lines
26 KiB
PHP

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