new API structure

This commit is contained in:
Roman Hergenreder 2020-06-20 20:13:51 +02:00
parent a3ef5ac084
commit 9fc9a2a43f
29 changed files with 1332 additions and 1322 deletions

@ -1,44 +0,0 @@
<?php
namespace Api\ApiKey;
use \Api\Request;
class Create extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->apiKeyAllowed = false;
$this->csrfTokenRequired = true;
$this->loginRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$apiKey = generateRandomString(64);
$sql = $this->user->getSQL();
$validUntil = (new \DateTime())->modify("+30 DAY");
$this->success = $sql->insert("ApiKey", array("user_id", "api_key", "valid_until"))
->addRow($this->user->getId(), $apiKey, $validUntil)
->returning("uid")
->execute();
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["api_key"] = array(
"api_key" => $apiKey,
"valid_until" => $validUntil->getTimestamp(),
"uid" => $sql->getLastInsertId(),
);
} else {
$this->result["api_key"] = null;
}
return $this->success;
}
}

@ -1,53 +0,0 @@
<?php
namespace Api\ApiKey;
use \Api\Request;
use DateTime;
use \Driver\SQL\Condition\Compare;
use Exception;
class Fetch extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("uid", "api_key", "valid_until")
->from("ApiKey")
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", $sql->currentTimestamp(), ">"))
->where(new Compare("active", true))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result["api_keys"] = array();
foreach($res as $row) {
try {
$validUntil = (new DateTime($row["valid_until"]))->getTimestamp();
} catch (Exception $e) {
$validUntil = $row["valid_until"];
}
$this->result["api_keys"][] = array(
"uid" => intval($row["uid"]),
"api_key" => $row["api_key"],
"valid_until" => $validUntil,
);
}
}
return $this->success;
}
}

@ -1,66 +0,0 @@
<?php
namespace Api\ApiKey;
use \Api\Request;
use \Api\Parameter\Parameter;
use \Driver\SQL\Condition\Compare;
class Refresh extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
"id" => new Parameter("id", Parameter::TYPE_INT),
));
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
private function apiKeyExists() {
$id = $this->getParam("id");
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("ApiKey")
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", $sql->currentTimestamp(), ">"))
->where(new Compare("active", 1))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success && $res[0]["count"] === 0) {
$this->success = false;
$this->lastError = "This API-Key does not exist.";
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$id = $this->getParam("id");
if(!$this->apiKeyExists())
return false;
$validUntil = (new \DateTime)->modify("+30 DAY");
$sql = $this->user->getSQL();
$this->success = $sql->update("ApiKey")
->set("valid_until", $validUntil)
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->execute();
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["valid_until"] = $validUntil->getTimestamp();
}
return $this->success;
}
}

@ -1,61 +0,0 @@
<?php
namespace Api\ApiKey;
use \Api\Request;
use \Api\Parameter\Parameter;
use \Driver\SQL\Condition\Compare;
class Revoke extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
"id" => new Parameter("id", Parameter::TYPE_INT),
));
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
private function apiKeyExists() {
$id = $this->getParam("id");
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("ApiKey")
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", $sql->currentTimestamp(), ">"))
->where(new Compare("active", 1))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success && $res[0]["count"] === 0) {
$this->success = false;
$this->lastError = "This API-Key does not exist.";
}
return $this->success;
}
public function execute($aValues = array()) {
if(!parent::execute($aValues)) {
return false;
}
$id = $this->getParam("id");
if(!$this->apiKeyExists())
return false;
$sql = $this->user->getSQL();
$this->success = $sql->update("ApiKey")
->set("active", false)
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->execute();
$this->lastError = $sql->getLastError();
return $this->success;
}
}

@ -0,0 +1,191 @@
<?php
namespace Api {
use Driver\SQL\Condition\Compare;
class ApiKeyAPI extends Request {
protected function apiKeyExists($id) {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("ApiKey")
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", $sql->currentTimestamp(), ">"))
->where(new Compare("active", 1))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success && $res[0]["count"] === 0) {
return $this->createError("This API-Key does not exist.");
}
return $this->success;
}
}
}
namespace Api\ApiKey {
use Api\ApiKeyAPI;
use Api\Parameter\Parameter;
use DateTime;
use Driver\SQL\Condition\Compare;
use Exception;
class Create extends ApiKeyAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->apiKeyAllowed = false;
$this->csrfTokenRequired = true;
$this->loginRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$apiKey = generateRandomString(64);
$sql = $this->user->getSQL();
$validUntil = (new \DateTime())->modify("+30 DAY");
$this->success = $sql->insert("ApiKey", array("user_id", "api_key", "valid_until"))
->addRow($this->user->getId(), $apiKey, $validUntil)
->returning("uid")
->execute();
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["api_key"] = array(
"api_key" => $apiKey,
"valid_until" => $validUntil->getTimestamp(),
"uid" => $sql->getLastInsertId(),
);
} else {
$this->result["api_key"] = null;
}
return $this->success;
}
}
class Fetch extends ApiKeyAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("uid", "api_key", "valid_until")
->from("ApiKey")
->where(new Compare("user_id", $this->user->getId()))
->where(new Compare("valid_until", $sql->currentTimestamp(), ">"))
->where(new Compare("active", true))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result["api_keys"] = array();
foreach($res as $row) {
try {
$validUntil = (new DateTime($row["valid_until"]))->getTimestamp();
} catch (Exception $e) {
$validUntil = $row["valid_until"];
}
$this->result["api_keys"][] = array(
"uid" => intval($row["uid"]),
"api_key" => $row["api_key"],
"valid_until" => $validUntil,
);
}
}
return $this->success;
}
}
class Refresh extends ApiKeyAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
"id" => new Parameter("id", Parameter::TYPE_INT),
));
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$id = $this->getParam("id");
if(!$this->apiKeyExists($id))
return false;
$validUntil = (new \DateTime)->modify("+30 DAY");
$sql = $this->user->getSQL();
$this->success = $sql->update("ApiKey")
->set("valid_until", $validUntil)
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->execute();
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["valid_until"] = $validUntil->getTimestamp();
}
return $this->success;
}
}
class Revoke extends ApiKeyAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
"id" => new Parameter("id", Parameter::TYPE_INT),
));
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
public function execute($aValues = array()) {
if(!parent::execute($aValues)) {
return false;
}
$id = $this->getParam("id");
if(!$this->apiKeyExists($id))
return false;
$sql = $this->user->getSQL();
$this->success = $sql->update("ApiKey")
->set("active", false)
->where(new Compare("uid", $id))
->where(new Compare("user_id", $this->user->getId()))
->execute();
$this->lastError = $sql->getLastError();
return $this->success;
}
}
}

