v2.0-alpha
This commit is contained in:
parent
b549af3166
commit
ce647d4423
@ -59,7 +59,7 @@ Each endpoint is represented by a class inheriting the [Request Class](/core/Api
|
||||
```php
|
||||
namespace Api;
|
||||
use Api\Parameter\Parameter;
|
||||
use Objects\User;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
class SingleEndpoint extends Request {
|
||||
|
||||
@ -108,7 +108,7 @@ Some endpoints are set to private, which means, they can be only accessed inside
|
||||
can be used by creating the desired request object, and calling the execute function with our parameters like shown below:
|
||||
|
||||
```php
|
||||
$req = new \Api\Mail\Send($user);
|
||||
$req = new \Api\Mail\Send($context);
|
||||
$success = $req->execute(array(
|
||||
"to" => "mail@example.org",
|
||||
"subject" => "Example Mail",
|
||||
|
@ -92,7 +92,7 @@ export default class API {
|
||||
}
|
||||
|
||||
async deleteGroup(id) {
|
||||
return this.apiCall("groups/delete", { uid: id });
|
||||
return this.apiCall("groups/delete", { id: id });
|
||||
}
|
||||
|
||||
async getSettings(key = "") {
|
||||
|
@ -25,10 +25,10 @@ export default function Header(props) {
|
||||
let notificationItems = [];
|
||||
for (let i = 0; i < parent.notifications.length; i++) {
|
||||
const notification = parent.notifications[i];
|
||||
const uid = notification.uid;
|
||||
const id = notification.id;
|
||||
const createdAt = getPeriodString(notification["created_at"]);
|
||||
notificationItems.push(
|
||||
<Link to={"/admin/logs?notification=" + uid} className={"dropdown-item"} key={"notification-" + uid}>
|
||||
<Link to={"/admin/logs?notification=" + id} className={"dropdown-item"} key={"notification-" + id}>
|
||||
{mailIcon}
|
||||
<span className={"ml-2"}>{notification.title}</span>
|
||||
<span className={"float-right text-muted text-sm"}>{createdAt}</span>
|
||||
|
@ -83,7 +83,7 @@ export default class Logs extends React.Component {
|
||||
for (let event of dates[date]) {
|
||||
let timeString = moment(event.timestamp).fromNow();
|
||||
elements.push(
|
||||
<div key={"time-entry-" + event.uid}>
|
||||
<div key={"time-entry-" + event.id}>
|
||||
<Icon icon={event.icon} className={"bg-" + color}/>
|
||||
<div className="timeline-item">
|
||||
<span className="time"><Icon icon={"clock"}/> {timeString}</span>
|
||||
|
@ -161,12 +161,12 @@ export default class UserOverview extends React.Component {
|
||||
createUserCard() {
|
||||
|
||||
let userRows = [];
|
||||
for (let uid in this.state.users.data) {
|
||||
if (!this.state.users.data.hasOwnProperty(uid)) {
|
||||
for (let id in this.state.users.data) {
|
||||
if (!this.state.users.data.hasOwnProperty(id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let user = this.state.users.data[uid];
|
||||
let user = this.state.users.data[id];
|
||||
let confirmedIcon = <Icon icon={user["confirmed"] ? "check" : "times"}/>;
|
||||
|
||||
let groups = [];
|
||||
@ -184,7 +184,7 @@ export default class UserOverview extends React.Component {
|
||||
}
|
||||
|
||||
userRows.push(
|
||||
<tr key={"user-" + uid}>
|
||||
<tr key={"user-" + id}>
|
||||
<td>{user.name}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>{groups}</td>
|
||||
@ -197,7 +197,7 @@ export default class UserOverview extends React.Component {
|
||||
</td>
|
||||
<td className={"text-center"}>{confirmedIcon}</td>
|
||||
<td>
|
||||
<Link to={"/admin/user/edit/" + uid} className={"text-reset"}>
|
||||
<Link to={"/admin/user/edit/" + id} className={"text-reset"}>
|
||||
<Icon icon={"pencil-alt"} data-effect={"solid"}
|
||||
data-tip={"Modify user details & group membership"}
|
||||
data-type={"info"} data-place={"right"}/>
|
||||
@ -291,15 +291,15 @@ export default class UserOverview extends React.Component {
|
||||
|
||||
createGroupCard() {
|
||||
let groupRows = [];
|
||||
for (let uid in this.state.groups.data) {
|
||||
if (!this.state.groups.data.hasOwnProperty(uid)) {
|
||||
for (let id in this.state.groups.data) {
|
||||
if (!this.state.groups.data.hasOwnProperty(id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let group = this.state.groups.data[uid];
|
||||
let group = this.state.groups.data[id];
|
||||
|
||||
groupRows.push(
|
||||
<tr key={"group-" + uid}>
|
||||
<tr key={"group-" + id}>
|
||||
<td>{group.name}</td>
|
||||
<td className={"text-center"}>{group["memberCount"]}</td>
|
||||
<td>
|
||||
@ -309,7 +309,7 @@ export default class UserOverview extends React.Component {
|
||||
</td>
|
||||
<td>
|
||||
<Icon icon={"trash"} style={{color: "red", cursor: "pointer"}}
|
||||
onClick={(e) => this.onDeleteGroup(e, uid)} data-effect={"solid"}
|
||||
onClick={(e) => this.onDeleteGroup(e, id)} data-effect={"solid"}
|
||||
data-tip={"Delete"} data-type={"error"}
|
||||
data-place={"bottom"}/>
|
||||
</td>
|
||||
@ -395,11 +395,11 @@ export default class UserOverview extends React.Component {
|
||||
</div>;
|
||||
}
|
||||
|
||||
onDeleteGroup(e, uid) {
|
||||
onDeleteGroup(e, id) {
|
||||
e.stopPropagation();
|
||||
this.parent.showDialog("Are you really sure you want to delete this group?", "Delete Group?", ["Yes", "No"], (btn) => {
|
||||
if (btn === "Yes") {
|
||||
this.parent.api.deleteGroup(uid).then((res) => {
|
||||
this.parent.api.deleteGroup(id).then((res) => {
|
||||
if (!res.success) {
|
||||
let errors = this.state.errors.slice();
|
||||
errors.push({title: "Error deleting group", message: res.msg});
|
||||
|
103
cli.php
103
cli.php
@ -6,14 +6,13 @@ include_once 'core/core.php';
|
||||
require_once 'core/datetime.php';
|
||||
include_once 'core/constants.php';
|
||||
|
||||
use Configuration\Configuration;
|
||||
use Configuration\DatabaseScript;
|
||||
use Driver\SQL\Column\Column;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondIn;
|
||||
use Driver\SQL\Expression\DateSub;
|
||||
use Driver\SQL\SQL;
|
||||
use Objects\ConnectionData;
|
||||
use Objects\User;
|
||||
|
||||
function printLine(string $line = "") {
|
||||
echo $line . PHP_EOL;
|
||||
@ -24,10 +23,6 @@ function _exit(string $line = "") {
|
||||
die();
|
||||
}
|
||||
|
||||
if (!is_cli()) {
|
||||
_exit("Can only be executed via CLI");
|
||||
}
|
||||
|
||||
function getDatabaseConfig(): ConnectionData {
|
||||
$configClass = "\\Configuration\\Database";
|
||||
$file = getClassPath($configClass);
|
||||
@ -39,8 +34,12 @@ function getDatabaseConfig(): ConnectionData {
|
||||
return new $configClass();
|
||||
}
|
||||
|
||||
$config = new Configuration();
|
||||
$database = $config->getDatabase();
|
||||
$context = new \Objects\Context();
|
||||
if (!$context->isCLI()) {
|
||||
_exit("Can only be executed via CLI");
|
||||
}
|
||||
|
||||
$database = $context->getConfig()->getDatabase();
|
||||
if ($database !== null && $database->getProperty("isDocker", false) && !is_file("/.dockerenv")) {
|
||||
if (count($argv) < 3 || $argv[1] !== "db" || !in_array($argv[2], ["shell", "import", "export"])) {
|
||||
$command = array_merge(["docker", "exec", "-it", "php", "php"], $argv);
|
||||
@ -49,7 +48,7 @@ if ($database !== null && $database->getProperty("isDocker", false) && !is_file(
|
||||
}
|
||||
}
|
||||
|
||||
function getUser(): ?User {
|
||||
/*function getUser(): ?User {
|
||||
global $config;
|
||||
$user = new User($config);
|
||||
if (!$user->getSQL() || !$user->getSQL()->isConnected()) {
|
||||
@ -58,6 +57,17 @@ function getUser(): ?User {
|
||||
}
|
||||
|
||||
return $user;
|
||||
}*/
|
||||
|
||||
function connectSQL(): ?SQL {
|
||||
global $context;
|
||||
$sql = $context->initSQL();
|
||||
if (!$sql || !$sql->isConnected()) {
|
||||
printLine("Could not establish database connection");
|
||||
return null;
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
@ -100,8 +110,7 @@ function handleDatabase(array $argv) {
|
||||
_exit("Usage: cli.php db migrate <class name>");
|
||||
}
|
||||
|
||||
$user = getUser() or die();
|
||||
$sql = $user->getSQL();
|
||||
$sql = connectSQL() or die();
|
||||
applyPatch($sql, $class);
|
||||
} else if (in_array($action, ["export", "import", "shell"])) {
|
||||
|
||||
@ -193,9 +202,7 @@ function handleDatabase(array $argv) {
|
||||
proc_close($process);
|
||||
}
|
||||
} else if ($action === "clean") {
|
||||
$user = getUser() or die();
|
||||
$sql = $user->getSQL();
|
||||
|
||||
$sql = connectSQL() or die();
|
||||
printLine("Deleting user related data older than 90 days...");
|
||||
|
||||
// 1st: Select all related tables and entities
|
||||
@ -221,9 +228,9 @@ function handleDatabase(array $argv) {
|
||||
}
|
||||
|
||||
// 2nd: delete!
|
||||
foreach ($tables as $table => $uids) {
|
||||
foreach ($tables as $table => $ids) {
|
||||
$success = $sql->delete($table)
|
||||
->where(new CondIn(new Column("uid"), $uids))
|
||||
->where(new CondIn(new Column("id"), $ids))
|
||||
->execute();
|
||||
|
||||
if (!$success) {
|
||||
@ -336,9 +343,8 @@ function onMaintenance(array $argv) {
|
||||
$newPatchFiles = array_diff($newPatchFiles, $oldPatchFiles);
|
||||
if (count($newPatchFiles) > 0) {
|
||||
printLine("Applying new database patches");
|
||||
$user = getUser();
|
||||
if ($user) {
|
||||
$sql = $user->getSQL();
|
||||
$sql = connectSQL();
|
||||
if ($sql) {
|
||||
foreach ($newPatchFiles as $patchFile) {
|
||||
if (preg_match("/core\/Configuration\/(Patch\/.*)\.class\.php/", $patchFile, $match)) {
|
||||
$patchName = $match[1];
|
||||
@ -408,12 +414,13 @@ function printTable(array $head, array $body) {
|
||||
}
|
||||
|
||||
function onSettings(array $argv) {
|
||||
$user = getUser() or die();
|
||||
global $context;
|
||||
$sql = connectSQL() or die();
|
||||
$action = $argv[2] ?? "list";
|
||||
|
||||
if ($action === "list" || $action === "get") {
|
||||
$key = (($action === "list" || count($argv) < 4) ? null : $argv[3]);
|
||||
$req = new Api\Settings\Get($user);
|
||||
$req = new Api\Settings\Get($context);
|
||||
$success = $req->execute(["key" => $key]);
|
||||
if (!$success) {
|
||||
_exit("Error listings settings: " . $req->getLastError());
|
||||
@ -430,7 +437,7 @@ function onSettings(array $argv) {
|
||||
} else {
|
||||
$key = $argv[3];
|
||||
$value = $argv[4];
|
||||
$req = new Api\Settings\Set($user);
|
||||
$req = new Api\Settings\Set($context);
|
||||
$success = $req->execute(["settings" => [$key => $value]]);
|
||||
if (!$success) {
|
||||
_exit("Error updating settings: " . $req->getLastError());
|
||||
@ -441,7 +448,7 @@ function onSettings(array $argv) {
|
||||
_exit("Usage: $argv[0] settings $argv[2] <key>");
|
||||
} else {
|
||||
$key = $argv[3];
|
||||
$req = new Api\Settings\Set($user);
|
||||
$req = new Api\Settings\Set($context);
|
||||
$success = $req->execute(["settings" => [$key => null]]);
|
||||
if (!$success) {
|
||||
_exit("Error updating settings: " . $req->getLastError());
|
||||
@ -453,18 +460,18 @@ function onSettings(array $argv) {
|
||||
}
|
||||
|
||||
function onRoutes(array $argv) {
|
||||
|
||||
$user = getUser() or die();
|
||||
global $context;
|
||||
$sql = connectSQL() or die();
|
||||
$action = $argv[2] ?? "list";
|
||||
|
||||
if ($action === "list") {
|
||||
$req = new Api\Routes\Fetch($user);
|
||||
$req = new Api\Routes\Fetch($context);
|
||||
$success = $req->execute();
|
||||
if (!$success) {
|
||||
_exit("Error fetching routes: " . $req->getLastError());
|
||||
} else {
|
||||
$routes = $req->getResult()["routes"];
|
||||
$head = ["uid", "request", "action", "target", "extra", "active", "exact"];
|
||||
$head = ["id", "request", "action", "target", "extra", "active", "exact"];
|
||||
|
||||
// strict boolean
|
||||
foreach ($routes as &$route) {
|
||||
@ -486,7 +493,7 @@ function onRoutes(array $argv) {
|
||||
"extra" => $argv[7] ?? "",
|
||||
);
|
||||
|
||||
$req = new Api\Routes\Add($user);
|
||||
$req = new Api\Routes\Add($context);
|
||||
$success = $req->execute($params);
|
||||
if (!$success) {
|
||||
_exit($req->getLastError());
|
||||
@ -497,13 +504,13 @@ function onRoutes(array $argv) {
|
||||
$uid = $argv[3] ?? null;
|
||||
if ($uid === null || ($action === "modify" && count($argv) < 7)) {
|
||||
if ($action === "modify") {
|
||||
_exit("Usage: cli.php routes $action <uid> <request> <action> <target> [extra]");
|
||||
_exit("Usage: cli.php routes $action <id> <request> <action> <target> [extra]");
|
||||
} else {
|
||||
_exit("Usage: cli.php routes $action <uid>");
|
||||
_exit("Usage: cli.php routes $action <id>");
|
||||
}
|
||||
}
|
||||
|
||||
$params = ["uid" => $uid];
|
||||
$params = ["id" => $uid];
|
||||
if ($action === "remove") {
|
||||
$input = null;
|
||||
do {
|
||||
@ -513,13 +520,13 @@ function onRoutes(array $argv) {
|
||||
echo "Remove route #$uid? (y|n): ";
|
||||
} while(($input = trim(fgets(STDIN))) !== "y");
|
||||
|
||||
$req = new Api\Routes\Remove($user);
|
||||
$req = new Api\Routes\Remove($context);
|
||||
} else if ($action === "enable") {
|
||||
$req = new Api\Routes\Enable($user);
|
||||
$req = new Api\Routes\Enable($context);
|
||||
} else if ($action === "disable") {
|
||||
$req = new Api\Routes\Disable($user);
|
||||
$req = new Api\Routes\Disable($context);
|
||||
} else if ($action === "modify") {
|
||||
$req = new Api\Routes\Update($user);
|
||||
$req = new Api\Routes\Update($context);
|
||||
$params["request"] = $argv[4];
|
||||
$params["action"] = $argv[5];
|
||||
$params["target"] = $argv[6];
|
||||
@ -597,14 +604,15 @@ function onTest($argv) {
|
||||
}
|
||||
|
||||
function onMail($argv) {
|
||||
global $context;
|
||||
$action = $argv[2] ?? null;
|
||||
if ($action === "sync") {
|
||||
$user = getUser() or die();
|
||||
if (!$user->getConfiguration()->getSettings()->isMailEnabled()) {
|
||||
$sql = connectSQL() or die();
|
||||
if (!$context->getSettings()->isMailEnabled()) {
|
||||
_exit("Mails are not configured yet.");
|
||||
}
|
||||
|
||||
$req = new Api\Mail\Sync($user);
|
||||
$req = new Api\Mail\Sync($context);
|
||||
printLine("Syncing emails…");
|
||||
if (!$req->execute()) {
|
||||
_exit("Error syncing mails: " . $req->getLastError());
|
||||
@ -612,8 +620,8 @@ function onMail($argv) {
|
||||
|
||||
_exit("Done.");
|
||||
} else if ($action === "send_queue") {
|
||||
$user = getUser() or die();
|
||||
$req = new \Api\Mail\SendQueue($user);
|
||||
$sql = connectSQL() or die();
|
||||
$req = new \Api\Mail\SendQueue($context);
|
||||
$debug = in_array("debug", $argv);
|
||||
if (!$req->execute(["debug" => $debug])) {
|
||||
_exit("Error processing mail queue: " . $req->getLastError());
|
||||
@ -624,30 +632,31 @@ function onMail($argv) {
|
||||
}
|
||||
|
||||
function onImpersonate($argv) {
|
||||
global $context;
|
||||
|
||||
if (count($argv) < 3) {
|
||||
_exit("Usage: cli.php impersonate <user_id|user_name>");
|
||||
}
|
||||
|
||||
$user = getUser() or exit;
|
||||
$sql = connectSQL() or die();
|
||||
|
||||
$userId = $argv[2];
|
||||
if (!is_numeric($userId)) {
|
||||
$sql = $user->getSQL();
|
||||
$res = $sql->select("uid")
|
||||
$res = $sql->select("id")
|
||||
->from("User")
|
||||
->where(new Compare("name", $userId))
|
||||
->execute();
|
||||
if ($res === false) {
|
||||
_exit("SQL error: " . $sql->getLastError());
|
||||
} else {
|
||||
$userId = $res[0]["uid"];
|
||||
$userId = $res[0]["id"];
|
||||
}
|
||||
}
|
||||
|
||||
$user->createSession(intval($userId));
|
||||
$session = $user->getSession();
|
||||
$user = new \Objects\DatabaseEntity\User($userId);
|
||||
$session = new \Objects\DatabaseEntity\Session($context, $user);
|
||||
$session->setData(["2faAuthenticated" => true]);
|
||||
$session->update(false);
|
||||
$session->update();
|
||||
echo "session=" . $session->getCookie() . PHP_EOL;
|
||||
}
|
||||
|
||||
|
@ -3,15 +3,20 @@
|
||||
namespace Api {
|
||||
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Objects\Context;
|
||||
|
||||
abstract class ApiKeyAPI extends Request {
|
||||
|
||||
protected function apiKeyExists($id): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
|
||||
protected function apiKeyExists(int $id): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select($sql->count())
|
||||
->from("ApiKey")
|
||||
->where(new Compare("uid", $id))
|
||||
->where(new Compare("user_id", $this->user->getId()))
|
||||
->where(new Compare("id", $id))
|
||||
->where(new Compare("user_id", $this->context->getUser()->getId()))
|
||||
->where(new Compare("valid_until", $sql->currentTimestamp(), ">"))
|
||||
->where(new Compare("active", 1))
|
||||
->execute();
|
||||
@ -32,37 +37,32 @@ namespace Api\ApiKey {
|
||||
|
||||
use Api\ApiKeyAPI;
|
||||
use Api\Parameter\Parameter;
|
||||
use Api\Request;
|
||||
use DateTime;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Exception;
|
||||
use Driver\SQL\Condition\CondAnd;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\ApiKey;
|
||||
|
||||
class Create extends ApiKeyAPI {
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array());
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array());
|
||||
$this->apiKeyAllowed = false;
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$apiKey = generateRandomString(64);
|
||||
$sql = $this->user->getSQL();
|
||||
$validUntil = (new \DateTime())->modify("+30 DAY");
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
$this->success = $sql->insert("ApiKey", array("user_id", "api_key", "valid_until"))
|
||||
->addRow($this->user->getId(), $apiKey, $validUntil)
|
||||
->returning("uid")
|
||||
->execute();
|
||||
$apiKey = new ApiKey();
|
||||
$apiKey->apiKey = generateRandomString(64);
|
||||
$apiKey->validUntil = (new \DateTime())->modify("+30 DAY");
|
||||
$apiKey->user = $this->context->getUser();
|
||||
|
||||
$this->success = $apiKey->save($sql);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
$this->result["api_key"] = array(
|
||||
"api_key" => $apiKey,
|
||||
"valid_until" => $validUntil->format("Y-m-d H:i:s"),
|
||||
"uid" => $sql->getLastInsertId(),
|
||||
);
|
||||
$this->result["api_key"] = $apiKey->jsonSerialize();
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
@ -71,39 +71,33 @@ namespace Api\ApiKey {
|
||||
|
||||
class Fetch extends ApiKeyAPI {
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"showActiveOnly" => new Parameter("showActiveOnly", Parameter::TYPE_BOOLEAN, true, true)
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$query = $sql->select("uid", "api_key", "valid_until", "active")
|
||||
->from("ApiKey")
|
||||
->where(new Compare("user_id", $this->user->getId()));
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
$condition = new Compare("user_id", $this->context->getUser()->getId());
|
||||
if ($this->getParam("showActiveOnly")) {
|
||||
$query->where(new Compare("valid_until", $sql->currentTimestamp(), ">"))
|
||||
->where(new Compare("active", true));
|
||||
$condition = new CondAnd(
|
||||
$condition,
|
||||
new Compare("valid_until", $sql->currentTimestamp(), ">"),
|
||||
new Compare("active", true)
|
||||
);
|
||||
}
|
||||
|
||||
$res = $query->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$apiKeys = ApiKey::findAll($sql, $condition);
|
||||
$this->success = ($apiKeys !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if($this->success) {
|
||||
if ($this->success) {
|
||||
$this->result["api_keys"] = array();
|
||||
foreach($res as $row) {
|
||||
$apiKeyId = intval($row["uid"]);
|
||||
$this->result["api_keys"][$apiKeyId] = array(
|
||||
"id" => $apiKeyId,
|
||||
"api_key" => $row["api_key"],
|
||||
"valid_until" => $row["valid_until"],
|
||||
"revoked" => !$sql->parseBool($row["active"])
|
||||
);
|
||||
foreach($apiKeys as $apiKey) {
|
||||
$this->result["api_keys"][$apiKey->getId()] = $apiKey->jsonSerialize();
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,8 +107,8 @@ namespace Api\ApiKey {
|
||||
|
||||
class Refresh extends ApiKeyAPI {
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT),
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
@ -122,15 +116,16 @@ namespace Api\ApiKey {
|
||||
|
||||
public function _execute(): bool {
|
||||
$id = $this->getParam("id");
|
||||
if(!$this->apiKeyExists($id))
|
||||
if (!$this->apiKeyExists($id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$validUntil = (new \DateTime)->modify("+30 DAY");
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $sql->update("ApiKey")
|
||||
->set("valid_until", $validUntil)
|
||||
->where(new Compare("uid", $id))
|
||||
->where(new Compare("user_id", $this->user->getId()))
|
||||
->where(new Compare("id", $id))
|
||||
->where(new Compare("user_id", $this->context->getUser()->getId()))
|
||||
->execute();
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
@ -153,20 +148,19 @@ namespace Api\ApiKey {
|
||||
|
||||
public function _execute(): bool {
|
||||
$id = $this->getParam("id");
|
||||
if (!$this->apiKeyExists($id))
|
||||
if (!$this->apiKeyExists($id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $sql->update("ApiKey")
|
||||
->set("active", false)
|
||||
->where(new Compare("uid", $id))
|
||||
->where(new Compare("user_id", $this->user->getId()))
|
||||
->where(new Compare("id", $id))
|
||||
->where(new Compare("user_id", $this->context->getUser()->getId()))
|
||||
->execute();
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -2,21 +2,20 @@
|
||||
|
||||
namespace Api {
|
||||
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
abstract class ContactAPI extends Request {
|
||||
|
||||
protected ?string $messageId;
|
||||
|
||||
public function __construct(User $user, bool $externalCall, array $params) {
|
||||
parent::__construct($user, $externalCall, $params);
|
||||
public function __construct(Context $context, bool $externalCall, array $params) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
$this->messageId = null;
|
||||
$this->csrfTokenRequired = false;
|
||||
|
||||
}
|
||||
|
||||
protected function sendMail(string $name, ?string $fromEmail, string $subject, string $message, ?string $to = null): bool {
|
||||
$request = new \Api\Mail\Send($this->user);
|
||||
$request = new \Api\Mail\Send($this->context);
|
||||
$this->success = $request->execute(array(
|
||||
"subject" => $subject,
|
||||
"body" => $message,
|
||||
@ -45,30 +44,30 @@ namespace Api\Contact {
|
||||
use Driver\SQL\Condition\CondNot;
|
||||
use Driver\SQL\Expression\CaseWhen;
|
||||
use Driver\SQL\Expression\Sum;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
class Request extends ContactAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
$parameters = array(
|
||||
'fromName' => new StringType('fromName', 32),
|
||||
'fromEmail' => new Parameter('fromEmail', Parameter::TYPE_EMAIL),
|
||||
'message' => new StringType('message', 512),
|
||||
);
|
||||
|
||||
$settings = $user->getConfiguration()->getSettings();
|
||||
$settings = $context->getSettings();
|
||||
if ($settings->isRecaptchaEnabled()) {
|
||||
$parameters["captcha"] = new StringType("captcha");
|
||||
}
|
||||
|
||||
parent::__construct($user, $externalCall, $parameters);
|
||||
parent::__construct($context, $externalCall, $parameters);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$settings = $this->context->getSettings();
|
||||
if ($settings->isRecaptchaEnabled()) {
|
||||
$captcha = $this->getParam("captcha");
|
||||
$req = new VerifyCaptcha($this->user);
|
||||
$req = new VerifyCaptcha($this->context);
|
||||
if (!$req->execute(array("captcha" => $captcha, "action" => "contact"))) {
|
||||
return $this->createError($req->getLastError());
|
||||
}
|
||||
@ -80,23 +79,7 @@ namespace Api\Contact {
|
||||
$email = $this->getParam("fromEmail");
|
||||
|
||||
$sendMail = $this->sendMail($name, $email, "Contact Request", $message);
|
||||
$mailError = $this->getLastError();
|
||||
|
||||
$insertDB = $this->insertContactRequest();
|
||||
$dbError = $this->getLastError();
|
||||
|
||||
// Create a log entry
|
||||
if (!$sendMail || $mailError) {
|
||||
$message = "Error processing contact request.";
|
||||
if (!$sendMail) {
|
||||
$message .= " Mail: $mailError";
|
||||
}
|
||||
|
||||
if (!$insertDB) {
|
||||
$message .= " Mail: $dbError";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$sendMail && !$insertDB) {
|
||||
return $this->createError("The contact request could not be sent. The Administrator is already informed. Please try again later.");
|
||||
}
|
||||
@ -105,7 +88,7 @@ namespace Api\Contact {
|
||||
}
|
||||
|
||||
private function insertContactRequest(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$name = $this->getParam("fromName");
|
||||
$email = $this->getParam("fromEmail");
|
||||
$message = $this->getParam("message");
|
||||
@ -113,7 +96,7 @@ namespace Api\Contact {
|
||||
|
||||
$res = $sql->insert("ContactRequest", array("from_name", "from_email", "message", "messageId"))
|
||||
->addRow($name, $email, $message, $messageId)
|
||||
->returning("uid")
|
||||
->returning("id")
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
@ -124,21 +107,20 @@ namespace Api\Contact {
|
||||
|
||||
class Respond extends ContactAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"requestId" => new Parameter("requestId", Parameter::TYPE_INT),
|
||||
'message' => new StringType('message', 512),
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
private function getSenderMail(): ?string {
|
||||
$requestId = $this->getParam("requestId");
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select("from_email")
|
||||
->from("ContactRequest")
|
||||
->where(new Compare("uid", $requestId))
|
||||
->where(new Compare("id", $requestId))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== false);
|
||||
@ -156,12 +138,12 @@ namespace Api\Contact {
|
||||
}
|
||||
|
||||
private function insertResponseMessage(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$message = $this->getParam("message");
|
||||
$requestId = $this->getParam("requestId");
|
||||
|
||||
$this->success = $sql->insert("ContactMessage", ["request_id", "user_id", "message", "messageId", "read"])
|
||||
->addRow($requestId, $this->user->getId(), $message, $this->messageId, true)
|
||||
->addRow($requestId, $this->context->getUser()->getId(), $message, $this->messageId, true)
|
||||
->execute();
|
||||
|
||||
$this->lastError = $sql->getLastError();
|
||||
@ -169,7 +151,7 @@ namespace Api\Contact {
|
||||
}
|
||||
|
||||
private function updateEntity() {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$requestId = $this->getParam("requestId");
|
||||
|
||||
$sql->update("EntityLog")
|
||||
@ -185,8 +167,9 @@ namespace Api\Contact {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fromName = $this->user->getUsername();
|
||||
$fromEmail = $this->user->getEmail();
|
||||
$user = $this->context->getUser();
|
||||
$fromName = $user->getUsername();
|
||||
$fromEmail = $user->getEmail();
|
||||
|
||||
if (!$this->sendMail($fromName, $fromEmail, "Re: Contact Request", $message, $senderMail)) {
|
||||
return false;
|
||||
@ -203,19 +186,19 @@ namespace Api\Contact {
|
||||
|
||||
class Fetch extends ContactAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array());
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array());
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("ContactRequest.uid", "from_name", "from_email", "from_name",
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select("ContactRequest.id", "from_name", "from_email", "from_name",
|
||||
new Sum(new CaseWhen(new CondNot("ContactMessage.read"), 1, 0), "unread"))
|
||||
->from("ContactRequest")
|
||||
->groupBy("ContactRequest.uid")
|
||||
->leftJoin("ContactMessage", "ContactRequest.uid", "ContactMessage.request_id")
|
||||
->groupBy("ContactRequest.id")
|
||||
->leftJoin("ContactMessage", "ContactRequest.id", "ContactMessage.request_id")
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== false);
|
||||
@ -225,7 +208,7 @@ namespace Api\Contact {
|
||||
$this->result["contactRequests"] = [];
|
||||
foreach ($res as $row) {
|
||||
$this->result["contactRequests"][] = array(
|
||||
"uid" => intval($row["uid"]),
|
||||
"id" => intval($row["id"]),
|
||||
"from_name" => $row["from_name"],
|
||||
"from_email" => $row["from_email"],
|
||||
"unread" => intval($row["unread"]),
|
||||
@ -239,8 +222,8 @@ namespace Api\Contact {
|
||||
|
||||
class Get extends ContactAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"requestId" => new Parameter("requestId", Parameter::TYPE_INT),
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
@ -249,7 +232,7 @@ namespace Api\Contact {
|
||||
|
||||
private function updateRead() {
|
||||
$requestId = $this->getParam("requestId");
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$sql->update("ContactMessage")
|
||||
->set("read", 1)
|
||||
->where(new Compare("request_id", $requestId))
|
||||
@ -258,11 +241,11 @@ namespace Api\Contact {
|
||||
|
||||
public function _execute(): bool {
|
||||
$requestId = $this->getParam("requestId");
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
$res = $sql->select("from_name", "from_email", "message", "created_at")
|
||||
->from("ContactRequest")
|
||||
->where(new Compare("uid", $requestId))
|
||||
->where(new Compare("id", $requestId))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== false);
|
||||
|
@ -3,11 +3,16 @@
|
||||
namespace Api {
|
||||
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Objects\Context;
|
||||
|
||||
abstract class GroupsAPI extends Request {
|
||||
|
||||
protected function groupExists($name) {
|
||||
$sql = $this->user->getSQL();
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
|
||||
protected function groupExists($name): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select($sql->count())
|
||||
->from("Group")
|
||||
->where(new Compare("name", $name))
|
||||
@ -25,14 +30,15 @@ namespace Api\Groups {
|
||||
use Api\GroupsAPI;
|
||||
use Api\Parameter\Parameter;
|
||||
use Api\Parameter\StringType;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\Group;
|
||||
|
||||
class Fetch extends GroupsAPI {
|
||||
|
||||
private int $groupCount;
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1),
|
||||
'count' => new Parameter('count', Parameter::TYPE_INT, true, 20)
|
||||
));
|
||||
@ -40,9 +46,9 @@ namespace Api\Groups {
|
||||
$this->groupCount = 0;
|
||||
}
|
||||
|
||||
private function getGroupCount() {
|
||||
private function fetchGroupCount(): bool {
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select($sql->count())->from("Group")->execute();
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
@ -65,16 +71,16 @@ namespace Api\Groups {
|
||||
return $this->createError("Invalid fetch count");
|
||||
}
|
||||
|
||||
if (!$this->getGroupCount()) {
|
||||
if (!$this->fetchGroupCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("Group.uid as groupId", "Group.name as groupName", "Group.color as groupColor", $sql->count("UserGroup.user_id"))
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select("Group.id as groupId", "Group.name as groupName", "Group.color as groupColor", $sql->count("UserGroup.user_id"))
|
||||
->from("Group")
|
||||
->leftJoin("UserGroup", "UserGroup.group_id", "Group.uid")
|
||||
->groupBy("Group.uid")
|
||||
->orderBy("Group.uid")
|
||||
->leftJoin("UserGroup", "UserGroup.group_id", "Group.id")
|
||||
->groupBy("Group.id")
|
||||
->orderBy("Group.id")
|
||||
->ascending()
|
||||
->limit($count)
|
||||
->offset(($page - 1) * $count)
|
||||
@ -105,8 +111,8 @@ namespace Api\Groups {
|
||||
}
|
||||
|
||||
class Create extends GroupsAPI {
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'name' => new StringType('name', 32),
|
||||
'color' => new StringType('color', 10),
|
||||
));
|
||||
@ -130,17 +136,17 @@ namespace Api\Groups {
|
||||
return $this->createError("A group with this name already exists");
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->insert("Group", array("name", "color"))
|
||||
->addRow($name, $color)
|
||||
->returning("uid")
|
||||
->execute();
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$group = new Group();
|
||||
$group->name = $name;
|
||||
$group->color = $color;
|
||||
|
||||
$this->success = ($group->save($sql) !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
$this->result["uid"] = $sql->getLastInsertId();
|
||||
$this->result["id"] = $group->getId();
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
@ -148,33 +154,29 @@ namespace Api\Groups {
|
||||
}
|
||||
|
||||
class Delete extends GroupsAPI {
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
'uid' => new Parameter('uid', Parameter::TYPE_INT)
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'id' => new Parameter('id', Parameter::TYPE_INT)
|
||||
));
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$id = $this->getParam("uid");
|
||||
$id = $this->getParam("id");
|
||||
if (in_array($id, DEFAULT_GROUPS)) {
|
||||
return $this->createError("You cannot delete a default group.");
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select($sql->count())
|
||||
->from("Group")
|
||||
->where(new Compare("uid", $id))
|
||||
->execute();
|
||||
$sql = $this->context->getSQL();
|
||||
$group = Group::find($sql, $id);
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->success = ($group !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success && $res[0]["count"] === 0) {
|
||||
if ($this->success && $group === null) {
|
||||
return $this->createError("This group does not exist.");
|
||||
}
|
||||
|
||||
$res = $sql->delete("Group")->where(new Compare("uid", $id))->execute();
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->success = ($group->delete($sql) !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
namespace Api {
|
||||
|
||||
use Objects\Context;
|
||||
|
||||
abstract class LanguageAPI extends Request {
|
||||
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Api\Language {
|
||||
@ -15,30 +18,28 @@ namespace Api\Language {
|
||||
use Api\Parameter\StringType;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondOr;
|
||||
use Objects\Language;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\Language;
|
||||
|
||||
class Get extends LanguageAPI {
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array());
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array());
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("uid", "code", "name")
|
||||
->from("Language")
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$sql = $this->context->getSQL();
|
||||
$languages = Language::findAll($sql);
|
||||
$this->success = ($languages !== null);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if($this->success) {
|
||||
$this->result['languages'] = array();
|
||||
if(empty($res) === 0) {
|
||||
if ($this->success) {
|
||||
$this->result['languages'] = [];
|
||||
if (count($languages) === 0) {
|
||||
$this->lastError = L("No languages found");
|
||||
} else {
|
||||
foreach($res as $row) {
|
||||
$this->result['languages'][$row['uid']] = $row;
|
||||
foreach ($languages as $language) {
|
||||
$this->result['languages'][$language->getId()] = $language->jsonSerialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,70 +52,65 @@ namespace Api\Language {
|
||||
|
||||
private Language $language;
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'langId' => new Parameter('langId', Parameter::TYPE_INT, true, NULL),
|
||||
'langCode' => new StringType('langCode', 5, true, NULL),
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
private function checkLanguage() {
|
||||
private function checkLanguage(): bool {
|
||||
$langId = $this->getParam("langId");
|
||||
$langCode = $this->getParam("langCode");
|
||||
|
||||
if(is_null($langId) && is_null($langCode)) {
|
||||
if (is_null($langId) && is_null($langCode)) {
|
||||
return $this->createError(L("Either langId or langCode must be given"));
|
||||
}
|
||||
|
||||
$res = $this->user->getSQL()
|
||||
->select("uid", "code", "name")
|
||||
->from("Language")
|
||||
->where(new CondOr(new Compare("uid", $langId), new Compare("code", $langCode)))
|
||||
->execute();
|
||||
$sql = $this->context->getSQL();
|
||||
$languages = Language::findAll($sql,
|
||||
new CondOr(new Compare("id", $langId), new Compare("code", $langCode))
|
||||
);
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $this->user->getSQL()->getLastError();
|
||||
$this->success = ($languages !== null);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
if(count($res) == 0) {
|
||||
if (count($languages) === 0) {
|
||||
return $this->createError(L("This Language does not exist"));
|
||||
} else {
|
||||
$row = $res[0];
|
||||
$this->language = Language::newInstance($row['uid'], $row['code'], $row['name']);
|
||||
if(!$this->language) {
|
||||
return $this->createError(L("Error while loading language"));
|
||||
}
|
||||
$this->language = array_shift($languages);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
private function updateLanguage() {
|
||||
private function updateLanguage(): bool {
|
||||
$languageId = $this->language->getId();
|
||||
$userId = $this->user->getId();
|
||||
$sql = $this->user->getSQL();
|
||||
$userId = $this->context->getUser()->getId();
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
$this->success = $sql->update("User")
|
||||
->set("language_id", $languageId)
|
||||
->where(new Compare("uid", $userId))
|
||||
->where(new Compare("id", $userId))
|
||||
->execute();
|
||||
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
if(!$this->checkLanguage())
|
||||
if (!$this->checkLanguage())
|
||||
return false;
|
||||
|
||||
if($this->user->isLoggedIn()) {
|
||||
if ($this->context->getSession()) {
|
||||
$this->updateLanguage();
|
||||
}
|
||||
|
||||
$this->user->setLanguage($this->language);
|
||||
$this->context->setLanguage($this->language);
|
||||
return $this->success;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,11 +2,11 @@
|
||||
|
||||
namespace Api {
|
||||
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
abstract class LogsAPI extends Request {
|
||||
public function __construct(User $user, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($user, $externalCall, $params);
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,20 +21,22 @@ namespace Api\Logs {
|
||||
use Driver\SQL\Column\Column;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondIn;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\SystemLog;
|
||||
|
||||
class Get extends LogsAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"since" => new Parameter("since", Parameter::TYPE_DATE_TIME, true),
|
||||
"severity" => new StringType("severity", 32, true, "debug")
|
||||
]);
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
$since = $this->getParam("since");
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$severity = strtolower(trim($this->getParam("severity")));
|
||||
$shownLogLevels = Logger::LOG_LEVELS;
|
||||
|
||||
@ -45,8 +47,7 @@ namespace Api\Logs {
|
||||
$shownLogLevels = array_slice(Logger::LOG_LEVELS, $logLevel);
|
||||
}
|
||||
|
||||
$query = $sql->select("id", "module", "message", "severity", "timestamp")
|
||||
->from("SystemLog")
|
||||
$query = SystemLog::findAllBuilder($sql)
|
||||
->orderBy("timestamp")
|
||||
->descending();
|
||||
|
||||
@ -58,12 +59,15 @@ namespace Api\Logs {
|
||||
$query->where(new CondIn(new Column("severity"), $shownLogLevels));
|
||||
}
|
||||
|
||||
$res = $query->execute();
|
||||
$this->success = $res !== false;
|
||||
$logEntries = $query->execute();
|
||||
$this->success = $logEntries !== false;
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
$this->result["logs"] = $res;
|
||||
$this->result["logs"] = [];
|
||||
foreach ($logEntries as $logEntry) {
|
||||
$this->result["logs"][] = $logEntry->jsonSerialize();
|
||||
}
|
||||
} else {
|
||||
// we couldn't fetch logs from database, return a message and proceed to log files
|
||||
$this->result["logs"] = [
|
||||
|
@ -3,10 +3,16 @@
|
||||
namespace Api {
|
||||
|
||||
use Objects\ConnectionData;
|
||||
use Objects\Context;
|
||||
|
||||
abstract class MailAPI extends Request {
|
||||
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
|
||||
protected function getMailConfig(): ?ConnectionData {
|
||||
$req = new \Api\Settings\Get($this->user);
|
||||
$req = new \Api\Settings\Get($this->context);
|
||||
$this->success = $req->execute(array("key" => "^mail_"));
|
||||
$this->lastError = $req->getLastError();
|
||||
|
||||
@ -47,13 +53,13 @@ namespace Api\Mail {
|
||||
use External\PHPMailer\Exception;
|
||||
use External\PHPMailer\PHPMailer;
|
||||
use Objects\ConnectionData;
|
||||
use Objects\GpgKey;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\GpgKey;
|
||||
|
||||
class Test extends MailAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"receiver" => new Parameter("receiver", Parameter::TYPE_EMAIL),
|
||||
"gpgFingerprint" => new StringType("gpgFingerprint", 64, true, null)
|
||||
));
|
||||
@ -62,7 +68,7 @@ namespace Api\Mail {
|
||||
public function _execute(): bool {
|
||||
|
||||
$receiver = $this->getParam("receiver");
|
||||
$req = new \Api\Mail\Send($this->user);
|
||||
$req = new \Api\Mail\Send($this->context);
|
||||
$this->success = $req->execute(array(
|
||||
"to" => $receiver,
|
||||
"subject" => "Test E-Mail",
|
||||
@ -76,16 +82,15 @@ namespace Api\Mail {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: expired gpg keys?
|
||||
class Send extends MailAPI {
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'to' => new Parameter('to', Parameter::TYPE_EMAIL, true, null),
|
||||
'subject' => new StringType('subject', -1),
|
||||
'body' => new StringType('body', -1),
|
||||
'replyTo' => new Parameter('replyTo', Parameter::TYPE_EMAIL, true, null),
|
||||
'replyName' => new StringType('replyName', 32, true, ""),
|
||||
"gpgFingerprint" => new StringType("gpgFingerprint", 64, true, null),
|
||||
'gpgFingerprint' => new StringType("gpgFingerprint", 64, true, null),
|
||||
'async' => new Parameter("async", Parameter::TYPE_BOOLEAN, true, true)
|
||||
));
|
||||
$this->isPublic = false;
|
||||
@ -108,7 +113,7 @@ namespace Api\Mail {
|
||||
$gpgFingerprint = $this->getParam("gpgFingerprint");
|
||||
|
||||
if ($this->getParam("async")) {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $sql->insert("MailQueue", ["from", "to", "subject", "body",
|
||||
"replyTo", "replyName", "gpgFingerprint"])
|
||||
->addRow($fromMail, $toMail, $subject, $body, $replyTo, $replyName, $gpgFingerprint)
|
||||
@ -200,14 +205,14 @@ namespace Api\Mail {
|
||||
// TODO: attachments
|
||||
class Sync extends MailAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array());
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array());
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
private function fetchMessageIds() {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("uid", "messageId")
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select("id", "messageId")
|
||||
->from("ContactRequest")
|
||||
->where(new Compare("messageId", NULL, "!="))
|
||||
->execute();
|
||||
@ -220,7 +225,7 @@ namespace Api\Mail {
|
||||
|
||||
$messageIds = [];
|
||||
foreach ($res as $row) {
|
||||
$messageIds[$row["messageId"]] = $row["uid"];
|
||||
$messageIds[$row["messageId"]] = $row["id"];
|
||||
}
|
||||
return $messageIds;
|
||||
}
|
||||
@ -241,7 +246,7 @@ namespace Api\Mail {
|
||||
}
|
||||
|
||||
private function insertMessages($messages): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
$query = $sql->insert("ContactMessage", ["request_id", "user_id", "message", "messageId", "created_at"])
|
||||
->onDuplicateKeyStrategy(new UpdateStrategy(["messageId"], ["message" => new Column("message")]));
|
||||
@ -251,7 +256,7 @@ namespace Api\Mail {
|
||||
$requestId = $message["requestId"];
|
||||
$query->addRow(
|
||||
$requestId,
|
||||
$sql->select("uid")->from("User")->where(new Compare("email", $message["from"]))->limit(1),
|
||||
$sql->select("id")->from("User")->where(new Compare("email", $message["from"]))->limit(1),
|
||||
$message["body"],
|
||||
$message["messageId"],
|
||||
(new \DateTime())->setTimeStamp($message["timestamp"]),
|
||||
@ -450,7 +455,7 @@ namespace Api\Mail {
|
||||
return false;
|
||||
}
|
||||
|
||||
$req = new \Api\Settings\Set($this->user);
|
||||
$req = new \Api\Settings\Set($this->context);
|
||||
$this->success = $req->execute(array("settings" => array("mail_last_sync" => "$now")));
|
||||
$this->lastError = $req->getLastError();
|
||||
return $this->success;
|
||||
@ -458,8 +463,8 @@ namespace Api\Mail {
|
||||
}
|
||||
|
||||
class SendQueue extends MailAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"debug" => new Parameter("debug", Parameter::TYPE_BOOLEAN, true, false)
|
||||
]);
|
||||
$this->isPublic = false;
|
||||
@ -473,8 +478,8 @@ namespace Api\Mail {
|
||||
echo "Start of processing mail queue at $startTime" . PHP_EOL;
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("uid", "from", "to", "subject", "body",
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select("id", "from", "to", "subject", "body",
|
||||
"replyTo", "replyName", "gpgFingerprint", "retryCount")
|
||||
->from("MailQueue")
|
||||
->where(new Compare("retryCount", 0, ">"))
|
||||
@ -505,9 +510,9 @@ namespace Api\Mail {
|
||||
echo "Sending subject=$subject to=$to" . PHP_EOL;
|
||||
}
|
||||
|
||||
$mailId = intval($row["uid"]);
|
||||
$mailId = intval($row["id"]);
|
||||
$retryCount = intval($row["retryCount"]);
|
||||
$req = new Send($this->user);
|
||||
$req = new Send($this->context);
|
||||
$args = [
|
||||
"to" => $to,
|
||||
"subject" => $subject,
|
||||
@ -529,7 +534,7 @@ namespace Api\Mail {
|
||||
->set("status", "error")
|
||||
->set("errorMessage", $error)
|
||||
->set("nextTry", $nextTry)
|
||||
->where(new Compare("uid", $mailId))
|
||||
->where(new Compare("id", $mailId))
|
||||
->execute();
|
||||
} else {
|
||||
$successfulMails[] = $mailId;
|
||||
@ -540,7 +545,7 @@ namespace Api\Mail {
|
||||
if (!empty($successfulMails)) {
|
||||
$res = $sql->update("MailQueue")
|
||||
->set("status", "success")
|
||||
->where(new CondIn(new Column("uid"), $successfulMails))
|
||||
->where(new CondIn(new Column("id"), $successfulMails))
|
||||
->execute();
|
||||
$this->success = $res !== false;
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
namespace Api {
|
||||
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
abstract class NewsAPI extends Request {
|
||||
public function __construct(User $user, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($user, $externalCall, $params);
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
}
|
||||
@ -18,57 +18,47 @@ namespace Api\News {
|
||||
use Api\Parameter\Parameter;
|
||||
use Api\Parameter\StringType;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\News;
|
||||
|
||||
class Get extends NewsAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"since" => new Parameter("since", Parameter::TYPE_DATE_TIME, true, null),
|
||||
"limit" => new Parameter("limit", Parameter::TYPE_INT, true, 10)
|
||||
]);
|
||||
|
||||
$this->loginRequired = false;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$query = $sql->select("News.uid", "title", "text", "publishedAt",
|
||||
"User.uid as publisherId", "User.name as publisherName", "User.fullName as publisherFullName")
|
||||
->from("News")
|
||||
->innerJoin("User", "User.uid", "News.publishedBy")
|
||||
->orderBy("publishedAt")
|
||||
->descending();
|
||||
|
||||
$since = $this->getParam("since");
|
||||
if ($since) {
|
||||
$query->where(new Compare("publishedAt", $since, ">="));
|
||||
}
|
||||
|
||||
$limit = $this->getParam("limit");
|
||||
if ($limit < 1 || $limit > 30) {
|
||||
return $this->createError("Limit must be in range 1-30");
|
||||
} else {
|
||||
$query->limit($limit);
|
||||
}
|
||||
|
||||
$res = $query->execute();
|
||||
$this->success = $res !== false;
|
||||
$sql = $this->context->getSQL();
|
||||
$newsQuery = News::findAllBuilder($sql)
|
||||
->limit($limit)
|
||||
->orderBy("published_at")
|
||||
->descending()
|
||||
->fetchEntities();
|
||||
|
||||
if ($since) {
|
||||
$newsQuery->where(new Compare("published_at", $since, ">="));
|
||||
}
|
||||
|
||||
$newsArray = $newsQuery->execute();
|
||||
$this->success = $newsArray !== null;
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
$this->result["news"] = [];
|
||||
foreach ($res as $row) {
|
||||
$newsId = intval($row["uid"]);
|
||||
$this->result["news"][$newsId] = [
|
||||
"id" => $newsId,
|
||||
"title" => $row["title"],
|
||||
"text" => $row["text"],
|
||||
"publishedAt" => $row["publishedAt"],
|
||||
"publisher" => [
|
||||
"id" => intval($row["publisherId"]),
|
||||
"name" => $row["publisherName"],
|
||||
"fullName" => $row["publisherFullName"]
|
||||
]
|
||||
];
|
||||
foreach ($newsArray as $news) {
|
||||
$newsId = $news->getId();
|
||||
$this->result["news"][$newsId] = $news->jsonSerialize();
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,28 +67,27 @@ namespace Api\News {
|
||||
}
|
||||
|
||||
class Publish extends NewsAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"title" => new StringType("title", 128),
|
||||
"text" => new StringType("text", 1024)
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$title = $this->getParam("title");
|
||||
$text = $this->getParam("text");
|
||||
|
||||
$res = $sql->insert("News", ["title", "text"])
|
||||
->addRow($title, $text)
|
||||
->returning("uid")
|
||||
->execute();
|
||||
$news = new News();
|
||||
$news->text = $this->getParam("text");
|
||||
$news->title = $this->getParam("title");
|
||||
$news->publishedBy = $this->context->getUser();
|
||||
|
||||
$this->success = $res !== false;
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $news->save($sql);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
$this->result["newsId"] = $sql->getLastInsertId();
|
||||
$this->result["newsId"] = $news->getId();
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
@ -106,77 +95,62 @@ namespace Api\News {
|
||||
}
|
||||
|
||||
class Delete extends NewsAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT)
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$id = $this->getParam("id");
|
||||
$res = $sql->select("publishedBy")
|
||||
->from("News")
|
||||
->where(new Compare("uid", $id))
|
||||
->execute();
|
||||
$sql = $this->context->getSQL();
|
||||
$currentUser = $this->context->getUser();
|
||||
|
||||
$this->success = ($res !== false);
|
||||
$news = News::find($sql, $this->getParam("id"));
|
||||
$this->success = ($news !== false);
|
||||
$this->lastError = $sql->getLastError();
|
||||
if (!$this->success) {
|
||||
return false;
|
||||
} else if (empty($res) || !is_array($res)) {
|
||||
} else if ($news === null) {
|
||||
return $this->createError("News Post not found");
|
||||
} else if (intval($res[0]["publishedBy"]) !== $this->user->getId() && !$this->user->hasGroup(USER_GROUP_ADMIN)) {
|
||||
} else if ($news->publishedBy->getId() !== $currentUser->getId() && !$currentUser->hasGroup(USER_GROUP_ADMIN)) {
|
||||
return $this->createError("You do not have permissions to delete news post of other users.");
|
||||
}
|
||||
|
||||
$res = $sql->delete("News")
|
||||
->where(new Compare("uid", $id))
|
||||
->execute();
|
||||
|
||||
$this->success = $res !== false;
|
||||
$this->success = $news->delete($sql);
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
}
|
||||
|
||||
class Edit extends NewsAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT),
|
||||
"title" => new StringType("title", 128),
|
||||
"text" => new StringType("text", 1024)
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$currentUser = $this->context->getUser();
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$id = $this->getParam("id");
|
||||
$text = $this->getParam("text");
|
||||
$title = $this->getParam("title");
|
||||
$res = $sql->select("publishedBy")
|
||||
->from("News")
|
||||
->where(new Compare("uid", $id))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== false);
|
||||
$news = News::find($sql, $this->getParam("id"));
|
||||
$this->success = ($news !== false);
|
||||
$this->lastError = $sql->getLastError();
|
||||
if (!$this->success) {
|
||||
return false;
|
||||
} else if (empty($res) || !is_array($res)) {
|
||||
} else if ($news === null) {
|
||||
return $this->createError("News Post not found");
|
||||
} else if (intval($res[0]["publishedBy"]) !== $this->user->getId() && !$this->user->hasGroup(USER_GROUP_ADMIN)) {
|
||||
} else if ($news->publishedBy->getId() !== $currentUser->getId() && !$currentUser->hasGroup(USER_GROUP_ADMIN)) {
|
||||
return $this->createError("You do not have permissions to edit news post of other users.");
|
||||
}
|
||||
|
||||
$res = $sql->update("News")
|
||||
->set("title", $title)
|
||||
->set("text", $text)
|
||||
->where(new Compare("uid", $id))
|
||||
->execute();
|
||||
|
||||
$this->success = $res !== false;
|
||||
$news->text = $this->getParam("text");
|
||||
$news->title = $this->getParam("title");
|
||||
$this->success = $news->save($sql);
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Api {
|
||||
abstract class NotificationsAPI extends Request {
|
||||
|
||||
use Objects\Context;
|
||||
|
||||
abstract class NotificationsAPI extends Request {
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,12 +20,15 @@ namespace Api\Notifications {
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondIn;
|
||||
use Driver\SQL\Query\Select;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\Group;
|
||||
use Objects\DatabaseEntity\Notification;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
class Create extends NotificationsAPI {
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'groupId' => new Parameter('groupId', Parameter::TYPE_INT, true),
|
||||
'userId' => new Parameter('userId', Parameter::TYPE_INT, true),
|
||||
'title' => new StringType('title', 32),
|
||||
@ -29,28 +37,8 @@ namespace Api\Notifications {
|
||||
$this->isPublic = false;
|
||||
}
|
||||
|
||||
private function checkUser($userId) {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select($sql->count())
|
||||
->from("User")
|
||||
->where(new Compare("uid", $userId))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
if ($res[0]["count"] == 0) {
|
||||
$this->success = false;
|
||||
$this->lastError = "User not found";
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
private function insertUserNotification($userId, $notificationId) {
|
||||
$sql = $this->user->getSQL();
|
||||
private function insertUserNotification($userId, $notificationId): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->insert("UserNotification", array("user_id", "notification_id"))
|
||||
->addRow($userId, $notificationId)
|
||||
->execute();
|
||||
@ -60,28 +48,8 @@ namespace Api\Notifications {
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
private function checkGroup($groupId) {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select($sql->count())
|
||||
->from("Group")
|
||||
->where(new Compare("uid", $groupId))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
if ($res[0]["count"] == 0) {
|
||||
$this->success = false;
|
||||
$this->lastError = "Group not found";
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
private function insertGroupNotification($groupId, $notificationId) {
|
||||
$sql = $this->user->getSQL();
|
||||
private function insertGroupNotification($groupId, $notificationId): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->insert("GroupNotification", array("group_id", "notification_id"))
|
||||
->addRow($groupId, $notificationId)
|
||||
->execute();
|
||||
@ -91,24 +59,24 @@ namespace Api\Notifications {
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
private function createNotification($title, $message) {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->insert("Notification", array("title", "message"))
|
||||
->addRow($title, $message)
|
||||
->returning("uid")
|
||||
->execute();
|
||||
private function createNotification($title, $message): bool|int {
|
||||
$sql = $this->context->getSQL();
|
||||
$notification = new Notification();
|
||||
$notification->title = $title;
|
||||
$notification->message = $message;
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->success = ($notification->save($sql) !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
return $sql->getLastInsertId();
|
||||
return $notification->getId();
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$userId = $this->getParam("userId");
|
||||
$groupId = $this->getParam("groupId");
|
||||
$title = $this->getParam("title");
|
||||
@ -119,18 +87,22 @@ namespace Api\Notifications {
|
||||
} else if(!is_null($userId) && !is_null($groupId)) {
|
||||
return $this->createError("Only one of userId and groupId must be specified.");
|
||||
} else if(!is_null($userId)) {
|
||||
if ($this->checkUser($userId)) {
|
||||
if (User::exists($sql, $userId)) {
|
||||
$id = $this->createNotification($title, $message);
|
||||
if ($this->success) {
|
||||
return $this->insertUserNotification($userId, $id);
|
||||
}
|
||||
} else {
|
||||
return $this->createError("User not found: $userId");
|
||||
}
|
||||
} else if(!is_null($groupId)) {
|
||||
if ($this->checkGroup($groupId)) {
|
||||
if (Group::exists($sql, $groupId)) {
|
||||
$id = $this->createNotification($title, $message);
|
||||
if ($this->success) {
|
||||
return $this->insertGroupNotification($groupId, $id);
|
||||
}
|
||||
} else {
|
||||
return $this->createError("Group not found: $groupId");
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,22 +113,22 @@ namespace Api\Notifications {
|
||||
class Fetch extends NotificationsAPI {
|
||||
|
||||
private array $notifications;
|
||||
private array $notificationids;
|
||||
private array $notificationIds;
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'new' => new Parameter('new', Parameter::TYPE_BOOLEAN, true, true)
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
private function fetchUserNotifications() {
|
||||
private function fetchUserNotifications(): bool {
|
||||
$onlyNew = $this->getParam('new');
|
||||
$userId = $this->user->getId();
|
||||
$sql = $this->user->getSQL();
|
||||
$query = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message", "type")
|
||||
$userId = $this->context->getUser()->getId();
|
||||
$sql = $this->context->getSQL();
|
||||
$query = $sql->select($sql->distinct("Notification.id"), "created_at", "title", "message", "type")
|
||||
->from("Notification")
|
||||
->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.uid")
|
||||
->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.id")
|
||||
->where(new Compare("UserNotification.user_id", $userId))
|
||||
->orderBy("created_at")->descending();
|
||||
|
||||
@ -167,13 +139,13 @@ namespace Api\Notifications {
|
||||
return $this->fetchNotifications($query);
|
||||
}
|
||||
|
||||
private function fetchGroupNotifications() {
|
||||
private function fetchGroupNotifications(): bool {
|
||||
$onlyNew = $this->getParam('new');
|
||||
$userId = $this->user->getId();
|
||||
$sql = $this->user->getSQL();
|
||||
$query = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message", "type")
|
||||
$userId = $this->context->getUser()->getId();
|
||||
$sql = $this->context->getSQL();
|
||||
$query = $sql->select($sql->distinct("Notification.id"), "created_at", "title", "message", "type")
|
||||
->from("Notification")
|
||||
->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.uid")
|
||||
->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.id")
|
||||
->innerJoin("UserGroup", "GroupNotification.group_id", "UserGroup.group_id")
|
||||
->where(new Compare("UserGroup.user_id", $userId))
|
||||
->orderBy("created_at")->descending();
|
||||
@ -185,19 +157,19 @@ namespace Api\Notifications {
|
||||
return $this->fetchNotifications($query);
|
||||
}
|
||||
|
||||
private function fetchNotifications(Select $query) {
|
||||
$sql = $this->user->getSQL();
|
||||
private function fetchNotifications(Select $query): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $query->execute();
|
||||
$this->success = ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
foreach($res as $row) {
|
||||
$id = $row["uid"];
|
||||
if (!in_array($id, $this->notificationids)) {
|
||||
$this->notificationids[] = $id;
|
||||
$id = $row["id"];
|
||||
if (!in_array($id, $this->notificationIds)) {
|
||||
$this->notificationIds[] = $id;
|
||||
$this->notifications[] = array(
|
||||
"uid" => $id,
|
||||
"id" => $id,
|
||||
"title" => $row["title"],
|
||||
"message" => $row["message"],
|
||||
"created_at" => $row["created_at"],
|
||||
@ -212,7 +184,7 @@ namespace Api\Notifications {
|
||||
|
||||
public function _execute(): bool {
|
||||
$this->notifications = array();
|
||||
$this->notificationids = array();
|
||||
$this->notificationIds = array();
|
||||
if ($this->fetchUserNotifications() && $this->fetchGroupNotifications()) {
|
||||
$this->result["notifications"] = $this->notifications;
|
||||
}
|
||||
@ -223,17 +195,18 @@ namespace Api\Notifications {
|
||||
|
||||
class Seen extends NotificationsAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array());
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array());
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$currentUser = $this->context->getUser();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->update("UserNotification")
|
||||
->set("seen", true)
|
||||
->where(new Compare("user_id", $this->user->getId()))
|
||||
->where(new Compare("user_id", $currentUser->getId()))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
@ -245,7 +218,7 @@ namespace Api\Notifications {
|
||||
->where(new CondIn(new Column("group_id"),
|
||||
$sql->select("group_id")
|
||||
->from("UserGroup")
|
||||
->where(new Compare("user_id", $this->user->getId()))))
|
||||
->where(new Compare("user_id", $currentUser->getId()))))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
|
@ -2,9 +2,17 @@
|
||||
|
||||
namespace Api {
|
||||
|
||||
use Objects\Context;
|
||||
|
||||
abstract class PermissionAPI extends Request {
|
||||
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
|
||||
protected function checkStaticPermission(): bool {
|
||||
if (!$this->user->isLoggedIn() || !$this->user->hasGroup(USER_GROUP_ADMIN)) {
|
||||
$user = $this->context->getUser();
|
||||
if (!$user || !$user->hasGroup(USER_GROUP_ADMIN)) {
|
||||
return $this->createError("Permission denied.");
|
||||
}
|
||||
|
||||
@ -24,12 +32,14 @@ namespace Api\Permission {
|
||||
use Driver\SQL\Condition\CondLike;
|
||||
use Driver\SQL\Condition\CondNot;
|
||||
use Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\Group;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
class Check extends PermissionAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'method' => new StringType('method', 323)
|
||||
));
|
||||
|
||||
@ -39,7 +49,7 @@ namespace Api\Permission {
|
||||
public function _execute(): bool {
|
||||
|
||||
$method = $this->getParam("method");
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select("groups")
|
||||
->from("ApiPermission")
|
||||
->where(new CondLike($method, new Column("method")))
|
||||
@ -58,8 +68,9 @@ namespace Api\Permission {
|
||||
return true;
|
||||
}
|
||||
|
||||
$userGroups = $this->user->getGroups();
|
||||
if (empty($userGroups) || empty(array_intersect($groups, array_keys($this->user->getGroups())))) {
|
||||
$currentUser = $this->context->getUser();
|
||||
$userGroups = $currentUser ? $currentUser->getGroups() : [];
|
||||
if (empty($userGroups) || empty(array_intersect($groups, array_keys($userGroups)))) {
|
||||
http_response_code(401);
|
||||
return $this->createError("Permission denied.");
|
||||
}
|
||||
@ -71,33 +82,17 @@ namespace Api\Permission {
|
||||
|
||||
class Fetch extends PermissionAPI {
|
||||
|
||||
private array $groups;
|
||||
private ?array $groups;
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array());
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array());
|
||||
}
|
||||
|
||||
private function fetchGroups() {
|
||||
$sql = $this->user->getSQL();
|
||||
$res = $sql->select("uid", "name", "color")
|
||||
->from("Group")
|
||||
->orderBy("uid")
|
||||
->ascending()
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== FALSE);
|
||||
private function fetchGroups(): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$this->groups = Group::findAll($sql);
|
||||
$this->success = ($this->groups !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
if ($this->success) {
|
||||
$this->groups = array();
|
||||
foreach($res as $row) {
|
||||
$groupId = $row["uid"];
|
||||
$groupName = $row["name"];
|
||||
$groupColor = $row["color"];
|
||||
$this->groups[$groupId] = array("name" => $groupName, "color" => $groupColor);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
@ -107,7 +102,7 @@ namespace Api\Permission {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select("method", "groups", "description")
|
||||
->from("ApiPermission")
|
||||
->execute();
|
||||
@ -137,8 +132,8 @@ namespace Api\Permission {
|
||||
|
||||
class Save extends PermissionAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'permissions' => new Parameter('permissions', Parameter::TYPE_ARRAY)
|
||||
));
|
||||
}
|
||||
@ -150,27 +145,27 @@ namespace Api\Permission {
|
||||
}
|
||||
|
||||
$permissions = $this->getParam("permissions");
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$methodParam = new StringType('method', 32);
|
||||
$groupsParam = new Parameter('groups', Parameter::TYPE_ARRAY);
|
||||
|
||||
$updateQuery = $sql->insert("ApiPermission", array("method", "groups"))
|
||||
->onDuplicateKeyStrategy(new UpdateStrategy(array("method"), array( "groups" => new Column("groups") )));
|
||||
->onDuplicateKeyStrategy(new UpdateStrategy(array("method"), array("groups" => new Column("groups"))));
|
||||
|
||||
$insertedMethods = array();
|
||||
|
||||
foreach($permissions as $permission) {
|
||||
foreach ($permissions as $permission) {
|
||||
if (!is_array($permission)) {
|
||||
return $this->createError("Invalid data type found in parameter: permissions, expected: object");
|
||||
} else if(!isset($permission["method"]) || !array_key_exists("groups", $permission)) {
|
||||
} else if (!isset($permission["method"]) || !array_key_exists("groups", $permission)) {
|
||||
return $this->createError("Invalid object found in parameter: permissions, expected keys 'method' and 'groups'");
|
||||
} else if (!$methodParam->parseParam($permission["method"])) {
|
||||
$expectedType = $methodParam->getTypeName();
|
||||
return $this->createError("Invalid data type found for attribute 'method', expected: $expectedType");
|
||||
} else if(!$groupsParam->parseParam($permission["groups"])) {
|
||||
} else if (!$groupsParam->parseParam($permission["groups"])) {
|
||||
$expectedType = $groupsParam->getTypeName();
|
||||
return $this->createError("Invalid data type found for attribute 'groups', expected: $expectedType");
|
||||
} else if(empty(trim($methodParam->value))) {
|
||||
} else if (empty(trim($methodParam->value))) {
|
||||
return $this->createError("Method cannot be empty.");
|
||||
} else {
|
||||
$method = $methodParam->value;
|
||||
|
@ -3,7 +3,7 @@
|
||||
namespace Api;
|
||||
|
||||
use Driver\Logger\Logger;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use PhpMqtt\Client\MqttClient;
|
||||
|
||||
/**
|
||||
@ -14,7 +14,7 @@ use PhpMqtt\Client\MqttClient;
|
||||
|
||||
abstract class Request {
|
||||
|
||||
protected User $user;
|
||||
protected Context $context;
|
||||
protected Logger $logger;
|
||||
protected array $params;
|
||||
protected string $lastError;
|
||||
@ -31,9 +31,9 @@ abstract class Request {
|
||||
private array $allowedMethods;
|
||||
private bool $externalCall;
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false, array $params = array()) {
|
||||
$this->user = $user;
|
||||
$this->logger = new Logger($this->getAPIName(), $this->user->getSQL());
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
$this->context = $context;
|
||||
$this->logger = new Logger($this->getAPIName(), $this->context->getSQL());
|
||||
$this->defaultParams = $params;
|
||||
|
||||
$this->success = false;
|
||||
@ -137,8 +137,9 @@ abstract class Request {
|
||||
$this->result = array();
|
||||
$this->lastError = '';
|
||||
|
||||
if ($this->user->isLoggedIn()) {
|
||||
$this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds();
|
||||
$session = $this->context->getSession();
|
||||
if ($session) {
|
||||
$this->result['logoutIn'] = $session->getExpiresSeconds();
|
||||
}
|
||||
|
||||
if ($this->externalCall) {
|
||||
@ -183,25 +184,24 @@ abstract class Request {
|
||||
}
|
||||
|
||||
$apiKeyAuthorized = false;
|
||||
|
||||
if (!$this->user->isLoggedIn() && $this->apiKeyAllowed) {
|
||||
if (!$session && $this->apiKeyAllowed) {
|
||||
if (isset($_SERVER["HTTP_AUTHORIZATION"])) {
|
||||
$authHeader = $_SERVER["HTTP_AUTHORIZATION"];
|
||||
if (startsWith($authHeader, "Bearer ")) {
|
||||
$apiKey = substr($authHeader, strlen("Bearer "));
|
||||
$apiKeyAuthorized = $this->user->loadApiKey($apiKey);
|
||||
$apiKeyAuthorized = $this->context->loadApiKey($apiKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logged in or api key authorized?
|
||||
if ($this->loginRequired) {
|
||||
if (!$this->user->isLoggedIn() && !$apiKeyAuthorized) {
|
||||
if (!$session && !$apiKeyAuthorized) {
|
||||
$this->lastError = 'You are not logged in.';
|
||||
http_response_code(401);
|
||||
return false;
|
||||
} else if ($this->user->isLoggedIn()) {
|
||||
$tfaToken = $this->user->getTwoFactorToken();
|
||||
} else if ($session) {
|
||||
$tfaToken = $session->getUser()->getTwoFactorToken();
|
||||
if ($tfaToken && $tfaToken->isConfirmed() && !$tfaToken->isAuthenticated()) {
|
||||
$this->lastError = '2FA-Authorization is required';
|
||||
http_response_code(401);
|
||||
@ -211,11 +211,11 @@ abstract class Request {
|
||||
}
|
||||
|
||||
// CSRF Token
|
||||
if ($this->csrfTokenRequired && $this->user->isLoggedIn()) {
|
||||
if ($this->csrfTokenRequired && $session) {
|
||||
// csrf token required + external call
|
||||
// if it's not a call with API_KEY, check for csrf_token
|
||||
$csrfToken = $values["csrf_token"] ?? $_SERVER["HTTP_XSRF_TOKEN"] ?? null;
|
||||
if (!$csrfToken || strcmp($csrfToken, $this->user->getSession()->getCsrfToken()) !== 0) {
|
||||
if (!$csrfToken || strcmp($csrfToken, $session->getCsrfToken()) !== 0) {
|
||||
$this->lastError = "CSRF-Token mismatch";
|
||||
http_response_code(403);
|
||||
return false;
|
||||
@ -224,7 +224,7 @@ abstract class Request {
|
||||
|
||||
// Check for permission
|
||||
if (!($this instanceof \Api\Permission\Save)) {
|
||||
$req = new \Api\Permission\Check($this->user);
|
||||
$req = new \Api\Permission\Check($this->context);
|
||||
$this->success = $req->execute(array("method" => $this->getMethod()));
|
||||
$this->lastError = $req->getLastError();
|
||||
if (!$this->success) {
|
||||
@ -241,8 +241,9 @@ abstract class Request {
|
||||
$this->parseVariableParams($values);
|
||||
}
|
||||
|
||||
if (!$this->user->getSQL()->isConnected()) {
|
||||
$this->lastError = $this->user->getSQL()->getLastError();
|
||||
$sql = $this->context->getSQL();
|
||||
if (!$sql->isConnected()) {
|
||||
$this->lastError = $sql->getLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -254,7 +255,7 @@ abstract class Request {
|
||||
$this->success = $success;
|
||||
}
|
||||
|
||||
$this->user->getSQL()->setLastError('');
|
||||
$sql->setLastError('');
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
@ -331,8 +332,8 @@ abstract class Request {
|
||||
}
|
||||
|
||||
protected function setupSSE() {
|
||||
$this->user->getSQL()->close();
|
||||
$this->user->sendCookies();
|
||||
$this->context->sendCookies();
|
||||
$this->context->getSQL()?->close();
|
||||
set_time_limit(0);
|
||||
ignore_user_abort(true);
|
||||
header('Content-Type: text/event-stream');
|
||||
|
@ -4,7 +4,7 @@ namespace Api {
|
||||
|
||||
use Api\Routes\GenerateCache;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
abstract class RoutesAPI extends Request {
|
||||
|
||||
@ -13,16 +13,16 @@ namespace Api {
|
||||
|
||||
protected string $routerCachePath;
|
||||
|
||||
public function __construct(User $user, bool $externalCall, array $params) {
|
||||
parent::__construct($user, $externalCall, $params);
|
||||
public function __construct(Context $context, bool $externalCall, array $params) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
$this->routerCachePath = getClassPath(self::ROUTER_CACHE_CLASS);
|
||||
}
|
||||
|
||||
protected function routeExists($uid): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select($sql->count())
|
||||
->from("Route")
|
||||
->where(new Compare("uid", $uid))
|
||||
->where(new Compare("id", $uid))
|
||||
->execute();
|
||||
|
||||
$this->success = ($res !== false);
|
||||
@ -41,10 +41,10 @@ namespace Api {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $sql->update("Route")
|
||||
->set("active", $active)
|
||||
->where(new Compare("uid", $uid))
|
||||
->where(new Compare("id", $uid))
|
||||
->execute();
|
||||
|
||||
$this->lastError = $sql->getLastError();
|
||||
@ -53,7 +53,7 @@ namespace Api {
|
||||
}
|
||||
|
||||
protected function regenerateCache(): bool {
|
||||
$req = new GenerateCache($this->user);
|
||||
$req = new GenerateCache($this->context);
|
||||
$this->success = $req->execute();
|
||||
$this->lastError = $req->getLastError();
|
||||
return $this->success;
|
||||
@ -68,25 +68,25 @@ namespace Api\Routes {
|
||||
use Api\RoutesAPI;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondBool;
|
||||
use Objects\Context;
|
||||
use Objects\Router\DocumentRoute;
|
||||
use Objects\Router\RedirectRoute;
|
||||
use Objects\Router\Router;
|
||||
use Objects\Router\StaticFileRoute;
|
||||
use Objects\User;
|
||||
|
||||
class Fetch extends RoutesAPI {
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array());
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array());
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
$res = $sql
|
||||
->select("uid", "request", "action", "target", "extra", "active", "exact")
|
||||
->select("id", "request", "action", "target", "extra", "active", "exact")
|
||||
->from("Route")
|
||||
->orderBy("uid")
|
||||
->orderBy("id")
|
||||
->ascending()
|
||||
->execute();
|
||||
|
||||
@ -97,7 +97,7 @@ namespace Api\Routes {
|
||||
$routes = array();
|
||||
foreach ($res as $row) {
|
||||
$routes[] = array(
|
||||
"uid" => intval($row["uid"]),
|
||||
"id" => intval($row["id"]),
|
||||
"request" => $row["request"],
|
||||
"action" => $row["action"],
|
||||
"target" => $row["target"],
|
||||
@ -118,8 +118,8 @@ namespace Api\Routes {
|
||||
|
||||
private array $routes;
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'routes' => new Parameter('routes', Parameter::TYPE_ARRAY, false)
|
||||
));
|
||||
}
|
||||
@ -129,7 +129,7 @@ namespace Api\Routes {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
// DELETE old rules
|
||||
$this->success = ($sql->truncate("Route")->execute() !== FALSE);
|
||||
@ -210,8 +210,8 @@ namespace Api\Routes {
|
||||
|
||||
class Add extends RoutesAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"request" => new StringType("request", 128),
|
||||
"action" => new StringType("action"),
|
||||
"target" => new StringType("target", 128),
|
||||
@ -231,7 +231,7 @@ namespace Api\Routes {
|
||||
return $this->createError("Invalid action: $action");
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $sql->insert("Route", ["request", "action", "target", "extra"])
|
||||
->addRow($request, $action, $target, $extra)
|
||||
->execute();
|
||||
@ -243,9 +243,9 @@ namespace Api\Routes {
|
||||
}
|
||||
|
||||
class Update extends RoutesAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
"uid" => new Parameter("uid", Parameter::TYPE_INT),
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT),
|
||||
"request" => new StringType("request", 128),
|
||||
"action" => new StringType("action"),
|
||||
"target" => new StringType("target", 128),
|
||||
@ -256,8 +256,8 @@ namespace Api\Routes {
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$uid = $this->getParam("uid");
|
||||
if (!$this->routeExists($uid)) {
|
||||
$id = $this->getParam("id");
|
||||
if (!$this->routeExists($id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -269,13 +269,13 @@ namespace Api\Routes {
|
||||
return $this->createError("Invalid action: $action");
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $sql->update("Route")
|
||||
->set("request", $request)
|
||||
->set("action", $action)
|
||||
->set("target", $target)
|
||||
->set("extra", $extra)
|
||||
->where(new Compare("uid", $uid))
|
||||
->where(new Compare("id", $id))
|
||||
->execute();
|
||||
|
||||
$this->lastError = $sql->getLastError();
|
||||
@ -285,23 +285,23 @@ namespace Api\Routes {
|
||||
}
|
||||
|
||||
class Remove extends RoutesAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
"uid" => new Parameter("uid", Parameter::TYPE_INT)
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT)
|
||||
));
|
||||
$this->isPublic = false;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$uid = $this->getParam("uid");
|
||||
$uid = $this->getParam("id");
|
||||
if (!$this->routeExists($uid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $sql->delete("Route")
|
||||
->where(new Compare("uid", $uid))
|
||||
->where(new Compare("id", $uid))
|
||||
->execute();
|
||||
|
||||
$this->lastError = $sql->getLastError();
|
||||
@ -311,29 +311,29 @@ namespace Api\Routes {
|
||||
}
|
||||
|
||||
class Enable extends RoutesAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
"uid" => new Parameter("uid", Parameter::TYPE_INT)
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT)
|
||||
));
|
||||
$this->isPublic = false;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$uid = $this->getParam("uid");
|
||||
$uid = $this->getParam("id");
|
||||
return $this->toggleRoute($uid, true);
|
||||
}
|
||||
}
|
||||
|
||||
class Disable extends RoutesAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
"uid" => new Parameter("uid", Parameter::TYPE_INT)
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT)
|
||||
));
|
||||
$this->isPublic = false;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$uid = $this->getParam("uid");
|
||||
$uid = $this->getParam("id");
|
||||
return $this->toggleRoute($uid, false);
|
||||
}
|
||||
}
|
||||
@ -342,19 +342,19 @@ namespace Api\Routes {
|
||||
|
||||
private ?Router $router;
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, []);
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, []);
|
||||
$this->isPublic = false;
|
||||
$this->router = null;
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql
|
||||
->select("uid", "request", "action", "target", "extra", "exact")
|
||||
->select("id", "request", "action", "target", "extra", "exact")
|
||||
->from("Route")
|
||||
->where(new CondBool("active"))
|
||||
->orderBy("uid")->ascending()
|
||||
->orderBy("id")->ascending()
|
||||
->execute();
|
||||
|
||||
$this->success = $res !== false;
|
||||
@ -363,7 +363,7 @@ namespace Api\Routes {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->router = new Router($this->user);
|
||||
$this->router = new Router($this->context);
|
||||
foreach ($res as $row) {
|
||||
$request = $row["request"];
|
||||
$target = $row["target"];
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
namespace Api {
|
||||
|
||||
use Objects\Context;
|
||||
|
||||
abstract class SettingsAPI extends Request {
|
||||
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Api\Settings {
|
||||
@ -19,19 +22,19 @@ namespace Api\Settings {
|
||||
use Driver\SQL\Condition\CondNot;
|
||||
use Driver\SQL\Condition\CondRegex;
|
||||
use Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
class Get extends SettingsAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'key' => new StringType('key', -1, true, NULL)
|
||||
));
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$key = $this->getParam("key");
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
$query = $sql->select("name", "value") ->from("Settings");
|
||||
|
||||
@ -62,8 +65,8 @@ namespace Api\Settings {
|
||||
}
|
||||
|
||||
class Set extends SettingsAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'settings' => new Parameter('settings', Parameter::TYPE_ARRAY)
|
||||
));
|
||||
}
|
||||
@ -77,7 +80,7 @@ namespace Api\Settings {
|
||||
$paramKey = new StringType('key', 32);
|
||||
$paramValue = new StringType('value', 1024, true, NULL);
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$query = $sql->insert("Settings", array("name", "value"));
|
||||
$keys = array();
|
||||
$deleteKeys = array();
|
||||
@ -129,7 +132,7 @@ namespace Api\Settings {
|
||||
}
|
||||
|
||||
private function checkReadonly(array $keys) {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select("name")
|
||||
->from("Settings")
|
||||
->where(new CondBool("readonly"))
|
||||
@ -148,7 +151,7 @@ namespace Api\Settings {
|
||||
}
|
||||
|
||||
private function deleteKeys(array $keys) {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->delete("Settings")
|
||||
->where(new CondIn(new Column("name"), $keys))
|
||||
->execute();
|
||||
|
@ -5,18 +5,20 @@ namespace Api;
|
||||
use DateTime;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondBool;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
class Stats extends Request {
|
||||
|
||||
private bool $mailConfigured;
|
||||
private bool $recaptchaConfigured;
|
||||
|
||||
public function __construct($user, $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array());
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array());
|
||||
}
|
||||
|
||||
private function getUserCount() {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select($sql->count())->from("User")->execute();
|
||||
$this->success = $this->success && ($res !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
@ -25,7 +27,7 @@ class Stats extends Request {
|
||||
}
|
||||
|
||||
private function getPageCount() {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$res = $sql->select($sql->count())->from("Route")
|
||||
->where(new CondBool("active"))
|
||||
->execute();
|
||||
@ -36,7 +38,7 @@ class Stats extends Request {
|
||||
}
|
||||
|
||||
private function checkSettings(): bool {
|
||||
$req = new \Api\Settings\Get($this->user);
|
||||
$req = new \Api\Settings\Get($this->context);
|
||||
$this->success = $req->execute(array("key" => "^(mail_enabled|recaptcha_enabled)$"));
|
||||
$this->lastError = $req->getLastError();
|
||||
|
||||
@ -50,7 +52,7 @@ class Stats extends Request {
|
||||
}
|
||||
|
||||
private function getVisitorCount() {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$date = new DateTime();
|
||||
$monthStart = $date->format("Ym00");
|
||||
$monthEnd = $date->modify("+1 month")->format("Ym00");
|
||||
@ -69,7 +71,7 @@ class Stats extends Request {
|
||||
public function _execute(): bool {
|
||||
$userCount = $this->getUserCount();
|
||||
$pageCount = $this->getPageCount();
|
||||
$req = new \Api\Visitors\Stats($this->user);
|
||||
$req = new \Api\Visitors\Stats($this->context);
|
||||
$this->success = $req->execute(array("type"=>"monthly"));
|
||||
$this->lastError = $req->getLastError();
|
||||
if (!$this->success) {
|
||||
@ -100,7 +102,7 @@ class Stats extends Request {
|
||||
"server" => $_SERVER["SERVER_SOFTWARE"] ?? "Unknown",
|
||||
"memory_usage" => memory_get_usage(),
|
||||
"load_avg" => $loadAvg,
|
||||
"database" => $this->user->getSQL()->getStatus(),
|
||||
"database" => $this->context->getSQL()->getStatus(),
|
||||
"mail" => $this->mailConfigured,
|
||||
"reCaptcha" => $this->recaptchaConfigured
|
||||
);
|
||||
|
@ -3,12 +3,13 @@
|
||||
namespace Api;
|
||||
|
||||
use Api\Parameter\StringType;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
class Swagger extends Request {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, []);
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, []);
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
@ -61,7 +62,7 @@ class Swagger extends Request {
|
||||
}
|
||||
|
||||
private function fetchPermissions(): array {
|
||||
$req = new Permission\Fetch($this->user);
|
||||
$req = new Permission\Fetch($this->context);
|
||||
$this->success = $req->execute();
|
||||
$permissions = [];
|
||||
foreach( $req->getResult()["permissions"] as $permission) {
|
||||
@ -76,17 +77,19 @@ class Swagger extends Request {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($request->loginRequired() || !empty($requiredGroups)) && !$this->user->isLoggedIn()) {
|
||||
$currentUser = $this->context->getUser();
|
||||
if (($request->loginRequired() || !empty($requiredGroups)) && !$currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// special case: hardcoded permission
|
||||
if ($request instanceof Permission\Save && (!$this->user->isLoggedIn() || !$this->user->hasGroup(USER_GROUP_ADMIN))) {
|
||||
if ($request instanceof Permission\Save && (!$currentUser || !$currentUser->hasGroup(USER_GROUP_ADMIN))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($requiredGroups)) {
|
||||
return !empty(array_intersect($requiredGroups, $this->user->getGroups()));
|
||||
$userGroups = array_keys($currentUser?->getGroups() ?? []);
|
||||
return !empty(array_intersect($requiredGroups, $userGroups));
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -94,7 +97,7 @@ class Swagger extends Request {
|
||||
|
||||
private function getDocumentation(): string {
|
||||
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$settings = $this->context->getSettings();
|
||||
$siteName = $settings->getSiteName();
|
||||
$domain = parse_url($settings->getBaseUrl(), PHP_URL_HOST);
|
||||
|
||||
@ -105,7 +108,7 @@ class Swagger extends Request {
|
||||
foreach (self::getApiEndpoints() as $endpoint => $apiClass) {
|
||||
$body = null;
|
||||
$requiredProperties = [];
|
||||
$apiObject = $apiClass->newInstance($this->user, false);
|
||||
$apiObject = $apiClass->newInstance($this->context, false);
|
||||
if (!$this->canView($permissions[strtolower($endpoint)] ?? [], $apiObject)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
namespace Api {
|
||||
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
abstract class TemplateAPI extends Request {
|
||||
function __construct(User $user, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($user, $externalCall, $params);
|
||||
function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
$this->isPublic = false; // internal API
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,7 @@ namespace Api\Template {
|
||||
use Api\Parameter\Parameter;
|
||||
use Api\Parameter\StringType;
|
||||
use Api\TemplateAPI;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
@ -28,8 +28,8 @@ namespace Api\Template {
|
||||
|
||||
class Render extends TemplateAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"file" => new StringType("file"),
|
||||
"parameters" => new ArrayType("parameters", Parameter::TYPE_MIXED, false, true, [])
|
||||
]);
|
||||
|
@ -2,22 +2,23 @@
|
||||
|
||||
namespace Api {
|
||||
|
||||
use Objects\Context;
|
||||
use Objects\TwoFactor\AuthenticationData;
|
||||
use Objects\TwoFactor\KeyBasedTwoFactorToken;
|
||||
use Objects\User;
|
||||
|
||||
abstract class TfaAPI extends Request {
|
||||
|
||||
private bool $userVerficiationRequired;
|
||||
private bool $userVerificationRequired;
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($user, $externalCall, $params);
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
$this->loginRequired = true;
|
||||
$this->userVerficiationRequired = false;
|
||||
$this->apiKeyAllowed = false;
|
||||
$this->userVerificationRequired = false;
|
||||
}
|
||||
|
||||
protected function verifyAuthData(AuthenticationData $authData): bool {
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$settings = $this->context->getSettings();
|
||||
// $relyingParty = $settings->getSiteName();
|
||||
$domain = parse_url($settings->getBaseUrl(), PHP_URL_HOST);
|
||||
// $domain = "localhost";
|
||||
@ -26,7 +27,7 @@ namespace Api {
|
||||
return $this->createError("mismatched rpIDHash. expected: " . hash("sha256", $domain) . " got: " . bin2hex($authData->getHash()));
|
||||
} else if (!$authData->isUserPresent()) {
|
||||
return $this->createError("No user present");
|
||||
} else if ($this->userVerficiationRequired && !$authData->isUserVerified()) {
|
||||
} else if ($this->userVerificationRequired && !$authData->isUserVerified()) {
|
||||
return $this->createError("user was not verified on device (PIN/Biometric/...)");
|
||||
} else if ($authData->hasExtensionData()) {
|
||||
return $this->createError("No extensions supported");
|
||||
@ -36,7 +37,7 @@ namespace Api {
|
||||
}
|
||||
|
||||
protected function verifyClientDataJSON($jsonData, KeyBasedTwoFactorToken $token): bool {
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$settings = $this->context->getSettings();
|
||||
$expectedType = $token->isConfirmed() ? "webauthn.get" : "webauthn.create";
|
||||
$type = $jsonData["type"] ?? "null";
|
||||
if ($type !== $expectedType) {
|
||||
@ -58,33 +59,34 @@ namespace Api\TFA {
|
||||
use Api\Parameter\StringType;
|
||||
use Api\TfaAPI;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Objects\Context;
|
||||
use Objects\TwoFactor\AttestationObject;
|
||||
use Objects\TwoFactor\AuthenticationData;
|
||||
use Objects\TwoFactor\KeyBasedTwoFactorToken;
|
||||
use Objects\TwoFactor\TimeBasedTwoFactorToken;
|
||||
use Objects\User;
|
||||
|
||||
// General
|
||||
class Remove extends TfaAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"password" => new StringType("password", 0, true)
|
||||
]);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$token = $this->user->getTwoFactorToken();
|
||||
$currentUser = $this->context->getUser();
|
||||
$token = $currentUser->getTwoFactorToken();
|
||||
if (!$token) {
|
||||
return $this->createError("You do not have an active 2FA-Token");
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$password = $this->getParam("password");
|
||||
if ($password) {
|
||||
$res = $sql->select("password")
|
||||
->from("User")
|
||||
->where(new Compare("uid", $this->user->getId()))
|
||||
->where(new Compare("id", $currentUser->getId()))
|
||||
->execute();
|
||||
$this->success = !empty($res);
|
||||
$this->lastError = $sql->getLastError();
|
||||
@ -99,7 +101,7 @@ namespace Api\TFA {
|
||||
}
|
||||
|
||||
$res = $sql->delete("2FA")
|
||||
->where(new Compare("uid", $token->getId()))
|
||||
->where(new Compare("id", $token->getId()))
|
||||
->execute();
|
||||
|
||||
$this->success = $res !== false;
|
||||
@ -107,12 +109,12 @@ namespace Api\TFA {
|
||||
|
||||
if ($this->success && $token->isConfirmed()) {
|
||||
// send an email
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$req = new \Api\Template\Render($this->user);
|
||||
$settings = $this->context->getSettings();
|
||||
$req = new \Api\Template\Render($this->context);
|
||||
$this->success = $req->execute([
|
||||
"file" => "mail/2fa_remove.twig",
|
||||
"parameters" => [
|
||||
"username" => $this->user->getFullName() ?? $this->user->getUsername(),
|
||||
"username" => $currentUser->getFullName() ?? $currentUser->getUsername(),
|
||||
"site_name" => $settings->getSiteName(),
|
||||
"sender_mail" => $settings->getMailSender()
|
||||
]
|
||||
@ -120,13 +122,13 @@ namespace Api\TFA {
|
||||
|
||||
if ($this->success) {
|
||||
$body = $req->getResult()["html"];
|
||||
$gpg = $this->user->getGPG();
|
||||
$req = new \Api\Mail\Send($this->user);
|
||||
$gpg = $currentUser->getGPG();
|
||||
$req = new \Api\Mail\Send($this->context);
|
||||
$this->success = $req->execute([
|
||||
"to" => $this->user->getEmail(),
|
||||
"to" => $currentUser->getEmail(),
|
||||
"subject" => "[Security Lab] 2FA-Authentication removed",
|
||||
"body" => $body,
|
||||
"gpgFingerprint" => $gpg ? $gpg->getFingerprint() : null
|
||||
"gpgFingerprint" => $gpg?->getFingerprint()
|
||||
]);
|
||||
}
|
||||
|
||||
@ -140,27 +142,28 @@ namespace Api\TFA {
|
||||
// TOTP
|
||||
class GenerateQR extends TfaAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall);
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall);
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$twoFactorToken = $this->user->getTwoFactorToken();
|
||||
$currentUser = $this->context->getUser();
|
||||
$twoFactorToken = $currentUser->getTwoFactorToken();
|
||||
if ($twoFactorToken && $twoFactorToken->isConfirmed()) {
|
||||
return $this->createError("You already added a two factor token");
|
||||
} else if (!($twoFactorToken instanceof TimeBasedTwoFactorToken)) {
|
||||
$twoFactorToken = new TimeBasedTwoFactorToken(generateRandomString(32, "base32"));
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $sql->insert("2FA", ["type", "data"])
|
||||
->addRow("totp", $twoFactorToken->getData())
|
||||
->returning("uid")
|
||||
->returning("id")
|
||||
->execute() !== false;
|
||||
$this->lastError = $sql->getLastError();
|
||||
if ($this->success) {
|
||||
$this->success = $sql->update("User")
|
||||
->set("2fa_id", $sql->getLastInsertId())->where(new Compare("uid", $this->user->getId()))
|
||||
->set("2fa_id", $sql->getLastInsertId())->where(new Compare("id", $currentUser->getId()))
|
||||
->execute() !== false;
|
||||
$this->lastError = $sql->getLastError();
|
||||
}
|
||||
@ -172,27 +175,27 @@ namespace Api\TFA {
|
||||
|
||||
header("Content-Type: image/png");
|
||||
$this->disableCache();
|
||||
die($twoFactorToken->generateQRCode($this->user));
|
||||
die($twoFactorToken->generateQRCode($this->context));
|
||||
}
|
||||
}
|
||||
|
||||
class ConfirmTotp extends VerifyTotp {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall);
|
||||
$this->loginRequired = true;
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$twoFactorToken = $this->user->getTwoFactorToken();
|
||||
$currentUser = $this->context->getUser();
|
||||
$twoFactorToken = $currentUser->getTwoFactorToken();
|
||||
if ($twoFactorToken->isConfirmed()) {
|
||||
return $this->createError("Your two factor token is already confirmed.");
|
||||
}
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $sql->update("2FA")
|
||||
->set("confirmed", true)
|
||||
->where(new Compare("uid", $twoFactorToken->getId()))
|
||||
->where(new Compare("id", $twoFactorToken->getId()))
|
||||
->execute() !== false;
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
@ -201,22 +204,22 @@ namespace Api\TFA {
|
||||
|
||||
class VerifyTotp extends TfaAPI {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"code" => new StringType("code", 6)
|
||||
]);
|
||||
$this->loginRequired = false;
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$session = $this->user->getSession();
|
||||
if (!$session) {
|
||||
$currentUser = $this->context->getUser();
|
||||
if (!$currentUser) {
|
||||
return $this->createError("You are not logged in.");
|
||||
}
|
||||
|
||||
$twoFactorToken = $this->user->getTwoFactorToken();
|
||||
$twoFactorToken = $currentUser->getTwoFactorToken();
|
||||
if (!$twoFactorToken) {
|
||||
return $this->createError("You did not add a two factor token yet.");
|
||||
} else if (!($twoFactorToken instanceof TimeBasedTwoFactorToken)) {
|
||||
@ -235,21 +238,23 @@ namespace Api\TFA {
|
||||
|
||||
// Key
|
||||
class RegisterKey extends TfaAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"clientDataJSON" => new StringType("clientDataJSON", 0, true, "{}"),
|
||||
"attestationObject" => new StringType("attestationObject", 0, true, "")
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$currentUser = $this->context->getUser();
|
||||
$clientDataJSON = json_decode($this->getParam("clientDataJSON"), true);
|
||||
$attestationObjectRaw = base64_decode($this->getParam("attestationObject"));
|
||||
$twoFactorToken = $this->user->getTwoFactorToken();
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$twoFactorToken = $currentUser->getTwoFactorToken();
|
||||
$settings = $this->context->getSettings();
|
||||
$relyingParty = $settings->getSiteName();
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
// TODO: for react development, localhost / HTTP_HOST is required, otherwise a DOMException is thrown
|
||||
$domain = parse_url($settings->getBaseUrl(), PHP_URL_HOST);
|
||||
@ -266,7 +271,7 @@ namespace Api\TFA {
|
||||
$challenge = base64_encode(generateRandomString(32, "raw"));
|
||||
$res = $sql->insert("2FA", ["type", "data"])
|
||||
->addRow("fido", $challenge)
|
||||
->returning("uid")
|
||||
->returning("id")
|
||||
->execute();
|
||||
$this->success = ($res !== false);
|
||||
$this->lastError = $sql->getLastError();
|
||||
@ -276,7 +281,7 @@ namespace Api\TFA {
|
||||
|
||||
$this->success = $sql->update("User")
|
||||
->set("2fa_id", $sql->getLastInsertId())
|
||||
->where(new Compare("uid", $this->user->getId()))
|
||||
->where(new Compare("id", $currentUser->getId()))
|
||||
->execute() !== false;
|
||||
$this->lastError = $sql->getLastError();
|
||||
if (!$this->success) {
|
||||
@ -286,7 +291,7 @@ namespace Api\TFA {
|
||||
|
||||
$this->result["data"] = [
|
||||
"challenge" => $challenge,
|
||||
"id" => $this->user->getId() . "@" . $domain, // <userId>@<domain>
|
||||
"id" => $currentUser->getId() . "@" . $domain, // <userId>@<domain>
|
||||
"relyingParty" => [
|
||||
"name" => $relyingParty,
|
||||
"id" => $domain
|
||||
@ -322,7 +327,7 @@ namespace Api\TFA {
|
||||
$this->success = $sql->update("2FA")
|
||||
->set("data", json_encode($data))
|
||||
->set("confirmed", true)
|
||||
->where(new Compare("uid", $twoFactorToken->getId()))
|
||||
->where(new Compare("id", $twoFactorToken->getId()))
|
||||
->execute() !== false;
|
||||
$this->lastError = $sql->getLastError();
|
||||
}
|
||||
@ -332,25 +337,25 @@ namespace Api\TFA {
|
||||
}
|
||||
|
||||
class VerifyKey extends TfaAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, [
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"credentialID" => new StringType("credentialID"),
|
||||
"clientDataJSON" => new StringType("clientDataJSON"),
|
||||
"authData" => new StringType("authData"),
|
||||
"signature" => new StringType("signature"),
|
||||
]);
|
||||
$this->loginRequired = false;
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$session = $this->user->getSession();
|
||||
if (!$session) {
|
||||
$currentUser = $this->context->getUser();
|
||||
if (!$currentUser) {
|
||||
return $this->createError("You are not logged in.");
|
||||
}
|
||||
|
||||
$twoFactorToken = $this->user->getTwoFactorToken();
|
||||
$twoFactorToken = $currentUser->getTwoFactorToken();
|
||||
if (!$twoFactorToken) {
|
||||
return $this->createError("You did not add a two factor token yet.");
|
||||
} else if (!($twoFactorToken instanceof KeyBasedTwoFactorToken)) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,12 +3,12 @@
|
||||
namespace Api;
|
||||
|
||||
use Api\Parameter\StringType;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
class VerifyCaptcha extends Request {
|
||||
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"captcha" => new StringType("captcha"),
|
||||
"action" => new StringType("action"),
|
||||
));
|
||||
@ -17,7 +17,7 @@ class VerifyCaptcha extends Request {
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$settings = $this->context->getSettings();
|
||||
if (!$settings->isRecaptchaEnabled()) {
|
||||
return $this->createError("Google reCaptcha is not enabled.");
|
||||
}
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
namespace Api {
|
||||
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
abstract class VisitorsAPI extends Request {
|
||||
public function __construct(User $user, bool $externalCall = false, array $params = []) {
|
||||
parent::__construct($user, $externalCall, $params);
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = []) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,18 +21,18 @@ namespace Api\Visitors {
|
||||
use Driver\SQL\Expression\Add;
|
||||
use Driver\SQL\Query\Select;
|
||||
use Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
class ProcessVisit extends VisitorsAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"cookie" => new StringType("cookie")
|
||||
));
|
||||
$this->isPublic = false;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$cookie = $this->getParam("cookie");
|
||||
$day = (new DateTime())->format("Ymd");
|
||||
$sql->insert("Visitor", array("cookie", "day"))
|
||||
@ -47,8 +47,8 @@ namespace Api\Visitors {
|
||||
}
|
||||
|
||||
class Stats extends VisitorsAPI {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, array(
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'type' => new StringType('type', 32),
|
||||
'date' => new Parameter('date', Parameter::TYPE_DATE, true, new DateTime())
|
||||
));
|
||||
@ -81,7 +81,7 @@ namespace Api\Visitors {
|
||||
$date = $this->getParam("date");
|
||||
$type = $this->getParam("type");
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
$sql = $this->context->getSQL();
|
||||
$query = $sql->select($sql->count(), "day")
|
||||
->from("Visitor")
|
||||
->where(new Compare("count", 1, ">"))
|
||||
|
@ -5,90 +5,27 @@ namespace Configuration;
|
||||
use Driver\SQL\SQL;
|
||||
use \Driver\SQL\Strategy\SetNullStrategy;
|
||||
use \Driver\SQL\Strategy\CascadeStrategy;
|
||||
use Objects\DatabaseEntity\DatabaseEntity;
|
||||
use PHPUnit\Util\Exception;
|
||||
|
||||
class CreateDatabase extends DatabaseScript {
|
||||
|
||||
public static function createQueries(SQL $sql): array {
|
||||
$queries = array();
|
||||
|
||||
// Language
|
||||
$queries[] = $sql->createTable("Language")
|
||||
->addSerial("uid")
|
||||
->addString("code", 5)
|
||||
->addString("name", 32)
|
||||
->primaryKey("uid")
|
||||
->unique("code")
|
||||
->unique("name");
|
||||
self::loadEntities($queries, $sql);
|
||||
|
||||
$queries[] = $sql->insert("Language", array("code", "name"))
|
||||
->addRow("en_US", 'American English')
|
||||
->addRow("de_DE", 'Deutsch Standard');
|
||||
|
||||
|
||||
$queries[] = $sql->createTable("GpgKey")
|
||||
->addSerial("uid")
|
||||
->addString("fingerprint", 64)
|
||||
->addDateTime("added", false, $sql->now())
|
||||
->addDateTime("expires")
|
||||
->addBool("confirmed")
|
||||
->addString("algorithm", 32)
|
||||
->primaryKey("uid");
|
||||
|
||||
$queries[] = $sql->createTable("2FA")
|
||||
->addSerial("uid")
|
||||
->addEnum("type", ["totp","fido"])
|
||||
->addString("data", 512) // either totp secret, fido challenge or fido public key information
|
||||
->addBool("confirmed", false)
|
||||
->addDateTime("added", false, $sql->now())
|
||||
->primaryKey("uid");
|
||||
|
||||
$queries[] = $sql->createTable("User")
|
||||
->addSerial("uid")
|
||||
->addString("email", 64, true)
|
||||
->addString("name", 32)
|
||||
->addString("password", 128)
|
||||
->addString("fullName", 64, false, "")
|
||||
->addString("profilePicture", 64, true)
|
||||
->addDateTime("last_online", true, NULL)
|
||||
->addBool("confirmed", false)
|
||||
->addInt("language_id", true, 1)
|
||||
->addInt("gpg_id", true)
|
||||
->addInt("2fa_id", true)
|
||||
->addDateTime("registered_at", false, $sql->currentTimestamp())
|
||||
->primaryKey("uid")
|
||||
->unique("email")
|
||||
->unique("name")
|
||||
->foreignKey("language_id", "Language", "uid", new SetNullStrategy())
|
||||
->foreignKey("gpg_id", "GpgKey", "uid", new SetNullStrategy())
|
||||
->foreignKey("2fa_id", "2FA", "uid", new SetNullStrategy());
|
||||
|
||||
$queries[] = $sql->createTable("Session")
|
||||
->addSerial("uid")
|
||||
->addBool("active", true)
|
||||
->addDateTime("expires")
|
||||
->addInt("user_id")
|
||||
->addString("ipAddress", 45)
|
||||
->addString("os", 64)
|
||||
->addString("browser", 64)
|
||||
->addJson("data", false, '{}')
|
||||
->addBool("stay_logged_in", true)
|
||||
->addString("csrf_token", 16)
|
||||
->primaryKey("uid", "user_id")
|
||||
->foreignKey("user_id", "User", "uid", new CascadeStrategy());
|
||||
|
||||
$queries[] = $sql->createTable("UserToken")
|
||||
->addInt("user_id")
|
||||
->addString("token", 36)
|
||||
->addEnum("token_type", array("password_reset", "email_confirm", "invite", "gpg_confirm"))
|
||||
->addDateTime("valid_until")
|
||||
->addBool("used", false)
|
||||
->foreignKey("user_id", "User", "uid", new CascadeStrategy());
|
||||
$queries[] = $sql->createTable("Group")
|
||||
->addSerial("uid")
|
||||
->addString("name", 32)
|
||||
->addString("color", 10)
|
||||
->primaryKey("uid")
|
||||
->unique("name");
|
||||
->foreignKey("user_id", "User", "id", new CascadeStrategy());
|
||||
|
||||
$queries[] = $sql->insert("Group", array("name", "color"))
|
||||
->addRow(USER_GROUP_MODERATOR_NAME, "#007bff")
|
||||
@ -99,42 +36,25 @@ class CreateDatabase extends DatabaseScript {
|
||||
->addInt("user_id")
|
||||
->addInt("group_id")
|
||||
->unique("user_id", "group_id")
|
||||
->foreignKey("user_id", "User", "uid", new CascadeStrategy())
|
||||
->foreignKey("group_id", "Group", "uid", new CascadeStrategy());
|
||||
|
||||
$queries[] = $sql->createTable("Notification")
|
||||
->addSerial("uid")
|
||||
->addEnum("type", array("default", "message", "warning"), false, "default")
|
||||
->addDateTime("created_at", false, $sql->currentTimestamp())
|
||||
->addString("title", 32)
|
||||
->addString("message", 256)
|
||||
->primaryKey("uid");
|
||||
->foreignKey("user_id", "User", "id", new CascadeStrategy())
|
||||
->foreignKey("group_id", "Group", "id", new CascadeStrategy());
|
||||
|
||||
$queries[] = $sql->createTable("UserNotification")
|
||||
->addInt("user_id")
|
||||
->addInt("notification_id")
|
||||
->addBool("seen", false)
|
||||
->foreignKey("user_id", "User", "uid")
|
||||
->foreignKey("notification_id", "Notification", "uid")
|
||||
->foreignKey("user_id", "User", "id")
|
||||
->foreignKey("notification_id", "Notification", "id")
|
||||
->unique("user_id", "notification_id");
|
||||
|
||||
$queries[] = $sql->createTable("GroupNotification")
|
||||
->addInt("group_id")
|
||||
->addInt("notification_id")
|
||||
->addBool("seen", false)
|
||||
->foreignKey("group_id", "Group", "uid")
|
||||
->foreignKey("notification_id", "Notification", "uid")
|
||||
->foreignKey("group_id", "Group", "id")
|
||||
->foreignKey("notification_id", "Notification", "id")
|
||||
->unique("group_id", "notification_id");
|
||||
|
||||
$queries[] = $sql->createTable("ApiKey")
|
||||
->addSerial("uid")
|
||||
->addInt("user_id")
|
||||
->addBool("active", true)
|
||||
->addString("api_key", 64)
|
||||
->addDateTime("valid_until")
|
||||
->primaryKey("uid")
|
||||
->foreignKey("user_id", "User", "uid");
|
||||
|
||||
$queries[] = $sql->createTable("Visitor")
|
||||
->addInt("day")
|
||||
->addInt("count", false, 1)
|
||||
@ -142,14 +62,14 @@ class CreateDatabase extends DatabaseScript {
|
||||
->unique("day", "cookie");
|
||||
|
||||
$queries[] = $sql->createTable("Route")
|
||||
->addSerial("uid")
|
||||
->addSerial("id")
|
||||
->addString("request", 128)
|
||||
->addEnum("action", array("redirect_temporary", "redirect_permanently", "static", "dynamic"))
|
||||
->addString("target", 128)
|
||||
->addString("extra", 64, true)
|
||||
->addBool("active", true)
|
||||
->addBool("exact", true)
|
||||
->primaryKey("uid")
|
||||
->primaryKey("id")
|
||||
->unique("request");
|
||||
|
||||
$queries[] = $sql->insert("Route", ["request", "action", "target", "extra", "exact"])
|
||||
@ -184,17 +104,17 @@ class CreateDatabase extends DatabaseScript {
|
||||
$queries[] = $settingsQuery;
|
||||
|
||||
$queries[] = $sql->createTable("ContactRequest")
|
||||
->addSerial("uid")
|
||||
->addSerial("id")
|
||||
->addString("from_name", 32)
|
||||
->addString("from_email", 64)
|
||||
->addString("message", 512)
|
||||
->addString("messageId", 78, true) # null = don't sync with mails (usually if mail could not be sent)
|
||||
->addDateTime("created_at", false, $sql->currentTimestamp())
|
||||
->unique("messageId")
|
||||
->primaryKey("uid");
|
||||
->primaryKey("id");
|
||||
|
||||
$queries[] = $sql->createTable("ContactMessage")
|
||||
->addSerial("uid")
|
||||
->addSerial("id")
|
||||
->addInt("request_id")
|
||||
->addInt("user_id", true) # null = customer has sent this message
|
||||
->addString("message", 512)
|
||||
@ -202,9 +122,9 @@ class CreateDatabase extends DatabaseScript {
|
||||
->addDateTime("created_at", false, $sql->currentTimestamp())
|
||||
->addBool("read", false)
|
||||
->unique("messageId")
|
||||
->primaryKey("uid")
|
||||
->foreignKey("request_id", "ContactRequest", "uid", new CascadeStrategy())
|
||||
->foreignKey("user_id", "User", "uid", new SetNullStrategy());
|
||||
->primaryKey("id")
|
||||
->foreignKey("request_id", "ContactRequest", "id", new CascadeStrategy())
|
||||
->foreignKey("user_id", "User", "id", new SetNullStrategy());
|
||||
|
||||
$queries[] = $sql->createTable("ApiPermission")
|
||||
->addString("method", 32)
|
||||
@ -213,7 +133,7 @@ class CreateDatabase extends DatabaseScript {
|
||||
->primaryKey("method");
|
||||
|
||||
$queries[] = $sql->createTable("MailQueue")
|
||||
->addSerial("uid")
|
||||
->addSerial("id")
|
||||
->addString("from", 64)
|
||||
->addString("to", 64)
|
||||
->addString("subject")
|
||||
@ -225,18 +145,9 @@ class CreateDatabase extends DatabaseScript {
|
||||
->addInt("retryCount", false, 5)
|
||||
->addDateTime("nextTry", false, $sql->now())
|
||||
->addString("errorMessage", NULL, true)
|
||||
->primaryKey("uid");
|
||||
->primaryKey("id");
|
||||
$queries = array_merge($queries, \Configuration\Patch\EntityLog_2021_04_08::createTableLog($sql, "MailQueue", 30));
|
||||
|
||||
$queries[] = $sql->createTable("News")
|
||||
->addSerial("uid")
|
||||
->addInt("publishedBy")
|
||||
->addDateTime("publishedAt", false, $sql->now())
|
||||
->addString("title", 128)
|
||||
->addString("text", 1024)
|
||||
->foreignKey("publishedBy", "User", "uid", new CascadeStrategy())
|
||||
->primaryKey("uid");
|
||||
|
||||
$queries[] = $sql->insert("ApiPermission", array("method", "groups", "description"))
|
||||
->addRow("ApiKey/create", array(), "Allows users to create API-Keys for themselves")
|
||||
->addRow("ApiKey/fetch", array(), "Allows users to list their API-Keys")
|
||||
@ -265,7 +176,6 @@ class CreateDatabase extends DatabaseScript {
|
||||
->addRow("Contact/get", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see messages within a contact request");
|
||||
|
||||
self::loadPatches($queries, $sql);
|
||||
self::loadEntities($queries, $sql);
|
||||
|
||||
return $queries;
|
||||
}
|
||||
@ -293,18 +203,47 @@ class CreateDatabase extends DatabaseScript {
|
||||
if (file_exists($entityDirectory) && is_dir($entityDirectory)) {
|
||||
$scan_arr = scandir($entityDirectory);
|
||||
$files_arr = array_diff($scan_arr, array('.', '..'));
|
||||
$handlers = [];
|
||||
foreach ($files_arr as $file) {
|
||||
$suffix = ".class.php";
|
||||
if (endsWith($file, $suffix)) {
|
||||
$className = substr($file, 0, strlen($file) - strlen($suffix));
|
||||
if (!in_array($className, ["DatabaseEntity", "DatabaseEntityHandler"])) {
|
||||
if (!in_array($className, ["DatabaseEntity", "DatabaseEntityQuery", "DatabaseEntityHandler"])) {
|
||||
$className = "\\Objects\\DatabaseEntity\\$className";
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
if ($reflectionClass->isSubclassOf(DatabaseEntity::class)) {
|
||||
$method = "$className::getHandler";
|
||||
$handler = call_user_func($method, $sql);
|
||||
$queries[] = $handler->getTableQuery();
|
||||
$handlers[$handler->getTableName()] = $handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tableCount = count($handlers);
|
||||
$createdTables = [];
|
||||
while (!empty($handlers)) {
|
||||
$prevCount = $tableCount;
|
||||
$unmetDependenciesTotal = [];
|
||||
|
||||
foreach ($handlers as $tableName => $handler) {
|
||||
$dependsOn = $handler->dependsOn();
|
||||
$unmetDependencies = array_diff($dependsOn, $createdTables);
|
||||
if (empty($unmetDependencies)) {
|
||||
$queries[] = $handler->getTableQuery();
|
||||
$createdTables[] = $tableName;
|
||||
unset($handlers[$tableName]);
|
||||
} else {
|
||||
$unmetDependenciesTotal = array_merge($unmetDependenciesTotal, $unmetDependencies);
|
||||
}
|
||||
}
|
||||
|
||||
$tableCount = count($handlers);
|
||||
if ($tableCount === $prevCount) {
|
||||
throw new Exception("Circular or unmet table dependency detected. Unmet dependencies: "
|
||||
. implode(", ", $unmetDependenciesTotal));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class EntityLog_2021_04_08 extends DatabaseScript {
|
||||
->after()->insert($table)
|
||||
->exec(new CreateProcedure($sql, "InsertEntityLog"), [
|
||||
"tableName" => new CurrentTable(),
|
||||
"entityId" => new CurrentColumn("uid"),
|
||||
"entityId" => new CurrentColumn("id"),
|
||||
"lifetime" => $lifetime,
|
||||
]),
|
||||
|
||||
@ -27,14 +27,14 @@ class EntityLog_2021_04_08 extends DatabaseScript {
|
||||
->after()->update($table)
|
||||
->exec(new CreateProcedure($sql, "UpdateEntityLog"), [
|
||||
"tableName" => new CurrentTable(),
|
||||
"entityId" => new CurrentColumn("uid"),
|
||||
"entityId" => new CurrentColumn("id"),
|
||||
]),
|
||||
|
||||
$sql->createTrigger("${table}_trg_delete")
|
||||
->after()->delete($table)
|
||||
->exec(new CreateProcedure($sql, "DeleteEntityLog"), [
|
||||
"tableName" => new CurrentTable(),
|
||||
"entityId" => new CurrentColumn("uid"),
|
||||
"entityId" => new CurrentColumn("id"),
|
||||
])
|
||||
];
|
||||
}
|
||||
@ -51,32 +51,32 @@ class EntityLog_2021_04_08 extends DatabaseScript {
|
||||
|
||||
$insertProcedure = $sql->createProcedure("InsertEntityLog")
|
||||
->param(new CurrentTable())
|
||||
->param(new IntColumn("uid"))
|
||||
->param(new IntColumn("id"))
|
||||
->param(new IntColumn("lifetime", false, 90))
|
||||
->returns(new Trigger())
|
||||
->exec(array(
|
||||
$sql->insert("EntityLog", ["entityId", "tableName", "lifetime"])
|
||||
->addRow(new CurrentColumn("uid"), new CurrentTable(), new CurrentColumn("lifetime"))
|
||||
->addRow(new CurrentColumn("id"), new CurrentTable(), new CurrentColumn("lifetime"))
|
||||
));
|
||||
|
||||
$updateProcedure = $sql->createProcedure("UpdateEntityLog")
|
||||
->param(new CurrentTable())
|
||||
->param(new IntColumn("uid"))
|
||||
->param(new IntColumn("id"))
|
||||
->returns(new Trigger())
|
||||
->exec(array(
|
||||
$sql->update("EntityLog")
|
||||
->set("modified", $sql->now())
|
||||
->where(new Compare("entityId", new CurrentColumn("uid")))
|
||||
->where(new Compare("entityId", new CurrentColumn("id")))
|
||||
->where(new Compare("tableName", new CurrentTable()))
|
||||
));
|
||||
|
||||
$deleteProcedure = $sql->createProcedure("DeleteEntityLog")
|
||||
->param(new CurrentTable())
|
||||
->param(new IntColumn("uid"))
|
||||
->param(new IntColumn("id"))
|
||||
->returns(new Trigger())
|
||||
->exec(array(
|
||||
$sql->delete("EntityLog")
|
||||
->where(new Compare("entityId", new CurrentColumn("uid")))
|
||||
->where(new Compare("entityId", new CurrentColumn("id")))
|
||||
->where(new Compare("tableName", new CurrentTable()))
|
||||
));
|
||||
|
||||
|
@ -9,14 +9,6 @@ class SystemLog_2022_03_30 extends DatabaseScript {
|
||||
|
||||
public static function createQueries(SQL $sql): array {
|
||||
return [
|
||||
$sql->createTable("SystemLog")
|
||||
->onlyIfNotExists()
|
||||
->addSerial("id")
|
||||
->addDateTime("timestamp", false, $sql->now())
|
||||
->addString("message")
|
||||
->addString("module", 64, false, "global")
|
||||
->addEnum("severity", ["debug", "info", "warning", "error", "severe"])
|
||||
->primaryKey("id"),
|
||||
$sql->insert("ApiPermission", ["method", "groups", "description"])
|
||||
->addRow("Logs/get", [USER_GROUP_ADMIN], "Allows users to fetch system logs")
|
||||
];
|
||||
|
@ -7,7 +7,7 @@
|
||||
namespace Configuration;
|
||||
|
||||
use Driver\SQL\Query\Insert;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
class Settings {
|
||||
|
||||
@ -58,8 +58,8 @@ class Settings {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public function loadFromDatabase(User $user): bool {
|
||||
$req = new \Api\Settings\Get($user);
|
||||
public function loadFromDatabase(Context $context): bool {
|
||||
$req = new \Api\Settings\Get($context);
|
||||
$success = $req->execute();
|
||||
|
||||
if ($success) {
|
||||
@ -78,7 +78,7 @@ class Settings {
|
||||
$this->allowedExtensions = explode(",", $result["allowed_extensions"] ?? strtolower(implode(",", $this->allowedExtensions)));
|
||||
|
||||
if (!isset($result["jwt_secret"])) {
|
||||
$req = new \Api\Settings\Set($user);
|
||||
$req = new \Api\Settings\Set($context);
|
||||
$req->execute(array("settings" => array(
|
||||
"jwt_secret" => $this->jwtSecret
|
||||
)));
|
||||
@ -135,4 +135,8 @@ class Settings {
|
||||
public function isExtensionAllowed(string $ext): bool {
|
||||
return empty($this->allowedExtensions) || in_array(strtolower(trim($ext)), $this->allowedExtensions);
|
||||
}
|
||||
|
||||
public function getDomain(): string {
|
||||
return parse_url($this->getBaseUrl(), PHP_URL_HOST);
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ class Account extends TemplateDocument {
|
||||
if ($this->getTemplateName() === "account/reset_password.twig") {
|
||||
if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) {
|
||||
$this->parameters["view"]["token"] = $_GET["token"];
|
||||
$req = new \Api\User\CheckToken($this->getUser());
|
||||
$req = new \Api\User\CheckToken($this->getContext());
|
||||
$this->parameters["view"]["success"] = $req->execute(array("token" => $_GET["token"]));
|
||||
if ($this->parameters["view"]["success"]) {
|
||||
if (strcmp($req->getResult()["token"]["type"], "password_reset") !== 0) {
|
||||
@ -35,18 +35,18 @@ class Account extends TemplateDocument {
|
||||
}
|
||||
} else if ($this->getTemplateName() === "account/register.twig") {
|
||||
$settings = $this->getSettings();
|
||||
if ($this->getUser()->isLoggedIn()) {
|
||||
if ($this->getUser()) {
|
||||
$this->createError("You are already logged in.");
|
||||
} else if (!$settings->isRegistrationAllowed()) {
|
||||
$this->createError("Registration is not enabled on this website.");
|
||||
}
|
||||
} else if ($this->getTemplateName() === "account/login.twig" && $this->getUser()->isLoggedIn()) {
|
||||
} else if ($this->getTemplateName() === "account/login.twig" && $this->getUser()) {
|
||||
header("Location: /admin");
|
||||
exit();
|
||||
} else if ($this->getTemplateName() === "account/accept_invite.twig") {
|
||||
if (isset($_GET["token"]) && is_string($_GET["token"]) && !empty($_GET["token"])) {
|
||||
$this->parameters["view"]["token"] = $_GET["token"];
|
||||
$req = new \Api\User\CheckToken($this->getUser());
|
||||
$req = new \Api\User\CheckToken($this->getContext());
|
||||
$this->parameters["view"]["success"] = $req->execute(array("token" => $_GET["token"]));
|
||||
if ($this->parameters["view"]["success"]) {
|
||||
if (strcmp($req->getResult()["token"]["type"], "invite") !== 0) {
|
||||
|
@ -7,9 +7,9 @@ use Objects\Router\Router;
|
||||
|
||||
class Admin extends TemplateDocument {
|
||||
public function __construct(Router $router) {
|
||||
$user = $router->getUser();
|
||||
$template = $user->isLoggedIn() ? "admin.twig" : "redirect.twig";
|
||||
$params = $user->isLoggedIn() ? [] : ["url" => "/login"];
|
||||
$user = $router->getContext()->getUser();
|
||||
$template = $user ? "admin.twig" : "redirect.twig";
|
||||
$params = $user ? [] : ["url" => "/login"];
|
||||
parent::__construct($router, $template, $params);
|
||||
$this->enableCSP();
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class Info extends HtmlDocument {
|
||||
class InfoBody extends SimpleBody {
|
||||
protected function getContent(): string {
|
||||
$user = $this->getDocument()->getUser();
|
||||
if ($user->isLoggedIn() && $user->hasGroup(USER_GROUP_ADMIN)) {
|
||||
if ($user && $user->hasGroup(USER_GROUP_ADMIN)) {
|
||||
phpinfo();
|
||||
return "";
|
||||
} else {
|
||||
|
@ -159,15 +159,15 @@ namespace Documents\Install {
|
||||
}
|
||||
}
|
||||
|
||||
$user = $this->getDocument()->getUser();
|
||||
$config = $user->getConfiguration();
|
||||
$context = $this->getDocument()->getContext();
|
||||
$config = $context->getConfig();
|
||||
|
||||
// Check if database configuration exists
|
||||
if (!$config->getDatabase()) {
|
||||
return self::DATABASE_CONFIGURATION;
|
||||
}
|
||||
|
||||
$sql = $user->getSQL();
|
||||
$sql = $context->getSQL();
|
||||
if (!$sql || !$sql->isConnected()) {
|
||||
return self::DATABASE_CONFIGURATION;
|
||||
}
|
||||
@ -185,7 +185,7 @@ namespace Documents\Install {
|
||||
}
|
||||
|
||||
if ($step === self::ADD_MAIL_SERVICE) {
|
||||
$req = new \Api\Settings\Get($user);
|
||||
$req = new \Api\Settings\Get($context);
|
||||
$success = $req->execute(array("key" => "^mail_enabled$"));
|
||||
if (!$success) {
|
||||
$this->errorString = $req->getLastError();
|
||||
@ -193,12 +193,12 @@ namespace Documents\Install {
|
||||
} else if (isset($req->getResult()["settings"]["mail_enabled"])) {
|
||||
$step = self::FINISH_INSTALLATION;
|
||||
|
||||
$req = new \Api\Settings\Set($user);
|
||||
$req = new \Api\Settings\Set($context);
|
||||
$success = $req->execute(array("settings" => array("installation_completed" => "1")));
|
||||
if (!$success) {
|
||||
$this->errorString = $req->getLastError();
|
||||
} else {
|
||||
$req = new \Api\Notifications\Create($user);
|
||||
$req = new \Api\Notifications\Create($context);
|
||||
$req->execute(array(
|
||||
"title" => "Welcome",
|
||||
"message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!",
|
||||
@ -365,24 +365,26 @@ namespace Documents\Install {
|
||||
}
|
||||
}
|
||||
|
||||
$user = $this->getDocument()->getUser();
|
||||
$config = $user->getConfiguration();
|
||||
if ($success) {
|
||||
$context = $this->getDocument()->getContext();
|
||||
$config = $context->getConfig();
|
||||
if (Configuration::create("Database", $connectionData) === false) {
|
||||
$success = false;
|
||||
$msg = "Unable to write database file";
|
||||
} else {
|
||||
$config->setDatabase($connectionData);
|
||||
if (!$user->connectDB()) {
|
||||
if (!$context->initSQL()) {
|
||||
$success = false;
|
||||
$msg = "Unable to verify database connection after installation";
|
||||
} else {
|
||||
$req = new \Api\Routes\GenerateCache($user);
|
||||
$req = new \Api\Routes\GenerateCache($context);
|
||||
if (!$req->execute()) {
|
||||
$success = false;
|
||||
$msg = "Unable to write route file: " . $req->getLastError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sql->close();
|
||||
}
|
||||
@ -393,9 +395,9 @@ namespace Documents\Install {
|
||||
|
||||
private function createUser(): array {
|
||||
|
||||
$user = $this->getDocument()->getUser();
|
||||
$context = $this->getDocument()->getContext();
|
||||
if ($this->getParameter("prev") === "true") {
|
||||
$success = $user->getConfiguration()->delete("Database");
|
||||
$success = $context->getConfig()->delete("Database");
|
||||
$msg = $success ? "" : error_get_last();
|
||||
return array("success" => $success, "msg" => $msg);
|
||||
}
|
||||
@ -427,8 +429,7 @@ namespace Documents\Install {
|
||||
$msg = "Please fill out the following inputs:<br>" .
|
||||
$this->createUnorderedList($missingInputs);
|
||||
} else {
|
||||
$sql = $user->getSQL();
|
||||
$req = new \Api\User\Create($user);
|
||||
$req = new \Api\User\Create($context);
|
||||
$success = $req->execute(array(
|
||||
'username' => $username,
|
||||
'email' => $email,
|
||||
@ -438,6 +439,7 @@ namespace Documents\Install {
|
||||
|
||||
$msg = $req->getLastError();
|
||||
if ($success) {
|
||||
$sql = $context->getSQL();
|
||||
$success = $sql->insert("UserGroup", array("group_id", "user_id"))
|
||||
->addRow(USER_GROUP_ADMIN, $req->getResult()["userId"])
|
||||
->execute();
|
||||
@ -450,18 +452,16 @@ namespace Documents\Install {
|
||||
|
||||
private function addMailService(): array {
|
||||
|
||||
$user = $this->getDocument()->getUser();
|
||||
$context = $this->getDocument()->getContext();
|
||||
if ($this->getParameter("prev") === "true") {
|
||||
$sql = $user->getSQL();
|
||||
$sql = $context->getSQL();
|
||||
$success = $sql->delete("User")->execute();
|
||||
$msg = $sql->getLastError();
|
||||
return array("success" => $success, "msg" => $msg);
|
||||
}
|
||||
|
||||
$success = true;
|
||||
$msg = $this->errorString;
|
||||
if ($this->getParameter("skip") === "true") {
|
||||
$req = new \Api\Settings\Set($user);
|
||||
$req = new \Api\Settings\Set($context);
|
||||
$success = $req->execute(array("settings" => array("mail_enabled" => "0")));
|
||||
$msg = $req->getLastError();
|
||||
} else {
|
||||
@ -473,17 +473,17 @@ namespace Documents\Install {
|
||||
$success = true;
|
||||
|
||||
$missingInputs = array();
|
||||
if (is_null($address) || empty($address)) {
|
||||
if (empty($address)) {
|
||||
$success = false;
|
||||
$missingInputs[] = "SMTP Address";
|
||||
}
|
||||
|
||||
if (is_null($port) || empty($port)) {
|
||||
if (empty($port)) {
|
||||
$success = false;
|
||||
$missingInputs[] = "Port";
|
||||
}
|
||||
|
||||
if (is_null($username) || empty($username)) {
|
||||
if (empty($username)) {
|
||||
$success = false;
|
||||
$missingInputs[] = "Username";
|
||||
}
|
||||
@ -527,7 +527,7 @@ namespace Documents\Install {
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
$req = new \Api\Settings\Set($user);
|
||||
$req = new \Api\Settings\Set($context);
|
||||
$success = $req->execute(array("settings" => array(
|
||||
"mail_enabled" => "1",
|
||||
"mail_host" => "$address",
|
||||
@ -655,39 +655,25 @@ namespace Documents\Install {
|
||||
}
|
||||
}
|
||||
|
||||
$replacements = array("+" => " ", "&" => "\" ", "=" => "=\"");
|
||||
$attributes = http_build_query($attributes) . "\"";
|
||||
foreach ($replacements as $key => $val) {
|
||||
$attributes = str_replace($key, $val, $attributes);
|
||||
}
|
||||
|
||||
// $attributes = html_attributes($attributes);
|
||||
if ($type === "select") {
|
||||
$items = $formItem["items"] ?? array();
|
||||
$element = "<select $attributes>";
|
||||
$options = [];
|
||||
foreach ($items as $key => $val) {
|
||||
$element .= "<option value=\"$key\">$val</option>";
|
||||
}
|
||||
$element .= "</select>";
|
||||
} else {
|
||||
$element = "<input $attributes>";
|
||||
$options[] = html_tag_ex("option", ["value" => $key], $val, true, false);
|
||||
}
|
||||
|
||||
if (!$inline) {
|
||||
return
|
||||
"<div class=\"d-block my-3\">
|
||||
<label for=\"$name\">$title</label>
|
||||
$element
|
||||
</div>";
|
||||
$element = html_tag_ex("select", $attributes, $options, false);
|
||||
} else {
|
||||
return
|
||||
"<div class=\"col-md-6 mb-3\">
|
||||
<label for=\"$name\">$title</label>
|
||||
$element
|
||||
</div>";
|
||||
}
|
||||
$element = html_tag_short("input", $attributes);
|
||||
}
|
||||
|
||||
private function createProgessMainview(): string {
|
||||
$label = html_tag_ex("label", ["for" => $name], $title, true, false);
|
||||
$className = ($inline ? "col-md-6 mb-3" : "d-block my-3");
|
||||
return html_tag_ex("div", ["class" => $className], $label . $element, false);
|
||||
}
|
||||
|
||||
private function createProgressMainview(): string {
|
||||
|
||||
$isDocker = $this->isDocker();
|
||||
$defaultHost = ($isDocker ? "db" : "localhost");
|
||||
@ -773,7 +759,7 @@ namespace Documents\Install {
|
||||
$spinnerIcon = $this->createIcon("spinner");
|
||||
$title = $currentView["title"];
|
||||
|
||||
$html = "<h4 class=\"mb-3\">$title</h4><hr class=\"mb-4\">";
|
||||
$html = "<h4 class=\"mb-3\">$title</h4><hr class=\"mb-4\" />";
|
||||
|
||||
if (isset($currentView["text"])) {
|
||||
$text = $currentView["text"];
|
||||
@ -905,7 +891,7 @@ namespace Documents\Install {
|
||||
}
|
||||
|
||||
$progressSidebar = $this->createProgressSidebar();
|
||||
$progressMainview = $this->createProgessMainview();
|
||||
$progressMainView = $this->createProgressMainview();
|
||||
|
||||
$errorStyle = ($this->errorString ? '' : ' style="display:none"');
|
||||
$errorClass = ($this->errorString ? ' alert-danger' : '');
|
||||
@ -931,7 +917,7 @@ namespace Documents\Install {
|
||||
</ul>
|
||||
</div>
|
||||
<div class=\"col-md-8 order-md-1\">
|
||||
$progressMainview
|
||||
$progressMainView
|
||||
<div class=\"alert$errorClass mt-4\" id=\"status\"$errorStyle>$this->errorString</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,13 +34,16 @@ class Logger {
|
||||
}
|
||||
|
||||
protected function getStackTrace(int $pop = 2): string {
|
||||
$debugTrace = debug_backtrace();
|
||||
$debugTrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
if ($pop > 0) {
|
||||
array_splice($debugTrace, 0, $pop);
|
||||
}
|
||||
|
||||
return implode("\n", array_map(function ($trace) {
|
||||
if (isset($trace["file"])) {
|
||||
return $trace["file"] . "#" . $trace["line"] . ": " . $trace["function"] . "()";
|
||||
} else {
|
||||
return $trace["function"] . "()";
|
||||
}
|
||||
}, $debugTrace));
|
||||
}
|
||||
|
||||
@ -93,8 +96,8 @@ class Logger {
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function debug(string $message): string {
|
||||
$this->log($message, "debug");
|
||||
public function debug(string $message, bool $appendStackTrace = false): string {
|
||||
$this->log($message, "debug", $appendStackTrace);
|
||||
return $message;
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,11 @@ class Select extends Query {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addValue($value): Select {
|
||||
$this->selectValues[] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where(...$conditions): Select {
|
||||
$this->conditions[] = (count($conditions) === 1 ? $conditions : new CondOr($conditions));
|
||||
return $this;
|
||||
@ -63,6 +68,11 @@ class Select extends Query {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addJoin(Join $join): Select {
|
||||
$this->joins[] = $join;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function groupBy(...$columns): Select {
|
||||
$this->groupColumns = $columns;
|
||||
return $this;
|
||||
|
@ -52,16 +52,19 @@ abstract class SQL {
|
||||
protected ConnectionData $connectionData;
|
||||
protected int $lastInsertId;
|
||||
|
||||
protected bool $logQueries;
|
||||
|
||||
public function __construct($connectionData) {
|
||||
$this->connection = NULL;
|
||||
$this->lastError = 'Unknown Error';
|
||||
$this->connectionData = $connectionData;
|
||||
$this->lastInsertId = 0;
|
||||
$this->logger = new Logger(getClassName($this), $this);
|
||||
$this->logQueries = false;
|
||||
}
|
||||
|
||||
public function isConnected(): bool {
|
||||
return !is_null($this->connection);
|
||||
return !is_null($this->connection) && !is_bool($this->connection);
|
||||
}
|
||||
|
||||
public function getLastError(): string {
|
||||
@ -131,7 +134,7 @@ abstract class SQL {
|
||||
$parameters = [];
|
||||
$queryStr = $query->build($parameters);
|
||||
|
||||
if($query->dump) {
|
||||
if ($query->dump) {
|
||||
var_dump($queryStr);
|
||||
var_dump($parameters);
|
||||
}
|
||||
@ -149,6 +152,31 @@ abstract class SQL {
|
||||
$this->fetchReturning($res, $generatedColumn);
|
||||
}
|
||||
|
||||
if ($this->logQueries && (!($query instanceof Insert) || $query->getTableName() !== "SystemLog")) {
|
||||
|
||||
if ($success === false || $fetchType == self::FETCH_NONE) {
|
||||
$result = var_export($success, true);
|
||||
} else if ($fetchType === self::FETCH_ALL) {
|
||||
$result = count($res) . " rows";
|
||||
} else if ($fetchType === self::FETCH_ONE) {
|
||||
$result = ($res === null ? "(empty)" : "1 row");
|
||||
} else if ($fetchType === self::FETCH_ITERATIVE) {
|
||||
$result = $res->getNumRows() . " rows (iterative)";
|
||||
} else {
|
||||
$result = "Unknown";
|
||||
}
|
||||
|
||||
$message = sprintf("Query: %s, Parameters: %s, Result: %s",
|
||||
var_export($queryStr, true), var_export($parameters, true), $result
|
||||
);
|
||||
|
||||
if ($success === false) {
|
||||
$message .= "Error: " . var_export($this->lastError, true);
|
||||
}
|
||||
|
||||
$this->logger->debug($message);
|
||||
}
|
||||
|
||||
return $fetchType === self::FETCH_NONE ? $success : $res;
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,9 @@ namespace Elements;
|
||||
use Configuration\Settings;
|
||||
use Driver\Logger\Logger;
|
||||
use Driver\SQL\SQL;
|
||||
use Objects\Context;
|
||||
use Objects\Router\Router;
|
||||
use Objects\User;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
abstract class Document {
|
||||
|
||||
@ -32,16 +33,20 @@ abstract class Document {
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
public function getUser(): User {
|
||||
return $this->router->getUser();
|
||||
public function getUser(): ?User {
|
||||
return $this->getContext()->getUser();
|
||||
}
|
||||
|
||||
public function getContext(): Context {
|
||||
return $this->router->getContext();
|
||||
}
|
||||
|
||||
public function getSQL(): ?SQL {
|
||||
return $this->getUser()->getSQL();
|
||||
return $this->getContext()->getSQL();
|
||||
}
|
||||
|
||||
public function getSettings(): Settings {
|
||||
return $this->getUser()->getConfiguration()->getSettings();
|
||||
return $this->getContext()->getSettings();
|
||||
}
|
||||
|
||||
public function getCSPNonce(): ?string {
|
||||
|
@ -65,10 +65,10 @@ class HtmlDocument extends Document {
|
||||
}
|
||||
|
||||
$head = $this->head->getCode();
|
||||
$lang = $this->getUser()->getLanguage()->getShortCode();
|
||||
$lang = $this->getContext()->getLanguage();
|
||||
|
||||
$code = "<!DOCTYPE html>";
|
||||
$code .= html_tag("html", ["lang" => $lang], $head . $body, false);
|
||||
$code .= html_tag("html", ["lang" => $lang->getShortCode()], $head . $body, false);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
@ -46,13 +46,14 @@ class TemplateDocument extends Document {
|
||||
public function renderTemplate(string $name, array $params = []): string {
|
||||
try {
|
||||
|
||||
$user = $this->getUser();
|
||||
$context = $this->getContext();
|
||||
$session = $context->getSession();
|
||||
$params["user"] = [
|
||||
"lang" => $user->getLanguage()->getShortCode(),
|
||||
"loggedIn" => $user->isLoggedIn(),
|
||||
"session" => (!$user->isLoggedIn() ? null : [
|
||||
"csrfToken" => $user->getSession()->getCsrfToken()
|
||||
])
|
||||
"lang" => $context->getLanguage()->getShortCode(),
|
||||
"loggedIn" => $session !== null,
|
||||
"session" => ($session ? [
|
||||
"csrfToken" => $session->getCsrfToken()
|
||||
] : null)
|
||||
];
|
||||
|
||||
$settings = $this->getSettings();
|
||||
|
@ -23,8 +23,7 @@ abstract class View extends StaticView {
|
||||
public function isSearchable(): bool { return $this->searchable; }
|
||||
|
||||
public function getSiteName(): string {
|
||||
// what a chain lol
|
||||
return $this->getDocument()->getUser()->getConfiguration()->getSettings()->getSiteName();
|
||||
return $this->getDocument()->getSettings()->getSiteName();
|
||||
}
|
||||
|
||||
protected function load(string $viewClass) : string {
|
||||
@ -43,7 +42,7 @@ abstract class View extends StaticView {
|
||||
}
|
||||
|
||||
private function loadLanguageModules() {
|
||||
$lang = $this->document->getUser()->getLanguage();
|
||||
$lang = $this->document->getContext()->getLanguage();
|
||||
foreach ($this->langModules as $langModule) {
|
||||
$lang->loadModule($langModule);
|
||||
}
|
||||
|
202
core/Objects/Context.class.php
Normal file
202
core/Objects/Context.class.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace Objects;
|
||||
|
||||
use Configuration\Configuration;
|
||||
use Configuration\Settings;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\CondLike;
|
||||
use Driver\SQL\Condition\CondOr;
|
||||
use Driver\SQL\SQL;
|
||||
use Firebase\JWT\JWT;
|
||||
use Objects\DatabaseEntity\Language;
|
||||
use Objects\DatabaseEntity\Session;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
class Context {
|
||||
|
||||
private ?SQL $sql;
|
||||
private ?Session $session;
|
||||
private ?User $user;
|
||||
private Configuration $configuration;
|
||||
private Language $language;
|
||||
|
||||
public function __construct() {
|
||||
|
||||
$this->sql = null;
|
||||
$this->session = null;
|
||||
$this->user = null;
|
||||
$this->configuration = new Configuration();
|
||||
$this->setLanguage(Language::DEFAULT_LANGUAGE());
|
||||
|
||||
if (!$this->isCLI()) {
|
||||
@session_start();
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
if ($this->sql && $this->sql->isConnected()) {
|
||||
$this->sql->close();
|
||||
$this->sql = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function setLanguage(Language $language) {
|
||||
$this->language = $language;
|
||||
$this->language->activate();
|
||||
|
||||
if ($this->user && $this->user->language->getId() !== $language->getId()) {
|
||||
$this->user->language = $language;
|
||||
}
|
||||
}
|
||||
|
||||
public function initSQL(): ?SQL {
|
||||
$databaseConf = $this->configuration->getDatabase();
|
||||
if ($databaseConf) {
|
||||
$this->sql = SQL::createConnection($databaseConf);
|
||||
if ($this->sql->isConnected()) {
|
||||
$settings = $this->configuration->getSettings();
|
||||
$settings->loadFromDatabase($this);
|
||||
return $this->sql;
|
||||
}
|
||||
} else {
|
||||
$this->sql = null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getSQL(): ?SQL {
|
||||
return $this->sql;
|
||||
}
|
||||
|
||||
public function getSettings(): Settings {
|
||||
return $this->configuration->getSettings();
|
||||
}
|
||||
|
||||
public function getUser(): ?User {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function sendCookies() {
|
||||
$domain = $this->getSettings()->getDomain();
|
||||
$this->language->sendCookie($domain);
|
||||
$this->session?->sendCookie($domain);
|
||||
$this->session?->update();
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
private function loadSession(int $userId, int $sessionId) {
|
||||
$this->session = Session::init($this, $userId, $sessionId);
|
||||
$this->user = $this->session?->getUser();
|
||||
}
|
||||
|
||||
public function parseCookies() {
|
||||
if (isset($_COOKIE['session']) && is_string($_COOKIE['session']) && !empty($_COOKIE['session'])) {
|
||||
try {
|
||||
$token = $_COOKIE['session'];
|
||||
$settings = $this->configuration->getSettings();
|
||||
$decoded = (array)JWT::decode($token, $settings->getJwtKey());
|
||||
if (!is_null($decoded)) {
|
||||
$userId = ($decoded['userId'] ?? NULL);
|
||||
$sessionId = ($decoded['sessionId'] ?? NULL);
|
||||
if (!is_null($userId) && !is_null($sessionId)) {
|
||||
$this->loadSession($userId, $sessionId);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
// set language by priority: 1. GET parameter, 2. cookie, 3. user's settings
|
||||
if (isset($_GET['lang']) && is_string($_GET["lang"]) && !empty($_GET["lang"])) {
|
||||
$this->updateLanguage($_GET['lang']);
|
||||
} else if (isset($_COOKIE['lang']) && is_string($_COOKIE["lang"]) && !empty($_COOKIE["lang"])) {
|
||||
$this->updateLanguage($_COOKIE['lang']);
|
||||
} else if ($this->user) {
|
||||
$this->setLanguage($this->user->language);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateLanguage(string $lang): bool {
|
||||
if ($this->sql) {
|
||||
$language = Language::findBuilder($this->sql)
|
||||
->where(new CondOr(
|
||||
new CondLike("name", "%$lang%"), // english
|
||||
new Compare("code", $lang), // de_DE
|
||||
new CondLike("code", $lang . "_%"))) // de -> de_%
|
||||
->execute();
|
||||
if ($language) {
|
||||
$this->setLanguage($language);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function processVisit() {
|
||||
if (isset($_COOKIE["PHPSESSID"]) && !empty($_COOKIE["PHPSESSID"])) {
|
||||
if ($this->isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cookie = $_COOKIE["PHPSESSID"];
|
||||
$req = new \Api\Visitors\ProcessVisit($this);
|
||||
$req->execute(["cookie" => $cookie]);
|
||||
}
|
||||
}
|
||||
|
||||
private function isBot(): bool {
|
||||
if (empty($_SERVER["HTTP_USER_AGENT"])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return preg_match('/robot|spider|crawler|curl|^$/i', $_SERVER['HTTP_USER_AGENT']) === 1;
|
||||
}
|
||||
|
||||
public function isCLI(): bool {
|
||||
return php_sapi_name() === "cli";
|
||||
}
|
||||
|
||||
public function getConfig(): Configuration {
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
public function getSession(): ?Session {
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
public function loadApiKey(string $apiKey): bool {
|
||||
$this->user = User::findBuilder($this->sql)
|
||||
->addJoin(new \Driver\SQL\Join("INNER","ApiKey", "ApiKey.user_id", "User.id"))
|
||||
->where(new Compare("ApiKey.api_key", $apiKey))
|
||||
->where(new Compare("valid_until", $this->sql->currentTimestamp(), ">"))
|
||||
->where(new Compare("ApiKey.active", true))
|
||||
->where(new Compare("User.confirmed", true))
|
||||
->fetchEntities()
|
||||
->execute();
|
||||
|
||||
return $this->user !== null;
|
||||
}
|
||||
|
||||
public function createSession(int $userId, bool $stayLoggedIn): ?Session {
|
||||
$this->user = User::find($this->sql, $userId);
|
||||
if ($this->user) {
|
||||
$this->session = new Session($this, $this->user);
|
||||
$this->session->stayLoggedIn = $stayLoggedIn;
|
||||
if ($this->session->update()) {
|
||||
return $this->session;
|
||||
}
|
||||
}
|
||||
|
||||
$this->user = null;
|
||||
$this->session = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getLanguage(): Language {
|
||||
return $this->language;
|
||||
}
|
||||
}
|
27
core/Objects/DatabaseEntity/ApiKey.class.php
Normal file
27
core/Objects/DatabaseEntity/ApiKey.class.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
|
||||
class ApiKey extends DatabaseEntity {
|
||||
|
||||
private bool $active;
|
||||
#[MaxLength(64)] public String $apiKey;
|
||||
public \DateTime $validUntil;
|
||||
public User $user;
|
||||
|
||||
public function __construct(?int $id = null) {
|
||||
parent::__construct($id);
|
||||
$this->active = true;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
"id" => $this->getId(),
|
||||
"active" => $this->active,
|
||||
"apiKey" => $this->apiKey,
|
||||
"validUntil" => $this->validUntil->getTimestamp()
|
||||
];
|
||||
}
|
||||
}
|
21
core/Objects/DatabaseEntity/Attribute/DefaultValue.class.php
Normal file
21
core/Objects/DatabaseEntity/Attribute/DefaultValue.class.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)] class DefaultValue {
|
||||
|
||||
private mixed $value;
|
||||
|
||||
public function __construct(mixed $value) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getValue() {
|
||||
if (is_string($this->value) && isClass($this->value)) {
|
||||
return new $this->value();
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
}
|
17
core/Objects/DatabaseEntity/Attribute/Enum.class.php
Normal file
17
core/Objects/DatabaseEntity/Attribute/Enum.class.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)] class Enum {
|
||||
|
||||
private array $values;
|
||||
|
||||
public function __construct(string ...$values) {
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
public function getValues(): array {
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
}
|
7
core/Objects/DatabaseEntity/Attribute/Json.class.php
Normal file
7
core/Objects/DatabaseEntity/Attribute/Json.class.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)] class Json {
|
||||
|
||||
}
|
16
core/Objects/DatabaseEntity/Attribute/Many.class.php
Normal file
16
core/Objects/DatabaseEntity/Attribute/Many.class.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)] class Many {
|
||||
|
||||
private string $type;
|
||||
|
||||
public function __construct(string $type) {
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getValue(): string {
|
||||
return $this->type;
|
||||
}
|
||||
}
|
15
core/Objects/DatabaseEntity/Attribute/MaxLength.class.php
Normal file
15
core/Objects/DatabaseEntity/Attribute/MaxLength.class.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)] class MaxLength {
|
||||
private int $maxLength;
|
||||
|
||||
function __construct(int $maxLength) {
|
||||
$this->maxLength = $maxLength;
|
||||
}
|
||||
|
||||
public function getValue(): int {
|
||||
return $this->maxLength;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)] class Transient {
|
||||
|
||||
}
|
7
core/Objects/DatabaseEntity/Attribute/Unique.class.php
Normal file
7
core/Objects/DatabaseEntity/Attribute/Unique.class.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)] class Unique {
|
||||
|
||||
}
|
@ -2,30 +2,71 @@
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\Condition;
|
||||
use Driver\SQL\SQL;
|
||||
|
||||
abstract class DatabaseEntity {
|
||||
|
||||
private static array $handlers = [];
|
||||
private ?int $id = null;
|
||||
protected ?int $id;
|
||||
|
||||
public function __construct() {
|
||||
public function __construct(?int $id = null) {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public static function find(SQL $sql, int $id): ?DatabaseEntity {
|
||||
public abstract function jsonSerialize(): array;
|
||||
|
||||
public function preInsert(array &$row) { }
|
||||
public function postFetch(SQL $sql, array $row) { }
|
||||
|
||||
public static function fromRow(SQL $sql, array $row): static {
|
||||
$handler = self::getHandler($sql);
|
||||
return $handler->entityFromRow($row);
|
||||
}
|
||||
|
||||
public static function newInstance(\ReflectionClass $reflectionClass, array $row) {
|
||||
return $reflectionClass->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
public static function find(SQL $sql, int $id, bool $fetchEntities = false, bool $fetchRecursive = false): static|bool|null {
|
||||
$handler = self::getHandler($sql);
|
||||
if ($fetchEntities) {
|
||||
return DatabaseEntityQuery::fetchOne(self::getHandler($sql))
|
||||
->where(new Compare($handler->getTableName() . ".id", $id))
|
||||
->fetchEntities($fetchRecursive)
|
||||
->execute();
|
||||
} else {
|
||||
return $handler->fetchOne($id);
|
||||
}
|
||||
}
|
||||
|
||||
public static function exists(SQL $sql, int $id): bool {
|
||||
$handler = self::getHandler($sql);
|
||||
$res = $sql->select($sql->count())
|
||||
->from($handler->getTableName())
|
||||
->where(new Compare($handler->getTableName() . ".id", $id))
|
||||
->execute();
|
||||
|
||||
return $res !== false && $res[0]["count"] !== 0;
|
||||
}
|
||||
|
||||
public static function findBuilder(SQL $sql): DatabaseEntityQuery {
|
||||
return DatabaseEntityQuery::fetchOne(self::getHandler($sql));
|
||||
}
|
||||
|
||||
public static function findAll(SQL $sql, ?Condition $condition = null): ?array {
|
||||
$handler = self::getHandler($sql);
|
||||
return $handler->fetchMultiple($condition);
|
||||
}
|
||||
|
||||
public function save(SQL $sql): bool {
|
||||
public static function findAllBuilder(SQL $sql): DatabaseEntityQuery {
|
||||
return DatabaseEntityQuery::fetchAll(self::getHandler($sql));
|
||||
}
|
||||
|
||||
public function save(SQL $sql, ?array $columns = null): bool {
|
||||
$handler = self::getHandler($sql);
|
||||
$res = $handler->insertOrUpdate($this);
|
||||
$res = $handler->insertOrUpdate($this, $columns);
|
||||
if ($res === false) {
|
||||
return false;
|
||||
} else if ($this->id === null) {
|
||||
|
@ -5,7 +5,9 @@ namespace Objects\DatabaseEntity;
|
||||
use Driver\Logger\Logger;
|
||||
use Driver\SQL\Column\BoolColumn;
|
||||
use Driver\SQL\Column\DateTimeColumn;
|
||||
use Driver\SQL\Column\EnumColumn;
|
||||
use Driver\SQL\Column\IntColumn;
|
||||
use Driver\SQL\Column\JsonColumn;
|
||||
use Driver\SQL\Column\StringColumn;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Condition\Condition;
|
||||
@ -13,19 +15,27 @@ use Driver\SQL\Column\DoubleColumn;
|
||||
use Driver\SQL\Column\FloatColumn;
|
||||
use Driver\SQL\Constraint\ForeignKey;
|
||||
use Driver\SQL\Query\CreateTable;
|
||||
use Driver\SQL\Query\Select;
|
||||
use Driver\SQL\SQL;
|
||||
use Driver\SQL\Strategy\CascadeStrategy;
|
||||
use Driver\SQL\Strategy\SetNullStrategy;
|
||||
use Objects\DatabaseEntity\Attribute\Enum;
|
||||
use Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
use Objects\DatabaseEntity\Attribute\Json;
|
||||
use Objects\DatabaseEntity\Attribute\Many;
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
use Objects\DatabaseEntity\Attribute\Transient;
|
||||
use Objects\DatabaseEntity\Attribute\Unique;
|
||||
use PHPUnit\Util\Exception;
|
||||
|
||||
class DatabaseEntityHandler {
|
||||
|
||||
private \ReflectionClass $entityClass;
|
||||
private static \ReflectionProperty $ID_FIELD;
|
||||
private string $tableName;
|
||||
private array $columns;
|
||||
private array $properties;
|
||||
private array $relations;
|
||||
private array $constraints;
|
||||
private SQL $sql;
|
||||
private Logger $logger;
|
||||
|
||||
@ -34,22 +44,23 @@ class DatabaseEntityHandler {
|
||||
$className = $entityClass->getName();
|
||||
$this->logger = new Logger($entityClass->getShortName(), $sql);
|
||||
$this->entityClass = $entityClass;
|
||||
if (!$this->entityClass->isSubclassOf(DatabaseEntity::class) ||
|
||||
!$this->entityClass->isInstantiable()) {
|
||||
if (!$this->entityClass->isSubclassOf(DatabaseEntity::class)) {
|
||||
$this->raiseError("Cannot persist class '$className': Not an instance of DatabaseEntity or not instantiable.");
|
||||
}
|
||||
|
||||
$this->tableName = $this->entityClass->getShortName();
|
||||
$this->columns = [];
|
||||
$this->properties = [];
|
||||
$this->relations = [];
|
||||
|
||||
if (!isset(self::$ID_FIELD)) {
|
||||
self::$ID_FIELD = (new \ReflectionClass(DatabaseEntity::class))->getProperty("id");
|
||||
}
|
||||
$this->columns = []; // property name => database column name
|
||||
$this->properties = []; // property name => \ReflectionProperty
|
||||
$this->relations = []; // property name => referenced table name
|
||||
$this->constraints = []; // \Driver\SQL\Constraint\Constraint
|
||||
|
||||
foreach ($this->entityClass->getProperties() as $property) {
|
||||
$propertyName = $property->getName();
|
||||
if ($propertyName === "id") {
|
||||
$this->properties[$propertyName] = $property;
|
||||
continue;
|
||||
}
|
||||
|
||||
$propertyType = $property->getType();
|
||||
$columnName = self::getColumnName($propertyName);
|
||||
if (!($propertyType instanceof \ReflectionNamedType)) {
|
||||
@ -58,38 +69,83 @@ class DatabaseEntityHandler {
|
||||
|
||||
$nullable = $propertyType->allowsNull();
|
||||
$propertyTypeName = $propertyType->getName();
|
||||
if (!empty($property->getAttributes(Transient::class))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$defaultValue = (self::getAttribute($property, DefaultValue::class))?->getValue();
|
||||
$isUnique = !empty($property->getAttributes(Unique::class));
|
||||
|
||||
if ($propertyTypeName === 'string') {
|
||||
$this->columns[$propertyName] = new StringColumn($columnName, null, $nullable);
|
||||
} else if ($propertyTypeName === 'int') {
|
||||
$this->columns[$propertyName] = new IntColumn($columnName, $nullable);
|
||||
} else if ($propertyTypeName === 'float') {
|
||||
$this->columns[$propertyName] = new FloatColumn($columnName, $nullable);
|
||||
} else if ($propertyTypeName === 'double') {
|
||||
$this->columns[$propertyName] = new DoubleColumn($columnName, $nullable);
|
||||
} else if ($propertyTypeName === 'bool') {
|
||||
$this->columns[$propertyName] = new BoolColumn($columnName, $nullable);
|
||||
} else if ($propertyTypeName === 'DateTime') {
|
||||
$this->columns[$propertyName] = new DateTimeColumn($columnName, $nullable);
|
||||
$enum = self::getAttribute($property, Enum::class);
|
||||
if ($enum) {
|
||||
$this->columns[$propertyName] = new EnumColumn($columnName, $enum->getValues(), $nullable, $defaultValue);
|
||||
} else {
|
||||
$maxLength = self::getAttribute($property, MaxLength::class);
|
||||
$this->columns[$propertyName] = new StringColumn($columnName, $maxLength?->getValue(), $nullable, $defaultValue);
|
||||
}
|
||||
} else if ($propertyTypeName === 'int') {
|
||||
$this->columns[$propertyName] = new IntColumn($columnName, $nullable, $defaultValue);
|
||||
} else if ($propertyTypeName === 'float') {
|
||||
$this->columns[$propertyName] = new FloatColumn($columnName, $nullable, $defaultValue);
|
||||
} else if ($propertyTypeName === 'double') {
|
||||
$this->columns[$propertyName] = new DoubleColumn($columnName, $nullable, $defaultValue);
|
||||
} else if ($propertyTypeName === 'bool') {
|
||||
$this->columns[$propertyName] = new BoolColumn($columnName, $defaultValue ?? false);
|
||||
} else if ($propertyTypeName === 'DateTime') {
|
||||
$this->columns[$propertyName] = new DateTimeColumn($columnName, $nullable, $defaultValue);
|
||||
/*} else if ($propertyName === 'array') {
|
||||
$many = self::getAttribute($property, Many::class);
|
||||
if ($many) {
|
||||
$requestedType = $many->getValue();
|
||||
if (isClass($requestedType)) {
|
||||
$requestedClass = new \ReflectionClass($requestedType);
|
||||
} else {
|
||||
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $requestedType");
|
||||
}
|
||||
} else {
|
||||
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName");
|
||||
}*/
|
||||
} else if ($propertyTypeName !== "mixed") {
|
||||
try {
|
||||
$requestedClass = new \ReflectionClass($propertyTypeName);
|
||||
if ($requestedClass->isSubclassOf(DatabaseEntity::class)) {
|
||||
$columnName .= "_id";
|
||||
$requestedHandler = ($requestedClass->getName() === $this->entityClass->getName()) ?
|
||||
$this : DatabaseEntity::getHandler($this->sql, $requestedClass);
|
||||
$strategy = $nullable ? new SetNullStrategy() : new CascadeStrategy();
|
||||
$this->columns[$propertyName] = new IntColumn($columnName, $nullable);
|
||||
$this->relations[$propertyName] = new ForeignKey($columnName, $requestedHandler->tableName, "id", $strategy);
|
||||
$this->columns[$propertyName] = new IntColumn($columnName, $nullable, $defaultValue);
|
||||
$this->constraints[] = new ForeignKey($columnName, $requestedHandler->tableName, "id", $strategy);
|
||||
$this->relations[$propertyName] = $requestedHandler;
|
||||
} else {
|
||||
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName");
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
$this->raiseError("Cannot persist class '$className' property '$propertyTypeName': " . $ex->getMessage());
|
||||
}
|
||||
} else {
|
||||
if (!empty($property->getAttributes(Json::class))) {
|
||||
$this->columns[$propertyName] = new JsonColumn($columnName, $nullable, $defaultValue);
|
||||
} else {
|
||||
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName");
|
||||
}
|
||||
}
|
||||
|
||||
$this->properties[$propertyName] = $property;
|
||||
|
||||
if ($isUnique) {
|
||||
$this->constraints[] = new \Driver\SQL\Constraint\Unique($columnName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function getColumnName(string $propertyName): string {
|
||||
private static function getAttribute(\ReflectionProperty $property, string $attributeClass): ?object {
|
||||
$attributes = $property->getAttributes($attributeClass);
|
||||
$attribute = array_shift($attributes);
|
||||
return $attribute?->newInstance();
|
||||
}
|
||||
|
||||
public static function getColumnName(string $propertyName): string {
|
||||
// abcTestLOL => abc_test_lol
|
||||
return strtolower(preg_replace_callback("/([a-z])([A-Z]+)/", function ($m) {
|
||||
return $m[1] . "_" . strtolower($m[2]);
|
||||
@ -108,46 +164,111 @@ class DatabaseEntityHandler {
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
private function entityFromRow(array $row): DatabaseEntity {
|
||||
public function getRelations(): array {
|
||||
return $this->relations;
|
||||
}
|
||||
|
||||
public function getColumnNames(): array {
|
||||
$columns = ["$this->tableName.id"];
|
||||
foreach ($this->columns as $column) {
|
||||
$columns[] = $this->tableName . "." . $column->getName();
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
public function getColumns(): array {
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
public function dependsOn(): array {
|
||||
$foreignTables = array_map(function (DatabaseEntityHandler $relationHandler) {
|
||||
return $relationHandler->getTableName();
|
||||
}, $this->relations);
|
||||
return array_unique($foreignTables);
|
||||
}
|
||||
|
||||
public static function getPrefixedRow(array $row, string $prefix): array {
|
||||
$rel_row = [];
|
||||
foreach ($row as $relKey => $relValue) {
|
||||
if (startsWith($relKey, $prefix)) {
|
||||
$rel_row[substr($relKey, strlen($prefix))] = $relValue;
|
||||
}
|
||||
}
|
||||
return $rel_row;
|
||||
}
|
||||
|
||||
public function entityFromRow(array $row): ?DatabaseEntity {
|
||||
try {
|
||||
$entity = $this->entityClass->newInstanceWithoutConstructor();
|
||||
|
||||
$entity = call_user_func($this->entityClass->getName() . "::newInstance", $this->entityClass, $row);
|
||||
if (!($entity instanceof DatabaseEntity)) {
|
||||
$this->logger->error("Created Object is not of type DatabaseEntity");
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->columns as $propertyName => $column) {
|
||||
$value = $row[$column->getName()];
|
||||
$columnName = $column->getName();
|
||||
if (array_key_exists($columnName, $row)) {
|
||||
$value = $row[$columnName];
|
||||
$property = $this->properties[$propertyName];
|
||||
|
||||
if ($property->getType()->getName() === "DateTime") {
|
||||
if ($column instanceof DateTimeColumn) {
|
||||
$value = new \DateTime($value);
|
||||
} else if ($column instanceof JsonColumn) {
|
||||
$value = json_decode($value);
|
||||
} else if (isset($this->relations[$propertyName])) {
|
||||
$relColumnPrefix = self::getColumnName($propertyName) . "_";
|
||||
if (array_key_exists($relColumnPrefix . "id", $row)) {
|
||||
$relId = $row[$relColumnPrefix . "id"];
|
||||
if ($relId !== null) {
|
||||
$relationHandler = $this->relations[$propertyName];
|
||||
$value = $relationHandler->entityFromRow(self::getPrefixedRow($row, $relColumnPrefix));
|
||||
} else if (!$column->notNull()) {
|
||||
$value = null;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($entity, $value);
|
||||
}
|
||||
}
|
||||
|
||||
self::$ID_FIELD->setAccessible(true);
|
||||
self::$ID_FIELD->setValue($entity, $row["id"]);
|
||||
$this->properties["id"]->setAccessible(true);
|
||||
$this->properties["id"]->setValue($entity, $row["id"]);
|
||||
$entity->postFetch($this->sql, $row);
|
||||
return $entity;
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->error("Error creating entity from database row: " . $exception->getMessage());
|
||||
throw $exception;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchOne(int $id): ?DatabaseEntity {
|
||||
$res = $this->sql->select("id", ...array_keys($this->columns))
|
||||
->from($this->tableName)
|
||||
->where(new Compare("id", $id))
|
||||
public function getSelectQuery(): Select {
|
||||
return $this->sql->select(...$this->getColumnNames())
|
||||
->from($this->tableName);
|
||||
}
|
||||
|
||||
public function fetchOne(int $id): DatabaseEntity|bool|null {
|
||||
$res = $this->getSelectQuery()
|
||||
->where(new Compare($this->tableName . ".id", $id))
|
||||
->first()
|
||||
->execute();
|
||||
|
||||
if (empty($res)) {
|
||||
return null;
|
||||
if ($res === false || $res === null) {
|
||||
return $res;
|
||||
} else {
|
||||
return $this->entityFromRow($res);
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchMultiple(?Condition $condition = null): ?array {
|
||||
$query = $this->sql->select("id", ...array_keys($this->columns))
|
||||
->from($this->tableName);
|
||||
$query = $this->getSelectQuery();
|
||||
|
||||
if ($condition) {
|
||||
$query->where($condition);
|
||||
@ -159,7 +280,10 @@ class DatabaseEntityHandler {
|
||||
} else {
|
||||
$entities = [];
|
||||
foreach ($res as $row) {
|
||||
$entities[] = $this->entityFromRow($row);
|
||||
$entity = $this->entityFromRow($row);
|
||||
if ($entity) {
|
||||
$entities[$entity->getId()] = $entity;
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
@ -175,7 +299,7 @@ class DatabaseEntityHandler {
|
||||
$query->addColumn($column);
|
||||
}
|
||||
|
||||
foreach ($this->relations as $constraint) {
|
||||
foreach ($this->constraints as $constraint) {
|
||||
$query->addConstraint($constraint);
|
||||
}
|
||||
|
||||
@ -187,29 +311,43 @@ class DatabaseEntityHandler {
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
public function insertOrUpdate(DatabaseEntity $entity) {
|
||||
public function insertOrUpdate(DatabaseEntity $entity, ?array $columns = null) {
|
||||
$id = $entity->getId();
|
||||
if ($id === null) {
|
||||
$columns = [];
|
||||
$row = [];
|
||||
$action = $id === null ? "insert" : "update";
|
||||
|
||||
$row = [];
|
||||
foreach ($this->columns as $propertyName => $column) {
|
||||
$columns[] = $column->getName();
|
||||
if ($columns && !in_array($column->getName(), $columns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$property = $this->properties[$propertyName];
|
||||
$property->setAccessible(true);
|
||||
if ($property->isInitialized($entity)) {
|
||||
$value = $property->getValue($entity);
|
||||
if (isset($this->relations[$propertyName])) {
|
||||
$value = $value->getId();
|
||||
}
|
||||
} else if (!$this->columns[$propertyName]->notNull()) {
|
||||
$value = null;
|
||||
} else {
|
||||
$this->logger->error("Cannot insert entity: property '$propertyName' was not initialized yet.");
|
||||
if ($action !== "update") {
|
||||
$this->logger->error("Cannot $action entity: property '$propertyName' was not initialized yet.");
|
||||
return false;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$row[] = $value;
|
||||
$row[$column->getName()] = $value;
|
||||
}
|
||||
|
||||
$res = $this->sql->insert($this->tableName, $columns)
|
||||
->addRow(...$row)
|
||||
$entity->preInsert($row);
|
||||
|
||||
|
||||
if ($id === null) {
|
||||
$res = $this->sql->insert($this->tableName, array_keys($row))
|
||||
->addRow(...array_values($row))
|
||||
->returning("id")
|
||||
->execute();
|
||||
|
||||
@ -220,20 +358,9 @@ class DatabaseEntityHandler {
|
||||
}
|
||||
} else {
|
||||
$query = $this->sql->update($this->tableName)
|
||||
->where(new Compare("id", $id));
|
||||
|
||||
foreach ($this->columns as $propertyName => $column) {
|
||||
$columnName = $column->getName();
|
||||
$property = $this->properties[$propertyName];
|
||||
if ($property->isInitialized($entity)) {
|
||||
$value = $property->getValue($entity);
|
||||
} else if (!$this->columns[$propertyName]->notNull()) {
|
||||
$value = null;
|
||||
} else {
|
||||
$this->logger->error("Cannot update entity: property '$propertyName' was not initialized yet.");
|
||||
return false;
|
||||
}
|
||||
->where(new Compare($this->tableName . ".id", $id));
|
||||
|
||||
foreach ($row as $columnName => $value) {
|
||||
$query->set($columnName, $value);
|
||||
}
|
||||
|
||||
@ -242,15 +369,14 @@ class DatabaseEntityHandler {
|
||||
}
|
||||
|
||||
public function delete(int $id) {
|
||||
return $this->sql->delete($this->tableName)->where(new Compare("id", $id))->execute();
|
||||
return $this->sql
|
||||
->delete($this->tableName)
|
||||
->where(new Compare($this->tableName . ".id", $id))
|
||||
->execute();
|
||||
}
|
||||
|
||||
private function raiseError(string $message) {
|
||||
$this->logger->error($message);
|
||||
throw new Exception($message);
|
||||
}
|
||||
|
||||
private function getPropertyValue() {
|
||||
|
||||
}
|
||||
}
|
135
core/Objects/DatabaseEntity/DatabaseEntityQuery.class.php
Normal file
135
core/Objects/DatabaseEntity/DatabaseEntityQuery.class.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use Driver\SQL\Condition\Condition;
|
||||
use Driver\SQL\Join;
|
||||
use Driver\SQL\Query\Select;
|
||||
use Driver\SQL\SQL;
|
||||
|
||||
/**
|
||||
* this class is similar to \Driver\SQL\Query\Select but with reduced functionality
|
||||
* and more adapted to entities.
|
||||
*/
|
||||
class DatabaseEntityQuery {
|
||||
|
||||
private DatabaseEntityHandler $handler;
|
||||
private Select $selectQuery;
|
||||
private int $resultType;
|
||||
|
||||
private function __construct(DatabaseEntityHandler $handler, int $resultType) {
|
||||
$this->handler = $handler;
|
||||
$this->selectQuery = $handler->getSelectQuery();
|
||||
$this->resultType = $resultType;
|
||||
|
||||
if ($this->resultType === SQL::FETCH_ONE) {
|
||||
$this->selectQuery->first();
|
||||
}
|
||||
}
|
||||
|
||||
public static function fetchAll(DatabaseEntityHandler $handler): DatabaseEntityQuery {
|
||||
return new DatabaseEntityQuery($handler, SQL::FETCH_ALL);
|
||||
}
|
||||
|
||||
public static function fetchOne(DatabaseEntityHandler $handler): DatabaseEntityQuery {
|
||||
return new DatabaseEntityQuery($handler, SQL::FETCH_ONE);
|
||||
}
|
||||
|
||||
public function limit(int $limit): DatabaseEntityQuery {
|
||||
$this->selectQuery->limit($limit);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where(Condition ...$condition): DatabaseEntityQuery {
|
||||
$this->selectQuery->where(...$condition);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orderBy(string ...$column): DatabaseEntityQuery {
|
||||
$this->selectQuery->orderBy(...$column);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function ascending(): DatabaseEntityQuery {
|
||||
$this->selectQuery->ascending();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function descending(): DatabaseEntityQuery {
|
||||
$this->selectQuery->descending();
|
||||
return $this;
|
||||
}
|
||||
|
||||
// TODO: clean this up
|
||||
public function fetchEntities(bool $recursive = false): DatabaseEntityQuery {
|
||||
|
||||
// $this->selectQuery->dump();
|
||||
|
||||
$relIndex = 1;
|
||||
foreach ($this->handler->getRelations() as $propertyName => $relationHandler) {
|
||||
$this->fetchRelation($propertyName, $this->handler->getTableName(), $this->handler, $relationHandler, $relIndex, $recursive);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function fetchRelation(string $propertyName, string $tableName, DatabaseEntityHandler $src, DatabaseEntityHandler $relationHandler,
|
||||
int &$relIndex = 1, bool $recursive = false, string $relationColumnPrefix = "") {
|
||||
|
||||
$columns = $src->getColumns();
|
||||
|
||||
$foreignColumn = $columns[$propertyName];
|
||||
$foreignColumnName = $foreignColumn->getName();
|
||||
$referencedTable = $relationHandler->getTableName();
|
||||
$isNullable = !$foreignColumn->notNull();
|
||||
$alias = "t$relIndex"; // t1, t2, t3, ...
|
||||
$relIndex++;
|
||||
|
||||
|
||||
if ($isNullable) {
|
||||
$this->selectQuery->leftJoin($referencedTable, "$tableName.$foreignColumnName", "$alias.id", $alias);
|
||||
} else {
|
||||
$this->selectQuery->innerJoin($referencedTable, "$tableName.$foreignColumnName", "$alias.id", $alias);
|
||||
}
|
||||
|
||||
$relationColumnPrefix .= DatabaseEntityHandler::getColumnName($propertyName) . "_";
|
||||
$recursiveRelations = $relationHandler->getRelations();
|
||||
foreach ($relationHandler->getColumns() as $relPropertyName => $relColumn) {
|
||||
$relColumnName = $relColumn->getName();
|
||||
if (!isset($recursiveRelations[$relPropertyName]) || $recursive) {
|
||||
$this->selectQuery->addValue("$alias.$relColumnName as $relationColumnPrefix$relColumnName");
|
||||
if (isset($recursiveRelations[$relPropertyName]) && $recursive) {
|
||||
$this->fetchRelation($relPropertyName, $alias, $relationHandler, $recursiveRelations[$relPropertyName], $relIndex, $recursive, $relationColumnPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function execute(): DatabaseEntity|array|null {
|
||||
$res = $this->selectQuery->execute();
|
||||
if ($res === null || $res === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->resultType === SQL::FETCH_ALL) {
|
||||
$entities = [];
|
||||
foreach ($res as $row) {
|
||||
$entity = $this->handler->entityFromRow($row);
|
||||
if ($entity) {
|
||||
$entities[$entity->getId()] = $entity;
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
} else if ($this->resultType === SQL::FETCH_ONE) {
|
||||
return $this->handler->entityFromRow($res);
|
||||
} else {
|
||||
$this->handler->getLogger()->error("Invalid result type for query builder, must be FETCH_ALL or FETCH_ONE");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function addJoin(Join $join): DatabaseEntityQuery {
|
||||
$this->selectQuery->addJoin($join);
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -1,19 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Objects;
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
class GpgKey extends ApiObject {
|
||||
use Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
use Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
|
||||
class GpgKey extends DatabaseEntity {
|
||||
|
||||
const GPG2 = "/usr/bin/gpg2";
|
||||
|
||||
private int $id;
|
||||
private bool $confirmed;
|
||||
private string $fingerprint;
|
||||
private string $algorithm;
|
||||
#[MaxLength(64)] private string $fingerprint;
|
||||
#[MaxLength(64)] private string $algorithm;
|
||||
private \DateTime $expires;
|
||||
#[DefaultValue(CurrentTimeStamp::class)] private \DateTime $added;
|
||||
|
||||
public function __construct(int $id, bool $confirmed, string $fingerprint, string $algorithm, string $expires) {
|
||||
$this->id = $id;
|
||||
parent::__construct($id);
|
||||
$this->confirmed = $confirmed;
|
||||
$this->fingerprint = $fingerprint;
|
||||
$this->algorithm = $algorithm;
|
||||
@ -25,26 +29,17 @@ class GpgKey extends ApiObject {
|
||||
$cmd = self::GPG2 . " --encrypt --output - --recipient $gpgFingerprint --trust-model always --batch --armor";
|
||||
list($out, $err) = self::proc_exec($cmd, $body, true);
|
||||
if ($out === null) {
|
||||
return self::createError("Error while communicating with GPG agent");
|
||||
return createError("Error while communicating with GPG agent");
|
||||
} else if ($err) {
|
||||
return self::createError($err);
|
||||
return createError($err);
|
||||
} else {
|
||||
return ["success" => true, "data" => $out];
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return array(
|
||||
"fingerprint" => $this->fingerprint,
|
||||
"algorithm" => $this->algorithm,
|
||||
"expires" => $this->expires->getTimestamp(),
|
||||
"confirmed" => $this->confirmed
|
||||
);
|
||||
}
|
||||
|
||||
private static function proc_exec(string $cmd, ?string $stdin = null, bool $raw = false): ?array {
|
||||
$descriptorSpec = array(0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => ["pipe", "w"]);
|
||||
$process = proc_open($cmd, $descriptorSpec,$pipes);
|
||||
$process = proc_open($cmd, $descriptorSpec, $pipes);
|
||||
if (!is_resource($process)) {
|
||||
return null;
|
||||
}
|
||||
@ -62,25 +57,21 @@ class GpgKey extends ApiObject {
|
||||
return [($raw ? $out : trim($out)), $err];
|
||||
}
|
||||
|
||||
private static function createError(string $error) : array {
|
||||
return ["success" => false, "error" => $error];
|
||||
}
|
||||
|
||||
public static function getKeyInfo(string $key): array {
|
||||
list($out, $err) = self::proc_exec(self::GPG2 . " --show-key", $key);
|
||||
if ($out === null) {
|
||||
return self::createError("Error while communicating with GPG agent");
|
||||
return createError("Error while communicating with GPG agent");
|
||||
}
|
||||
|
||||
if ($err) {
|
||||
return self::createError($err);
|
||||
return createError($err);
|
||||
}
|
||||
|
||||
$lines = explode("\n", $out);
|
||||
if (count($lines) > 4) {
|
||||
return self::createError("It seems like you have uploaded more than one GPG-Key");
|
||||
return createError("It seems like you have uploaded more than one GPG-Key");
|
||||
} else if (count($lines) !== 4 || !preg_match("/(\S+)\s+(\w+)\s+.*\[expires: ([0-9-]+)]/", $lines[0], $matches)) {
|
||||
return self::createError("Error parsing GPG output");
|
||||
return createError("Error parsing GPG output");
|
||||
}
|
||||
|
||||
$keyType = $matches[1];
|
||||
@ -94,7 +85,7 @@ class GpgKey extends ApiObject {
|
||||
public static function importKey(string $key): array {
|
||||
list($out, $err) = self::proc_exec(self::GPG2 . " --import", $key);
|
||||
if ($out === null) {
|
||||
return self::createError("Error while communicating with GPG agent");
|
||||
return createError("Error while communicating with GPG agent");
|
||||
}
|
||||
|
||||
if (preg_match("/gpg:\s+Total number processed:\s+(\d+)/", $err, $matches) && intval($matches[1]) > 0) {
|
||||
@ -104,7 +95,7 @@ class GpgKey extends ApiObject {
|
||||
}
|
||||
}
|
||||
|
||||
return self::createError($err);
|
||||
return createError($err);
|
||||
}
|
||||
|
||||
public static function export($gpgFingerprint, bool $armored): array {
|
||||
@ -115,7 +106,7 @@ class GpgKey extends ApiObject {
|
||||
$cmd .= escapeshellarg($gpgFingerprint);
|
||||
list($out, $err) = self::proc_exec($cmd);
|
||||
if ($err) {
|
||||
return self::createError($err);
|
||||
return createError($err);
|
||||
}
|
||||
|
||||
return ["success" => true, "data" => $out];
|
||||
@ -125,12 +116,18 @@ class GpgKey extends ApiObject {
|
||||
return $this->confirmed;
|
||||
}
|
||||
|
||||
public function getId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getFingerprint(): string {
|
||||
return $this->fingerprint;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
"id" => $this->getId(),
|
||||
"fingerprint" => $this->fingerprint,
|
||||
"algorithm" => $this->algorithm,
|
||||
"expires" => $this->expires->getTimestamp(),
|
||||
"added" => $this->added->getTimestamp(),
|
||||
"confirmed" => $this->confirmed
|
||||
];
|
||||
}
|
||||
}
|
23
core/Objects/DatabaseEntity/Group.class.php
Normal file
23
core/Objects/DatabaseEntity/Group.class.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
|
||||
class Group extends DatabaseEntity {
|
||||
|
||||
#[MaxLength(32)] public string $name;
|
||||
#[MaxLength(10)] public string $color;
|
||||
|
||||
public function __construct(?int $id = null) {
|
||||
parent::__construct($id);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
"id" => $this->getId(),
|
||||
"name" => $this->name,
|
||||
"color" => $this->color
|
||||
];
|
||||
}
|
||||
}
|
103
core/Objects/DatabaseEntity/Language.class.php
Normal file
103
core/Objects/DatabaseEntity/Language.class.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity {
|
||||
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
use Objects\DatabaseEntity\Attribute\Transient;
|
||||
use Objects\lang\LanguageModule;
|
||||
|
||||
// TODO: language from cookie?
|
||||
class Language extends DatabaseEntity {
|
||||
|
||||
const LANG_CODE_PATTERN = "/^[a-zA-Z]{2}_[a-zA-Z]{2}$/";
|
||||
|
||||
#[MaxLength(5)] private string $code;
|
||||
#[MaxLength(32)] private string $name;
|
||||
|
||||
#[Transient] private array $modules;
|
||||
#[Transient] protected array $entries;
|
||||
|
||||
public function __construct(int $id, string $code, string $name) {
|
||||
parent::__construct($id);
|
||||
$this->code = $code;
|
||||
$this->name = $name;
|
||||
$this->entries = array();
|
||||
$this->modules = array();
|
||||
}
|
||||
|
||||
public function getCode(): string {
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function getShortCode(): string {
|
||||
return substr($this->code, 0, 2);
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function loadModule(LanguageModule|string $module) {
|
||||
if (!is_object($module)) {
|
||||
$module = new $module();
|
||||
}
|
||||
|
||||
$moduleEntries = $module->getEntries($this->code);
|
||||
$this->entries = array_merge($this->entries, $moduleEntries);
|
||||
$this->modules[] = $module;
|
||||
}
|
||||
|
||||
public function translate(string $key): string {
|
||||
return $this->entries[$key] ?? $key;
|
||||
}
|
||||
|
||||
public function sendCookie(string $domain) {
|
||||
setcookie('lang', $this->code, 0, "/", $domain, false, false);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return array(
|
||||
'id' => $this->getId(),
|
||||
'code' => $this->code,
|
||||
'shortCode' => explode("_", $this->code)[0],
|
||||
'name' => $this->name,
|
||||
);
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
global $LANGUAGE;
|
||||
$LANGUAGE = $this;
|
||||
}
|
||||
|
||||
public static function DEFAULT_LANGUAGE(bool $fromCookie = true): Language {
|
||||
if ($fromCookie && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
$acceptLanguage = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
||||
$acceptedLanguages = explode(',', $acceptLanguage);
|
||||
foreach ($acceptedLanguages as $code) {
|
||||
if (strlen($code) == 2) {
|
||||
$code = $code . '_' . strtoupper($code);
|
||||
}
|
||||
|
||||
$code = str_replace("-", "_", $code);
|
||||
if (!preg_match(self::LANG_CODE_PATTERN, $code)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return new Language(0, $code, "");
|
||||
}
|
||||
}
|
||||
|
||||
return new Language(1, "en_US", "American English");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
function L($key) {
|
||||
if (!array_key_exists('LANGUAGE', $GLOBALS))
|
||||
return $key;
|
||||
|
||||
global $LANGUAGE;
|
||||
return $LANGUAGE->translate($key);
|
||||
}
|
||||
}
|
30
core/Objects/DatabaseEntity/News.class.php
Normal file
30
core/Objects/DatabaseEntity/News.class.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use Api\Parameter\Parameter;
|
||||
use Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
|
||||
class News extends DatabaseEntity {
|
||||
|
||||
public User $publishedBy;
|
||||
#[DefaultValue(CurrentTimeStamp::class)] private \DateTime $publishedAt;
|
||||
#[MaxLength(128)] public string $title;
|
||||
#[MaxLength(1024)] public string $text;
|
||||
|
||||
public function __construct(?int $id = null) {
|
||||
parent::__construct($id);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
"id" => $this->getId(),
|
||||
"publishedBy" => $this->publishedBy->jsonSerialize(),
|
||||
"publishedAt" => $this->publishedAt->format(Parameter::DATE_TIME_FORMAT),
|
||||
"title" => $this->title,
|
||||
"text" => $this->text
|
||||
];
|
||||
}
|
||||
}
|
30
core/Objects/DatabaseEntity/Notification.class.php
Normal file
30
core/Objects/DatabaseEntity/Notification.class.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use Api\Parameter\Parameter;
|
||||
use Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
use Objects\DatabaseEntity\Attribute\Enum;
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
|
||||
class Notification extends DatabaseEntity {
|
||||
|
||||
#[Enum('default', 'message', 'warning')] private string $type;
|
||||
#[DefaultValue(CurrentTimeStamp::class)] private \DateTime $createdAt;
|
||||
#[MaxLength(32)] public string $title;
|
||||
#[MaxLength(256)] public string $message;
|
||||
|
||||
public function __construct(?int $id = null) {
|
||||
parent::__construct($id);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
"id" => $this->getId(),
|
||||
"createdAt" => $this->createdAt->format(Parameter::DATE_TIME_FORMAT),
|
||||
"title" => $this->title,
|
||||
"message" => $this->message
|
||||
];
|
||||
}
|
||||
}
|
134
core/Objects/DatabaseEntity/Session.class.php
Normal file
134
core/Objects/DatabaseEntity/Session.class.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Firebase\JWT\JWT;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
use Objects\DatabaseEntity\Attribute\Json;
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
use Objects\DatabaseEntity\Attribute\Transient;
|
||||
|
||||
class Session extends DatabaseEntity {
|
||||
|
||||
# in minutes
|
||||
const DURATION = 60 * 60 * 24 * 14;
|
||||
#[Transient] private Context $context;
|
||||
|
||||
private User $user;
|
||||
private DateTime $expires;
|
||||
#[MaxLength(45)] private string $ipAddress;
|
||||
#[DefaultValue(true)] private bool $active;
|
||||
#[MaxLength(64)] private ?string $os;
|
||||
#[MaxLength(64)] private ?string $browser;
|
||||
#[DefaultValue(true)] public bool $stayLoggedIn;
|
||||
#[MaxLength(16)] private string $csrfToken;
|
||||
#[Json] private mixed $data;
|
||||
|
||||
public function __construct(Context $context, User $user, ?string $csrfToken = null) {
|
||||
parent::__construct();
|
||||
$this->context = $context;
|
||||
$this->user = $user;
|
||||
$this->stayLoggedIn = false;
|
||||
$this->csrfToken = $csrfToken ?? generateRandomString(16);
|
||||
$this->expires = (new DateTime())->modify(sprintf("+%d second", Session::DURATION));
|
||||
$this->active = true;
|
||||
}
|
||||
|
||||
public static function init(Context $context, int $userId, int $sessionId): ?Session {
|
||||
$session = Session::find($context->getSQL(), $sessionId, true, true);
|
||||
if (!$session || !$session->active || $session->user->getId() !== $userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$session->context = $context;
|
||||
return $session;
|
||||
}
|
||||
|
||||
public function getUser(): User {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
private function updateMetaData() {
|
||||
$this->expires = (new \DateTime())->modify(sprintf("+%d minutes", Session::DURATION));
|
||||
$this->ipAddress = $this->context->isCLI() ? "127.0.0.1" : $_SERVER['REMOTE_ADDR'];
|
||||
try {
|
||||
$userAgent = @get_browser($_SERVER['HTTP_USER_AGENT'], true);
|
||||
$this->os = $userAgent['platform'] ?? "Unknown";
|
||||
$this->browser = $userAgent['parent'] ?? "Unknown";
|
||||
} catch (Exception $ex) {
|
||||
$this->os = "Unknown";
|
||||
$this->browser = "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public function setData(array $data) {
|
||||
foreach ($data as $key => $value) {
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function getCookie(): string {
|
||||
$this->updateMetaData();
|
||||
$settings = $this->context->getSettings();
|
||||
$token = ['userId' => $this->user->getId(), 'sessionId' => $this->getId()];
|
||||
$jwtKey = $settings->getJwtKey();
|
||||
return JWT::encode($token, $jwtKey->getKeyMaterial(), $jwtKey->getAlgorithm());
|
||||
}
|
||||
|
||||
public function sendCookie(string $domain) {
|
||||
$sessionCookie = $this->getCookie();
|
||||
$secure = strcmp(getProtocol(), "https") === 0;
|
||||
setcookie('session', $sessionCookie, $this->getExpiresTime(), "/", $domain, $secure, true);
|
||||
}
|
||||
|
||||
public function getExpiresTime(): int {
|
||||
return ($this->stayLoggedIn ? $this->expires->getTimestamp() : 0);
|
||||
}
|
||||
|
||||
public function getExpiresSeconds(): int {
|
||||
return ($this->stayLoggedIn ? $this->expires->getTimestamp() - time() : -1);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return array(
|
||||
'id' => $this->getId(),
|
||||
'active' => $this->active,
|
||||
'expires' => $this->expires,
|
||||
'ipAddress' => $this->ipAddress,
|
||||
'os' => $this->os,
|
||||
'browser' => $this->browser,
|
||||
'csrf_token' => $this->csrfToken,
|
||||
'data' => $this->data,
|
||||
);
|
||||
}
|
||||
|
||||
public function insert(bool $stayLoggedIn = false): bool {
|
||||
$this->stayLoggedIn = $stayLoggedIn;
|
||||
$this->active = true;
|
||||
return $this->update();
|
||||
}
|
||||
|
||||
public function destroy(): bool {
|
||||
session_destroy();
|
||||
$this->active = false;
|
||||
return $this->save($this->context->getSQL());
|
||||
}
|
||||
|
||||
public function update(): bool {
|
||||
$this->updateMetaData();
|
||||
|
||||
$this->expires = (new DateTime())->modify(sprintf("+%d second", Session::DURATION));
|
||||
$this->data = json_encode($_SESSION ?? []);
|
||||
|
||||
$sql = $this->context->getSQL();
|
||||
return $this->user->update($sql) &&
|
||||
$this->save($sql);
|
||||
}
|
||||
|
||||
public function getCsrfToken(): string {
|
||||
return $this->csrfToken;
|
||||
}
|
||||
}
|
31
core/Objects/DatabaseEntity/SystemLog.class.php
Normal file
31
core/Objects/DatabaseEntity/SystemLog.class.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use Api\Parameter\Parameter;
|
||||
use Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
use Objects\DatabaseEntity\Attribute\Enum;
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
|
||||
class SystemLog extends DatabaseEntity {
|
||||
|
||||
#[DefaultValue(CurrentTimeStamp::class)] private \DateTime $timestamp;
|
||||
private string $message;
|
||||
#[MaxLength(64)] #[DefaultValue('global')] private string $module;
|
||||
#[Enum('debug','info','warning','error','severe')] private string $severity;
|
||||
|
||||
public function __construct(?int $id = null) {
|
||||
parent::__construct($id);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
"id" => $this->getId(),
|
||||
"timestamp" => $this->timestamp->format(Parameter::DATE_TIME_FORMAT),
|
||||
"message" => $this->message,
|
||||
"module" => $this->module,
|
||||
"severity" => $this->severity
|
||||
];
|
||||
}
|
||||
}
|
78
core/Objects/DatabaseEntity/TwoFactorToken.class.php
Normal file
78
core/Objects/DatabaseEntity/TwoFactorToken.class.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use Driver\SQL\SQL;
|
||||
use Objects\DatabaseEntity\Attribute\Enum;
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
use Objects\TwoFactor\KeyBasedTwoFactorToken;
|
||||
use Objects\TwoFactor\TimeBasedTwoFactorToken;
|
||||
|
||||
abstract class TwoFactorToken extends DatabaseEntity {
|
||||
|
||||
#[Enum('totp','fido')] private string $type;
|
||||
private bool $confirmed;
|
||||
private bool $authenticated;
|
||||
#[MaxLength(512)] private string $data;
|
||||
|
||||
public function __construct(string $type, ?int $id = null, bool $confirmed = false) {
|
||||
parent::__construct($id);
|
||||
$this->id = $id;
|
||||
$this->type = $type;
|
||||
$this->confirmed = $confirmed;
|
||||
$this->authenticated = $_SESSION["2faAuthenticated"] ?? false;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
"id" => $this->getId(),
|
||||
"type" => $this->type,
|
||||
"confirmed" => $this->confirmed,
|
||||
"authenticated" => $this->authenticated,
|
||||
];
|
||||
}
|
||||
|
||||
public abstract function getData(): string;
|
||||
protected abstract function readData(string $data);
|
||||
|
||||
public function preInsert(array &$row) {
|
||||
$row["data"] = $this->getData();
|
||||
}
|
||||
|
||||
public function postFetch(SQL $sql, array $row) {
|
||||
parent::postFetch($sql, $row);
|
||||
$this->readData($row["data"]);
|
||||
}
|
||||
|
||||
public function authenticate() {
|
||||
$this->authenticated = true;
|
||||
$_SESSION["2faAuthenticated"] = true;
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function isConfirmed(): bool {
|
||||
return $this->confirmed;
|
||||
}
|
||||
|
||||
public function getId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public static function newInstance(\ReflectionClass $reflectionClass, array $row) {
|
||||
if ($row["type"] === TimeBasedTwoFactorToken::TYPE) {
|
||||
return (new \ReflectionClass(TimeBasedTwoFactorToken::class))->newInstanceWithoutConstructor();
|
||||
} else if ($row["type"] === KeyBasedTwoFactorToken::TYPE) {
|
||||
return (new \ReflectionClass(KeyBasedTwoFactorToken::class))->newInstanceWithoutConstructor();
|
||||
} else {
|
||||
// TODO: error message
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function isAuthenticated(): bool {
|
||||
return $this->authenticated;
|
||||
}
|
||||
}
|
109
core/Objects/DatabaseEntity/User.class.php
Normal file
109
core/Objects/DatabaseEntity/User.class.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\DatabaseEntity;
|
||||
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Driver\SQL\Join;
|
||||
use Driver\SQL\SQL;
|
||||
use Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
use Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
use Objects\DatabaseEntity\Attribute\Transient;
|
||||
use Objects\DatabaseEntity\Attribute\Unique;
|
||||
|
||||
class User extends DatabaseEntity {
|
||||
|
||||
#[MaxLength(32)] #[Unique] public string $name;
|
||||
#[MaxLength(128)] public string $password;
|
||||
#[MaxLength(64)] public string $fullName;
|
||||
#[MaxLength(64)] #[Unique] public ?string $email;
|
||||
#[MaxLength(64)] private ?string $profilePicture;
|
||||
private ?\DateTime $lastOnline;
|
||||
#[DefaultValue(CurrentTimeStamp::class)] public \DateTime $registeredAt;
|
||||
public bool $confirmed;
|
||||
#[DefaultValue(1)] public Language $language;
|
||||
private ?GpgKey $gpgKey;
|
||||
private ?TwoFactorToken $twoFactorToken;
|
||||
|
||||
#[Transient] private array $groups;
|
||||
|
||||
public function __construct(?int $id = null) {
|
||||
parent::__construct($id);
|
||||
$this->groups = [];
|
||||
}
|
||||
|
||||
public function postFetch(SQL $sql, array $row) {
|
||||
parent::postFetch($sql, $row);
|
||||
$this->groups = [];
|
||||
|
||||
$groups = Group::findAllBuilder($sql)
|
||||
->fetchEntities()
|
||||
->addJoin(new Join("INNER", "UserGroup", "UserGroup.group_id", "Group.id"))
|
||||
->where(new Compare("UserGroup.user_id", $this->id))
|
||||
->execute();
|
||||
|
||||
if ($groups) {
|
||||
$this->groups = $groups;
|
||||
}
|
||||
}
|
||||
|
||||
public function getUsername(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getFullName(): string {
|
||||
return $this->fullName;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getGroups(): array {
|
||||
return $this->groups;
|
||||
}
|
||||
|
||||
public function hasGroup(int $group): bool {
|
||||
return isset($this->groups[$group]);
|
||||
}
|
||||
|
||||
public function getGPG(): ?GpgKey {
|
||||
return $this->gpgKey;
|
||||
}
|
||||
|
||||
public function getTwoFactorToken(): ?TwoFactorToken {
|
||||
return $this->twoFactorToken;
|
||||
}
|
||||
|
||||
public function getProfilePicture(): ?string {
|
||||
return $this->profilePicture;
|
||||
}
|
||||
|
||||
public function __debugInfo(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'username' => $this->name,
|
||||
'language' => $this->language->getName(),
|
||||
];
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'name' => $this->name,
|
||||
'fullName' => $this->fullName,
|
||||
'profilePicture' => $this->profilePicture,
|
||||
'email' => $this->email,
|
||||
'groups' => $this->groups ?? null,
|
||||
'language' => (isset($this->language) ? $this->language->jsonSerialize() : null),
|
||||
'session' => (isset($this->session) ? $this->session->jsonSerialize() : null),
|
||||
"gpg" => (isset($this->gpgKey) ? $this->gpgKey->jsonSerialize() : null),
|
||||
"2fa" => (isset($this->twoFactorToken) ? $this->twoFactorToken->jsonSerialize() : null),
|
||||
];
|
||||
}
|
||||
|
||||
public function update(SQL $sql): bool {
|
||||
$this->lastOnline = new \DateTime();
|
||||
return $this->save($sql, ["last_online", "language_id"]);
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Objects {
|
||||
|
||||
use Objects\lang\LanguageModule;
|
||||
|
||||
class Language extends ApiObject {
|
||||
|
||||
const LANG_CODE_PATTERN = "/^[a-zA-Z]+_[a-zA-Z]+$/";
|
||||
|
||||
private int $languageId;
|
||||
private string $langCode;
|
||||
private string $langName;
|
||||
private array $modules;
|
||||
|
||||
protected array $entries;
|
||||
|
||||
public function __construct($languageId, $langCode, $langName) {
|
||||
$this->languageId = $languageId;
|
||||
$this->langCode = $langCode;
|
||||
$this->langName = $langName;
|
||||
$this->entries = array();
|
||||
$this->modules = array();
|
||||
}
|
||||
|
||||
public function getId() { return $this->languageId; }
|
||||
public function getCode(): string { return $this->langCode; }
|
||||
public function getShortCode() { return substr($this->langCode, 0, 2); }
|
||||
public function getName() { return $this->langName; }
|
||||
|
||||
/**
|
||||
* @param $module LanguageModule class or object
|
||||
*/
|
||||
public function loadModule($module) {
|
||||
if(!is_object($module))
|
||||
$module = new $module;
|
||||
|
||||
$aModuleEntries = $module->getEntries($this->langCode);
|
||||
$this->entries = array_merge($this->entries, $aModuleEntries);
|
||||
$this->modules[] = $module;
|
||||
}
|
||||
|
||||
public function translate(string $key): string {
|
||||
if(isset($this->entries[$key]))
|
||||
return $this->entries[$key];
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
public function sendCookie(?string $domain = null) {
|
||||
$domain = empty($domain) ? "" : $domain;
|
||||
setcookie('lang', $this->langCode, 0, "/", $domain, false, false);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return array(
|
||||
'uid' => $this->languageId,
|
||||
'code' => $this->langCode,
|
||||
'shortCode' => explode("_", $this->langCode)[0],
|
||||
'name' => $this->langName,
|
||||
);
|
||||
}
|
||||
|
||||
public static function newInstance($languageId, $langCode, $langName) {
|
||||
|
||||
if(!preg_match(Language::LANG_CODE_PATTERN, $langCode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: include dynamically wanted Language
|
||||
return new Language($languageId, $langCode, $langName);
|
||||
|
||||
// $className = $langCode
|
||||
// return new $className($languageId, $langCode);
|
||||
}
|
||||
|
||||
public function load() {
|
||||
global $LANGUAGE;
|
||||
$LANGUAGE = $this;
|
||||
}
|
||||
|
||||
public static function DEFAULT_LANGUAGE() {
|
||||
if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
$acceptLanguage = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
||||
$aSplit = explode(',',$acceptLanguage);
|
||||
foreach($aSplit as $code) {
|
||||
if(strlen($code) == 2) {
|
||||
$code = $code . '_' . strtoupper($code);
|
||||
}
|
||||
|
||||
$code = str_replace("-", "_", $code);
|
||||
if(strlen($code) != 5)
|
||||
continue;
|
||||
|
||||
$lang = Language::newInstance(0, $code, "");
|
||||
if($lang)
|
||||
return $lang;
|
||||
}
|
||||
}
|
||||
|
||||
return Language::newInstance(1, "en_US", "American English");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
function L($key) {
|
||||
if(!array_key_exists('LANGUAGE', $GLOBALS))
|
||||
return $key;
|
||||
|
||||
global $LANGUAGE;
|
||||
return $LANGUAGE->translate($key);
|
||||
}
|
||||
|
||||
function LANG_NAME() {
|
||||
if(!array_key_exists('LANGUAGE', $GLOBALS))
|
||||
return "LANG_NAME";
|
||||
|
||||
global $LANGUAGE;
|
||||
return $LANGUAGE->getName();
|
||||
}
|
||||
|
||||
function LANG_CODE() {
|
||||
if(!array_key_exists('LANGUAGE', $GLOBALS))
|
||||
return "LANG_CODE";
|
||||
|
||||
global $LANGUAGE;
|
||||
return $LANGUAGE->getCode();
|
||||
}
|
||||
|
||||
function SHORT_LANG_CODE() {
|
||||
if(!array_key_exists('LANGUAGE', $GLOBALS))
|
||||
return "SHORT_LANG_CODE";
|
||||
|
||||
global $LANGUAGE;
|
||||
return $LANGUAGE->getShortCode();
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ class ApiRoute extends AbstractRoute {
|
||||
}
|
||||
|
||||
public function call(Router $router, array $params): string {
|
||||
$user = $router->getUser();
|
||||
if (empty($params["endpoint"])) {
|
||||
header("Content-Type: text/html");
|
||||
$document = new \Elements\TemplateDocument($router, "swagger.twig");
|
||||
@ -43,9 +42,11 @@ class ApiRoute extends AbstractRoute {
|
||||
http_response_code(400);
|
||||
$response = createError("Invalid Method");
|
||||
} else {
|
||||
$request = $apiClass->newInstanceArgs(array($user, true));
|
||||
$request->execute();
|
||||
$response = $request->getJsonResult();
|
||||
$request = $apiClass->newInstanceArgs(array($router->getContext(), true));
|
||||
$success = $request->execute();
|
||||
$response = $request->getResult();
|
||||
$response["success"] = $success;
|
||||
$response["msg"] = $request->getLastError();
|
||||
}
|
||||
}
|
||||
} catch (ReflectionException $e) {
|
||||
@ -55,6 +56,6 @@ class ApiRoute extends AbstractRoute {
|
||||
}
|
||||
|
||||
header("Content-Type: application/json");
|
||||
return $response;
|
||||
return json_encode($response);
|
||||
}
|
||||
}
|
@ -3,23 +3,24 @@
|
||||
namespace Objects\Router;
|
||||
|
||||
use Driver\Logger\Logger;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
|
||||
class Router {
|
||||
|
||||
private ?User $user;
|
||||
private Context $context;
|
||||
private Logger $logger;
|
||||
protected array $routes;
|
||||
protected array $statusCodeRoutes;
|
||||
|
||||
public function __construct(?User $user = null) {
|
||||
$this->user = $user;
|
||||
public function __construct(Context $context) {
|
||||
$this->context = $context;
|
||||
$this->routes = [];
|
||||
$this->statusCodeRoutes = [];
|
||||
|
||||
if ($user) {
|
||||
$sql = $context->getSQL();
|
||||
if ($sql) {
|
||||
$this->addRoute(new ApiRoute());
|
||||
$this->logger = new Logger("Router", $user->getSQL());
|
||||
$this->logger = new Logger("Router", $sql);
|
||||
} else {
|
||||
$this->logger = new Logger("Router");
|
||||
}
|
||||
@ -48,7 +49,7 @@ class Router {
|
||||
if ($route) {
|
||||
return $route->call($this, $params);
|
||||
} else {
|
||||
$req = new \Api\Template\Render($this->user);
|
||||
$req = new \Api\Template\Render($this->context);
|
||||
$res = $req->execute(["file" => "error_document.twig", "parameters" => $params]);
|
||||
if ($res) {
|
||||
return $req->getResult()["html"];
|
||||
@ -90,13 +91,13 @@ class Router {
|
||||
*/
|
||||
|
||||
namespace Cache;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Objects\Router\Router;
|
||||
|
||||
class RouterCache extends Router {
|
||||
|
||||
public function __construct(User \$user) {
|
||||
parent::__construct(\$user);$routes
|
||||
public function __construct(Context \$context) {
|
||||
parent::__construct(\$context);$routes
|
||||
}
|
||||
}
|
||||
";
|
||||
@ -109,8 +110,8 @@ class RouterCache extends Router {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getUser(): User {
|
||||
return $this->user;
|
||||
public function getContext(): Context {
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function getLogger(): Logger {
|
||||
|
@ -37,9 +37,9 @@ class StaticFileRoute extends AbstractRoute {
|
||||
}
|
||||
|
||||
$pathInfo = pathinfo($path);
|
||||
if ($router !== null && ($user = $router->getUser()) !== null) {
|
||||
if ($router !== null) {
|
||||
$ext = $pathInfo["extension"] ?? "";
|
||||
if (!$user->getConfiguration()->getSettings()->isExtensionAllowed($ext)) {
|
||||
if (!$router->getContext()->getSettings()->isExtensionAllowed($ext)) {
|
||||
http_response_code(406);
|
||||
echo "<b>Access restricted:</b> Extension '" . htmlspecialchars($ext) . "' not allowed to serve.";
|
||||
}
|
||||
|
@ -1,164 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Objects;
|
||||
|
||||
use DateTime;
|
||||
use \Driver\SQL\Condition\Compare;
|
||||
use Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Exception;
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
class Session extends ApiObject {
|
||||
|
||||
# in minutes
|
||||
const DURATION = 60*60*24*14;
|
||||
|
||||
private ?int $sessionId;
|
||||
private User $user;
|
||||
private int $expires;
|
||||
private string $ipAddress;
|
||||
private ?string $os;
|
||||
private ?string $browser;
|
||||
private bool $stayLoggedIn;
|
||||
private string $csrfToken;
|
||||
|
||||
public function __construct(User $user, ?int $sessionId, ?string $csrfToken) {
|
||||
$this->user = $user;
|
||||
$this->sessionId = $sessionId;
|
||||
$this->stayLoggedIn = false;
|
||||
$this->csrfToken = $csrfToken ?? generateRandomString(16);
|
||||
}
|
||||
|
||||
public static function create(User $user, bool $stayLoggedIn = false): ?Session {
|
||||
$session = new Session($user, null, null);
|
||||
if ($session->insert($stayLoggedIn)) {
|
||||
$session->stayLoggedIn = $stayLoggedIn;
|
||||
return $session;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function updateMetaData() {
|
||||
$this->expires = time() + Session::DURATION;
|
||||
$this->ipAddress = is_cli() ? "127.0.0.1" : $_SERVER['REMOTE_ADDR'];
|
||||
try {
|
||||
$userAgent = @get_browser($_SERVER['HTTP_USER_AGENT'], true);
|
||||
$this->os = $userAgent['platform'] ?? "Unknown";
|
||||
$this->browser = $userAgent['parent'] ?? "Unknown";
|
||||
} catch(Exception $ex) {
|
||||
$this->os = "Unknown";
|
||||
$this->browser = "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public function setData(array $data) {
|
||||
foreach($data as $key => $value) {
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function stayLoggedIn(bool $val) {
|
||||
$this->stayLoggedIn = $val;
|
||||
}
|
||||
|
||||
public function getCookie(): string {
|
||||
$this->updateMetaData();
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$token = ['userId' => $this->user->getId(), 'sessionId' => $this->sessionId];
|
||||
$jwtKey = $settings->getJwtKey();
|
||||
return JWT::encode($token, $jwtKey->getKeyMaterial(), $jwtKey->getAlgorithm());
|
||||
}
|
||||
|
||||
public function sendCookie(?string $domain = null) {
|
||||
$domain = empty($domain) ? "" : $domain;
|
||||
$sessionCookie = $this->getCookie();
|
||||
$secure = strcmp(getProtocol(), "https") === 0;
|
||||
setcookie('session', $sessionCookie, $this->getExpiresTime(), "/", $domain, $secure, true);
|
||||
}
|
||||
|
||||
public function getExpiresTime(): int {
|
||||
return ($this->stayLoggedIn ? $this->expires : 0);
|
||||
}
|
||||
|
||||
public function getExpiresSeconds(): int {
|
||||
return ($this->stayLoggedIn ? $this->expires - time() : -1);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return array(
|
||||
'uid' => $this->sessionId,
|
||||
'user_id' => $this->user->getId(),
|
||||
'expires' => $this->expires,
|
||||
'ipAddress' => $this->ipAddress,
|
||||
'os' => $this->os,
|
||||
'browser' => $this->browser,
|
||||
'csrf_token' => $this->csrfToken
|
||||
);
|
||||
}
|
||||
|
||||
public function insert(bool $stayLoggedIn = false): bool {
|
||||
$this->updateMetaData();
|
||||
$sql = $this->user->getSQL();
|
||||
|
||||
$minutes = Session::DURATION;
|
||||
$data = [
|
||||
"expires" => (new DateTime())->modify("+$minutes minute"),
|
||||
"user_id" => $this->user->getId(),
|
||||
"ipAddress" => $this->ipAddress,
|
||||
"os" => $this->os,
|
||||
"browser" => $this->browser,
|
||||
"data" => json_encode($_SESSION ?? []),
|
||||
"stay_logged_in" => $stayLoggedIn,
|
||||
"csrf_token" => $this->csrfToken
|
||||
];
|
||||
|
||||
$success = $sql
|
||||
->insert("Session", array_keys($data))
|
||||
->addRow(...array_values($data))
|
||||
->returning("uid")
|
||||
->execute();
|
||||
|
||||
if ($success) {
|
||||
$this->sessionId = $this->user->getSQL()->getLastInsertId();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function destroy(): bool {
|
||||
session_destroy();
|
||||
return $this->user->getSQL()->update("Session")
|
||||
->set("active", false)
|
||||
->where(new Compare("Session.uid", $this->sessionId))
|
||||
->where(new Compare("Session.user_id", $this->user->getId()))
|
||||
->execute();
|
||||
}
|
||||
|
||||
public function update(): bool {
|
||||
$this->updateMetaData();
|
||||
$minutes = Session::DURATION;
|
||||
|
||||
$sql = $this->user->getSQL();
|
||||
return
|
||||
$sql->update("User")
|
||||
->set("last_online", new CurrentTimeStamp())
|
||||
->where(new Compare("uid", $this->user->getId()))
|
||||
->execute() &&
|
||||
$sql->update("Session")
|
||||
->set("Session.expires", (new DateTime())->modify("+$minutes second"))
|
||||
->set("Session.ipAddress", $this->ipAddress)
|
||||
->set("Session.os", $this->os)
|
||||
->set("Session.browser", $this->browser)
|
||||
->set("Session.data", json_encode($_SESSION ?? []))
|
||||
->set("Session.csrf_token", $this->csrfToken)
|
||||
->where(new Compare("Session.uid", $this->sessionId))
|
||||
->where(new Compare("Session.user_id", $this->user->getId()))
|
||||
->execute();
|
||||
}
|
||||
|
||||
public function getCsrfToken(): string {
|
||||
return $this->csrfToken;
|
||||
}
|
||||
}
|
@ -3,18 +3,19 @@
|
||||
namespace Objects\TwoFactor;
|
||||
|
||||
use Cose\Algorithm\Signature\ECDSA\ECSignature;
|
||||
use Objects\DatabaseEntity\Attribute\Transient;
|
||||
use Objects\DatabaseEntity\TwoFactorToken;
|
||||
|
||||
class KeyBasedTwoFactorToken extends TwoFactorToken {
|
||||
|
||||
const TYPE = "fido";
|
||||
|
||||
private ?string $challenge;
|
||||
private ?string $credentialId;
|
||||
private ?PublicKey $publicKey;
|
||||
#[Transient] private ?string $challenge;
|
||||
#[Transient] private ?string $credentialId;
|
||||
#[Transient] private ?PublicKey $publicKey;
|
||||
|
||||
public function __construct(string $data, ?int $id = null, bool $confirmed = false) {
|
||||
parent::__construct(self::TYPE, $id, $confirmed);
|
||||
if (!$confirmed) {
|
||||
protected function readData(string $data) {
|
||||
if ($this->isConfirmed()) {
|
||||
$this->challenge = base64_decode($data);
|
||||
$this->credentialId = null;
|
||||
$this->publicKey = null;
|
||||
@ -34,7 +35,7 @@ class KeyBasedTwoFactorToken extends TwoFactorToken {
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
public function getCredentialId() {
|
||||
public function getCredentialId(): ?string {
|
||||
return $this->credentialId;
|
||||
}
|
||||
|
||||
|
@ -5,22 +5,29 @@ namespace Objects\TwoFactor;
|
||||
use Base32\Base32;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\Attribute\Transient;
|
||||
use Objects\DatabaseEntity\TwoFactorToken;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
class TimeBasedTwoFactorToken extends TwoFactorToken {
|
||||
|
||||
const TYPE = "totp";
|
||||
private string $secret;
|
||||
#[Transient] private string $secret;
|
||||
|
||||
public function __construct(string $secret, ?int $id = null, bool $confirmed = false) {
|
||||
parent::__construct(self::TYPE, $id, $confirmed);
|
||||
public function __construct(string $secret) {
|
||||
parent::__construct(self::TYPE);
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
public function getUrl(User $user): string {
|
||||
protected function readData(string $data) {
|
||||
$this->secret = $data;
|
||||
}
|
||||
|
||||
public function getUrl(Context $context): string {
|
||||
$otpType = self::TYPE;
|
||||
$name = rawurlencode($user->getUsername());
|
||||
$settings = $user->getConfiguration()->getSettings();
|
||||
$name = rawurlencode($context->getUser()->getUsername());
|
||||
$settings = $context->getSettings();
|
||||
$urlArgs = [
|
||||
"secret" => $this->secret,
|
||||
"issuer" => $settings->getSiteName(),
|
||||
@ -30,10 +37,10 @@ class TimeBasedTwoFactorToken extends TwoFactorToken {
|
||||
return "otpauth://$otpType/$name?$urlArgs";
|
||||
}
|
||||
|
||||
public function generateQRCode(User $user) {
|
||||
public function generateQRCode(Context $context) {
|
||||
$options = new QROptions(['outputType' => QRCode::OUTPUT_IMAGE_PNG, "imageBase64" => false]);
|
||||
$qrcode = new QRCode($options);
|
||||
return $qrcode->render($this->getUrl($user));
|
||||
return $qrcode->render($this->getUrl($context));
|
||||
}
|
||||
|
||||
public function generate(?int $at = null, int $length = 6, int $period = 30): string {
|
||||
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Objects\TwoFactor;
|
||||
use Objects\ApiObject;
|
||||
|
||||
abstract class TwoFactorToken extends ApiObject {
|
||||
|
||||
private ?int $id;
|
||||
private string $type;
|
||||
private bool $confirmed;
|
||||
private bool $authenticated;
|
||||
|
||||
public function __construct(string $type, ?int $id = null, bool $confirmed = false) {
|
||||
$this->id = $id;
|
||||
$this->type = $type;
|
||||
$this->confirmed = $confirmed;
|
||||
$this->authenticated = $_SESSION["2faAuthenticated"] ?? false;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
"id" => $this->id,
|
||||
"type" => $this->type,
|
||||
"confirmed" => $this->confirmed,
|
||||
"authenticated" => $this->authenticated,
|
||||
];
|
||||
}
|
||||
|
||||
public abstract function getData(): string;
|
||||
|
||||
public function authenticate() {
|
||||
$this->authenticated = true;
|
||||
$_SESSION["2faAuthenticated"] = true;
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function isConfirmed(): bool {
|
||||
return $this->confirmed;
|
||||
}
|
||||
|
||||
public function getId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public static function newInstance(string $type, string $data, ?int $id = null, bool $confirmed = false) {
|
||||
if ($type === TimeBasedTwoFactorToken::TYPE) {
|
||||
return new TimeBasedTwoFactorToken($data, $id, $confirmed);
|
||||
} else if ($type === KeyBasedTwoFactorToken::TYPE) {
|
||||
return new KeyBasedTwoFactorToken($data, $id, $confirmed);
|
||||
} else {
|
||||
// TODO: error message
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function isAuthenticated(): bool {
|
||||
return $this->authenticated;
|
||||
}
|
||||
}
|
@ -1,376 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Objects;
|
||||
|
||||
use Configuration\Configuration;
|
||||
use Driver\SQL\Condition\CondAnd;
|
||||
use Exception;
|
||||
use Driver\SQL\SQL;
|
||||
use Driver\SQL\Condition\Compare;
|
||||
use Firebase\JWT\JWT;
|
||||
use Objects\TwoFactor\TwoFactorToken;
|
||||
|
||||
class User extends ApiObject {
|
||||
|
||||
private ?SQL $sql;
|
||||
private Configuration $configuration;
|
||||
private bool $loggedIn;
|
||||
private ?Session $session;
|
||||
private int $uid;
|
||||
private string $username;
|
||||
private string $fullName;
|
||||
private ?string $email;
|
||||
private ?string $profilePicture;
|
||||
private Language $language;
|
||||
private array $groups;
|
||||
private ?GpgKey $gpgKey;
|
||||
private ?TwoFactorToken $twoFactorToken;
|
||||
|
||||
public function __construct($configuration) {
|
||||
$this->configuration = $configuration;
|
||||
$this->reset();
|
||||
$this->connectDB();
|
||||
|
||||
if (!is_cli()) {
|
||||
@session_start();
|
||||
$this->setLanguage(Language::DEFAULT_LANGUAGE());
|
||||
$this->parseCookies();
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
if ($this->sql && $this->sql->isConnected()) {
|
||||
$this->sql->close();
|
||||
}
|
||||
}
|
||||
|
||||
public function connectDB(): bool {
|
||||
$databaseConf = $this->configuration->getDatabase();
|
||||
if ($databaseConf) {
|
||||
$this->sql = SQL::createConnection($databaseConf);
|
||||
if ($this->sql->isConnected()) {
|
||||
$settings = $this->configuration->getSettings();
|
||||
$settings->loadFromDatabase($this);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$this->sql = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getId(): int {
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
public function isLoggedIn(): bool {
|
||||
return $this->loggedIn;
|
||||
}
|
||||
|
||||
public function getUsername(): string {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getFullName(): string {
|
||||
return $this->fullName;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getSQL(): ?SQL {
|
||||
return $this->sql;
|
||||
}
|
||||
|
||||
public function getLanguage(): Language {
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
public function setLanguage(Language $language) {
|
||||
$this->language = $language;
|
||||
$language->load();
|
||||
}
|
||||
|
||||
public function getSession(): ?Session {
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
public function getConfiguration(): Configuration {
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
public function getGroups(): array {
|
||||
return $this->groups;
|
||||
}
|
||||
|
||||
public function hasGroup(int $group): bool {
|
||||
return isset($this->groups[$group]);
|
||||
}
|
||||
|
||||
public function getGPG(): ?GpgKey {
|
||||
return $this->gpgKey;
|
||||
}
|
||||
|
||||
public function getTwoFactorToken(): ?TwoFactorToken {
|
||||
return $this->twoFactorToken;
|
||||
}
|
||||
|
||||
public function getProfilePicture(): ?string {
|
||||
return $this->profilePicture;
|
||||
}
|
||||
|
||||
public function __debugInfo(): array {
|
||||
$debugInfo = array(
|
||||
'loggedIn' => $this->loggedIn,
|
||||
'language' => $this->language->getName(),
|
||||
);
|
||||
|
||||
if ($this->loggedIn) {
|
||||
$debugInfo['uid'] = $this->uid;
|
||||
$debugInfo['username'] = $this->username;
|
||||
}
|
||||
|
||||
return $debugInfo;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
if ($this->isLoggedIn()) {
|
||||
return array(
|
||||
'uid' => $this->uid,
|
||||
'name' => $this->username,
|
||||
'fullName' => $this->fullName,
|
||||
'profilePicture' => $this->profilePicture,
|
||||
'email' => $this->email,
|
||||
'groups' => $this->groups,
|
||||
'language' => $this->language->jsonSerialize(),
|
||||
'session' => $this->session->jsonSerialize(),
|
||||
"gpg" => ($this->gpgKey ? $this->gpgKey->jsonSerialize() : null),
|
||||
"2fa" => ($this->twoFactorToken ? $this->twoFactorToken->jsonSerialize() : null),
|
||||
);
|
||||
} else {
|
||||
return array(
|
||||
'language' => $this->language->jsonSerialize(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function reset() {
|
||||
$this->uid = 0;
|
||||
$this->username = '';
|
||||
$this->email = '';
|
||||
$this->groups = [];
|
||||
$this->loggedIn = false;
|
||||
$this->session = null;
|
||||
$this->profilePicture = null;
|
||||
$this->gpgKey = null;
|
||||
$this->twoFactorToken = null;
|
||||
}
|
||||
|
||||
public function logout(): bool {
|
||||
$success = true;
|
||||
if ($this->loggedIn) {
|
||||
$success = $this->session->destroy();
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function updateLanguage($lang): bool {
|
||||
if ($this->sql) {
|
||||
$request = new \Api\Language\Set($this);
|
||||
return $request->execute(array("langCode" => $lang));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function sendCookies() {
|
||||
|
||||
$baseUrl = $this->getConfiguration()->getSettings()->getBaseUrl();
|
||||
$domain = parse_url($baseUrl, PHP_URL_HOST);
|
||||
|
||||
if ($this->loggedIn) {
|
||||
$this->session->sendCookie($domain);
|
||||
}
|
||||
|
||||
$this->language->sendCookie($domain);
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId user's id
|
||||
* @param $sessionId session's id
|
||||
* @param bool $sessionUpdate update session information, including session's lifetime and browser information
|
||||
* @return bool true, if the data could be loaded
|
||||
*/
|
||||
public function loadSession($userId, $sessionId, bool $sessionUpdate = true): bool {
|
||||
|
||||
$userRow = $this->loadUser("Session", ["Session.data", "Session.stay_logged_in", "Session.csrf_token"], [
|
||||
new Compare("User.uid", $userId),
|
||||
new Compare("Session.uid", $sessionId),
|
||||
new Compare("Session.active", true),
|
||||
]);
|
||||
|
||||
if ($userRow !== false) {
|
||||
$this->session = new Session($this, $sessionId, $userRow["csrf_token"]);
|
||||
$this->session->setData(json_decode($userRow["data"] ?? '{}', true));
|
||||
$this->session->stayLoggedIn($this->sql->parseBool($userRow["stay_logged_in"]));
|
||||
if ($sessionUpdate) {
|
||||
$this->session->update();
|
||||
}
|
||||
$this->loggedIn = true;
|
||||
}
|
||||
|
||||
return $userRow !== false;
|
||||
}
|
||||
|
||||
private function parseCookies() {
|
||||
if (isset($_COOKIE['session']) && is_string($_COOKIE['session']) && !empty($_COOKIE['session'])) {
|
||||
try {
|
||||
$token = $_COOKIE['session'];
|
||||
$settings = $this->configuration->getSettings();
|
||||
$decoded = (array)JWT::decode($token, $settings->getJwtKey());
|
||||
if (!is_null($decoded)) {
|
||||
$userId = ($decoded['userId'] ?? NULL);
|
||||
$sessionId = ($decoded['sessionId'] ?? NULL);
|
||||
if (!is_null($userId) && !is_null($sessionId)) {
|
||||
$this->loadSession($userId, $sessionId);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['lang']) && is_string($_GET["lang"]) && !empty($_GET["lang"])) {
|
||||
$this->updateLanguage($_GET['lang']);
|
||||
} else if (isset($_COOKIE['lang']) && is_string($_COOKIE["lang"]) && !empty($_COOKIE["lang"])) {
|
||||
$this->updateLanguage($_COOKIE['lang']);
|
||||
}
|
||||
}
|
||||
|
||||
public function createSession(int $userId, bool $stayLoggedIn = false): bool {
|
||||
$this->uid = $userId;
|
||||
$this->session = Session::create($this, $stayLoggedIn);
|
||||
if ($this->session) {
|
||||
$this->loggedIn = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function loadUser(string $table, array $columns, array $conditions) {
|
||||
$userRow = $this->sql->select(
|
||||
// User meta
|
||||
"User.uid as userId", "User.name", "User.email", "User.fullName", "User.profilePicture", "User.confirmed",
|
||||
|
||||
// GPG
|
||||
"User.gpg_id", "GpgKey.confirmed as gpg_confirmed", "GpgKey.fingerprint as gpg_fingerprint",
|
||||
"GpgKey.expires as gpg_expires", "GpgKey.algorithm as gpg_algorithm",
|
||||
|
||||
// 2FA
|
||||
"User.2fa_id", "2FA.confirmed as 2fa_confirmed", "2FA.data as 2fa_data", "2FA.type as 2fa_type",
|
||||
|
||||
// Language
|
||||
"Language.uid as langId", "Language.code as langCode", "Language.name as langName",
|
||||
|
||||
// additional data
|
||||
...$columns)
|
||||
->from("User")
|
||||
->innerJoin("$table", "$table.user_id", "User.uid")
|
||||
->leftJoin("Language", "User.language_id", "Language.uid")
|
||||
->leftJoin("GpgKey", "User.gpg_id", "GpgKey.uid")
|
||||
->leftJoin("2FA", "User.2fa_id", "2FA.uid")
|
||||
->where(new CondAnd(...$conditions))
|
||||
->first()
|
||||
->execute();
|
||||
|
||||
if ($userRow === null || $userRow === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Meta data
|
||||
$userId = $userRow["userId"];
|
||||
$this->uid = $userId;
|
||||
$this->username = $userRow['name'];
|
||||
$this->fullName = $userRow["fullName"];
|
||||
$this->email = $userRow['email'];
|
||||
$this->profilePicture = $userRow["profilePicture"];
|
||||
|
||||
// GPG
|
||||
if (!empty($userRow["gpg_id"])) {
|
||||
$this->gpgKey = new GpgKey($userRow["gpg_id"], $this->sql->parseBool($userRow["gpg_confirmed"]),
|
||||
$userRow["gpg_fingerprint"], $userRow["gpg_algorithm"], $userRow["gpg_expires"]
|
||||
);
|
||||
}
|
||||
|
||||
// 2FA
|
||||
if (!empty($userRow["2fa_id"])) {
|
||||
$this->twoFactorToken = TwoFactorToken::newInstance($userRow["2fa_type"], $userRow["2fa_data"],
|
||||
$userRow["2fa_id"], $this->sql->parseBool($userRow["2fa_confirmed"]));
|
||||
}
|
||||
|
||||
// Language
|
||||
if (!is_null($userRow['langId'])) {
|
||||
$this->setLanguage(Language::newInstance($userRow['langId'], $userRow['langCode'], $userRow['langName']));
|
||||
}
|
||||
|
||||
// select groups
|
||||
$groupRows = $this->sql->select("Group.uid as groupId", "Group.name as groupName")
|
||||
->from("UserGroup")
|
||||
->where(new Compare("UserGroup.user_id", $userId))
|
||||
->innerJoin("Group", "UserGroup.group_id", "Group.uid")
|
||||
->execute();
|
||||
if (is_array($groupRows)) {
|
||||
foreach ($groupRows as $row) {
|
||||
$this->groups[$row["groupId"]] = $row["groupName"];
|
||||
}
|
||||
}
|
||||
|
||||
return $userRow;
|
||||
}
|
||||
|
||||
public function loadApiKey($apiKey): bool {
|
||||
|
||||
if ($this->loggedIn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$userRow = $this->loadUser("ApiKey", [], [
|
||||
new Compare("ApiKey.api_key", $apiKey),
|
||||
new Compare("valid_until", $this->sql->currentTimestamp(), ">"),
|
||||
new Compare("ApiKey.active", 1),
|
||||
]);
|
||||
|
||||
// User must be confirmed to use API-Keys
|
||||
if ($userRow === false || !$this->sql->parseBool($userRow["confirmed"])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function processVisit() {
|
||||
if ($this->sql && $this->sql->isConnected() && isset($_COOKIE["PHPSESSID"]) && !empty($_COOKIE["PHPSESSID"])) {
|
||||
if ($this->isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cookie = $_COOKIE["PHPSESSID"];
|
||||
$req = new \Api\Visitors\ProcessVisit($this);
|
||||
$req->execute(array("cookie" => $cookie));
|
||||
}
|
||||
}
|
||||
|
||||
private function isBot(): bool {
|
||||
if (empty($_SERVER["HTTP_USER_AGENT"])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return preg_match('/robot|spider|crawler|curl|^$/i', $_SERVER['HTTP_USER_AGENT']) === 1;
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ if (is_file($autoLoad)) {
|
||||
require_once $autoLoad;
|
||||
}
|
||||
|
||||
define("WEBBASE_VERSION", "1.5.2");
|
||||
define("WEBBASE_VERSION", "2.0.0-alpha");
|
||||
|
||||
spl_autoload_extensions(".php");
|
||||
spl_autoload_register(function ($class) {
|
||||
@ -28,10 +28,6 @@ spl_autoload_register(function ($class) {
|
||||
}
|
||||
});
|
||||
|
||||
function is_cli(): bool {
|
||||
return php_sapi_name() === "cli";
|
||||
}
|
||||
|
||||
function getProtocol(): string {
|
||||
$isSecure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ||
|
||||
(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ||
|
||||
@ -246,7 +242,7 @@ function getClassName($class, bool $short = true): string {
|
||||
}
|
||||
|
||||
function createError($msg) {
|
||||
return json_encode(array("success" => false, "msg" => $msg));
|
||||
return ["success" => false, "msg" => $msg];
|
||||
}
|
||||
|
||||
function downloadFile($handle, $offset = 0, $length = null): bool {
|
||||
@ -278,3 +274,8 @@ function parseClass($class): string {
|
||||
$parts = array_map('ucfirst', $parts);
|
||||
return implode("\\", $parts);
|
||||
}
|
||||
|
||||
function isClass(string $str): bool {
|
||||
$path = getClassPath($str);
|
||||
return is_file($path) && class_exists($str);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
FROM composer:latest AS composer
|
||||
FROM php:7.4-fpm
|
||||
FROM php:8.0-fpm
|
||||
WORKDIR "/application"
|
||||
RUN mkdir -p /application/core/Configuration
|
||||
RUN chown -R www-data:www-data /application
|
||||
|
22
index.php
22
index.php
@ -20,12 +20,12 @@ if (!is_readable(getClassPath(Configuration::class))) {
|
||||
die(json_encode([ "success" => false, "msg" => "Configuration class is not readable, check permissions before proceeding." ]));
|
||||
}
|
||||
|
||||
$config = new Configuration();
|
||||
$user = new Objects\User($config);
|
||||
$sql = $user->getSQL();
|
||||
$settings = $config->getSettings();
|
||||
$installation = !$sql || ($sql->isConnected() && !$settings->isInstalled());
|
||||
$context = new \Objects\Context();
|
||||
$sql = $context->initSQL();
|
||||
$settings = $context->getSettings();
|
||||
$context->parseCookies();
|
||||
|
||||
$installation = !$sql || ($sql->isConnected() && !$settings->isInstalled());
|
||||
$requestedUri = $_GET["site"] ?? $_GET["api"] ?? $_SERVER["REQUEST_URI"];
|
||||
|
||||
if ($installation) {
|
||||
@ -34,7 +34,7 @@ if ($installation) {
|
||||
$response = "Redirecting to <a href=\"/\">/</a>";
|
||||
header("Location: /");
|
||||
} else {
|
||||
$document = new Documents\Install(new Router($user));
|
||||
$document = new Documents\Install(new Router($context));
|
||||
$response = $document->getCode();
|
||||
}
|
||||
} else {
|
||||
@ -45,17 +45,17 @@ if ($installation) {
|
||||
if (is_file($routerCachePath)) {
|
||||
@include_once $routerCachePath;
|
||||
if (class_exists($routerCacheClass)) {
|
||||
$router = new $routerCacheClass($user);
|
||||
$router = new $routerCacheClass($context);
|
||||
}
|
||||
}
|
||||
|
||||
if ($router === null) {
|
||||
$req = new \Api\Routes\GenerateCache($user);
|
||||
$req = new \Api\Routes\GenerateCache($context);
|
||||
if ($req->execute()) {
|
||||
$router = $req->getRouter();
|
||||
} else {
|
||||
$message = "Unable to generate router cache: " . $req->getLastError();
|
||||
$response = (new Router($user))->returnStatusCode(500, [ "message" => $message ]);
|
||||
$response = (new Router($context))->returnStatusCode(500, [ "message" => $message ]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,8 +68,8 @@ if ($installation) {
|
||||
}
|
||||
}
|
||||
|
||||
$user->processVisit();
|
||||
$context->processVisit();
|
||||
}
|
||||
|
||||
$user->sendCookies();
|
||||
$context->sendCookies();
|
||||
die($response);
|
2
js/admin.min.js
vendored
2
js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,21 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Configuration\Configuration;
|
||||
use Driver\SQL\Query\CreateTable;
|
||||
use Driver\SQL\SQL;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\DatabaseEntityHandler;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
class DatabaseEntityTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
static \Objects\User $USER;
|
||||
static \Driver\SQL\SQL $SQL;
|
||||
static \Objects\DatabaseEntity\DatabaseEntityHandler $HANDLER;
|
||||
static User $USER;
|
||||
static SQL $SQL;
|
||||
static Context $CONTEXT;
|
||||
static DatabaseEntityHandler $HANDLER;
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
parent::setUpBeforeClass();
|
||||
self::$USER = new Objects\User(new \Configuration\Configuration());
|
||||
self::$SQL = self::$USER->getSQL();
|
||||
self::$CONTEXT = new Context();
|
||||
if (!self::$CONTEXT->initSQL()) {
|
||||
throw new Exception("Could not establish database connection");
|
||||
}
|
||||
|
||||
self::$SQL = self::$CONTEXT->getSQL();
|
||||
self::$HANDLER = TestEntity::getHandler(self::$SQL);
|
||||
self::$HANDLER->getLogger()->unitTestMode();
|
||||
}
|
||||
|
||||
public function testCreateTable() {
|
||||
$this->assertInstanceOf(\Driver\SQL\Query\CreateTable::class, self::$HANDLER->getTableQuery());
|
||||
$this->assertInstanceOf(CreateTable::class, self::$HANDLER->getTableQuery());
|
||||
$this->assertTrue(self::$HANDLER->createTable());
|
||||
}
|
||||
|
||||
@ -60,7 +72,8 @@ class DatabaseEntityTest extends \PHPUnit\Framework\TestCase {
|
||||
$allEntities = TestEntity::findAll(self::$SQL);
|
||||
$this->assertIsArray($allEntities);
|
||||
$this->assertCount(1, $allEntities);
|
||||
$this->assertEquals($entityId, $allEntities[0]->getId());
|
||||
$this->assertTrue(array_key_exists($entityId, $allEntities));
|
||||
$this->assertEquals($entityId, $allEntities[$entityId]->getId());
|
||||
|
||||
// delete
|
||||
$this->assertTrue($entity->delete(self::$SQL));
|
||||
@ -94,4 +107,16 @@ class TestEntity extends \Objects\DatabaseEntity\DatabaseEntity {
|
||||
public float $d;
|
||||
public \DateTime $e;
|
||||
public ?int $f;
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
"id" => $this->getId(),
|
||||
"a" => $this->a,
|
||||
"b" => $this->b,
|
||||
"c" => $this->c,
|
||||
"d" => $this->d,
|
||||
"e" => $this->e,
|
||||
"f" => $this->f,
|
||||
];
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@
|
||||
|
||||
use Api\Request;
|
||||
use Configuration\Configuration;
|
||||
use Objects\User;
|
||||
use Objects\Context;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
function __new_header_impl(string $line) {
|
||||
if (preg_match("/^HTTP\/([0-9.]+) (\d+) (.*)$/", $line, $m)) {
|
||||
@ -34,6 +35,7 @@ class RequestTest extends \PHPUnit\Framework\TestCase {
|
||||
const FUNCTION_OVERRIDES = ["header", "http_response_code"];
|
||||
static User $USER;
|
||||
static User $USER_LOGGED_IN;
|
||||
static Context $CONTEXT;
|
||||
|
||||
static ?string $SENT_CONTENT;
|
||||
static array $SENT_HEADERS;
|
||||
@ -41,13 +43,9 @@ class RequestTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
|
||||
$config = new Configuration();
|
||||
RequestTest::$USER = new User($config);
|
||||
RequestTest::$USER_LOGGED_IN = new User($config);
|
||||
if (!RequestTest::$USER->getSQL() || !RequestTest::$USER->getSQL()->isConnected()) {
|
||||
RequestTest::$CONTEXT = new Context();
|
||||
if (!RequestTest::$CONTEXT->initSQL()) {
|
||||
throw new Exception("Could not establish database connection");
|
||||
} else {
|
||||
RequestTest::$USER->setLanguage(\Objects\Language::DEFAULT_LANGUAGE());
|
||||
}
|
||||
|
||||
if (!function_exists("runkit7_function_rename") || !function_exists("runkit7_function_remove")) {
|
||||
@ -65,7 +63,7 @@ class RequestTest extends \PHPUnit\Framework\TestCase {
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void {
|
||||
RequestTest::$USER->getSQL()->close();
|
||||
RequestTest::$CONTEXT->getSQL()?->close();
|
||||
foreach (self::FUNCTION_OVERRIDES as $functionName) {
|
||||
runkit7_function_remove($functionName);
|
||||
runkit7_function_rename("__orig_${functionName}_impl", $functionName);
|
||||
@ -74,7 +72,7 @@ class RequestTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
private function simulateRequest(Request $request, string $method, array $get = [], array $post = [], array $headers = []): bool {
|
||||
|
||||
if (!is_cli()) {
|
||||
if (!self::$CONTEXT->isCLI()) {
|
||||
self::throwException(new \Exception("Cannot simulate request outside cli"));
|
||||
}
|
||||
|
||||
@ -97,7 +95,7 @@ class RequestTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
public function testAllMethods() {
|
||||
// all methods allowed
|
||||
$allMethodsAllowed = new RequestAllMethods(RequestTest::$USER, true);
|
||||
$allMethodsAllowed = new RequestAllMethods(RequestTest::$CONTEXT, true);
|
||||
$this->assertTrue($this->simulateRequest($allMethodsAllowed, "GET"), $allMethodsAllowed->getLastError());
|
||||
$this->assertTrue($this->simulateRequest($allMethodsAllowed, "POST"), $allMethodsAllowed->getLastError());
|
||||
$this->assertFalse($this->simulateRequest($allMethodsAllowed, "PUT"), $allMethodsAllowed->getLastError());
|
||||
@ -109,7 +107,7 @@ class RequestTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
public function testOnlyPost() {
|
||||
// only post allowed
|
||||
$onlyPostAllowed = new RequestOnlyPost(RequestTest::$USER, true);
|
||||
$onlyPostAllowed = new RequestOnlyPost(RequestTest::$CONTEXT, true);
|
||||
$this->assertFalse($this->simulateRequest($onlyPostAllowed, "GET"));
|
||||
$this->assertEquals("This method is not allowed", $onlyPostAllowed->getLastError(), $onlyPostAllowed->getLastError());
|
||||
$this->assertEquals(405, self::$SENT_STATUS_CODE);
|
||||
@ -121,25 +119,25 @@ class RequestTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
public function testPrivate() {
|
||||
// private method
|
||||
$privateExternal = new RequestPrivate(RequestTest::$USER, true);
|
||||
$privateExternal = new RequestPrivate(RequestTest::$CONTEXT, true);
|
||||
$this->assertFalse($this->simulateRequest($privateExternal, "GET"));
|
||||
$this->assertEquals("This function is private.", $privateExternal->getLastError());
|
||||
$this->assertEquals(403, self::$SENT_STATUS_CODE);
|
||||
|
||||
$privateInternal = new RequestPrivate(RequestTest::$USER, false);
|
||||
$privateInternal = new RequestPrivate(RequestTest::$CONTEXT, false);
|
||||
$this->assertTrue($privateInternal->execute());
|
||||
}
|
||||
|
||||
public function testDisabled() {
|
||||
// disabled method
|
||||
$disabledMethod = new RequestDisabled(RequestTest::$USER, true);
|
||||
$disabledMethod = new RequestDisabled(RequestTest::$CONTEXT, true);
|
||||
$this->assertFalse($this->simulateRequest($disabledMethod, "GET"));
|
||||
$this->assertEquals("This function is currently disabled.", $disabledMethod->getLastError(), $disabledMethod->getLastError());
|
||||
$this->assertEquals(503, self::$SENT_STATUS_CODE);
|
||||
}
|
||||
|
||||
public function testLoginRequired() {
|
||||
$loginRequired = new RequestLoginRequired(RequestTest::$USER, true);
|
||||
$loginRequired = new RequestLoginRequired(RequestTest::$CONTEXT, true);
|
||||
$this->assertFalse($this->simulateRequest($loginRequired, "GET"));
|
||||
$this->assertEquals("You are not logged in.", $loginRequired->getLastError(), $loginRequired->getLastError());
|
||||
$this->assertEquals(401, self::$SENT_STATUS_CODE);
|
||||
@ -147,8 +145,8 @@ class RequestTest extends \PHPUnit\Framework\TestCase {
|
||||
}
|
||||
|
||||
abstract class TestRequest extends Request {
|
||||
public function __construct(User $user, bool $externalCall = false, $params = []) {
|
||||
parent::__construct($user, $externalCall, $params);
|
||||
public function __construct(Context $context, bool $externalCall = false, $params = []) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
}
|
||||
|
||||
protected function _die(string $data = ""): bool {
|
||||
@ -162,35 +160,35 @@ abstract class TestRequest extends Request {
|
||||
}
|
||||
|
||||
class RequestAllMethods extends TestRequest {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, []);
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, []);
|
||||
}
|
||||
}
|
||||
|
||||
class RequestOnlyPost extends TestRequest {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, []);
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, []);
|
||||
$this->forbidMethod("GET");
|
||||
}
|
||||
}
|
||||
|
||||
class RequestPrivate extends TestRequest {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, []);
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, []);
|
||||
$this->isPublic = false;
|
||||
}
|
||||
}
|
||||
|
||||
class RequestDisabled extends TestRequest {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, []);
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, []);
|
||||
$this->isDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
class RequestLoginRequired extends TestRequest {
|
||||
public function __construct(User $user, bool $externalCall = false) {
|
||||
parent::__construct($user, $externalCall, []);
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, []);
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
}
|
@ -1,20 +1,17 @@
|
||||
<?php
|
||||
|
||||
use Configuration\Configuration;
|
||||
use Objects\Context;
|
||||
use Objects\Router\EmptyRoute;
|
||||
use Objects\Router\Router;
|
||||
use Objects\User;
|
||||
|
||||
class RouterTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
private static User $USER;
|
||||
private static Router $ROUTER;
|
||||
private static Context $CONTEXT;
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
|
||||
$config = new Configuration();
|
||||
RouterTest::$USER = new User($config);
|
||||
RouterTest::$ROUTER = new Router(RouterTest::$USER);
|
||||
RouterTest::$CONTEXT = new Context();
|
||||
RouterTest::$ROUTER = new Router(RouterTest::$CONTEXT);
|
||||
}
|
||||
|
||||
public function testSimpleRoutes() {
|
||||
|
@ -3,7 +3,7 @@
|
||||
use Base32\Base32;
|
||||
use Configuration\Configuration;
|
||||
use Objects\TwoFactor\TimeBasedTwoFactorToken;
|
||||
use Objects\User;
|
||||
use Objects\DatabaseEntity\User;
|
||||
|
||||
class TimeBasedTwoFactorTokenTest extends PHPUnit\Framework\TestCase {
|
||||
|
||||
@ -31,13 +31,14 @@ class TimeBasedTwoFactorTokenTest extends PHPUnit\Framework\TestCase {
|
||||
|
||||
public function testURL() {
|
||||
$secret = Base32::encode("12345678901234567890");
|
||||
$configuration = new Configuration();
|
||||
$user = new User($configuration);
|
||||
$context = new \Objects\Context();
|
||||
|
||||
// $context->
|
||||
|
||||
$token = new TimeBasedTwoFactorToken($secret);
|
||||
$siteName = $configuration->getSettings()->getSiteName();
|
||||
$username = $user->getUsername();
|
||||
$url = $token->getUrl($user);
|
||||
$siteName = $context->getSettings()->getSiteName();
|
||||
$username = $context->getUser()->getUsername();
|
||||
$url = $token->getUrl($context);
|
||||
$this->assertEquals("otpauth://totp/$username?secret=$secret&issuer=$siteName", $url);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user