Property Visibilities

This commit is contained in:
2023-01-07 15:34:05 +01:00
parent 99bfd7e505
commit d115d8b970
30 changed files with 241 additions and 215 deletions

View File

@@ -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;
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Core\Objects\DatabaseEntity\Attribute;
#[\Attribute(\Attribute::TARGET_PROPERTY)] class Visibility {
// Visibility enum
const NONE = 0;
const BY_GROUP = 1;
const ALL = 2;
private int $visibility;
private array $groups;
public function __construct(int $visibility, int ...$groups) {
$this->visibility = $visibility;
$this->groups = $groups;
}
public function getType(): int {
return $this->visibility;
}
public function getGroups(): array {
return $this->groups;
}
}

View File

@@ -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) { }

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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"]);

View File

@@ -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"]);
}
}

View File

@@ -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() {

View File

@@ -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 = [

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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
];
}
}

View File

@@ -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);

View File

@@ -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;
}
}
}

View File

@@ -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;
}