@ -1,37 +0,0 @@
<?php
namespace Api;
class GetLanguages extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("uid", "code", "name")
->from("Language")
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result['languages'] = array();
if(empty($res) === 0) {
$this->lastError = L("No languages found");
} else {
foreach($res as $row) {
$this->result['languages'][$row['uid']] = $row;
}
}
}
return $this->success;
}
}

@ -1,84 +0,0 @@
<?php
namespace Api\Groups;
use \Api\Parameter\Parameter;
use \Api\Request;
class Fetch extends Request {
const SELECT_SIZE = 10;
private int $groupCount;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1)
));
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
$this->csrfTokenRequired = true;
$this->groupCount = 0;
}
private function getGroupCount() {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())->from("Group")->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->groupCount = $res[0]["count"];
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$page = $this->getParam("page");
if($page < 1) {
return $this->createError("Invalid page count");
}
if (!$this->getGroupCount()) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("Group.uid as groupId", "Group.name as groupName", $sql->count("UserGroup.user_id"))
->from("Group")
->innerJoin("UserGroup", "UserGroup.group_id", "Group.uid")
->groupBy("Group.uid")
->orderBy("Group.uid")
->ascending()
->limit(Fetch::SELECT_SIZE)
->offset(($page - 1) * Fetch::SELECT_SIZE)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result["groups"] = array();
foreach($res as $row) {
$groupId = intval($row["groupId"]);
$groupName = $row["groupName"];
$memberCount = $row["usergroup_user_id_count"];
$this->result["groups"][$groupId] = array(
"name" => $groupName,
"memberCount" => $memberCount
);
}
$this->result["pageCount"] = intval(ceil($this->groupCount / Fetch::SELECT_SIZE));
$this->result["totalCount"] = $this->groupCount;
}
return $this->success;
}
}

@ -0,0 +1,94 @@
<?php
namespace Api {
class GroupsAPI extends Request {
}
}
namespace Api\Groups {
use Api\GroupsAPI;
use Api\Parameter\Parameter;
class Fetch extends GroupsAPI {
const SELECT_SIZE = 10;
private int $groupCount;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1)
));
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
$this->csrfTokenRequired = true;
$this->groupCount = 0;
}
private function getGroupCount() {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())->from("Group")->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->groupCount = $res[0]["count"];
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$page = $this->getParam("page");
if($page < 1) {
return $this->createError("Invalid page count");
}
if (!$this->getGroupCount()) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("Group.uid as groupId", "Group.name as groupName", $sql->count("UserGroup.user_id"))
->from("Group")
->innerJoin("UserGroup", "UserGroup.group_id", "Group.uid")
->groupBy("Group.uid")
->orderBy("Group.uid")
->ascending()
->limit(Fetch::SELECT_SIZE)
->offset(($page - 1) * Fetch::SELECT_SIZE)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result["groups"] = array();
foreach($res as $row) {
$groupId = intval($row["groupId"]);
$groupName = $row["groupName"];
$memberCount = $row["usergroup_user_id_count"];
$this->result["groups"][$groupId] = array(
"name" => $groupName,
"memberCount" => $memberCount
);
}
$this->result["pageCount"] = intval(ceil($this->groupCount / Fetch::SELECT_SIZE));
$this->result["totalCount"] = $this->groupCount;
}
return $this->success;
}
}
}

@ -0,0 +1,128 @@
<?php
namespace Api {
class LanguageAPI extends Request {
}
}
namespace Api\Language {
use Api\LanguageAPI;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Driver\SQL\Condition\Compare;
use Driver\SQL\Condition\CondOr;
use Objects\Language;
class Get extends LanguageAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("uid", "code", "name")
->from("Language")
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result['languages'] = array();
if(empty($res) === 0) {
$this->lastError = L("No languages found");
} else {
foreach($res as $row) {
$this->result['languages'][$row['uid']] = $row;
}
}
}
return $this->success;
}
}
class Set extends LanguageAPI {
private Language $language;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'langId' => new Parameter('langId', Parameter::TYPE_INT, true, NULL),
'langCode' => new StringType('langCode', 5, true, NULL),
));
$this->csrfTokenRequired = true;
}
private function checkLanguage() {
$langId = $this->getParam("langId");
$langCode = $this->getParam("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();
$this->success = ($res !== FALSE);
$this->lastError = $this->user->getSQL()->getLastError();
if ($this->success) {
if(count($res) == 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"));
}
}
}
return $this->success;
}
private function updateLanguage() {
$languageId = $this->language->getId();
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$this->success = $sql->update("User")
->set("language_id", $languageId)
->where(new Compare("uid", $userId))
->execute();
$this->lastError = $sql->getLastError();
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if(!$this->checkLanguage())
return false;
if($this->user->isLoggedIn()) {
$this->updateLanguage();
}
$this->user->setLanguage($this->language);
return $this->success;
}
}
}

