diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ded17b58c1..5af1bf7a4b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -23,6 +23,7 @@ use Utopia\Database\Validator\UID; use Utopia\Exception; use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; +use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; @@ -1182,13 +1183,15 @@ App::get('/v1/account/logs') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) + ->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) + ->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true) ->inject('response') ->inject('user') ->inject('locale') ->inject('geodb') ->inject('dbForInternal') ->inject('usage') - ->action(function ($response, $user, $locale, $geodb, $dbForInternal, $usage) { + ->action(function ($limit, $offset, $response, $user, $locale, $geodb, $dbForInternal, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Document $user */ @@ -1198,8 +1201,7 @@ App::get('/v1/account/logs') /** @var Appwrite\Stats\Stats $usage */ $audit = new Audit($dbForInternal); - - $logs = $audit->getLogsByUserAndEvents($user->getId(), [ + $auditEvents = [ 'account.create', 'account.delete', 'account.update.name', @@ -1215,7 +1217,9 @@ App::get('/v1/account/logs') 'teams.membership.create', 'teams.membership.update', 'teams.membership.delete', - ]); + ]; + + $logs = $audit->getLogsByUserAndEvents($user->getId(), $auditEvents, $limit, $offset); $output = []; @@ -1245,7 +1249,11 @@ App::get('/v1/account/logs') $usage ->setParam('users.read', 1) ; - $response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST); + + $response->dynamic(new Document([ + 'sum' => $audit->countLogsByUserAndEvents($user->getId(), $auditEvents), + 'logs' => $output, + ]), Response::MODEL_LOG_LIST); }); App::get('/v1/account/sessions/:sessionId') diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 5b228d6004..df16918fd2 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -458,12 +458,14 @@ App::get('/v1/database/collections/:collectionId/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('collectionId', '', new UID(), 'Collection unique ID.') + ->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) + ->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true) ->inject('response') ->inject('dbForInternal') ->inject('dbForExternal') ->inject('locale') ->inject('geodb') - ->action(function ($collectionId, $response, $dbForInternal, $dbForExternal, $locale, $geodb) { + ->action(function ($collectionId, $limit, $offset, $response, $dbForInternal, $dbForExternal, $locale, $geodb) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForInternal */ @@ -478,8 +480,8 @@ App::get('/v1/database/collections/:collectionId/logs') } $audit = new Audit($dbForInternal); - - $logs = $audit->getLogsByResource('collection/'.$collection->getId()); + $resource = 'collection/'.$collection->getId(); + $logs = $audit->getLogsByResource($resource, $limit, $offset); $output = []; @@ -539,7 +541,10 @@ App::get('/v1/database/collections/:collectionId/logs') } } - $response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST); + $response->dynamic(new Document([ + 'sum' => $audit->countLogsByResource($resource), + 'logs' => $output, + ]), Response::MODEL_LOG_LIST); }); App::put('/v1/database/collections/:collectionId') diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index d6c9f1212f..a398922c9d 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -259,12 +259,14 @@ App::get('/v1/users/:userId/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('userId', '', new UID(), 'User unique ID.') + ->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) + ->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true) ->inject('response') ->inject('dbForInternal') ->inject('locale') ->inject('geodb') ->inject('usage') - ->action(function ($userId, $response, $dbForInternal, $locale, $geodb, $usage) { + ->action(function ($userId, $limit, $offset, $response, $dbForInternal, $locale, $geodb, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForInternal */ @@ -279,8 +281,7 @@ App::get('/v1/users/:userId/logs') } $audit = new Audit($dbForInternal); - - $logs = $audit->getLogsByUserAndEvents($user->getId(), [ + $auditEvents = [ 'account.create', 'account.delete', 'account.update.name', @@ -296,7 +297,9 @@ App::get('/v1/users/:userId/logs') 'teams.membership.create', 'teams.membership.update', 'teams.membership.delete', - ]); + ]; + + $logs = $audit->getLogsByUserAndEvents($user->getId(), $auditEvents, $limit, $offset); $output = []; @@ -355,7 +358,11 @@ App::get('/v1/users/:userId/logs') $usage ->setParam('users.read', 1) ; - $response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST); + + $response->dynamic(new Document([ + 'sum' => $audit->countLogsByUserAndEvents($user->getId(), $auditEvents), + 'logs' => $output, + ]), Response::MODEL_LOG_LIST); }); App::patch('/v1/users/:userId/status') diff --git a/composer.json b/composer.json index 694dd2fdb0..0f5eb22d76 100644 --- a/composer.json +++ b/composer.json @@ -41,11 +41,11 @@ "utopia-php/framework": "0.19.*", "utopia-php/abuse": "0.6.*", "utopia-php/analytics": "0.2.*", - "utopia-php/audit": "0.6.*", + "utopia-php/audit": "0.7.*", "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.10.*", + "utopia-php/database": "0.11.*", "utopia-php/locale": "0.4.*", "utopia-php/orchestration": "0.2.*", "utopia-php/registry": "0.5.*", diff --git a/composer.lock b/composer.lock index c8715dcd49..6af13d6184 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "de8b7360734c246c97d8cee4779983e3", + "content-hash": "8427bbf013694d9771cd09341a81562e", "packages": [ { "name": "adhocore/jwt", @@ -1928,22 +1928,22 @@ }, { "name": "utopia-php/audit", - "version": "0.6.3", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "d79b467fbc7d03e5e02f12cdeb08761507a60ca0" + "reference": "485cdd2354db7eb8f7aa74bbe39c39b583e99c04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/d79b467fbc7d03e5e02f12cdeb08761507a60ca0", - "reference": "d79b467fbc7d03e5e02f12cdeb08761507a60ca0", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/485cdd2354db7eb8f7aa74bbe39c39b583e99c04", + "reference": "485cdd2354db7eb8f7aa74bbe39c39b583e99c04", "shasum": "" }, "require": { "ext-pdo": "*", "php": ">=7.4", - "utopia-php/database": ">=0.6 <1.0" + "utopia-php/database": ">=0.11 <1.0" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1975,9 +1975,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.6.3" + "source": "https://github.com/utopia-php/audit/tree/0.7.0" }, - "time": "2021-08-16T18:49:55+00:00" + "time": "2021-11-17T17:23:42+00:00" }, { "name": "utopia-php/cache", @@ -2138,16 +2138,16 @@ }, { "name": "utopia-php/database", - "version": "0.10.1", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "9b4697612a2cd1ad55beeb6a02570f6ffe26dc1e" + "reference": "5fc0476d05567d1a156b00e17033f32148c93a38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/9b4697612a2cd1ad55beeb6a02570f6ffe26dc1e", - "reference": "9b4697612a2cd1ad55beeb6a02570f6ffe26dc1e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/5fc0476d05567d1a156b00e17033f32148c93a38", + "reference": "5fc0476d05567d1a156b00e17033f32148c93a38", "shasum": "" }, "require": { @@ -2195,9 +2195,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.10.1" + "source": "https://github.com/utopia-php/database/tree/0.11.0" }, - "time": "2021-11-02T15:10:39+00:00" + "time": "2021-11-17T09:53:02+00:00" }, { "name": "utopia-php/domains", diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 76432ef88f..1cbc1321d5 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -194,7 +194,7 @@ class Response extends SwooleResponse ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT)) ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER)) ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION)) - ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG, false)) + ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG)) ->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE)) ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM)) ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP)) diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index bcb4208917..fc615fef8e 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -283,10 +283,10 @@ trait AccountBase $this->assertEquals('', $response['body']['sessions'][0]['deviceBrand']); $this->assertEquals('', $response['body']['sessions'][0]['deviceModel']); $this->assertEquals($response['body']['sessions'][0]['ip'], filter_var($response['body']['sessions'][0]['ip'], FILTER_VALIDATE_IP)); - + $this->assertEquals('--', $response['body']['sessions'][0]['countryCode']); $this->assertEquals('Unknown', $response['body']['sessions'][0]['countryName']); - + $this->assertEquals(true, $response['body']['sessions'][0]['current']); /** @@ -325,7 +325,8 @@ trait AccountBase $this->assertIsArray($response['body']['logs']); $this->assertNotEmpty($response['body']['logs']); $this->assertCount(2, $response['body']['logs']); - + $this->assertIsNumeric($response['body']['sum']); + $this->assertContains($response['body']['logs'][0]['event'], ['account.create', 'account.sessions.create']); $this->assertEquals($response['body']['logs'][0]['ip'], filter_var($response['body']['logs'][0]['ip'], FILTER_VALIDATE_IP)); $this->assertIsNumeric($response['body']['logs'][0]['time']); @@ -344,7 +345,7 @@ trait AccountBase $this->assertEquals('', $response['body']['logs'][0]['deviceBrand']); $this->assertEquals('', $response['body']['logs'][0]['deviceModel']); $this->assertEquals($response['body']['logs'][0]['ip'], filter_var($response['body']['logs'][0]['ip'], FILTER_VALIDATE_IP)); - + $this->assertEquals('--', $response['body']['logs'][0]['countryCode']); $this->assertEquals('Unknown', $response['body']['logs'][0]['countryName']); @@ -366,10 +367,61 @@ trait AccountBase $this->assertEquals('', $response['body']['logs'][1]['deviceBrand']); $this->assertEquals('', $response['body']['logs'][1]['deviceModel']); $this->assertEquals($response['body']['logs'][1]['ip'], filter_var($response['body']['logs'][1]['ip'], FILTER_VALIDATE_IP)); - + $this->assertEquals('--', $response['body']['logs'][1]['countryCode']); $this->assertEquals('Unknown', $response['body']['logs'][1]['countryName']); - + + $responseLimit = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'limit' => 1 + ]); + + $this->assertEquals($responseLimit['headers']['status-code'], 200); + $this->assertIsArray($responseLimit['body']['logs']); + $this->assertNotEmpty($responseLimit['body']['logs']); + $this->assertCount(1, $responseLimit['body']['logs']); + $this->assertIsNumeric($responseLimit['body']['sum']); + + $this->assertEquals($response['body']['logs'][0], $responseLimit['body']['logs'][0]); + + $responseOffset = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'offset' => 1 + ]); + + $this->assertEquals($responseOffset['headers']['status-code'], 200); + $this->assertIsArray($responseOffset['body']['logs']); + $this->assertNotEmpty($responseOffset['body']['logs']); + $this->assertCount(1, $responseOffset['body']['logs']); + $this->assertIsNumeric($responseOffset['body']['sum']); + + $this->assertEquals($response['body']['logs'][1], $responseOffset['body']['logs'][0]); + + $responseLimitOffset = $this->client->call(Client::METHOD_GET, '/account/logs', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'limit' => 1, + 'offset' => 1 + ]); + + $this->assertEquals($responseLimitOffset['headers']['status-code'], 200); + $this->assertIsArray($responseLimitOffset['body']['logs']); + $this->assertNotEmpty($responseLimitOffset['body']['logs']); + $this->assertCount(1, $responseLimitOffset['body']['logs']); + $this->assertIsNumeric($responseLimitOffset['body']['sum']); + + $this->assertEquals($response['body']['logs'][1], $responseLimitOffset['body']['logs'][0]); /** * Test for FAILURE */ diff --git a/tests/e2e/Services/Database/DatabaseConsoleClientTest.php b/tests/e2e/Services/Database/DatabaseConsoleClientTest.php index 625f26107b..e0f1cc96c7 100644 --- a/tests/e2e/Services/Database/DatabaseConsoleClientTest.php +++ b/tests/e2e/Services/Database/DatabaseConsoleClientTest.php @@ -33,7 +33,7 @@ class DatabaseConsoleClientTest extends Scope return ['moviesId' => $movies['body']['$id']]; } - + public function testGetDatabaseUsage() { /** @@ -48,7 +48,7 @@ class DatabaseConsoleClientTest extends Scope ]); $this->assertEquals($response['headers']['status-code'], 400); - + /** * Test for SUCCESS */ @@ -122,4 +122,58 @@ class DatabaseConsoleClientTest extends Scope $this->assertIsArray($response['body']['documents.update']); $this->assertIsArray($response['body']['documents.delete']); } + + /** + * @depends testCreateCollection + */ + public function testGetCollectionLogs(array $data) + { + /** + * Test for SUCCESS + */ + $logs = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertIsNumeric($logs['body']['sum']); + + $logs = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 1 + ]); + + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); + $this->assertIsNumeric($logs['body']['sum']); + + $logs = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'offset' => 1 + ]); + + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertIsNumeric($logs['body']['sum']); + + $logs = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'offset' => 1, + 'limit' => 1 + ]); + + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); + $this->assertIsNumeric($logs['body']['sum']); + } } \ No newline at end of file diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 89c9cdf8ac..17a28377f0 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -204,14 +204,6 @@ trait UsersBase $this->assertEquals($sessions['headers']['status-code'], 200); $this->assertIsArray($sessions['body']); - $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals($logs['headers']['status-code'], 200); - $this->assertIsArray($logs['body']); - $users = $this->client->call(Client::METHOD_GET, '/users', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -428,6 +420,61 @@ trait UsersBase return $data; } + + /** + * @depends testGetUser + */ + public function testGetLogs(array $data): void + { + /** + * Test for SUCCESS + */ + $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertIsNumeric($logs['body']['sum']); + + $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 1 + ]); + + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); + $this->assertIsNumeric($logs['body']['sum']); + + $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'offset' => 1 + ]); + + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertIsNumeric($logs['body']['sum']); + + $logs = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'offset' => 1, + 'limit' => 1 + ]); + + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertLessThanOrEqual(1, count($logs['body']['logs'])); + $this->assertIsNumeric($logs['body']['sum']); + } + /** * @depends testGetUser */