diff options
| -rw-r--r-- | Dockerfile | 29 | ||||
| -rwxr-xr-x | bin/create-user | 24 | ||||
| -rw-r--r-- | matrix-specification/Data/PushCondition.php | 56 | ||||
| -rw-r--r-- | matrix-specification/Data/PushRule.php | 32 | ||||
| -rw-r--r-- | matrix-specification/Data/Ruleset.php | 33 | ||||
| -rw-r--r-- | matrix-specification/Enums/PushConditionKind.php | 17 | ||||
| -rw-r--r-- | matrix-specification/Responses/ClientRegisterPostResponse.php | 8 | ||||
| -rw-r--r-- | src/Controllers/Client/ClientController.php | 108 | ||||
| -rw-r--r-- | src/Models/User.php | 2 | ||||
| -rw-r--r-- | src/Router.php | 2 |
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]; |
