frontend & backend update
This commit is contained in:
@@ -139,7 +139,7 @@ class Context {
|
||||
->where(new CondOr(
|
||||
new CondLike("name", "%$lang%"), // english
|
||||
new Compare("code", $lang), // de_DE
|
||||
new CondLike("code", "${lang}_%") // de -> de_%
|
||||
new CondLike("code", "{$lang}_%") // de -> de_%
|
||||
))
|
||||
);
|
||||
if ($language) {
|
||||
|
||||
@@ -57,6 +57,12 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
||||
public function jsonSerialize(?array $propertyNames = null): array {
|
||||
$reflectionClass = (new \ReflectionClass(get_called_class()));
|
||||
$properties = $reflectionClass->getProperties();
|
||||
|
||||
while ($reflectionClass->getParentClass()->getName() !== DatabaseEntity::class) {
|
||||
$reflectionClass = $reflectionClass->getParentClass();
|
||||
$properties = array_merge($reflectionClass->getProperties(), $properties);
|
||||
}
|
||||
|
||||
$ignoredProperties = ["entityLogConfig", "customData"];
|
||||
|
||||
$jsonArray = [];
|
||||
|
||||
@@ -524,7 +524,15 @@ class DatabaseEntityHandler implements Persistable {
|
||||
$thisIdProperty->setValue($relEntity, $entity);
|
||||
}
|
||||
|
||||
$success = $otherHandler->getInsertQuery($relEntities)->execute() && $success;
|
||||
$statement = $otherHandler->getInsertQuery($relEntities);
|
||||
if ($ignoreExisting) {
|
||||
$columns = $nmRelation->getRefColumns();
|
||||
$statement->onDuplicateKeyStrategy(new UpdateStrategy($columns, [
|
||||
$thisIdColumn => $entity->getId()
|
||||
]));
|
||||
}
|
||||
|
||||
$success = $statement->execute() && $success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,6 +565,10 @@ class DatabaseEntityHandler implements Persistable {
|
||||
}
|
||||
|
||||
$entityIds = array_keys($entities);
|
||||
if (empty($entityIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->nmRelations as $nmProperty => $nmRelation) {
|
||||
$nmTable = $nmRelation->getTableName();
|
||||
$property = $this->properties[$nmProperty];
|
||||
@@ -599,29 +611,27 @@ class DatabaseEntityHandler implements Persistable {
|
||||
|
||||
$otherHandler = $nmRelation->getRelHandler();
|
||||
$thisIdColumn = $otherHandler->getColumnName($nmRelation->getThisProperty(), false);
|
||||
$relIdColumn = $otherHandler->getColumnName($nmRelation->getRefProperty(), false);
|
||||
if (!empty($entityIds)) {
|
||||
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||
$relIdColumn = $otherHandler->getColumnName($nmRelation->getRefProperty(), false);
|
||||
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||
|
||||
$relEntityQuery->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE);
|
||||
$rows = $relEntityQuery->executeSQL();
|
||||
if (!is_array($rows)) {
|
||||
$this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError());
|
||||
return;
|
||||
}
|
||||
$relEntityQuery->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE);
|
||||
$rows = $relEntityQuery->executeSQL();
|
||||
if (!is_array($rows)) {
|
||||
$this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
$thisIdProperty = $otherHandler->properties[$nmRelation->getThisProperty()];
|
||||
$thisIdProperty->setAccessible(true);
|
||||
$thisIdProperty = $otherHandler->properties[$nmRelation->getThisProperty()];
|
||||
$thisIdProperty->setAccessible(true);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities);
|
||||
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||
$thisIdProperty->setValue($relEntity, $thisEntity);
|
||||
$targetArray = $property->getValue($thisEntity);
|
||||
$targetArray[$row[$relIdColumn]] = $relEntity;
|
||||
$property->setValue($thisEntity, $targetArray);
|
||||
}
|
||||
foreach ($rows as $row) {
|
||||
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities);
|
||||
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||
$thisIdProperty->setValue($relEntity, $thisEntity);
|
||||
$targetArray = $property->getValue($thisEntity);
|
||||
$targetArray[$row[$relIdColumn]] = $relEntity;
|
||||
$property->setValue($thisEntity, $targetArray);
|
||||
}
|
||||
} else {
|
||||
$this->logger->error("fetchNMRelations for type '" . get_class($nmRelation) . "' is not implemented");
|
||||
|
||||
@@ -94,7 +94,7 @@ class DatabaseEntityQuery extends Select {
|
||||
|
||||
$relIndex = 1;
|
||||
foreach ($this->handler->getRelations() as $propertyName => $relationHandler) {
|
||||
if ($this->handler !== $relationHandler) {
|
||||
if ($this->handler !== $relationHandler || !$recursive) {
|
||||
$this->fetchRelation($propertyName, $this->handler->getTableName(), $this->handler, $relationHandler, $relIndex, $recursive);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,13 @@ class NMRelationReference implements Persistable {
|
||||
return $this->refProperty;
|
||||
}
|
||||
|
||||
public function getRefColumns(): array {
|
||||
return [
|
||||
$this->handler->getColumnName($this->getThisProperty(), false),
|
||||
$this->handler->getColumnName($this->getRefProperty(), false),
|
||||
];
|
||||
}
|
||||
|
||||
public function getRelHandler(): DatabaseEntityHandler {
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace Core\Objects\DatabaseEntity;
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Objects\DatabaseEntity\Attribute\ExtendingEnum;
|
||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Visibility;
|
||||
use Core\Objects\TwoFactor\KeyBasedTwoFactorToken;
|
||||
use Core\Objects\TwoFactor\TimeBasedTwoFactorToken;
|
||||
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||
@@ -18,8 +20,13 @@ abstract class TwoFactorToken extends DatabaseEntity {
|
||||
|
||||
#[ExtendingEnum(self::TWO_FACTOR_TOKEN_TYPES)] private string $type;
|
||||
private bool $confirmed;
|
||||
|
||||
#[Transient]
|
||||
private bool $authenticated;
|
||||
#[MaxLength(512)] private ?string $data;
|
||||
|
||||
#[MaxLength(512)]
|
||||
#[Visibility(Visibility::NONE)]
|
||||
private ?string $data;
|
||||
|
||||
public function __construct(string $type, ?int $id = null, bool $confirmed = false) {
|
||||
parent::__construct($id);
|
||||
@@ -39,6 +46,7 @@ abstract class TwoFactorToken extends DatabaseEntity {
|
||||
|
||||
public function postFetch(SQL $sql, array $row) {
|
||||
parent::postFetch($sql, $row);
|
||||
$this->authenticated = $_SESSION["2faAuthenticated"] ?? false;
|
||||
$this->readData($row["data"]);
|
||||
}
|
||||
|
||||
@@ -63,4 +71,14 @@ abstract class TwoFactorToken extends DatabaseEntity {
|
||||
$this->confirmed = true;
|
||||
return $this->save($sql) !== false;
|
||||
}
|
||||
|
||||
public function jsonSerialize(?array $propertyNames = null): array {
|
||||
$jsonData = parent::jsonSerialize($propertyNames);
|
||||
|
||||
if ($propertyNames === null || in_array("authenticated", $propertyNames)) {
|
||||
$jsonData["authenticated"] = $this->authenticated;
|
||||
}
|
||||
|
||||
return $jsonData;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
namespace Core\Objects\DatabaseEntity;
|
||||
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\Driver\SQL\Expression\Alias;
|
||||
use Core\Driver\SQL\Expression\Coalesce;
|
||||
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
||||
use Core\Driver\SQL\Expression\NullIf;
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
@@ -141,4 +145,13 @@ class User extends DatabaseEntity {
|
||||
public function getDisplayName(): string {
|
||||
return !empty($this->fullName) ? $this->fullName : $this->name;
|
||||
}
|
||||
|
||||
public static function buildSQLDisplayName(SQL $sql, string $joinColumn): Alias {
|
||||
return new Alias(
|
||||
$sql->select(new Coalesce(
|
||||
new NullIf(new Column("User.full_name"), ""),
|
||||
new NullIf(new Column("User.name"), ""))
|
||||
)->from("User")->whereEq("User.id", new Column($joinColumn)),
|
||||
"user");
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use Core\Objects\ApiObject;
|
||||
|
||||
class AttestationObject extends ApiObject {
|
||||
|
||||
use Core\Objects\TwoFactor\CBORDecoder;
|
||||
use CBORDecoder;
|
||||
|
||||
private string $format;
|
||||
private array $statement;
|
||||
|
||||
@@ -3,70 +3,92 @@
|
||||
namespace Core\Objects\TwoFactor;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
||||
use Cose\Algorithm\Signature\ECDSA\ECSignature;
|
||||
use Core\Objects\DatabaseEntity\TwoFactorToken;
|
||||
use Cose\Key\Key;
|
||||
|
||||
class KeyBasedTwoFactorToken extends TwoFactorToken {
|
||||
|
||||
const TYPE = "fido";
|
||||
|
||||
#[Transient]
|
||||
private ?string $challenge;
|
||||
private ?string $credentialId;
|
||||
|
||||
#[Transient]
|
||||
private ?string $credentialID;
|
||||
|
||||
#[Transient]
|
||||
private ?PublicKey $publicKey;
|
||||
|
||||
public function __construct(string $challenge) {
|
||||
private function __construct() {
|
||||
parent::__construct(self::TYPE);
|
||||
$this->challenge = $challenge;
|
||||
}
|
||||
|
||||
public function generateChallenge(int $length = 32) {
|
||||
$this->challenge = base64_encode(generateRandomString($length, "raw"));
|
||||
$_SESSION["challenge"] = $this->challenge;
|
||||
}
|
||||
|
||||
public static function create(int $challengeLength = 32): KeyBasedTwoFactorToken {
|
||||
$token = new KeyBasedTwoFactorToken();
|
||||
$token->generateChallenge($challengeLength);
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function getChallenge(): string {
|
||||
return $this->challenge;
|
||||
}
|
||||
|
||||
protected function readData(string $data) {
|
||||
if (!$this->isConfirmed()) {
|
||||
$this->challenge = base64_decode($data);
|
||||
$this->credentialId = null;
|
||||
$this->challenge = $data;
|
||||
$this->credentialID = null;
|
||||
$this->publicKey = null;
|
||||
} else {
|
||||
$jsonData = json_decode($data, true);
|
||||
$this->challenge = base64_decode($_SESSION["challenge"] ?? "");
|
||||
$this->credentialId = base64_decode($jsonData["credentialID"]);
|
||||
$this->challenge = $_SESSION["challenge"] ?? "";
|
||||
$this->credentialID = base64_decode($jsonData["credentialID"]);
|
||||
$this->publicKey = PublicKey::fromJson($jsonData["publicKey"]);
|
||||
}
|
||||
}
|
||||
|
||||
public function getData(): string {
|
||||
if ($this->isConfirmed()) {
|
||||
return base64_encode($this->challenge);
|
||||
if (!$this->isConfirmed()) {
|
||||
return $this->challenge;
|
||||
} else {
|
||||
return json_encode([
|
||||
"credentialId" => $this->credentialId,
|
||||
"credentialID" => $this->credentialID,
|
||||
"publicKey" => $this->publicKey->jsonSerialize()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function confirmKeyBased(SQL $sql, string $credentialId, PublicKey $publicKey): bool {
|
||||
$this->credentialId = $credentialId;
|
||||
public function confirmKeyBased(SQL $sql, string $credentialID, PublicKey $publicKey): bool {
|
||||
$this->credentialID = $credentialID;
|
||||
$this->publicKey = $publicKey;
|
||||
return parent::confirm($sql);
|
||||
}
|
||||
|
||||
|
||||
public function getPublicKey(): ?PublicKey {
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
public function getCredentialId(): ?string {
|
||||
return $this->credentialId;
|
||||
return $this->credentialID;
|
||||
}
|
||||
|
||||
public function jsonSerialize(?array $propertyNames = null): array {
|
||||
$jsonData = parent::jsonSerialize();
|
||||
|
||||
if (!empty($this->challenge) && !$this->isAuthenticated() && in_array("challenge", $propertyNames)) {
|
||||
$jsonData["challenge"] = base64_encode($this->challenge);
|
||||
}
|
||||
if (!$this->isAuthenticated()) {
|
||||
if (!empty($this->challenge) && ($propertyNames === null || in_array("challenge", $propertyNames))) {
|
||||
$jsonData["challenge"] = $this->challenge;
|
||||
}
|
||||
|
||||
if (!empty($this->credentialId) && in_array("credentialID", $propertyNames)) {
|
||||
$jsonData["credentialID"] = base64_encode($this->credentialId);
|
||||
if (!empty($this->credentialID) && ($propertyNames === null || in_array("credentialID", $propertyNames))) {
|
||||
$jsonData["credentialID"] = base64_encode($this->credentialID);
|
||||
}
|
||||
}
|
||||
|
||||
return $jsonData;
|
||||
|
||||
@@ -6,7 +6,7 @@ use Core\Objects\ApiObject;
|
||||
|
||||
class PublicKey extends ApiObject {
|
||||
|
||||
use Core\Objects\TwoFactor\CBORDecoder;
|
||||
use CBORDecoder;
|
||||
|
||||
private int $keyType;
|
||||
private int $usedAlgorithm;
|
||||
|
||||
@@ -7,11 +7,14 @@ use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Objects\Context;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Visibility;
|
||||
use Core\Objects\DatabaseEntity\TwoFactorToken;
|
||||
|
||||
class TimeBasedTwoFactorToken extends TwoFactorToken {
|
||||
|
||||
const TYPE = "totp";
|
||||
|
||||
#[Visibility(Visibility::NONE)]
|
||||
private string $secret;
|
||||
|
||||
public function __construct(string $secret) {
|
||||
|
||||
Reference in New Issue
Block a user