@ -1,134 +0,0 @@
<?php
namespace Api\Notifications;
use \Api\Request;
use \Api\Parameter\Parameter;
use \Api\Parameter\StringType;
use \Driver\SQL\Condition\Compare;
class Create extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'groupId' => new Parameter('groupId', Parameter::TYPE_INT, true),
'userId' => new Parameter('userId', Parameter::TYPE_INT, true),
'title' => new StringType('title', 32),
'message' => new StringType('message', 256),
));
$this->isPublic = false;
$this->csrfTokenRequired = true;
}
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();
$res = $sql->insert("UserNotification", array("user_id", "notification_id"))
->addRow($userId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
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();
$res = $sql->insert("GroupNotification", array("group_id", "notification_id"))
->addRow($groupId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
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();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
return $sql->getLastInsertId();
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$userId = $this->getParam("userId");
$groupId = $this->getParam("groupId");
$title = $this->getParam("title");
$message = $this->getParam("message");
if (is_null($userId) && is_null($groupId)) {
return $this->createError("Either userId or groupId must be specified.");
} 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)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertUserNotification($userId, $id);
}
}
} else if(!is_null($groupId)) {
if ($this->checkGroup($groupId)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertGroupNotification($groupId, $id);
}
}
}
return $this->success;
}
}

@ -1,93 +0,0 @@
<?php
namespace Api\Notifications;
use \Api\Request;
use \Driver\SQL\Condition\Compare;
class Fetch extends Request {
private array $notifications;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
private function fetchUserNotifications() {
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message")
->from("Notification")
->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.uid")
->where(new Compare("UserNotification.user_id", $userId))
->where(new Compare("UserNotification.seen", false))
->orderBy("created_at")->descending()
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
foreach($res as $row) {
$id = $row["uid"];
if (!isset($this->notifications[$id])) {
$this->notifications[$id] = array(
"uid" => $id,
"title" => $row["title"],
"message" => $row["message"],
"created_at" => $row["created_at"],
);
}
}
}
return $this->success;
}
private function fetchGroupNotifications() {
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message")
->from("Notification")
->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.uid")
->innerJoin("UserGroup", "GroupNotification.group_id", "UserGroup.group_id")
->where(new Compare("UserGroup.user_id", $userId))
->where(new Compare("GroupNotification.seen", false))
->orderBy("created_at")->descending()
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
foreach($res as $row) {
$id = $row["uid"];
if (!isset($this->notifications[$id])) {
$this->notifications[] = array(
"uid" => $id,
"title" => $row["title"],
"message" => $row["message"],
"created_at" => $row["created_at"],
);
}
}
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$this->notifications = array();
if ($this->fetchUserNotifications() && $this->fetchGroupNotifications()) {
$this->result["notifications"] = $this->notifications;
}
return $this->success;
}
}

@ -0,0 +1,231 @@
<?php
namespace Api {
class NotificationsAPI extends Request {
}
}
namespace Api\Notifications {
use Api\NotificationsAPI;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Driver\SQL\Condition\Compare;
class Create extends NotificationsAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'groupId' => new Parameter('groupId', Parameter::TYPE_INT, true),
'userId' => new Parameter('userId', Parameter::TYPE_INT, true),
'title' => new StringType('title', 32),
'message' => new StringType('message', 256),
));
$this->isPublic = false;
$this->csrfTokenRequired = true;
}
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();
$res = $sql->insert("UserNotification", array("user_id", "notification_id"))
->addRow($userId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
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();
$res = $sql->insert("GroupNotification", array("group_id", "notification_id"))
->addRow($groupId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
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();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
return $sql->getLastInsertId();
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$userId = $this->getParam("userId");
$groupId = $this->getParam("groupId");
$title = $this->getParam("title");
$message = $this->getParam("message");
if (is_null($userId) && is_null($groupId)) {
return $this->createError("Either userId or groupId must be specified.");
} 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)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertUserNotification($userId, $id);
}
}
} else if(!is_null($groupId)) {
if ($this->checkGroup($groupId)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertGroupNotification($groupId, $id);
}
}
}
return $this->success;
}
}
class Fetch extends NotificationsAPI {
private array $notifications;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
private function fetchUserNotifications() {
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message")
->from("Notification")
->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.uid")
->where(new Compare("UserNotification.user_id", $userId))
->where(new Compare("UserNotification.seen", false))
->orderBy("created_at")->descending()
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
foreach($res as $row) {
$id = $row["uid"];
if (!isset($this->notifications[$id])) {
$this->notifications[$id] = array(
"uid" => $id,
"title" => $row["title"],
"message" => $row["message"],
"created_at" => $row["created_at"],
);
}
}
}
return $this->success;
}
private function fetchGroupNotifications() {
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message")
->from("Notification")
->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.uid")
->innerJoin("UserGroup", "GroupNotification.group_id", "UserGroup.group_id")
->where(new Compare("UserGroup.user_id", $userId))
->where(new Compare("GroupNotification.seen", false))
->orderBy("created_at")->descending()
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
foreach($res as $row) {
$id = $row["uid"];
if (!isset($this->notifications[$id])) {
$this->notifications[] = array(
"uid" => $id,
"title" => $row["title"],
"message" => $row["message"],
"created_at" => $row["created_at"],
);
}
}
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$this->notifications = array();
if ($this->fetchUserNotifications() && $this->fetchGroupNotifications()) {
$this->result["notifications"] = $this->notifications;
}
return $this->success;
}
}
}

@ -1,51 +0,0 @@
<?php
namespace Api\Routes;
use \Api\Request;
use \Driver\SQL\Condition\Compare;
class Fetch extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql
->select("uid", "request", "action", "target", "extra", "active")
->from("Route")
->orderBy("uid")
->ascending()
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
if ($this->success) {
$routes = array();
foreach($res as $row) {
$routes[] = array(
"uid" => intval($row["uid"]),
"request" => $row["request"],
"action" => $row["action"],
"target" => $row["target"],
"extra" => $row["extra"] ?? "",
"active" => intval($row["active"]),
);
}
$this->result["routes"] = $routes;
}
return $this->success;
}
}

