diff --git a/app/config/collections2.php b/app/config/collections2.php index 2e0bf087d5..365fac2c55 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -360,6 +360,254 @@ $collections = [ ], ], + 'sessions' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'sessions', + 'name' => 'Sessions', + 'attributes' => [ + [ + '$id' => 'userId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'provider', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'providerUid', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'providerToken', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'secret', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 64, // https://www.tutorialspoint.com/how-long-is-the-sha256-hash-in-mysql + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'expire', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'userAgent', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'ip', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 45, // https://stackoverflow.com/a/166157/2299554 + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'countryCode', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'osCode', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'osName', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'osVersion', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'clientType', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'clientCode', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'clientName', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'clientVersion', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'clientEngine', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'clientEngineVersion', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'deviceName', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'deviceBrand', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + [ + '$id' => 'deviceModel', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [] + ], + ], + 'indexes' => [ + // [ + // '$id' => '_key_provider_providerUid', + // 'type' => Database::INDEX_KEY, + // 'attributes' => ['provider', 'providerUid'], + // 'lengths' => [100, 100], + // 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], + // ] + ], + ], + 'teams' => [ '$collection' => Database::COLLECTIONS, '$id' => 'teams', @@ -1047,6 +1295,7 @@ $collections = [ ] ], ], + 'certificates' => [ '$collection' => Database::COLLECTIONS, '$id' => 'certificates', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 72d46ca177..4a44d4fc4d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -192,8 +192,12 @@ App::post('/v1/account/sessions') Authorization::setRole('user:'.$profile->getId()); - $profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); + $session = $dbForInternal->createDocument('sessions', $session + ->setAttribute('$read', ['user:'.$profile->getId()]) + ->setAttribute('$write', ['user:'.$profile->getId()]) + ); + $profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); $profile = $dbForInternal->updateDocument('users', $profile->getId(), $profile); $audits @@ -428,35 +432,33 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $current = Auth::sessionVerify($sessions, Auth::$secret); if($current) { // Delete current session of new one. - foreach ($sessions as $key => $session) { + foreach ($sessions as $key => $session) { /** @var Document $session */ if ($current === $session['$id']) { unset($sessions[$key]); + + $dbForInternal->deleteDocument('sessions', $session->getId()); $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions)); } } } - $user = (empty($user->getId())) ? $dbForInternal->getCollectionFirst([ // Get user by provider id - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'sessions.provider='.$provider, - 'sessions.providerUid='.$oauth2ID - ], - ]) : $user; + $user = ($user->isEmpty()) ? $dbForInternal->findFirst('sessions', [ // Get user by provider id + new Query('provider', QUERY::TYPE_EQUAL, [$provider]), + new Query('providerUid', QUERY::TYPE_EQUAL, [$oauth2ID]), + ], 1) : $user; - if (empty($user)) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email + if ($user === false || $user->isEmpty()) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email $name = $oauth2->getUserName($accessToken); $email = $oauth2->getUserEmail($accessToken); $user = $dbForInternal->findFirst('users', [new Query('email', Query::TYPE_EQUAL, [$email])], 1); // Get user by email address - if (!$user || empty($user->getId())) { // Last option -> create the user, generate random password + if ($user === false || $user->isEmpty()) { // Last option -> create the user, generate random password $limit = $project->getAttribute('usersAuthLimit', 0); if ($limit !== 0) { $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); - + if($sum >= $limit) { throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); } @@ -530,6 +532,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') Authorization::setRole('user:'.$user->getId()); + $session = $dbForInternal->createDocument('sessions', $session + ->setAttribute('$read', ['user:'.$user->getId()]) + ->setAttribute('$write', ['user:'.$user->getId()]) + ); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user); $audits @@ -668,6 +675,11 @@ App::post('/v1/account/sessions/anonymous') Authorization::setRole('user:'.$user->getId()); + $session = $dbForInternal->createDocument('sessions', $session + ->setAttribute('$read', ['user:'.$user->getId()]) + ->setAttribute('$write', ['user:'.$user->getId()]) + ); + $user = $dbForInternal->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)); @@ -814,9 +826,7 @@ App::get('/v1/account/sessions') $countries = $locale->getText('countries'); $current = Auth::sessionVerify($sessions, Auth::$secret); - foreach ($sessions as $key => $session) { - /** @var Document $session */ - + foreach ($sessions as $key => $session) { /** @var Document $session */ $countryName = (isset($countries[strtoupper($session->getAttribute('countryCode'))])) ? $countries[strtoupper($session->getAttribute('countryCode'))] : $locale->getText('locale.country.unknown'); @@ -1213,12 +1223,12 @@ App::delete('/v1/account/sessions/:sessionId') $sessions = $user->getAttribute('sessions', []); - foreach ($sessions as $key => $session) { - /** @var Document $session */ - + foreach ($sessions as $key => $session) { /** @var Document $session */ if ($sessionId == $session->getId()) { unset($sessions[$key]); + $dbForInternal->deleteDocument('sessions', $session->getId()); + $audits ->setParam('userId', $user->getId()) ->setParam('event', 'account.sessions.delete') @@ -1289,8 +1299,8 @@ App::delete('/v1/account/sessions') $protocol = $request->getProtocol(); $sessions = $user->getAttribute('sessions', []); - foreach ($sessions as $session) { - /** @var Document $session */ + foreach ($sessions as $session) { /** @var Document $session */ + $dbForInternal->deleteDocument('sessions', $session->getId()); $audits ->setParam('userId', $user->getId()) @@ -1371,7 +1381,7 @@ App::post('/v1/account/recovery') $profile = $dbForInternal->findFirst('users', [new Query('email', Query::TYPE_EQUAL, [$email])], 1); // Get user by email address if (!$profile) { - throw new Exception('User not found', 404); // TODO maybe hide this + throw new Exception('User not found', 404); } if (Auth::USER_STATUS_BLOCKED == $profile->getAttribute('status')) { // Account is blocked @@ -1482,7 +1492,7 @@ App::put('/v1/account/recovery') $profile = $dbForInternal->getDocument('users', $userId); if ($profile->isEmpty()) { - throw new Exception('User not found', 404); // TODO maybe hide this + throw new Exception('User not found', 404); } $tokens = $profile->getAttribute('tokens', []); @@ -1662,7 +1672,7 @@ App::put('/v1/account/verification') $profile = $dbForInternal->getDocument('users', $userId); if ($profile->isEmpty()) { - throw new Exception('User not found', 404); // TODO maybe hide this + throw new Exception('User not found', 404); } $tokens = $profile->getAttribute('tokens', []); diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 01bfd8c69a..fe2fc39127 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -143,7 +143,6 @@ App::get('/v1/functions/:functionId/usage') ->action(function ($functionId, $range, $response, $project, $dbForInternal, $register) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ - /** @var Utopia\Database\Database $consoleDB */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Registry\Registry $register */ diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 39ced4b747..a8022cafcd 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -608,6 +608,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') // Log user in + Authorization::setRole('user:'.$user->getId()); + $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; @@ -624,6 +626,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', ], $detector->getOS(), $detector->getClient(), $detector->getDevice())); + $session = $dbForInternal->createDocument('sessions', $session + ->setAttribute('$read', ['user:'.$user->getId()]) + ->setAttribute('$write', ['user:'.$user->getId()]) + ); + $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); Authorization::setRole('user:'.$userId); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 9777e5b250..96e9af7872 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -430,12 +430,13 @@ App::delete('/v1/users/:userId/sessions/:sessionId') $sessions = $user->getAttribute('sessions', []); - foreach ($sessions as $key => $session) { - /** @var Document $session */ + foreach ($sessions as $key => $session) { /** @var Document $session */ if ($sessionId == $session->getId()) { unset($sessions[$key]); + $dbForInternal->deleteDocument('sessions', $session->getId()); + $user->setAttribute('sessions', $sessions); $events @@ -476,13 +477,18 @@ App::delete('/v1/users/:userId/sessions') throw new Exception('User not found', 404); } + $sessions = $user->getAttribute('sessions', []); + + foreach ($sessions as $key => $session) { /** @var Document $session */ + $dbForInternal->deleteDocument('sessions', $session->getId()); + } + $dbForInternal->updateDocument('users', $user->getId(), $user->getAttribute('sessions', [])); $events ->setParam('eventData', $response->output2($user, Response::MODEL_USER)) ; - // TODO : Response filter implementation $response->noContent(); }); diff --git a/app/controllers/general.php b/app/controllers/general.php index cfe06effb1..88dcb5d152 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -57,7 +57,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons $certificate = $dbForConsole->createDocument('certificates', $certificate); Authorization2::enable(); - Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in a few seconds...'); // TODO move this to installation script + Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in a few seconds...'); Resque::enqueue('v1-certificates', 'CertificatesV1', [ 'document' => $certificate, @@ -370,10 +370,12 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) { $comp = new View($template); $comp + ->setParam('development', App::isDevelopment()) ->setParam('projectName', $project->getAttribute('name')) ->setParam('projectURL', $project->getAttribute('url')) ->setParam('message', $error->getMessage()) ->setParam('code', $code) + ->setParam('trace', $error->getTrace()) ; $layout diff --git a/app/views/general/error.phtml b/app/views/general/error.phtml index be7e3dc9c5..66f6cf77d7 100644 --- a/app/views/general/error.phtml +++ b/app/views/general/error.phtml @@ -1,7 +1,9 @@ getParam('development', false); $code = $this->getParam('code', 500); $errorID = $this->getParam('errorID', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'); $message = $this->getParam('message', ''); +$trace = $this->getParam('trace', []); $projectName = $this->getParam('projectName', ''); $projectURL = $this->getParam('projectURL', ''); ?> @@ -18,4 +20,30 @@ $projectURL = $this->getParam('projectURL', '');

Back to

+ + +
+ +

Error Trace

+ + + + $value): ?> + + + + + + +
+ + + + + +
+ +
+ + diff --git a/app/workers/functions.php b/app/workers/functions.php index 8cb6fafbb6..6b8910cece 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -123,7 +123,7 @@ Console::info(count($list)." functions listed in " . ($executionEnd - $execution * 7. Trigger usage log - DONE */ -//TODO aviod scheduled execution if delay is bigger than X offest +//TODO avoid scheduled execution if delay is bigger than X offest class FunctionsV1 extends Worker { diff --git a/composer.lock b/composer.lock index c0fbb00ce8..0ce07f2453 100644 --- a/composer.lock +++ b/composer.lock @@ -6258,5 +6258,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.0.0" } diff --git a/src/Appwrite/Utopia/Response/Model/Task.php b/src/Appwrite/Utopia/Response/Model/Task.php index fa5816ed15..4d5c8a963f 100644 --- a/src/Appwrite/Utopia/Response/Model/Task.php +++ b/src/Appwrite/Utopia/Response/Model/Task.php @@ -96,7 +96,7 @@ class Task extends Model ]) ->addRule('status', [ 'type' => self::TYPE_STRING, - 'description' => 'Task status. Possible values: play, pause', // TODO - change to enabled disabled + 'description' => 'Task status. Possible values: play, pause', 'default' => '', 'example' => 'enabled', ]) diff --git a/src/Appwrite/Utopia/Response/Model/Team.php b/src/Appwrite/Utopia/Response/Model/Team.php index b5f7482a19..48660324f5 100644 --- a/src/Appwrite/Utopia/Response/Model/Team.php +++ b/src/Appwrite/Utopia/Response/Model/Team.php @@ -28,7 +28,7 @@ class Team extends Model 'default' => 0, 'example' => 1592981250, ]) - ->addRule('sum', [ // TODO change key name? + ->addRule('sum', [ 'type' => self::TYPE_INTEGER, 'description' => 'Total sum of team members.', 'default' => 0, diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 7c04b59f63..5ff9d46d7c 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -14,6 +14,9 @@ class AccountCustomClientTest extends Scope use ProjectCustom; use SideClient; + /** + * @depends testCreateAccountSession + */ public function testCreateOAuth2AccountSession():array { $provider = 'mock'; @@ -384,6 +387,17 @@ class AccountCustomClientTest extends Scope /** * Test for SUCCESS */ + $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ])); + + $this->assertEquals($response['headers']['status-code'], 200); + + $userId = $response['body']['$id'] ?? ''; + $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$this->getProject()['$id'].'/oauth2', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -406,6 +420,8 @@ class AccountCustomClientTest extends Scope 'success' => 'http://localhost/v1/mock/tests/general/oauth2/success', 'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure', ]); + + $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('success', $response['body']['result']); @@ -418,6 +434,7 @@ class AccountCustomClientTest extends Scope ])); $this->assertEquals($response['headers']['status-code'], 200); + $this->assertEquals($response['body']['$id'], $userId); $this->assertEquals($response['body']['name'], 'User Name'); $this->assertEquals($response['body']['email'], 'user@localhost.test');