diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ded17b58c..5af1bf7a4 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 5b228d600..df16918fd 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 d6c9f1212..a398922c9 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 694dd2fdb..0f5eb22d7 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 c8715dcd4..6af13d618 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 76432ef88..1cbc1321d 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 bcb420891..fc615fef8 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 625f26107..e0f1cc96c 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 89c9cdf8a..17a28377f 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 */