@ -1,61 +0,0 @@
<?php
namespace Api\Routes;
use Api\Parameter\StringType;
use \Api\Request;
use Driver\SQL\Column\Column;
use Driver\SQL\Condition\CondBool;
use Driver\SQL\Condition\Regex;
class Find extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'request' => new StringType('request', 128, true, '/')
));
$this->isPublic = false;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$request = $this->getParam('request');
if (!startsWith($request, '/')) {
$request = "/$request";
}
$sql = $this->user->getSQL();
$res = $sql
->select("uid", "request", "action", "target", "extra")
->from("Route")
->where(new CondBool("active"))
->where(new Regex($request, new Column("request")))
->limit(1)
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
if ($this->success) {
if (!empty($res)) {
$row = $res[0];
$this->result["route"] = array(
"uid" => intval($row["uid"]),
"request" => $row["request"],
"action" => $row["action"],
"target" => $row["target"],
"extra" => $row["extra"]
);
} else {
$this->result["route"] = NULL;
}
}
return $this->success;
}
}

@ -1,99 +0,0 @@
<?php
namespace Api\Routes;
use Api\Parameter\Parameter;
use \Api\Request;
class Save extends Request {
private array $routes;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'routes' => new Parameter('routes',Parameter::TYPE_ARRAY, false)
));
$this->loginRequired = true;
$this->csrfTokenRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if (!$this->validateRoutes()) {
return false;
}
$sql = $this->user->getSQL();
// DELETE old rules
$this->success = ($sql->truncate("Route")->execute() !== FALSE);
$this->lastError = $sql->getLastError();
// INSERT new routes
if ($this->success) {
$stmt = $sql->insert("Route", array("request", "action", "target", "extra", "active"));
foreach($this->routes as $route) {
$stmt->addRow($route["request"], $route["action"], $route["target"], $route["extra"], $route["active"]);
}
$this->success = ($stmt->execute() !== FALSE);
$this->lastError = $sql->getLastError();
}
return $this->success;
}
private function validateRoutes() {
$this->routes = array();
$keys = array(
"request" => Parameter::TYPE_STRING,
"action" => Parameter::TYPE_STRING,
"target" => Parameter::TYPE_STRING,
"extra" => Parameter::TYPE_STRING,
"active" => Parameter::TYPE_BOOLEAN
);
$actions = array(
"redirect_temporary", "redirect_permanently", "static", "dynamic"
);
foreach($this->getParam("routes") as $index => $route) {
foreach($keys as $key => $expectedType) {
if (!array_key_exists($key, $route)) {
return $this->createError("Route $index missing key: $key");
}
$value = $route[$key];
$type = Parameter::parseType($value);
if ($type !== $expectedType && ($key !== "active" || !is_null($value))) {
$expectedTypeName = Parameter::names[$expectedType];
$gotTypeName = Parameter::names[$type];
return $this->createError("Route $index has invalid value for key: $key, expected: $expectedTypeName, got: $gotTypeName");
}
}
$action = $route["action"];
if (!in_array($action, $actions)) {
return $this->createError("Invalid action: $action");
}
if(empty($route["request"])) {
return $this->createError("Request cannot be empty.");
}
if(empty($route["target"])) {
return $this->createError("Target cannot be empty.");
}
$this->routes[] = $route;
}
return true;
}
}

@ -0,0 +1,208 @@
<?php
namespace Api {
abstract class RoutesAPI extends Request {
}
}
namespace Api\Routes {
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Api\RoutesAPI;
use Driver\SQL\Column\Column;
use Driver\SQL\Condition\CondBool;
use Driver\SQL\Condition\Regex;
class Fetch extends RoutesAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->loginRequired = true;
$this->csrfTokenRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql
->select("uid", "request", "action", "target", "extra", "active")
->from("Route")
->orderBy("uid")
->ascending()
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
if ($this->success) {
$routes = array();
foreach($res as $row) {
$routes[] = array(
"uid" => intval($row["uid"]),
"request" => $row["request"],
"action" => $row["action"],
"target" => $row["target"],
"extra" => $row["extra"] ?? "",
"active" => intval($row["active"]),
);
}
$this->result["routes"] = $routes;
}
return $this->success;
}
}
class Find extends RoutesAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'request' => new StringType('request', 128, true, '/')
));
$this->isPublic = false;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$request = $this->getParam('request');
if (!startsWith($request, '/')) {
$request = "/$request";
}
$sql = $this->user->getSQL();
$res = $sql
->select("uid", "request", "action", "target", "extra")
->from("Route")
->where(new CondBool("active"))
->where(new Regex($request, new Column("request")))
->limit(1)
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
if ($this->success) {
if (!empty($res)) {
$row = $res[0];
$this->result["route"] = array(
"uid" => intval($row["uid"]),
"request" => $row["request"],
"action" => $row["action"],
"target" => $row["target"],
"extra" => $row["extra"]
);
} else {
$this->result["route"] = NULL;
}
}
return $this->success;
}
}
class Save extends RoutesAPI {
private array $routes;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'routes' => new Parameter('routes',Parameter::TYPE_ARRAY, false)
));
$this->loginRequired = true;
$this->csrfTokenRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if (!$this->validateRoutes()) {
return false;
}
$sql = $this->user->getSQL();
// DELETE old rules
$this->success = ($sql->truncate("Route")->execute() !== FALSE);
$this->lastError = $sql->getLastError();
// INSERT new routes
if ($this->success) {
$stmt = $sql->insert("Route", array("request", "action", "target", "extra", "active"));
foreach($this->routes as $route) {
$stmt->addRow($route["request"], $route["action"], $route["target"], $route["extra"], $route["active"]);
}
$this->success = ($stmt->execute() !== FALSE);
$this->lastError = $sql->getLastError();
}
return $this->success;
}
private function validateRoutes() {
$this->routes = array();
$keys = array(
"request" => Parameter::TYPE_STRING,
"action" => Parameter::TYPE_STRING,
"target" => Parameter::TYPE_STRING,
"extra" => Parameter::TYPE_STRING,
"active" => Parameter::TYPE_BOOLEAN
);
$actions = array(
"redirect_temporary", "redirect_permanently", "static", "dynamic"
);
foreach($this->getParam("routes") as $index => $route) {
foreach($keys as $key => $expectedType) {
if (!array_key_exists($key, $route)) {
return $this->createError("Route $index missing key: $key");
}
$value = $route[$key];
$type = Parameter::parseType($value);
if ($type !== $expectedType && ($key !== "active" || !is_null($value))) {
$expectedTypeName = Parameter::names[$expectedType];
$gotTypeName = Parameter::names[$type];
return $this->createError("Route $index has invalid value for key: $key, expected: $expectedTypeName, got: $gotTypeName");
}
}
$action = $route["action"];
if (!in_array($action, $actions)) {
return $this->createError("Invalid action: $action");
}
if(empty($route["request"])) {
return $this->createError("Request cannot be empty.");
}
if(empty($route["target"])) {
return $this->createError("Target cannot be empty.");
}
$this->routes[] = $route;
}
return true;
}
}
}

