1
0
Fork 0
mirror of synced 2024-06-26 18:20:43 +12:00

feat(realtime): team events and permission validation

This commit is contained in:
Torsten Dittmann 2021-06-16 19:43:06 +02:00
parent 0acbb6097c
commit 6ebf6bd155
6 changed files with 163 additions and 14 deletions

View file

@ -37,10 +37,12 @@ App::post('/v1/teams')
->inject('response')
->inject('user')
->inject('projectDB')
->action(function ($name, $roles, $response, $user, $projectDB) {
->inject('events')
->action(function ($name, $roles, $response, $user, $projectDB, $events) {
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $user */
/** @var Appwrite\Database\Database $projectDB */
/** @var Appwrite\Event\Event $events */
Authorization::disable();
@ -90,6 +92,10 @@ App::post('/v1/teams')
}
}
if (!empty($user->getId())) {
$events->setParam('userId', $user->getId());
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($team, Response::MODEL_TEAM)

View file

@ -201,6 +201,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits
if ($project->getId() !== 'console') {
$realtime
->setEvent($events->getParam('event'))
->setUserId($events->getParam('userId'))
->setProject($project->getId())
->setPayload($response->getPayload())
->trigger();

View file

@ -17,6 +17,11 @@ class Realtime
*/
protected $event = '';
/**
* @var string
*/
protected $userId = '';
/**
* @var array
*/
@ -27,6 +32,11 @@ class Realtime
*/
protected $permissions = [];
/**
* @var false
*/
protected $permissionsChanged = false;
/**
* @var Document
*/
@ -57,6 +67,16 @@ class Realtime
return $this;
}
/**
* @param string $userId
* return $this
*/
public function setUserId(string $userId): self
{
$this->userId = $userId;
return $this;
}
/**
* @return string
*/
@ -120,6 +140,25 @@ class Realtime
$this->channels[] = 'account.' . $this->payload->getId();
$this->permissions = ['user:' . $this->payload->getId()];
break;
case strpos($this->event, 'teams.memberships') === 0:
$this->channels[] = 'memberships';
$this->channels[] = 'memberships.' . $this->payload->getId();
$this->permissions = ['team:' . $this->payload->getAttribute('teamId')];
break;
case strpos($this->event, 'teams.create') === 0:
$this->permissionsChanged = true;
$this->channels[] = 'teams';
$this->channels[] = 'teams.' . $this->payload->getId();
$this->permissions = ['user:' . $this->userId];
break;
case strpos($this->event, 'teams.') === 0:
$this->channels[] = 'teams';
$this->channels[] = 'teams.' . $this->payload->getId();
$this->permissions = ['team:' . $this->payload->getId()];
break;
case strpos($this->event, 'database.collections.') === 0:
$this->channels[] = 'collections';
@ -166,6 +205,8 @@ class Realtime
$redis->publish('realtime', json_encode([
'project' => $this->project,
'permissions' => $this->permissions,
'permissionsChanged' => $this->permissionsChanged,
'userId' => $this->userId,
'data' => [
'event' => $this->event,
'channels' => $this->channels,

View file

@ -163,6 +163,7 @@ class Parser
$connections[$connection] = [
'projectId' => $projectId,
'roles' => $roles,
'channels' => $channels
];
}

View file

@ -2,6 +2,9 @@
namespace Appwrite\Realtime;
use Appwrite\Database\Database;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
use Appwrite\Database\Adapter\Redis as RedisAdapter;
use Appwrite\Event\Event;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Response;
@ -17,6 +20,7 @@ use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Exception as UtopiaException;
use Utopia\Registry\Registry;
use Utopia\Swoole\Request as SwooleRequest;
@ -176,7 +180,7 @@ class Server
return $db;
});
$this->register->set('cache', function () use (&$redis) { // Register cache connection
$this->register->set('cache', function () use (&$redis) {
return $redis;
});
@ -318,20 +322,12 @@ class Server
*/
public function onRedisPublish(string $payload, SwooleServer &$server, int $workerId)
{
/**
* Supported Resources:
* - Collection
* - Document
* - File
* - Account
* - Session
* - Team? (not implemented yet)
* - Membership? (not implemented yet)
* - Function
* - Execution
*/
$event = json_decode($payload, true);
if ($event['permissionsChanged'] && $event['userId']) {
$this->addPermission($event);
}
$receivers = Parser::identifyReceivers($event, $this->subscriptions);
// Temporarily print debug logs by default for Alpha testing.
@ -390,4 +386,43 @@ class Server
}
}
}
private function addPermission(array $event)
{
$project = $event['project'];
$userId = $event['userId'];
if (array_key_exists($project, $this->subscriptions) && array_key_exists('user:'.$userId, $this->subscriptions[$project])) {
$connection = array_key_first(reset($this->subscriptions[$project]['user:'.$userId]));
} else {
return;
}
/**
* This is redundant soon and will be gone with merging the usage branch.
*/
$db = $this->register->get('dbPool')->get();
$redis = $this->register->get('redisPool')->get();
$this->register->set('db', function () use (&$db) {
return $db;
});
$this->register->set('cache', function () use (&$redis) {
return $redis;
});
$projectDB = new Database();
$projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($this->register), $this->register));
$projectDB->setNamespace('app_'.$project);
$projectDB->setMocks(Config::getParam('collections', []));
$user = $projectDB->getDocument($userId);
Parser::setUser($user);
$roles = Parser::getRoles();
Parser::subscribe($project, $connection, $roles, $this->subscriptions, $this->connections, $this->connections[$connection]['channels']);
}
}

View file

@ -686,4 +686,69 @@ trait RealtimeBase
$client->close();
}
public function testChannelTeams()
{
$user = $this->getUser();
$session = $user['session'] ?? '';
$projectId = $this->getProject()['$id'];
$client = $this->getWebsocket(['teams'], [
'origin' => 'http://localhost',
'cookie' => 'a_session_'.$projectId.'=' . $session
]);
$response = json_decode($client->receive(), true);
$this->assertCount(1, $response);
$this->assertArrayHasKey('teams', $response);
/**
* Test Team Create
*/
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'name' => 'Arsenal'
]);
$teamId = $team['body']['$id'] ?? '';
$this->assertEquals(201, $team['headers']['status-code']);
$this->assertNotEmpty($team['body']['$id']);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('timestamp', $response);
$this->assertCount(2, $response['channels']);
$this->assertContains('teams', $response['channels']);
$this->assertContains('teams.' . $teamId, $response['channels']);
$this->assertEquals('teams.create', $response['event']);
$this->assertNotEmpty($response['payload']);
/**
* Test Team Update
*/
$team = $this->client->call(Client::METHOD_PUT, '/teams/'.$teamId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'name' => 'Manchester'
]);
$this->assertEquals($team['headers']['status-code'], 200);
$this->assertNotEmpty($team['body']['$id']);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('timestamp', $response);
$this->assertCount(2, $response['channels']);
$this->assertContains('teams', $response['channels']);
$this->assertContains('teams.' . $teamId, $response['channels']);
$this->assertEquals('teams.update', $response['event']);
$this->assertNotEmpty($response['payload']);
$client->close();
}
}