summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile29
-rwxr-xr-xbin/create-user24
-rw-r--r--matrix-specification/Data/PushCondition.php56
-rw-r--r--matrix-specification/Data/PushRule.php32
-rw-r--r--matrix-specification/Data/Ruleset.php33
-rw-r--r--matrix-specification/Enums/PushConditionKind.php17
-rw-r--r--matrix-specification/Responses/ClientRegisterPostResponse.php8
-rw-r--r--src/Controllers/Client/ClientController.php108
-rw-r--r--src/Models/User.php2
-rw-r--r--src/Router.php2
10 files changed, 305 insertions, 6 deletions
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..d481b5a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,29 @@
+FROM composer/composer:latest-bin AS composer
+
+WORKDIR /app
+
+COPY composer.* /app
+
+RUN composer install \
+ --no-interaction \
+ --no-plugins \
+ --no-scripts \
+ --no-dev \
+ && composer dump-autoload -o
+
+
+FROM php:fpm-alpine
+
+RUN : \
+ && apk add libpq-dev icu-dev icu-data-full \
+ && docker-php-ext-install pdo_pgsql intl
+
+WORKDIR /usr/src/app
+
+COPY --from=composer /app/vendor /usr/src/app/vendor
+
+COPY src /usr/src/app/src
+COPY public /usr/src/app/public
+COPY migrations /usr/src/app/migrations
+COPY bin/db-migrate /usr/src/app/bin/db-migrate
+COPY matrix-specification /usr/src/app/matrix-specification
diff --git a/bin/create-user b/bin/create-user
new file mode 100755
index 0000000..02cb7da
--- /dev/null
+++ b/bin/create-user
@@ -0,0 +1,24 @@
+#!/usr/bin/env php
+
+<?php
+
+$options = getopt("", ["username:", "password:", "server:"]);
+
+foreach (["username", "password", "server"] as $variable) {
+ if (! array_key_exists($variable, $options)) {
+ die("missing $variable");
+ }
+}
+
+file_get_contents("https://$options[server]/_matrix/client/v3/register", false, stream_context_create([
+ "http" => [
+ "header" => ["Content-Type: application/json"],
+ "method" => "POST",
+ "content" => json_encode([
+ "device_id" => "matrix-php-cli",
+ "inhibit_login" => true,
+ "password" => $options["password"],
+ "username" => $options["username"],
+ ]),
+ ]
+]));
diff --git a/matrix-specification/Data/PushCondition.php b/matrix-specification/Data/PushCondition.php
new file mode 100644
index 0000000..ca94de9
--- /dev/null
+++ b/matrix-specification/Data/PushCondition.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Matrix\Data;
+
+use Matrix\Enums\PushConditionKind;
+use Matrix\Events\Event;
+
+class PushCondition implements \JsonSerializable
+{
+ /**
+ * @param Event[] $events
+ */
+ public function __construct(
+ private PushConditionKind $kind,
+ private ?string $is = null,
+ private ?string $key = null,
+ private ?string $pattern = null,
+ private string|int|bool|null $value = null,
+ )
+ {
+ if ($kind === PushConditionKind::ROOM_MEMBER_COUNT && is_null($is)) {
+ throw new \InvalidArgumentException("\"is\" is required for room_member_count conditions");
+ }
+
+ if (
+ (
+ $kind === PushConditionKind::EVENT_MATCH ||
+ $kind === PushConditionKind::EVENT_PROPERTY_IS ||
+ $kind === PushConditionKind::EVENT_PROPERTY_CONTAINS ||
+ $kind === PushConditionKind::SENDER_NOTIFICATION_PERMISSION
+ )
+ && is_null($key)
+ ) {
+ throw new \InvalidArgumentException("\"key\" is required for event_match, event_property_is and event_property_contains conditions");
+ }
+
+ if ($kind === PushConditionKind::EVENT_MATCH && is_null($pattern)) {
+ throw new \InvalidArgumentException("\"pattern\" is required for event_match conditions");
+ }
+
+ if (($kind === PushConditionKind::EVENT_PROPERTY_IS or $kind === PushConditionKind::EVENT_PROPERTY_CONTAINS) && is_null($value)) {
+ throw new \InvalidArgumentException("\"value\" is required for event_property_is and event_property_contains conditions");
+ }
+ }
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "is" => $this->is,
+ "key" => $this->key,
+ "kind" => $this->kind,
+ "pattern" => $this->pattern,
+ "value" => $this->value,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/PushRule.php b/matrix-specification/Data/PushRule.php
new file mode 100644
index 0000000..2217a78
--- /dev/null
+++ b/matrix-specification/Data/PushRule.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Matrix\Data;
+
+class PushRule implements \JsonSerializable
+{
+ /**
+ * @param array<string|array> $actions
+ * @param PushCondition[] $conditions
+ */
+ public function __construct(
+ private array $actions,
+ private bool $default,
+ private bool $enabled,
+ private string $ruleId,
+ private ?array $conditions = null,
+ private ?string $pattern = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "actions" => $this->actions,
+ "conditions" => $this->conditions,
+ "default" => $this->default,
+ "enabled" => $this->enabled,
+ "pattern" => $this->pattern,
+ "rule_id" => $this->ruleId,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/Ruleset.php b/matrix-specification/Data/Ruleset.php
new file mode 100644
index 0000000..af75579
--- /dev/null
+++ b/matrix-specification/Data/Ruleset.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Matrix\Data;
+
+class Ruleset implements \JsonSerializable
+{
+ /**
+ * @param PushRule[] $content
+ * @param PushRule[] $override
+ * @param PushRule[] $room
+ * @param PushRule[] $sender
+ * @param PushRule[] $underride
+ */
+ public function __construct(
+ private array $content,
+ private array $override,
+ private array $room,
+ private array $sender,
+ private array $underride,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "content" => $this->content,
+ "override" => $this->override,
+ "room" => $this->room,
+ "sender" => $this->sender,
+ "underride" => $this->underride,
+ ];
+ }
+}
diff --git a/matrix-specification/Enums/PushConditionKind.php b/matrix-specification/Enums/PushConditionKind.php
new file mode 100644
index 0000000..fe61c16
--- /dev/null
+++ b/matrix-specification/Enums/PushConditionKind.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Matrix\Enums;
+
+enum PushConditionKind: string implements \JsonSerializable
+{
+ case EVENT_MATCH = "event_match";
+ case EVENT_PROPERTY_CONTAINS = "event_property_contains";
+ case EVENT_PROPERTY_IS = "event_property_is";
+ case ROOM_MEMBER_COUNT = "room_member_count";
+ case SENDER_NOTIFICATION_PERMISSION = "sender_notification_permission";
+
+ public function jsonSerialize(): string
+ {
+ return $this->value;
+ }
+}
diff --git a/matrix-specification/Responses/ClientRegisterPostResponse.php b/matrix-specification/Responses/ClientRegisterPostResponse.php
index 6ed65ce..2b6dabe 100644
--- a/matrix-specification/Responses/ClientRegisterPostResponse.php
+++ b/matrix-specification/Responses/ClientRegisterPostResponse.php
@@ -17,12 +17,16 @@ class ClientRegisterPostResponse extends Response
)
{}
- public function validateRequired(ClientRegisterPostRequest $request): void
+ public function validateRequired(/*?ClientRegisterPostRequest $request*/): void
{
+ /* if (is_null($request)) {
+ return;
+ }
+
$requestBody = $request->getBody();
if ($requestBody["inhibit_login"] === false) {
# TODO: validate
- }
+ } */
}
public function getBody(): array
diff --git a/src/Controllers/Client/ClientController.php b/src/Controllers/Client/ClientController.php
index 053d288..c9eb1fa 100644
--- a/src/Controllers/Client/ClientController.php
+++ b/src/Controllers/Client/ClientController.php
@@ -4,11 +4,14 @@ namespace App\Controllers\Client;
use App\Database;
use App\Errors\AppException;
+use App\Errors\ErrorResponse;
+use App\Errors\UnauthorizedError;
use App\Errors\UnknownError;
use App\Models\Device;
use App\Models\RoomEvent;
use App\Models\Tokens;
use App\Models\User;
+use App\Support\Id;
use App\Support\Logger;
use App\Support\Parser;
use App\Support\RequestValidator;
@@ -16,6 +19,8 @@ use Matrix\Data\AccountData;
use Matrix\Data\DeviceLists;
use Matrix\Data\LoginFlow;
use Matrix\Data\Presence;
+use Matrix\Data\PushCondition;
+use Matrix\Data\PushRule;
use Matrix\Data\Room\Ephemeral;
use Matrix\Data\Room\JoinedRoom;
use Matrix\Data\Room\RoomSummary;
@@ -23,11 +28,14 @@ use Matrix\Data\Room\Rooms;
use Matrix\Data\Room\State;
use Matrix\Data\Room\Timeline;
use Matrix\Data\Room\UnreadNotificationCounts;
+use Matrix\Data\Ruleset;
use Matrix\Data\ToDevice;
+use Matrix\Enums\AuthenticationType;
use Matrix\Enums\ErrorCode;
use Matrix\Enums\LoginType;
use Matrix\Enums\MembershipState;
use Matrix\Enums\PresenceState;
+use Matrix\Enums\PushConditionKind;
use Matrix\Enums\UserRegistrationKind;
use Matrix\Events\PresenceEvent;
use Matrix\Responses\ClientLoginGetResponse;
@@ -131,6 +139,21 @@ class ClientController
$body = json_decode($request->getContent(), true);
RequestValidator::validateJson();
+ if (empty($body)) {
+ return new JsonResponse([
+ "completed" => [],
+ "flows" => [
+ [
+ "stages" => [
+ AuthenticationType::DUMMY,
+ ],
+ ],
+ ],
+ "params" => [],
+ "session" => Id::generate(),
+ ], Response::HTTP_UNAUTHORIZED);
+ }
+
// validate kind
$kind = null;
try {
@@ -139,11 +162,24 @@ class ClientController
throw new UnknownError("Bad registration kind.", Response::HTTP_BAD_REQUEST);
}
- $username = $body["username"];
+ $username = $body["username"] ?? "";
+ if (empty($username)) {
+ $username = crc32(time() . random_bytes(64));
+ }
+
$userId = "@$username:$_ENV[DOMAIN]";
- Database::getInstance()->query("insert into users (id, password) values (:id, :password)", [
+ if (empty($body["password"])) {
+ throw new AppException(ErrorCode::MISSING_PARAM, "missing password", Response::HTTP_BAD_REQUEST);
+ }
+
+ if (Database::getInstance()->query("select id from users where id=:id", ["id" => $userId])->fetchColumn()) {
+ throw new AppException(ErrorCode::USER_IN_USE, "The desired user ID is already taken.", Response::HTTP_BAD_REQUEST);
+ }
+
+ Database::getInstance()->query("insert into users (id, name, password) values (:id, :name, :password)", [
"id" => $userId,
+ "name" => $username,
"password" => $body["password"],
]);
@@ -289,4 +325,72 @@ class ClientController
refreshToken: $newTokens->getRefreshToken(),
));
}
+
+ #[Route(path: "/_matrix/client/v3/pushrules", methods: ["GET"])]
+ public function pushRules(Request $request): Response
+ {
+ $user = User::authenticateWithRequest($request);
+
+ return new JsonResponse([
+ "global" => new Ruleset(
+ content: [
+ new PushRule(
+ actions: [
+ "notify",
+ [
+ "set_tweak" => "sound",
+ "value" => "default",
+ ],
+ [
+ "set_tweak" => "highlight",
+ ],
+ ],
+ default: true,
+ enabled: true,
+ pattern: "alice",
+ ruleId: ".m.rule.contains_user_name",
+ ),
+ ],
+
+ override: [
+ new PushRule(
+ actions: [],
+ conditions: [],
+ default: true,
+ enabled: false,
+ ruleId: ".m.rule.master",
+ ),
+ ],
+
+ room: [],
+ sender: [],
+
+ underride: [
+ new PushRule(
+ actions: [
+ "notify",
+ [
+ "set_tweak" => "sound",
+ "value" => "ring",
+ ],
+ [
+ "set_tweak" => "highlight",
+ "value" => false,
+ ],
+ ],
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.call.invite",
+ )
+ ],
+ default: true,
+ enabled: true,
+ ruleId: ".m.rule.master",
+ ),
+ ],
+ ),
+ ]);
+ }
}
diff --git a/src/Models/User.php b/src/Models/User.php
index b24afaf..4c016ad 100644
--- a/src/Models/User.php
+++ b/src/Models/User.php
@@ -22,7 +22,7 @@ class User implements ConnectsToDatabase
{
return new self(
$row["id"],
- $row["name"] ?? "",
+ $row["name"],
);
}
diff --git a/src/Router.php b/src/Router.php
index cda3006..ab0c37c 100644
--- a/src/Router.php
+++ b/src/Router.php
@@ -64,7 +64,7 @@ class Router
try {
$matcher = new UrlMatcher($this->routes, $context);
- $match = $matcher->matchRequest($request);
+ $match = $matcher->match(rtrim($request->getPathInfo(), "/"));
$class = $match["_controller"][0];
$method = $match["_controller"][1];