@ -1,83 +0,0 @@
<?php
namespace Api;
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Driver\SQL\Condition\CondOr;
use Driver\SQL\Condition\Compare;
use Objects\Language;
class SetLanguage extends Request {
private Language $language;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'langId' => new Parameter('langId', Parameter::TYPE_INT, true, NULL),
'langCode' => new StringType('langCode', 5, true, NULL),
));
$this->csrfTokenRequired = true;
}
private function checkLanguage() {
$langId = $this->getParam("langId");
$langCode = $this->getParam("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();
$this->success = ($res !== FALSE);
$this->lastError = $this->user->getSQL()->getLastError();
if ($this->success) {
if(count($res) == 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"));
}
}
}
return $this->success;
}
private function updateLanguage() {
$languageId = $this->language->getId();
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$this->success = $sql->update("User")
->set("language_id", $languageId)
->where(new Compare("uid", $userId))
->execute();
$this->lastError = $sql->getLastError();
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if(!$this->checkLanguage())
return false;
if($this->user->isLoggedIn()) {
$this->updateLanguage();
}
$this->user->setLanguage($this->language);
return $this->success;
}
}

@ -1,78 +0,0 @@
<?php
namespace Api\User;
use Api\Parameter\StringType;
use \Api\Request;
use Driver\SQL\Condition\Compare;
class Create extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'username' => new StringType('username', 32),
'email' => new StringType('email', 64, true),
'password' => new StringType('password'),
'confirmPassword' => new StringType('confirmPassword'),
));
$this->csrfTokenRequired = true;
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$username = $this->getParam('username');
$email = $this->getParam('email');
if (!$this->userExists($username, $email) || !$this->success) {
return false;
}
$password = $this->getParam('password');
$confirmPassword = $this->getParam('confirmPassword');
if ($password !== $confirmPassword) {
return $this->createError("The given passwords do not match.");
}
$this->success = $this->createUser($username, $email, $password);
return $this->success;
}
private function userExists($username, $email) {
$sql = $this->user->getSQL();
$res = $sql->select("User.name", "User.email")
->from("User")
->where(new Compare("User.name", $username), new Compare("User.email", $email))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success && !empty($res)) {
$row = $res[0];
if (strcasecmp($username, $row['name']) === 0) {
return $this->createError("This username is already taken.");
} else if (strcasecmp($username, $row['email']) === 0) {
return $this->createError("This email address is already in use.");
}
}
return $this->success;
}
private function createUser($username, $email, $password) {
$sql = $this->user->getSQL();
$salt = generateRandomString(16);
$hash = hash('sha256', $password . $salt);
$res = $sql->insert("User", array("name", "password", "salt", "email"))
->addRow($username, $hash, $salt, $email)
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
return $this->success;
}
}

@ -1,95 +0,0 @@
<?php
namespace Api\User;
use Api\Parameter\Parameter;
use \Api\Request;
class Fetch extends Request {
const SELECT_SIZE = 10;
private int $userCount;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1)
));
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
$this->userCount = 0;
$this->csrfTokenRequired = true;
}
private function getUserCount() {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())->from("User")->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->userCount = $res[0]["count"];
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$page = $this->getParam("page");
if($page < 1) {
return $this->createError("Invalid page count");
}
if (!$this->getUserCount()) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("User.uid as userId", "User.name", "User.email", "User.registered_at",
"Group.uid as groupId", "Group.name as groupName")
->from("User")
->leftJoin("UserGroup", "User.uid", "UserGroup.user_id")
->leftJoin("Group", "Group.uid", "UserGroup.group_id")
->orderBy("User.uid")
->ascending()
->limit(Fetch::SELECT_SIZE)
->offset(($page - 1) * Fetch::SELECT_SIZE)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result["users"] = array();
foreach($res as $row) {
$userId = intval($row["userId"]);
$groupId = intval($row["groupId"]);
$groupName = $row["groupName"];
if (!isset($this->result["users"][$userId])) {
$this->result["users"][$userId] = array(
"uid" => $userId,
"name" => $row["name"],
"email" => $row["email"],
"registered_at" => $row["registered_at"],
"groups" => array(),
);
}
if(!is_null($groupId)) {
$this->result["users"][$userId]["groups"][$groupId] = $groupName;
}
}
$this->result["pageCount"] = intval(ceil($this->userCount / Fetch::SELECT_SIZE));
$this->result["totalCount"] = $this->userCount;
}
return $this->success;
}
}

@ -1,28 +0,0 @@
<?php
namespace Api\User;
use \Api\Request;
class Info extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->csrfTokenRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if (!$this->user->isLoggedIn()) {
$this->result["loggedIn"] = false;
} else {
$this->result["loggedIn"] = true;
}
$this->result["user"] = $this->user->jsonSerialize();
return $this->success;
}
}

