diff --git a/Core/API/Request.class.php b/Core/API/Request.class.php index 59a5f83..5f5b9ef 100644 --- a/Core/API/Request.class.php +++ b/Core/API/Request.class.php @@ -213,8 +213,8 @@ abstract class Request { // CSRF Token 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 it's not a call with API_KEY, check for csrfToken + $csrfToken = $values["csrfToken"] ?? $_SERVER["HTTP_XSRF_TOKEN"] ?? null; if (!$csrfToken || strcmp($csrfToken, $session->getCsrfToken()) !== 0) { $this->lastError = "CSRF-Token mismatch"; http_response_code(403); diff --git a/Core/API/Traits/Pagination.trait.php b/Core/API/Traits/Pagination.trait.php index e50f746..b11b803 100644 --- a/Core/API/Traits/Pagination.trait.php +++ b/Core/API/Traits/Pagination.trait.php @@ -85,7 +85,7 @@ trait Pagination { if ($orderBy) { $handler = $baseQuery->getHandler(); $baseTable = $handler->getTableName(); - $sortColumn = DatabaseEntityHandler::getColumnName($orderBy); + $sortColumn = DatabaseEntityHandler::buildColumnName($orderBy); $fullyQualifiedColumn = "$baseTable.$sortColumn"; $selectedColumns = $baseQuery->getSelectValues(); diff --git a/Core/API/UserAPI.class.php b/Core/API/UserAPI.class.php index c5ba0ce..2a18679 100644 --- a/Core/API/UserAPI.class.php +++ b/Core/API/UserAPI.class.php @@ -225,7 +225,6 @@ namespace Core\API\User { $currentUser->hasGroup(Group::SUPPORT)); $orderBy = $this->getParam("orderBy"); - $publicAttributes = ["id", "name", "fullName", "profilePicture", "email"]; // TODO: , "groupNames"]; $condition = null; if (!$fullInfo) { @@ -234,7 +233,7 @@ namespace Core\API\User { new CondBool("User.confirmed") ); - if ($orderBy && !in_array($orderBy, $publicAttributes)) { + if ($orderBy && !$currentUser->canAccess(User::class, $orderBy)) { return $this->createError("Insufficient permissions for sorting by field '$orderBy'"); } } @@ -255,19 +254,8 @@ namespace Core\API\User { $users = User::findBy($userQuery); if ($users !== false && $users !== null) { $this->result["users"] = []; - - foreach ($users as $userId => $user) { - $serialized = $user->jsonSerialize(); - - if (!$fullInfo && $userId !== $currentUser->getId()) { - foreach (array_keys($serialized) as $attr) { - if (!in_array($attr, $publicAttributes)) { - unset ($serialized[$attr]); - } - } - } - - $this->result["users"][] = $serialized; + foreach ($users as $user) { + $this->result["users"][] = $user->jsonSerialize(); } } else { return $this->createError("Error fetching users: " . $sql->getLastError()); @@ -305,20 +293,10 @@ namespace Core\API\User { $currentUser->hasGroup(Group::ADMIN) || $currentUser->hasGroup(Group::SUPPORT)); - if (!$fullInfo) { - if (!$queriedUser["confirmed"]) { - return $this->createError("No permissions to access this user"); - } - - $publicAttributes = ["id", "name", "fullName", "profilePicture", "email", "groups"]; - foreach (array_keys($queriedUser) as $attr) { - if (!in_array($attr, $publicAttributes)) { - unset($queriedUser[$attr]); - } - } + if (!$fullInfo && !$queriedUser["confirmed"]) { + return $this->createError("No permissions to access this user"); } - unset($queriedUser["session"]); // strip session information $this->result["user"] = $queriedUser; } @@ -358,6 +336,7 @@ namespace Core\API\User { $this->result["permissions"] = $permissions; $this->result["user"] = $currentUser->jsonSerialize(); + $this->result["session"] = $this->context->getSession()->jsonSerialize(); } return $this->success; @@ -575,7 +554,7 @@ namespace Core\API\User { $tfaToken = $user->getTwoFactorToken(); $this->result["loggedIn"] = true; $this->result["logoutIn"] = $session->getExpiresSeconds(); - $this->result["csrf_token"] = $session->getCsrfToken(); + $this->result["csrfToken"] = $session->getCsrfToken(); if ($tfaToken && $tfaToken->isConfirmed()) { $this->result["2fa"] = ["type" => $tfaToken->getType()]; if ($tfaToken instanceof KeyBasedTwoFactorToken) { diff --git a/Core/Objects/ApiObject.class.php b/Core/Objects/ApiObject.class.php index 036b7d3..dc5c7db 100644 --- a/Core/Objects/ApiObject.class.php +++ b/Core/Objects/ApiObject.class.php @@ -4,8 +4,8 @@ namespace Core\Objects; abstract class ApiObject implements \JsonSerializable { - public abstract function jsonSerialize(): array; - - public function __toString() { return json_encode($this->jsonSerialize()); } + public function __toString() { + return json_encode($this->jsonSerialize()); + } } diff --git a/Core/Objects/Context.class.php b/Core/Objects/Context.class.php index 459df9f..4a09fe0 100644 --- a/Core/Objects/Context.class.php +++ b/Core/Objects/Context.class.php @@ -17,6 +17,8 @@ use Core\Objects\Router\Router; class Context { + private static Context $instance; + private ?SQL $sql; private ?Session $session; private ?User $user; @@ -24,7 +26,7 @@ class Context { private Language $language; public ?Router $router; - public function __construct() { + private function __construct() { $this->sql = null; $this->session = null; @@ -38,6 +40,13 @@ class Context { } } + public static function instance(): self { + if (!isset(self::$instance)) { + self::$instance = new Context(); + } + return self::$instance; + } + public function __destruct() { if ($this->sql && $this->sql->isConnected()) { $this->sql->close(); @@ -93,12 +102,9 @@ class Context { private function loadSession(int $userId, int $sessionId): void { $this->session = Session::init($this, $userId, $sessionId); $this->user = $this->session?->getUser(); - if ($this->user) { - $this->user->session = $this->session; - } } - public function parseCookies() { + public function parseCookies(): void { if (isset($_COOKIE['session']) && is_string($_COOKIE['session']) && !empty($_COOKIE['session'])) { try { $token = $_COOKIE['session']; @@ -194,7 +200,6 @@ class Context { $this->session = new Session($this, $this->user); $this->session->stayLoggedIn = $stayLoggedIn; if ($this->session->update()) { - $user->session = $this->session; return $this->session; } else { $this->user = null; diff --git a/Core/Objects/DatabaseEntity/ApiKey.class.php b/Core/Objects/DatabaseEntity/ApiKey.class.php index 24b00b3..175645f 100644 --- a/Core/Objects/DatabaseEntity/ApiKey.class.php +++ b/Core/Objects/DatabaseEntity/ApiKey.class.php @@ -18,15 +18,6 @@ class ApiKey extends DatabaseEntity { $this->active = true; } - public function jsonSerialize(): array { - return [ - "id" => $this->getId(), - "active" => $this->active, - "apiKey" => $this->apiKey, - "validUntil" => $this->validUntil->getTimestamp() - ]; - } - public function getValidUntil(): \DateTime { return $this->validUntil; } diff --git a/Core/Objects/DatabaseEntity/Attribute/Visibility.class.php b/Core/Objects/DatabaseEntity/Attribute/Visibility.class.php new file mode 100644 index 0000000..b21bd65 --- /dev/null +++ b/Core/Objects/DatabaseEntity/Attribute/Visibility.class.php @@ -0,0 +1,27 @@ +visibility = $visibility; + $this->groups = $groups; + } + + public function getType(): int { + return $this->visibility; + } + + public function getGroups(): array { + return $this->groups; + } +} \ No newline at end of file diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php index b46d3b0..53c07f0 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php @@ -6,7 +6,9 @@ use ArrayAccess; use Core\Driver\SQL\Condition\Condition; use Core\Driver\SQL\Expression\Count; use Core\Driver\SQL\SQL; +use Core\Objects\Context; use Core\Objects\DatabaseEntity\Attribute\Transient; +use Core\Objects\DatabaseEntity\Attribute\Visibility; use JsonSerializable; abstract class DatabaseEntity implements ArrayAccess, JsonSerializable { @@ -52,7 +54,61 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable { } } - public abstract function jsonSerialize(): array; + public function jsonSerialize(?array $propertyNames = null): array { + $properties = (new \ReflectionClass(get_called_class()))->getProperties(); + $ignoredProperties = ["entityLogConfig", "customData"]; + + $jsonArray = []; + foreach ($properties as $property) { + $property->setAccessible(true); + $propertyName = $property->getName(); + if (in_array($propertyName, $ignoredProperties)) { + continue; + } + + if (!empty($property->getAttributes(Transient::class))) { + continue; + } + + $visibility = DatabaseEntityHandler::getAttribute($property, Visibility::class); + if ($visibility) { + $visibilityType = $visibility->getType(); + if ($visibilityType === Visibility::NONE) { + continue; + } else if ($visibilityType === Visibility::BY_GROUP) { + $currentUser = Context::instance()->getUser(); + $groups = $visibility->getGroups(); + if (!empty($groups)) { + if (!$currentUser || empty(array_intersect(array_keys($currentUser->getGroups()), $groups))) { + continue; + } + } + } + } + + if ($propertyNames === null || isset($propertyNames[$propertyName]) || in_array($propertyName, $propertyNames)) { + if ($property->isInitialized($this)) { + $value = $property->getValue($this); + if ($value instanceof \DateTime) { + $value = $value->getTimestamp(); + } else if ($value instanceof DatabaseEntity) { + $subPropertyNames = $propertyNames[$propertyName] ?? null; + $value = $value->jsonSerialize($subPropertyNames); + } + + $jsonArray[$property->getName()] = $value; + } + } + } + + return $jsonArray; + } + + public static function toJsonArray(array $entities, ?array $properties = null): array { + return array_map(function ($entity) use ($properties) { + return $entity->jsonSerialize($properties); + }, $entities); + } public function preInsert(array &$row) { } public function postFetch(SQL $sql, array $row) { } diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php index d3cdbd9..20354ab 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php @@ -83,7 +83,7 @@ class DatabaseEntityHandler implements Persistable { } $propertyType = $property->getType(); - $columnName = self::getColumnName($propertyName); + $columnName = self::buildColumnName($propertyName); if (!($propertyType instanceof \ReflectionNamedType)) { $this->raiseError("Cannot persist class '$className': Property '$propertyName' has no valid type"); } @@ -206,13 +206,13 @@ class DatabaseEntityHandler implements Persistable { } } - private static function getAttribute(\ReflectionProperty $property, string $attributeClass): ?object { + public 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 { + public static function buildColumnName(string $propertyName): string { // abcTestLOL => abc_test_lol return strtolower(preg_replace_callback("/([a-z])([A-Z]+)/", function ($m) { return $m[1] . "_" . strtolower($m[2]); @@ -239,10 +239,18 @@ class DatabaseEntityHandler implements Persistable { return $this->nmRelations; } + public function getColumnName(string $property): string { + if ($property === "id") { + return "$this->tableName.id"; + } else { + return $this->tableName . "." . $this->columns[$property]->getName(); + } + } + public function getColumnNames(): array { $columns = ["$this->tableName.id"]; - foreach ($this->columns as $column) { - $columns[] = $this->tableName . "." . $column->getName(); + foreach (array_keys($this->columns) as $property) { + $columns[] = $this->getColumnName($property); } return $columns; @@ -286,7 +294,7 @@ class DatabaseEntityHandler implements Persistable { } else if ($column instanceof JsonColumn) { $value = json_decode($value); } else if (isset($this->relations[$propertyName])) { - $relColumnPrefix = self::getColumnName($propertyName) . "_"; + $relColumnPrefix = self::buildColumnName($propertyName) . "_"; if (array_key_exists($relColumnPrefix . "id", $row)) { $relId = $row[$relColumnPrefix . "id"]; if ($relId !== null) { diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php index bf6e171..2bf61fc 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php @@ -42,7 +42,18 @@ class DatabaseEntityQuery extends Select { } } - public function addCustomValue(mixed $selectValue): Select { + public function only(array $fields): DatabaseEntityQuery { + if (!in_array("id", $fields)) { + $fields[] = "id"; + } + + $this->select(array_map(function ($field) { + return $this->handler->getColumnName($field); + }, $fields)); + return $this; + } + + public function addCustomValue(mixed $selectValue): DatabaseEntityQuery { if (is_string($selectValue)) { $this->additionalColumns[] = $selectValue; } else if ($selectValue instanceof Alias) { @@ -108,7 +119,7 @@ class DatabaseEntityQuery extends Select { $this->innerJoin($referencedTable, "$tableName.$foreignColumnName", "$alias.id", $alias); } - $relationColumnPrefix .= DatabaseEntityHandler::getColumnName($propertyName) . "_"; + $relationColumnPrefix .= DatabaseEntityHandler::buildColumnName($propertyName) . "_"; $recursiveRelations = $relationHandler->getRelations(); foreach ($relationHandler->getColumns() as $relPropertyName => $relColumn) { $relColumnName = $relColumn->getName(); diff --git a/Core/Objects/DatabaseEntity/Controller/NMRelation.class.php b/Core/Objects/DatabaseEntity/Controller/NMRelation.class.php index cebf0a0..69fc281 100644 --- a/Core/Objects/DatabaseEntity/Controller/NMRelation.class.php +++ b/Core/Objects/DatabaseEntity/Controller/NMRelation.class.php @@ -34,7 +34,7 @@ class NMRelation implements Persistable { } public function getIdColumn(DatabaseEntityHandler $handler): string { - return DatabaseEntityHandler::getColumnName($handler->getTableName()) . "_id"; + return DatabaseEntityHandler::buildColumnName($handler->getTableName()) . "_id"; } public function getDataColumns(): array { @@ -56,8 +56,8 @@ class NMRelation implements Persistable { foreach ($this->properties as $tableName => $properties) { $columns[$tableName] = []; foreach ($properties as $property) { - $columnName = DatabaseEntityHandler::getColumnName($tableName) . "_" . - DatabaseEntityHandler::getColumnName($property->getName()); + $columnName = DatabaseEntityHandler::buildColumnName($tableName) . "_" . + DatabaseEntityHandler::buildColumnName($property->getName()); $columns[$tableName][$property->getName()] = $columnName; } } diff --git a/Core/Objects/DatabaseEntity/GpgKey.class.php b/Core/Objects/DatabaseEntity/GpgKey.class.php index 2c8062f..ce36bf4 100644 --- a/Core/Objects/DatabaseEntity/GpgKey.class.php +++ b/Core/Objects/DatabaseEntity/GpgKey.class.php @@ -123,17 +123,6 @@ class GpgKey extends DatabaseEntity { 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 - ]; - } - public function confirm(SQL $sql): bool { $this->confirmed = true; return $this->save($sql, ["confirmed"]); diff --git a/Core/Objects/DatabaseEntity/Group.class.php b/Core/Objects/DatabaseEntity/Group.class.php index fcda692..998c086 100644 --- a/Core/Objects/DatabaseEntity/Group.class.php +++ b/Core/Objects/DatabaseEntity/Group.class.php @@ -5,7 +5,6 @@ namespace Core\Objects\DatabaseEntity; use Core\Driver\SQL\SQL; use Core\Objects\DatabaseEntity\Attribute\MaxLength; use Core\Objects\DatabaseEntity\Controller\DatabaseEntity; -use Core\Objects\DatabaseEntity\Controller\DatabaseEntityHandler; use Core\Objects\DatabaseEntity\Controller\NMRelation; class Group extends DatabaseEntity { @@ -29,18 +28,12 @@ class Group extends DatabaseEntity { $this->color = $color; } - public function jsonSerialize(): array { - return [ - "id" => $this->getId(), - "name" => $this->name, - "color" => $this->color - ]; - } - public function getMembers(SQL $sql): array { $nmTable = NMRelation::buildTableName(User::class, Group::class); - return User::findBy(User::createBuilder($sql, false) + $users = User::findBy(User::createBuilder($sql, false) ->innerJoin($nmTable, "user_id", "User.id") ->whereEq("group_id", $this->id)); + + return User::toJsonArray($users, ["id", "name", "fullName", "profilePicture"]); } } \ No newline at end of file diff --git a/Core/Objects/DatabaseEntity/Language.class.php b/Core/Objects/DatabaseEntity/Language.class.php index 467f01c..5e7f344 100644 --- a/Core/Objects/DatabaseEntity/Language.class.php +++ b/Core/Objects/DatabaseEntity/Language.class.php @@ -42,13 +42,14 @@ namespace Core\Objects\DatabaseEntity { 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 jsonSerialize(?array $propertyNames = null): array { + $jsonData = parent::jsonSerialize($propertyNames); + + if ($propertyNames === null || in_array("shortCode", $propertyNames)) { + $jsonData["shortCode"] = explode("_", $this->code)[0]; + } + + return $jsonData; } public function activate() { diff --git a/Core/Objects/DatabaseEntity/MailQueueItem.class.php b/Core/Objects/DatabaseEntity/MailQueueItem.class.php index d655911..bc26d22 100644 --- a/Core/Objects/DatabaseEntity/MailQueueItem.class.php +++ b/Core/Objects/DatabaseEntity/MailQueueItem.class.php @@ -70,25 +70,6 @@ class MailQueueItem extends DatabaseEntity { $this->status = self::STATUS_WAITING; } - public function jsonSerialize(): array { - return [ - "id" => $this->getId(), - "from" => $this->from, - "to" => $this->to, - "gpgFingerprint" => $this->gpgFingerprint, - "subject" => $this->subject, - "message" => $this->body, - "status" => $this->status, - "reply" => [ - "to" => $this->replyTo, - "name" => $this->replyName, - ], - "retryCount" => $this->retryCount, - "nextTry" => $this->nextTry->getTimestamp(), - "errorMessage" => $this->errorMessage, - ]; - } - public function send(Context $context): bool { $args = [ diff --git a/Core/Objects/DatabaseEntity/Route.class.php b/Core/Objects/DatabaseEntity/Route.class.php index 0f84a63..5f54ac6 100644 --- a/Core/Objects/DatabaseEntity/Route.class.php +++ b/Core/Objects/DatabaseEntity/Route.class.php @@ -206,18 +206,6 @@ abstract class Route extends DatabaseEntity { return $parameterNames; } - public function jsonSerialize(): array { - return [ - "id" => $this->getId(), - "pattern" => $this->pattern, - "type" => $this->type, - "target" => $this->target, - "extra" => $this->extra, - "exact" => $this->exact, - "active" => $this->active, - ]; - } - public function setActive(bool $active) { $this->active = $active; } diff --git a/Core/Objects/DatabaseEntity/Session.class.php b/Core/Objects/DatabaseEntity/Session.class.php index 6c93318..8793fd2 100644 --- a/Core/Objects/DatabaseEntity/Session.class.php +++ b/Core/Objects/DatabaseEntity/Session.class.php @@ -93,19 +93,6 @@ class Session extends DatabaseEntity { return ($this->stayLoggedIn ? $this->expires->getTimestamp() - time() : -1); } - public function jsonSerialize(): array { - return array( - 'id' => $this->getId(), - 'active' => $this->active, - 'expires' => $this->expires->getTimestamp(), - 'ipAddress' => $this->ipAddress, - 'os' => $this->os, - 'browser' => $this->browser, - 'csrf_token' => $this->csrfToken, - 'data' => $this->data, - ); - } - public function destroy(): bool { session_destroy(); $this->active = false; diff --git a/Core/Objects/DatabaseEntity/SystemLog.class.php b/Core/Objects/DatabaseEntity/SystemLog.class.php index fffb258..c72bfa7 100644 --- a/Core/Objects/DatabaseEntity/SystemLog.class.php +++ b/Core/Objects/DatabaseEntity/SystemLog.class.php @@ -2,7 +2,6 @@ namespace Core\Objects\DatabaseEntity; -use Core\API\Parameter\Parameter; use Core\Driver\SQL\Expression\CurrentTimeStamp; use Core\Objects\DatabaseEntity\Attribute\DefaultValue; use Core\Objects\DatabaseEntity\Attribute\Enum; @@ -19,14 +18,4 @@ class SystemLog extends DatabaseEntity { 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 - ]; - } } \ No newline at end of file diff --git a/Core/Objects/DatabaseEntity/TwoFactorToken.class.php b/Core/Objects/DatabaseEntity/TwoFactorToken.class.php index 6382db1..608cc95 100644 --- a/Core/Objects/DatabaseEntity/TwoFactorToken.class.php +++ b/Core/Objects/DatabaseEntity/TwoFactorToken.class.php @@ -30,15 +30,6 @@ abstract class TwoFactorToken extends DatabaseEntity { $this->data = null; } - 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); diff --git a/Core/Objects/DatabaseEntity/User.class.php b/Core/Objects/DatabaseEntity/User.class.php index a4ed961..a581aa4 100644 --- a/Core/Objects/DatabaseEntity/User.class.php +++ b/Core/Objects/DatabaseEntity/User.class.php @@ -8,20 +8,45 @@ use Core\Objects\DatabaseEntity\Attribute\DefaultValue; use Core\Objects\DatabaseEntity\Attribute\MaxLength; use Core\Objects\DatabaseEntity\Attribute\Multiple; use Core\Objects\DatabaseEntity\Attribute\Unique; +use Core\Objects\DatabaseEntity\Attribute\Visibility; use Core\Objects\DatabaseEntity\Controller\DatabaseEntity; +use Core\Objects\DatabaseEntity\Controller\DatabaseEntityHandler; 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)] public ?string $profilePicture; + + #[MaxLength(128)] + #[Visibility(Visibility::NONE)] + public string $password; + + #[MaxLength(64)] + public string $fullName; + + #[MaxLength(64)] + #[Unique] + public ?string $email; + + #[MaxLength(64)] + public ?string $profilePicture; + + #[Visibility(Visibility::BY_GROUP, Group::ADMIN, Group::SUPPORT)] private ?\DateTime $lastOnline; - #[DefaultValue(CurrentTimeStamp::class)] public \DateTime $registeredAt; - #[DefaultValue(false)] public bool $confirmed; + + #[Visibility(Visibility::BY_GROUP, Group::ADMIN, Group::SUPPORT)] + #[DefaultValue(CurrentTimeStamp::class)] + public \DateTime $registeredAt; + + #[Visibility(Visibility::BY_GROUP, Group::ADMIN, Group::SUPPORT)] + #[DefaultValue(false)] + public bool $confirmed; + #[DefaultValue(Language::AMERICAN_ENGLISH)] public Language $language; + + #[Visibility(Visibility::BY_GROUP, Group::ADMIN, Group::SUPPORT)] public ?GpgKey $gpgKey; + + #[Visibility(Visibility::BY_GROUP, Group::ADMIN, Group::SUPPORT)] private ?TwoFactorToken $twoFactorToken; #[Multiple(Group::class)] @@ -71,24 +96,6 @@ class User extends DatabaseEntity { ]; } - 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), - "reqisteredAt" => $this->registeredAt->getTimestamp(), - "lastOnline" => $this->lastOnline->getTimestamp(), - "confirmed" => $this->confirmed - ]; - } - public function update(SQL $sql): bool { $this->lastOnline = new \DateTime(); return $this->save($sql, ["last_online", "language_id"]); @@ -97,4 +104,37 @@ class User extends DatabaseEntity { public function setTwoFactorToken(TwoFactorToken $twoFactorToken) { $this->twoFactorToken = $twoFactorToken; } + + public function canAccess(\ReflectionClass|DatabaseEntity|string $entityOrClass, string $propertyName): bool { + try { + $reflectionClass = ($entityOrClass instanceof \ReflectionClass + ? $entityOrClass + : new \ReflectionClass($entityOrClass)); + + $property = $reflectionClass->getProperty($propertyName); + $visibility = DatabaseEntityHandler::getAttribute($property, Visibility::class); + if ($visibility === null) { + return true; + } + + $visibilityType = $visibility->getType(); + if ($visibilityType === Visibility::NONE) { + return false; + } else if ($visibilityType === Visibility::BY_GROUP) { + // allow access to own entity + if ($entityOrClass instanceof User && $entityOrClass->getId() === $this->id) { + return true; + } + + // missing required group + if (empty(array_intersect(array_keys($this->groups), $visibility->getGroups()))) { + return false; + } + } + + return true; + } catch (\Exception $exception) { + return false; + } + } } \ No newline at end of file diff --git a/Core/Objects/DatabaseEntity/UserToken.class.php b/Core/Objects/DatabaseEntity/UserToken.class.php index bed008f..5387053 100644 --- a/Core/Objects/DatabaseEntity/UserToken.class.php +++ b/Core/Objects/DatabaseEntity/UserToken.class.php @@ -6,6 +6,7 @@ use Core\Driver\SQL\SQL; use Core\Objects\DatabaseEntity\Attribute\DefaultValue; use Core\Objects\DatabaseEntity\Attribute\EnumArr; use Core\Objects\DatabaseEntity\Attribute\MaxLength; +use Core\Objects\DatabaseEntity\Attribute\Visibility; use Core\Objects\DatabaseEntity\Controller\DatabaseEntity; class UserToken extends DatabaseEntity { @@ -21,6 +22,7 @@ class UserToken extends DatabaseEntity { ]; #[MaxLength(36)] + #[Visibility(Visibility::NONE)] private string $token; #[EnumArr(self::TOKEN_TYPES)] @@ -41,14 +43,6 @@ class UserToken extends DatabaseEntity { $this->used = false; } - public function jsonSerialize(): array { - return [ - "id" => $this->getId(), - "token" => $this->token, - "tokenType" => $this->tokenType - ]; - } - public function getType(): string { return $this->tokenType; } diff --git a/Core/Objects/TwoFactor/KeyBasedTwoFactorToken.class.php b/Core/Objects/TwoFactor/KeyBasedTwoFactorToken.class.php index a579b33..05925a8 100644 --- a/Core/Objects/TwoFactor/KeyBasedTwoFactorToken.class.php +++ b/Core/Objects/TwoFactor/KeyBasedTwoFactorToken.class.php @@ -58,18 +58,18 @@ class KeyBasedTwoFactorToken extends TwoFactorToken { return $this->credentialId; } - public function jsonSerialize(): array { - $json = parent::jsonSerialize(); + public function jsonSerialize(?array $propertyNames = null): array { + $jsonData = parent::jsonSerialize(); - if (!empty($this->challenge) && !$this->isAuthenticated()) { - $json["challenge"] = base64_encode($this->challenge); + if (!empty($this->challenge) && !$this->isAuthenticated() && in_array("challenge", $propertyNames)) { + $jsonData["challenge"] = base64_encode($this->challenge); } - if (!empty($this->credentialId)) { - $json["credentialID"] = base64_encode($this->credentialId); + if (!empty($this->credentialId) && in_array("credentialID", $propertyNames)) { + $jsonData["credentialID"] = base64_encode($this->credentialId); } - return $json; + return $jsonData; } // TODO: algorithms, hardcoded values, ... diff --git a/cli.php b/cli.php index 5e4bcea..91bc119 100644 --- a/cli.php +++ b/cli.php @@ -34,7 +34,7 @@ function getDatabaseConfig(): ConnectionData { return new $configClass(); } -$context = new \Core\Objects\Context(); +$context = \Core\Objects\Context::instance(); if (!$context->isCLI()) { _exit("Can only be executed via CLI"); } diff --git a/index.php b/index.php index d0ad459..2f2505c 100644 --- a/index.php +++ b/index.php @@ -13,6 +13,7 @@ if (is_file("MAINTENANCE") && !in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', ' } use Core\Configuration\Configuration; +use Core\Objects\Context; use Core\Objects\Router\Router; if (!is_readable(getClassPath(Configuration::class))) { @@ -20,7 +21,7 @@ if (!is_readable(getClassPath(Configuration::class))) { die(json_encode([ "success" => false, "msg" => "Configuration class is not readable, check permissions before proceeding." ])); } -$context = new \Core\Objects\Context(); +$context = Context::instance(); $sql = $context->initSQL(); $settings = $context->getSettings(); $context->parseCookies(); diff --git a/react/admin-panel/src/elements/sidebar.js b/react/admin-panel/src/elements/sidebar.js index ee37207..c07dc02 100644 --- a/react/admin-panel/src/elements/sidebar.js +++ b/react/admin-panel/src/elements/sidebar.js @@ -92,9 +92,9 @@ export default function Sidebar(props) {