diff --git a/app/config/events.php b/app/config/events.php index 0936cab1e..db1bbc243 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -79,4 +79,22 @@ return [ 'users.sessions.delete' => [ 'description' => 'This event triggers when a user session is deleted from users API.', ], + 'teams.create' => [ + 'description' => 'This event triggers when a team is created.', + ], + 'teams.update' => [ + 'description' => 'This event triggers when a team is updated.', + ], + 'teams.delete' => [ + 'description' => 'This event triggers when a team is deleted.', + ], + 'teams.memberships.create' => [ + 'description' => 'This event triggers when a team memberships is created.', + ], + 'teams.memberships.update.status' => [ + 'description' => 'This event triggers when a team memberships status is updated.', + ], + 'teams.memberships.delete' => [ + 'description' => 'This event triggers when a team memberships is deleted.', + ], ]; \ No newline at end of file diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 37fbad335..33ba1b333 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -23,6 +23,7 @@ use DeviceDetector\DeviceDetector; App::post('/v1/teams') ->desc('Create Team') ->groups(['api', 'teams']) + ->label('event', 'teams.create') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -157,6 +158,7 @@ App::get('/v1/teams/:teamId') App::put('/v1/teams/:teamId') ->desc('Update Team') ->groups(['api', 'teams']) + ->label('event', 'teams.update') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -191,6 +193,7 @@ App::put('/v1/teams/:teamId') App::delete('/v1/teams/:teamId') ->desc('Delete Team') ->groups(['api', 'teams']) + ->label('event', 'teams.delete') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -200,9 +203,10 @@ App::delete('/v1/teams/:teamId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_NONE) ->param('teamId', '', new UID(), 'Team unique ID.') - ->action(function ($teamId, $response, $projectDB) { + ->action(function ($teamId, $response, $projectDB, $webhooks) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhooks */ $team = $projectDB->getDocument($teamId); @@ -229,12 +233,17 @@ App::delete('/v1/teams/:teamId') throw new Exception('Failed to remove team from DB', 500); } + $webhooks + ->setParam('payload', $response->output($team, Response::MODEL_TEAM)) + ; + $response->noContent(); - }, ['response', 'projectDB']); + }, ['response', 'projectDB', 'webhooks']); App::post('/v1/teams/:teamId/memberships') ->desc('Create Team Membership') ->groups(['api', 'teams']) + ->label('event', 'teams.memberships.create') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -483,6 +492,7 @@ App::get('/v1/teams/:teamId/memberships') App::patch('/v1/teams/:teamId/memberships/:inviteId/status') ->desc('Update Team Membership Status') ->groups(['api', 'teams']) + ->label('event', 'teams.memberships.update.status') ->label('scope', 'public') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'teams') @@ -662,6 +672,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') App::delete('/v1/teams/:teamId/memberships/:inviteId') ->desc('Delete Team Membership') ->groups(['api', 'teams']) + ->label('event', 'teams.memberships.delete') ->label('scope', 'teams.write') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'teams') @@ -672,10 +683,11 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('teamId', '', new UID(), 'Team unique ID.') ->param('inviteId', '', new UID(), 'Invite unique ID.') - ->action(function ($teamId, $inviteId, $response, $projectDB, $audits) { + ->action(function ($teamId, $inviteId, $response, $projectDB, $audits, $webhooks) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $webhooks */ $membership = $projectDB->getDocument($inviteId); @@ -713,5 +725,9 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ->setParam('resource', 'teams/'.$teamId) ; + $webhooks + ->setParam('payload', $response->output($membership, Response::MODEL_MEMBERSHIP)) + ; + $response->noContent(); - }, ['response', 'projectDB', 'audits']); + }, ['response', 'projectDB', 'audits', 'webhooks']); diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index ca3f27222..55157c5e4 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -118,6 +118,12 @@ trait ProjectCustom 'users.update.status', 'users.delete', 'users.sessions.delete', + 'teams.create', + 'teams.update', + 'teams.delete', + 'teams.memberships.create', + 'teams.memberships.update.status', + 'teams.memberships.delete', ], 'url' => 'http://request-catcher:5000/webhook', 'security' => false, diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 76a23d5d8..a29dbf6ed 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -303,4 +303,226 @@ trait WebhooksBase return $data; } + + public function testCreateTeam(): array + { + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Arsenal' + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals('Arsenal', $webhook['data']['name']); + $this->assertGreaterThan(-1, $webhook['data']['sum']); + $this->assertIsInt($webhook['data']['sum']); + $this->assertIsInt($webhook['data']['dateCreated']); + + /** + * Test for FAILURE + */ + return ['teamId' => $team['body']['$id']]; + } + + /** + * @depends testCreateTeam + */ + public function testUpdateTeam($data): array + { + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_PUT, '/teams/'.$data['teamId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Demo New' + ]); + + $this->assertEquals(200, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.update'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals('Demo New', $webhook['data']['name']); + $this->assertGreaterThan(-1, $webhook['data']['sum']); + $this->assertIsInt($webhook['data']['sum']); + $this->assertIsInt($webhook['data']['dateCreated']); + + /** + * Test for FAILURE + */ + return ['teamId' => $team['body']['$id']]; + } + + public function testDeleteTeam(): array + { + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Chelsea' + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $team = $this->client->call(Client::METHOD_DELETE, '/teams/'.$team['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals('Chelsea', $webhook['data']['name']); + $this->assertGreaterThan(-1, $webhook['data']['sum']); + $this->assertIsInt($webhook['data']['sum']); + $this->assertIsInt($webhook['data']['dateCreated']); + + /** + * Test for FAILURE + */ + return []; + } + + /** + * @depends testCreateTeam + */ + public function testCreateTeamMembership($data): array + { + $teamUid = $data['teamId'] ?? ''; + $email = uniqid().'friend@localhost.test'; + + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => $email, + 'name' => 'Friend User', + 'roles' => ['admin', 'editor'], + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $lastEmail = $this->getLastEmail(); + + $secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); + $inviteUid = substr($lastEmail['text'], strpos($lastEmail['text'], '?inviteId=', 0) + 10, 13); + $userUid = substr($lastEmail['text'], strpos($lastEmail['text'], '&userId=', 0) + 8, 13); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.create'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['teamId']); + $this->assertCount(2, $webhook['data']['roles']); + $this->assertIsInt($webhook['data']['joined']); + $this->assertEquals(('server' === $this->getSide()), $webhook['data']['confirm']); + + /** + * Test for FAILURE + */ + return [ + 'teamId' => $teamUid, + 'secret' => $secret, + 'inviteId' => $inviteUid, + 'userId' => $webhook['data']['userId'], + ]; + } + + /** + * @depends testCreateTeam + */ + public function testDeleteTeamMembership($data): array + { + $teamUid = $data['teamId'] ?? ''; + $email = uniqid().'friend@localhost.test'; + + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => $email, + 'name' => 'Friend User', + 'roles' => ['admin', 'editor'], + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $team = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$team['body']['$id'], array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $team['headers']['status-code']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['teamId']); + $this->assertCount(2, $webhook['data']['roles']); + $this->assertIsInt($webhook['data']['joined']); + $this->assertEquals(('server' === $this->getSide()), $webhook['data']['confirm']); + + /** + * Test for FAILURE + */ + return []; + } } \ No newline at end of file diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index b2eadc9ab..09136bcbb 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -680,4 +680,50 @@ class WebhooksCustomClientTest extends Scope return $data; } + + /** + * @depends testCreateTeamMembership + */ + public function testUpdateTeamMembership($data): array + { + $teamUid = $data['teamId'] ?? ''; + $secret = $data['secret'] ?? ''; + $inviteUid = $data['inviteId'] ?? ''; + $userUid = $data['userId'] ?? ''; + + /** + * Test for SUCCESS + */ + $team = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$inviteUid.'/status', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'secret' => $secret, + 'userId' => $userUid, + ]); + + $this->assertEquals(200, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.update.status'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-Userid'] ?? ''), true); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertNotEmpty($webhook['data']['userId']); + $this->assertNotEmpty($webhook['data']['teamId']); + $this->assertCount(2, $webhook['data']['roles']); + $this->assertIsInt($webhook['data']['joined']); + $this->assertEquals(true, $webhook['data']['confirm']); + + /** + * Test for FAILURE + */ + return []; + } } \ No newline at end of file