@ -1,84 +0,0 @@
<?php
namespace Api\User;
use Api\Parameter\StringType;
use \Api\Request;
use Api\SendMail;
use DateTime;
use Driver\SQL\Condition\Compare;
class Invite extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'username' => new StringType('username', 32),
'email' => new StringType('email', 64),
));
$this->csrfTokenRequired = true;
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$username = $this->getParam('username');
$email = $this->getParam('email');
if (!$this->userExists($username, $email) || !$this->success) {
return false;
}
//add to DB
$token = generateRandomString(36);
$valid_until = (new DateTime())->modify("+48 hour");
$sql = $this->user->getSQL();
$res = $sql->insert("UserInvitation", array("username", "email", "token", "valid_until"))
->addRow($username, $email, $token, $valid_until)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
//send validation mail
if($this->success) {
$request = new SendMail($this->user);
$link = "http://localhost/acceptInvitation?token=$token";
$this->success = $request->execute(array(
"from" => "webmaster@romanh.de",
"to" => $email,
"subject" => "Account Invitation for web-base@localhost",
"body" =>
"Hello,<br>
you were invited to create an account on web-base@localhost. Click on the following link to confirm the registration, it is 48h valid from now.
If the invitation was not intended, you can simply ignore this email.<br><br><a href=\"$link\">$link</a>"
)
);
$this->lastError = $request->getLastError();
}
return $this->success;
}
private function userExists($username, $email) {
$sql = $this->user->getSQL();
$res = $sql->select("User.name", "User.email")
->from("User")
->where(new Compare("User.name", $username), new Compare("User.email", $email))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success && !empty($res)) {
$row = $res[0];
if (strcasecmp($username, $row['name']) === 0) {
return $this->createError("This username is already taken.");
} else if (strcasecmp($username, $row['email']) === 0) {
return $this->createError("This email address is already in use.");
}
}
return $this->success;
}
}

@ -1,81 +0,0 @@
<?php
namespace Api\User;
use \Api\Request;
use \Api\Parameter\Parameter;
use \Api\Parameter\StringType;
use \Driver\SQL\Condition\Compare;
class Login extends Request {
private int $startedAt;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'username' => new StringType('username', 32),
'password' => new StringType('password'),
'stayLoggedIn' => new Parameter('stayLoggedIn', Parameter::TYPE_BOOLEAN, true, true)
));
$this->forbidMethod("GET");
}
private function wrongCredentials() {
$runtime = microtime(true) - $this->startedAt;
$sleepTime = round(3e6 - $runtime);
if($sleepTime > 0) usleep($sleepTime);
return $this->createError(L('Wrong username or password'));
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if($this->user->isLoggedIn()) {
$this->lastError = L('You are already logged in');
$this->success = true;
return true;
}
$this->startedAt = microtime(true);
$this->success = false;
$username = $this->getParam('username');
$password = $this->getParam('password');
$stayLoggedIn = $this->getParam('stayLoggedIn');
$sql = $this->user->getSQL();
$res = $sql->select("User.uid", "User.password", "User.salt")
->from("User")
->where(new Compare("User.name", $username))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
if(count($res) === 0) {
return $this->wrongCredentials();
} else {
$row = $res[0];
$salt = $row['salt'];
$uid = $row['uid'];
$hash = hash('sha256', $password . $salt);
if($hash === $row['password']) {
if(!($this->success = $this->user->createSession($uid, $stayLoggedIn))) {
return $this->createError("Error creating Session: " . $sql->getLastError());
} else {
$this->result["loggedIn"] = true;
$this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds();
$this->success = true;
}
}
else {
return $this->wrongCredentials();
}
}
}
return $this->success;
}
}

@ -1,25 +0,0 @@
<?php
namespace Api\User;
use \Api\Request;
class Logout extends Request {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall);
$this->loginRequired = true;
$this->apiKeyAllowed = false;
$this->csrfTokenRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$this->success = $this->user->logout();
$this->lastError = $this->user->getSQL()->getLastError();
return $this->success;
}
}

@ -1,46 +0,0 @@
<?php
namespace Api\User;
use Api\Parameter\StringType;
use Api\SendMail;
use Api\User\Create;
class Register extends Create{
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall);
}
public function execute($values = array()) {
if ($this->user->isLoggedIn()) {
$this->lastError = L('You are already logged in');
$this->success = false;
return false;
}
if (!parent::execute($values)) {
return false;
}
if($this->success) {
$email = $this->getParam('email');
$token = generateRandomString(36);
$request = new SendMail($this->user);
$link = "http://localhost/acceptInvitation?token=$token";
$this->success = $request->execute(array(
"from" => "webmaster@romanh.de",
"to" => $email,
"subject" => "Account Invitation for web-base@localhost",
"body" =>
"Hello,<br>
you were invited to create an account on web-base@localhost. Click on the following link to confirm the registration, it is 48h valid from now.
If the invitation was not intended, you can simply ignore this email.<br><br><a href=\"$link\">$link</a>"
)
);
$this->lastError = $request->getLastError();
}
return $this->success;
}
}

442
core/Api/UserAPI.class.php Normal file

