Misc fixes, readme, comments
This commit is contained in:
parent
813150c92b
commit
83c34f4a47
59
README.md
59
README.md
@ -2,21 +2,52 @@
|
||||
|
||||
This is an implementation of the Mercure realtime protocol on steroids, built using ReactPHP.
|
||||
|
||||
**Mercureact is under development, and not ready for use in anything important.**
|
||||
|
||||
It is intended to be used standalone, but it may also be integrated into another PHP application.
|
||||
|
||||
## Installing
|
||||
|
||||
As PHAR:
|
||||
|
||||
* Download the latest [release](/noccy/mercureact/releases) from the forge.
|
||||
|
||||
As Composer dependency:
|
||||
|
||||
* `composer require noccylabs/mercureact`
|
||||
|
||||
## Using as PHAR
|
||||
|
||||
*TODO.*
|
||||
|
||||
```shell
|
||||
# Make a copy of the dist config and edit it
|
||||
$ cp mercureact.conf.dist mercurect.conf
|
||||
$ editor mercureact.conf
|
||||
# Use the config file when launching
|
||||
$ ./mercureact.phar -c mercureact.conf
|
||||
```
|
||||
|
||||
## Using as dependency
|
||||
|
||||
*TODO.*
|
||||
|
||||
## ToDos
|
||||
|
||||
**Mercureact is under development, and not ready for use in anything important.**
|
||||
|
||||
- [ ] Security Security Security
|
||||
- [ ] Check JWTs on connect
|
||||
- [ ] Check claims on subscribe and publish
|
||||
- [ ] WebSocket authentication
|
||||
- [ ] Subscription manager
|
||||
- [ ] Publish events
|
||||
- [ ] Server-Side Events distributor
|
||||
- [x] Distribute events
|
||||
- [ ] WebSocket distributor
|
||||
- [ ] Setup subscriptions
|
||||
- [ ] Dynamic subscriptions
|
||||
- [x] Distribute events
|
||||
* [ ] Read config from file
|
||||
* [ ] Security Security Security
|
||||
* [x] Check JWTs on connect
|
||||
* [ ] Check claims on subscribe and publish
|
||||
* [ ] WebSocket authentication
|
||||
* [ ] Subscription/Topic manager
|
||||
* [ ] Unify distribution
|
||||
* [ ] Publish events
|
||||
* [ ] Server-Side Events distributor
|
||||
* [x] Distribute events over SSE
|
||||
* [ ] WebSocket distributor
|
||||
* [ ] Setup subscriptions
|
||||
* [ ] Dynamic subscriptions
|
||||
* [x] Distribute events over WS
|
||||
* [ ] HTTP
|
||||
* [ ] Break out HTTP middleware into classes
|
||||
* [ ] HTTP middleware unittests
|
36
composer.lock
generated
36
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "67776ecc95ce924ca24488a6af3615eb",
|
||||
"content-hash": "794f155397a10dd2b61efedc40a661e8",
|
||||
"packages": [
|
||||
{
|
||||
"name": "evenement/evenement",
|
||||
@ -1676,16 +1676,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.10.59",
|
||||
"version": "1.10.60",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "e607609388d3a6d418a50a49f7940e8086798281"
|
||||
"reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e607609388d3a6d418a50a49f7940e8086798281",
|
||||
"reference": "e607609388d3a6d418a50a49f7940e8086798281",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe",
|
||||
"reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1734,20 +1734,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-20T13:59:13+00:00"
|
||||
"time": "2024-03-07T13:30:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "11.0.1",
|
||||
"version": "11.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "89702be0ad026873ef3a1605fe8726254eef4e2c"
|
||||
"reference": "9e0a298b4dc6438a1e70ac8e1b3ea4980ae5a09b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/89702be0ad026873ef3a1605fe8726254eef4e2c",
|
||||
"reference": "89702be0ad026873ef3a1605fe8726254eef4e2c",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9e0a298b4dc6438a1e70ac8e1b3ea4980ae5a09b",
|
||||
"reference": "9e0a298b4dc6438a1e70ac8e1b3ea4980ae5a09b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1804,7 +1804,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.1"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1812,7 +1812,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-02T07:34:25+00:00"
|
||||
"time": "2024-03-09T16:56:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
@ -2061,16 +2061,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "11.0.4",
|
||||
"version": "11.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "3f4261269c91370e9b2b3f64cc76c617c442c35a"
|
||||
"reference": "da2de3900beab025398ba37705b0f5ecafb3e1ab"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3f4261269c91370e9b2b3f64cc76c617c442c35a",
|
||||
"reference": "3f4261269c91370e9b2b3f64cc76c617c442c35a",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/da2de3900beab025398ba37705b0f5ecafb3e1ab",
|
||||
"reference": "da2de3900beab025398ba37705b0f5ecafb3e1ab",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2141,7 +2141,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.0.4"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -2157,7 +2157,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-29T16:21:10+00:00"
|
||||
"time": "2024-03-09T12:12:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
12
make-phar.sh
Executable file
12
make-phar.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
REPO=$PWD
|
||||
DIR=mercureact
|
||||
pushd /tmp
|
||||
rm -rf /tmp/$DIR
|
||||
git clone "$REPO"
|
||||
cd $DIR
|
||||
composer install --no-dev
|
||||
pharlite
|
||||
popd
|
||||
mv /tmp/$DIR/*.phar .
|
||||
rm -rf /tmp/$DIR
|
12
phpstan.neon
Normal file
12
phpstan.neon
Normal file
@ -0,0 +1,12 @@
|
||||
parameters:
|
||||
level: 5
|
||||
|
||||
excludePaths:
|
||||
- doc
|
||||
- vendor
|
||||
- tests
|
||||
|
||||
# Paths to include in the analysis
|
||||
paths:
|
||||
- src
|
||||
|
@ -7,4 +7,5 @@ use Exception;
|
||||
class SecurityException extends Exception
|
||||
{
|
||||
const ERR_ACCESS_DENIED = 50001;
|
||||
const ERR_NO_PERMISSION = 50002;
|
||||
}
|
@ -80,6 +80,7 @@ class Server
|
||||
*/
|
||||
private function createHttpServer(array $options): HttpServer
|
||||
{
|
||||
// TODO break out the middleware to facilitate testing
|
||||
return new HttpServer(
|
||||
$this->rejectionWrappingMiddleware(...),
|
||||
$this->checkRequestSecurityMiddleware(...),
|
||||
@ -91,7 +92,7 @@ class Server
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Resolves unhandled requests with a 404 error
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return PromiseInterface
|
||||
@ -106,7 +107,8 @@ class Server
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Wraps rejections into error messages, and also does some sanity checks on the returned
|
||||
* data, making sure it is a response.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param callable $next
|
||||
@ -275,6 +277,22 @@ class Server
|
||||
throw new \Exception("Invalid request");
|
||||
}
|
||||
|
||||
// Parse out the urlencoded body. Pretty sure there is a better way to do this?
|
||||
$body = explode("&", (string)$request->getBody());
|
||||
$data = [];
|
||||
foreach ($body as $param) {
|
||||
if (!str_contains($param, "="))
|
||||
throw new RequestException("Invalid request data", RequestException::ERR_INVALID_REQUEST_DATA);
|
||||
[ $name, $value ] = array_map('urldecode', explode("=", $param, 2));
|
||||
if (in_array($name, [ 'topic' ])) {
|
||||
if (!isset($data[$name]))
|
||||
$data[$name] = [];
|
||||
$data[$name][] = $value;
|
||||
} else {
|
||||
$data[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the JWT token from the requests authorization attribute
|
||||
$tok = $request->getAttribute('authorization');
|
||||
if ($tok instanceof JWTToken) {
|
||||
@ -282,22 +300,9 @@ class Server
|
||||
if (isset($claims['mercure']['publish'])) {
|
||||
$publishClaims = $claims['mercure']['publish'];
|
||||
// TODO check topic against publishClaims
|
||||
}
|
||||
}
|
||||
|
||||
// Parse out the urlencoded body. Pretty sure there is a better way to do this?
|
||||
$body = explode("&", (string)$request->getBody());
|
||||
$data = [];
|
||||
foreach ($body as $param) {
|
||||
if (!str_contains($param, "=")) throw new RequestException("Invalid request data", RequestException::ERR_INVALID_REQUEST_DATA);
|
||||
[ $name, $value ] = array_map('urldecode', explode("=", $param, 2));
|
||||
// FIXME support multiple topics?
|
||||
if (in_array($name, [ 'topic' ])) {
|
||||
if (!isset($data[$name]))
|
||||
$data[$name] = [];
|
||||
$data[$name][] = $value;
|
||||
} else {
|
||||
$data[$name] = $value;
|
||||
if (!$this->checkTopicClaims($data['topic']??[], $publishClaims)) {
|
||||
throw new SecurityException("Insufficient permissions for publish", SecurityException::ERR_NO_PERMISSION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,6 +321,18 @@ class Server
|
||||
return Response::plaintext("urn:uuid:".$message->id."\n");
|
||||
}
|
||||
|
||||
private function checkTopicClaims(string|array $topic, array $claims): bool
|
||||
{
|
||||
foreach ((array)$topic as $match) {
|
||||
foreach ($claims as $claim) {
|
||||
if ($claim === "*") return true;
|
||||
if ($claim === $match) return true;
|
||||
// TODO implement full matching
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
@ -327,7 +344,7 @@ class Server
|
||||
foreach ($this->webSocketClients as $webSocket) {
|
||||
$webSocket->write(json_encode([
|
||||
'type' => $message->type,
|
||||
//'topic' => $data['topic'],
|
||||
'topic' => $message->topic,
|
||||
'data' => (@json_decode($message->data))??$message->data
|
||||
]));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user