@ -0,0 +1,442 @@
<?php
namespace Api {
use Driver\SQL\Condition\Compare;
abstract class UserAPI extends Request {
protected function userExists($username, $email) {
$sql = $this->user->getSQL();
$res = $sql->select("User.name", "User.email")
->from("User")
->where(new Compare("User.name", $username), new Compare("User.email", $email))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success && !empty($res)) {
$row = $res[0];
if (strcasecmp($username, $row['name']) === 0) {
return $this->createError("This username is already taken.");
} else if (strcasecmp($username, $row['email']) === 0) {
return $this->createError("This email address is already in use.");
}
}
return $this->success;
}
protected function insertUser($username, $email, $password) {
$sql = $this->user->getSQL();
$salt = generateRandomString(16);
$hash = $this->hashPassword($password, $salt);
$res = $sql->insert("User", array("name", "password", "salt", "email"))
->addRow($username, $hash, $salt, $email)
->returning("uid")
->execute();
$this->lastError = $sql->getLastError();
$this->success = ($res !== FALSE);
if ($this->success) {
return $sql->getLastInsertId();
}
return $this->success;
}
// TODO: replace this with crypt() in the future
protected function hashPassword($password, $salt) {
return hash('sha256', $password . $salt);
}
}
}
namespace Api\User {
use Api\Parameter\Parameter;
use Api\Parameter\StringType;
use Api\SendMail;
use Api\UserAPI;
use DateTime;
use Driver\SQL\Condition\Compare;
class Create extends UserAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'username' => new StringType('username', 32),
'email' => new StringType('email', 64, true),
'password' => new StringType('password'),
'confirmPassword' => new StringType('confirmPassword'),
));
$this->csrfTokenRequired = true;
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
$username = $this->getParam('username');
$email = $this->getParam('email');
if (!$this->userExists($username, $email) || !$this->success) {
return false;
}
$password = $this->getParam('password');
$confirmPassword = $this->getParam('confirmPassword');
if ($password !== $confirmPassword) {
return $this->createError("The given passwords do not match.");
}
return $this->insertUser($username, $email, $password) !== FALSE;
}
}
class Fetch extends UserAPI {
const SELECT_SIZE = 10;
private int $userCount;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1)
));
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
$this->userCount = 0;
$this->csrfTokenRequired = true;
}
private function getUserCount() {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())->from("User")->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->userCount = $res[0]["count"];
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$page = $this->getParam("page");
if($page < 1) {
return $this->createError("Invalid page count");
}
if (!$this->getUserCount()) {
return false;
}
$sql = $this->user->getSQL();
$res = $sql->select("User.uid as userId", "User.name", "User.email", "User.registered_at",
"Group.uid as groupId", "Group.name as groupName")
->from("User")
->leftJoin("UserGroup", "User.uid", "UserGroup.user_id")
->leftJoin("Group", "Group.uid", "UserGroup.group_id")
->orderBy("User.uid")
->ascending()
->limit(Fetch::SELECT_SIZE)
->offset(($page - 1) * Fetch::SELECT_SIZE)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
$this->result["users"] = array();
foreach($res as $row) {
$userId = intval($row["userId"]);
$groupId = intval($row["groupId"]);
$groupName = $row["groupName"];
if (!isset($this->result["users"][$userId])) {
$this->result["users"][$userId] = array(
"uid" => $userId,
"name" => $row["name"],
"email" => $row["email"],
"registered_at" => $row["registered_at"],
"groups" => array(),
);
}
if(!is_null($groupId)) {
$this->result["users"][$userId]["groups"][$groupId] = $groupName;
}
}
$this->result["pageCount"] = intval(ceil($this->userCount / Fetch::SELECT_SIZE));
$this->result["totalCount"] = $this->userCount;
}
return $this->success;
}
}
class Info extends UserAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array());
$this->csrfTokenRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if (!$this->user->isLoggedIn()) {
$this->result["loggedIn"] = false;
} else {
$this->result["loggedIn"] = true;
}
$this->result["user"] = $this->user->jsonSerialize();
return $this->success;
}
}
class Invite extends UserAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'username' => new StringType('username', 32),
'email' => new StringType('email', 64),
));
$this->csrfTokenRequired = true;
$this->loginRequired = true;
$this->requiredGroup = USER_GROUP_ADMIN;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$username = $this->getParam('username');
$email = $this->getParam('email');
if (!$this->userExists($username, $email)) {
return false;
}
//add to DB
$token = generateRandomString(36);
$valid_until = (new DateTime())->modify("+48 hour");
$sql = $this->user->getSQL();
$res = $sql->insert("UserInvitation", array("username", "email", "token", "valid_until"))
->addRow($username, $email, $token, $valid_until)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
//send validation mail
if($this->success) {
$request = new SendMail($this->user);
$link = "http://localhost/acceptInvitation?token=$token";
$this->success = $request->execute(array(
"from" => "webmaster@romanh.de",
"to" => $email,
"subject" => "Account Invitation for web-base@localhost",
"body" =>
"Hello,<br>
you were invited to create an account on web-base@localhost. Click on the following link to confirm the registration, it is 48h valid from now.
If the invitation was not intended, you can simply ignore this email.<br><br><a href=\"$link\">$link</a>"
)
);
$this->lastError = $request->getLastError();
}
return $this->success;
}
}
class Login extends UserAPI {
private int $startedAt;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
'username' => new StringType('username', 32),
'password' => new StringType('password'),
'stayLoggedIn' => new Parameter('stayLoggedIn', Parameter::TYPE_BOOLEAN, true, true)
));
$this->forbidMethod("GET");
}
private function wrongCredentials() {
$runtime = microtime(true) - $this->startedAt;
$sleepTime = round(3e6 - $runtime);
if($sleepTime > 0) usleep($sleepTime);
return $this->createError(L('Wrong username or password'));
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
if($this->user->isLoggedIn()) {
$this->lastError = L('You are already logged in');
$this->success = true;
return true;
}
$this->startedAt = microtime(true);
$this->success = false;
$username = $this->getParam('username');
$password = $this->getParam('password');
$stayLoggedIn = $this->getParam('stayLoggedIn');
$sql = $this->user->getSQL();
$res = $sql->select("User.uid", "User.password", "User.salt")
->from("User")
->where(new Compare("User.name", $username))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if($this->success) {
if(count($res) === 0) {
return $this->wrongCredentials();
} else {
$row = $res[0];
$salt = $row['salt'];
$uid = $row['uid'];
$hash = $this->hashPassword($password, $salt);
if($hash === $row['password']) {
if(!($this->success = $this->user->createSession($uid, $stayLoggedIn))) {
return $this->createError("Error creating Session: " . $sql->getLastError());
} else {
$this->result["loggedIn"] = true;
$this->result['logoutIn'] = $this->user->getSession()->getExpiresSeconds();
$this->success = true;
}
}
else {
return $this->wrongCredentials();
}
}
}
return $this->success;
}
}
class Logout extends UserAPI {
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall);
$this->loginRequired = true;
$this->apiKeyAllowed = false;
$this->csrfTokenRequired = true;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$this->success = $this->user->logout();
$this->lastError = $this->user->getSQL()->getLastError();
return $this->success;
}
}
class Register extends UserAPI {
private ?int $userId;
private string $token;
public function __construct($user, $externalCall = false) {
parent::__construct($user, $externalCall, array(
"username" => new StringType("username", 32),
"email" => new StringType("email", 64),
"password" => new StringType("password"),
"confirmPassword" => new StringType("confirmPassword"),
));
}
private function insertToken() {
$validUntil = (new DateTime())->modify("+48 hour");
$sql = $this->user->getSQL();
$res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until"))
->addRow(array($this->userId, $this->token, "confirmation", $validUntil))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success;
}
public function execute($values = array()) {
if (!parent::execute($values)) {
return false;
}
if ($this->user->isLoggedIn()) {
$this->lastError = L('You are already logged in');
$this->success = false;
return false;
}
$username = $this->getParam("username");
$email = $this->getParam('email');
if (!$this->userExists($username, $email)) {
return false;
}
$password = $this->getParam("password");
$confirmPassword = $this->getParam("confirmPassword");
if(strcmp($password, $confirmPassword) !== 0) {
return $this->createError("The given passwords don't match");
}
$id = $this->insertUser($username, $email, $password);
if ($id === FALSE) {
return false;
}
$this->userId = $id;
$this->token = generateRandomString(36);
if ($this->insertToken()) {
return false;
}
$request = new SendMail($this->user);
$link = "http://localhost/confirmEmail?token=$this->token";
$this->success = $request->execute(array(
"from" => "webmaster@romanh.de",
"to" => $email,
"subject" => "E-Mail Confirmation for web-base@localhost",
"body" =>
"Hello,<br>
you recently registered an account on web-base@localhost. Click on the following link to confirm the registration, it is 48h valid from now.
If the registration was not intended, you can simply ignore this email.<br><br><a href=\"$link\">$link</a>"
)
);
if (!$this->success) {
$this->lastError = "Your account was registered but the confirmation email could not be sent. " .
"Please contact the server administration. Reason: " . $request->getLastError();
}
return $this->success;
}
}
}

@ -2,7 +2,6 @@
namespace Objects; namespace Objects;
use Api\SetLanguage;
use Configuration\Configuration; use Configuration\Configuration;
use DateTime; use DateTime;
use Driver\SQL\Expression\Add; use Driver\SQL\Expression\Add;
@ -108,7 +107,7 @@ class User extends ApiObject {
public function updateLanguage($lang) { public function updateLanguage($lang) {
if($this->sql) { if($this->sql) {
$request = new SetLanguage($this); $request = new \Api\Language\Set($this);
return $request->execute(array("langCode" => $lang)); return $request->execute(array("langCode" => $lang));
} else { } else {
return false; return false;

@ -2,7 +2,6 @@
namespace Views; namespace Views;
use Api\GetLanguages;
use Elements\View; use Elements\View;
class LanguageFlags extends View { class LanguageFlags extends View {
@ -17,7 +16,7 @@ class LanguageFlags extends View {
public function loadView() { public function loadView() {
parent::loadView(); parent::loadView();
$request = new GetLanguages($this->getDocument()->getUser()); $request = new \Api\Language\Get($this->getDocument()->getUser());
if($request->execute()) { if($request->execute()) {
$requestUri = $_SERVER["REQUEST_URI"]; $requestUri = $_SERVER["REQUEST_URI"];

@ -87,7 +87,14 @@
function getClassPath($class, $suffix=true) { function getClassPath($class, $suffix=true) {
$path = str_replace('\\', '/', $class); $path = str_replace('\\', '/', $class);
if (startsWith($path, "/")) $path = substr($path, 1); $path = array_values(array_filter(explode("/", $path)));
if (strcasecmp($path[0], "api") === 0 && count($path) > 2 && strcasecmp($path[1], "Parameter") !== 0) {
$path = "Api/" . $path[1] . "API";
} else {
$path = implode("/", $path);
}
$suffix = ($suffix ? ".class" : ""); $suffix = ($suffix ? ".class" : "");
return "core/$path$suffix.php"; return "core/$path$suffix.php";
} }

@ -41,23 +41,37 @@ if(isset($_GET["api"]) && is_string($_GET["api"])) {
header("400 Bad Request"); header("400 Bad Request");
$response = createError("Invalid Method"); $response = createError("Invalid Method");
} else { } else {
$apiFunction = implode("\\", array_map('ucfirst', explode("/", $apiFunction))); $apiFunction = array_filter(array_map('ucfirst', explode("/", $apiFunction)));
if($apiFunction[0] !== "\\") $apiFunction = "\\$apiFunction"; if (count($apiFunction) > 1) {
$class = "\\Api$apiFunction"; $parentClass = "\\Api\\" . reset($apiFunction) . "API";
$file = getClassPath($class); $apiClass = "\\Api\\" . implode("\\", $apiFunction);
} else {
$apiClass = "\\Api\\" . implode("\\", $apiFunction);
$parentClass = $apiClass;
}
try {
$file = getClassPath($parentClass);
if(!file_exists($file)) { if(!file_exists($file)) {
header("404 Not Found"); header("404 Not Found");
$response = createError("Not found"); $response = createError("Not found");
} else if(!is_subclass_of($class, Request::class)) { } else {
$parentClass = new ReflectionClass($parentClass);
$apiClass = new ReflectionClass($apiClass);
if(!$apiClass->isSubclassOf(Request::class) || !$apiClass->isInstantiable()) {
header("400 Bad Request"); header("400 Bad Request");
$response = createError("Invalid Method"); $response = createError("Invalid Method");
} else { } else {
$request = new $class($user, true); $request = $apiClass->newInstanceArgs(array($user, true));
$success = $request->execute(); $success = $request->execute();
$msg = $request->getLastError(); $msg = $request->getLastError();
$response = $request->getJsonResult(); $response = $request->getJsonResult();
} }
} }
} catch (ReflectionException $e) {
$response = createError("Error instantiating class: $e");
}
}
} }
} else { } else {
$documentName = $_GET["site"] ?? "/"; $documentName = $_GET["site"] ?? "/";