From 766b2ba13e2276ea719961bfaef7113ee10ed559 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Apr 2024 22:17:07 +0200 Subject: [PATCH] Avatars tests are green! --- app/controllers/api/account.php | 192 +++++++++++----------- app/controllers/api/projects.php | 32 ++-- app/controllers/api/teams.php | 66 ++++---- app/controllers/api/users.php | 12 +- app/controllers/general.php | 27 ++- app/controllers/shared/api.php | 65 ++++---- app/controllers/shared/api/auth.php | 13 +- app/init2.php | 135 +++++++++++---- composer.lock | 8 +- src/Appwrite/Utopia/Queue/Connections.php | 36 +--- 10 files changed, 330 insertions(+), 256 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9fb55c46ac..bc5efd6aab 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -58,13 +58,13 @@ use Utopia\System\System; $oauthDefaultSuccess = '/auth/oauth2/success'; $oauthDefaultFailure = '/auth/oauth2/failure'; -$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Authorization $auth) { - $roles = $auth->getRoles(); +$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Authorization $authorization) { + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); /** @var Utopia\Database\Document $user */ - $userFromRequest = $auth->skip(fn () => $dbForProject->getDocument('users', $userId)); + $userFromRequest = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId)); if ($userFromRequest->isEmpty()) { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -110,7 +110,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res $detector->getDevice() )); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $session = $dbForProject->createDocument('sessions', $session ->setAttribute('$permissions', [ @@ -120,7 +120,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res ])); $dbForProject->purgeCachedDocument('users', $user->getId()); - $auth->skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId())); + $authorization->skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId())); $dbForProject->purgeCachedDocument('users', $user->getId()); if ($verifiedToken->getAttribute('type') === Auth::TOKEN_TYPE_MAGIC_URL) { @@ -193,8 +193,8 @@ Http::post('/v1/account') ->inject('dbForProject') ->inject('queueForEvents') ->inject('hooks') - ->inject('auth') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Authorization $auth) { + ->inject('authorization') + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Authorization $authorization) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -269,9 +269,9 @@ Http::post('/v1/account') 'accessedAt' => DateTime::now(), ]); $user->removeAttribute('$internalId'); - $user = $auth->skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); try { - $target = $auth->skip(fn () => $dbForProject->createDocument('targets', new Document([ + $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([ '$permissions' => [ Permission::read(Role::user($user->getId())), Permission::update(Role::user($user->getId())), @@ -295,9 +295,9 @@ Http::post('/v1/account') throw new Exception(Exception::USER_ALREADY_EXISTS); } - $auth->removeRole(Role::guests()->toString()); - $auth->addRole(Role::user($user->getId())->toString()); - $auth->addRole(Role::users()->toString()); + $authorization->removeRole(Role::guests()->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::users()->toString()); $queueForEvents->setParam('userId', $user->getId()); @@ -392,10 +392,10 @@ Http::get('/v1/account/sessions') ->inject('response') ->inject('user') ->inject('locale') - ->inject('auth') - ->action(function (Response $response, Document $user, Locale $locale, Authorization $auth) { + ->inject('authorization') + ->action(function (Response $response, Document $user, Locale $locale, Authorization $authorization) { - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -500,10 +500,10 @@ Http::get('/v1/account/sessions/:sessionId') ->inject('response') ->inject('user') ->inject('locale') - ->inject('auth') - ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Authorization $auth) { + ->inject('authorization') + ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Authorization $authorization) { - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -714,8 +714,8 @@ Http::post('/v1/account/sessions/email') ->inject('geodb') ->inject('queueForEvents') ->inject('hooks') - ->inject('auth') - ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Hooks $hooks, Authorization $auth) { + ->inject('authorization') + ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Hooks $hooks, Authorization $authorization) { $email = \strtolower($email); $protocol = $request->getProtocol(); @@ -731,7 +731,7 @@ Http::post('/v1/account/sessions/email') throw new Exception(Exception::USER_BLOCKED); // User is in status blocked } - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -762,7 +762,7 @@ Http::post('/v1/account/sessions/email') $detector->getDevice() )); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); // Re-hash if not using recommended algo if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) { @@ -837,10 +837,10 @@ Http::post('/v1/account/sessions/anonymous') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->inject('auth') - ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $auth) { + ->inject('authorization') + ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization) { $protocol = $request->getProtocol(); - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -886,7 +886,7 @@ Http::post('/v1/account/sessions/anonymous') 'accessedAt' => DateTime::now(), ]); $user->removeAttribute('$internalId'); - $auth->skip(fn () => $dbForProject->createDocument('users', $user)); + $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); // Create session token $duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; @@ -912,7 +912,7 @@ Http::post('/v1/account/sessions/anonymous') $detector->getDevice() )); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $session = $dbForProject->createDocument('sessions', $session-> setAttribute('$permissions', [ Permission::read(Role::user($user->getId())), @@ -1138,8 +1138,8 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') ->inject('dbForProject') ->inject('geodb') ->inject('queueForEvents') - ->inject('auth') - ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $auth) use ($oauthDefaultSuccess) { + ->inject('authorization') + ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization) use ($oauthDefaultSuccess) { $protocol = $request->getProtocol(); $callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; @@ -1363,7 +1363,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') 'accessedAt' => DateTime::now(), ]); $user->removeAttribute('$internalId'); - $user = $auth->skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); $dbForProject->createDocument('targets', new Document([ '$permissions' => [ @@ -1382,8 +1382,8 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') } } - $auth->addRole(Role::user($user->getId())->toString()); - $auth->addRole(Role::users()->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::users()->toString()); if (false === $user->getAttribute('status')) { // Account is blocked $failureRedirect(Exception::USER_BLOCKED); // User is in status blocked @@ -1442,7 +1442,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') $dbForProject->updateDocument('users', $user->getId(), $user); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $state['success'] = URLParser::parse($state['success']); $query = URLParser::parseQuery($state['success']['query']); @@ -1464,7 +1464,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect') 'ip' => $request->getIP(), ]); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -1663,8 +1663,8 @@ Http::post('/v1/account/tokens/magic-url') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('Auth') - ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $auth) { + ->inject('authorization') + ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); @@ -1674,7 +1674,7 @@ Http::post('/v1/account/tokens/magic-url') $phrase = Phrase::generate(); } - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -1729,7 +1729,7 @@ Http::post('/v1/account/tokens/magic-url') ]); $user->removeAttribute('$internalId'); - $user = $auth->skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); } $tokenSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_MAGIC_URL); @@ -1746,7 +1746,7 @@ Http::post('/v1/account/tokens/magic-url') 'ip' => $request->getIP(), ]); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -1906,8 +1906,8 @@ Http::post('/v1/account/tokens/email') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('auth') - ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $auth) { + ->inject('authorization') + ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); } @@ -1916,7 +1916,7 @@ Http::post('/v1/account/tokens/email') $phrase = Phrase::generate(); } - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -1969,7 +1969,7 @@ Http::post('/v1/account/tokens/email') ]); $user->removeAttribute('$internalId'); - $user = $auth->skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); } $tokenSecret = Auth::codeGenerator(6); @@ -1986,7 +1986,7 @@ Http::post('/v1/account/tokens/email') 'ip' => $request->getIP(), ]); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -2136,7 +2136,7 @@ Http::put('/v1/account/sessions/magic-url') ->inject('locale') ->inject('geodb') ->inject('queueForEvents') - ->inject('auth') + ->inject('authorization') ->action($createSession); Http::put('/v1/account/sessions/phone') @@ -2167,7 +2167,7 @@ Http::put('/v1/account/sessions/phone') ->inject('locale') ->inject('geodb') ->inject('queueForEvents') - ->inject('auth') + ->inject('authorization') ->action($createSession); Http::post('/v1/account/tokens/phone') @@ -2198,13 +2198,13 @@ Http::post('/v1/account/tokens/phone') ->inject('queueForEvents') ->inject('queueForMessaging') ->inject('locale') - ->inject('auth') - ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, Authorization $auth) { + ->inject('authorization') + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, Authorization $authorization) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -2248,9 +2248,9 @@ Http::post('/v1/account/tokens/phone') ]); $user->removeAttribute('$internalId'); - $user = $auth->skip(fn () => $dbForProject->createDocument('users', $user)); + $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user)); try { - $target = $auth->skip(fn () => $dbForProject->createDocument('targets', new Document([ + $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([ '$permissions' => [ Permission::read(Role::user($user->getId())), Permission::update(Role::user($user->getId())), @@ -2285,7 +2285,7 @@ Http::post('/v1/account/tokens/phone') 'ip' => $request->getIP(), ]); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $token = $dbForProject->createDocument('tokens', $token ->setAttribute('$permissions', [ @@ -2427,8 +2427,8 @@ Http::get('/v1/account/logs') ->inject('locale') ->inject('geodb') ->inject('dbForProject') - ->inject('auth') - ->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject, Authorization $auth) { + ->inject('authorization') + ->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject, Authorization $authorization) { try { $queries = Query::parseQueries($queries); @@ -2440,7 +2440,7 @@ Http::get('/v1/account/logs') $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; $offset = $grouped['offset'] ?? 0; - $audit = new EventAudit($dbForProject, $auth); + $audit = new EventAudit($dbForProject, $authorization); $logs = $audit->getLogsByUser($user->getInternalId(), $limit, $offset); @@ -2603,8 +2603,8 @@ Http::patch('/v1/account/email') ->inject('queueForEvents') ->inject('project') ->inject('hooks') - ->inject('auth') - ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $auth) { + ->inject('authorization') + ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); @@ -2643,7 +2643,7 @@ Http::patch('/v1/account/email') ->setAttribute('passwordUpdate', DateTime::now()); } - $target = $auth->skip(fn () => $dbForProject->findOne('targets', [ + $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ])); @@ -2659,7 +2659,7 @@ Http::patch('/v1/account/email') $oldTarget = $user->find('identifier', $oldEmail, 'targets'); if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { - $auth->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email))); + $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email))); } $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate) { @@ -2696,8 +2696,8 @@ Http::patch('/v1/account/phone') ->inject('queueForEvents') ->inject('project') ->inject('hooks') - ->inject('auth') - ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $auth) { + ->inject('authorization') + ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) { // passwordUpdate will be empty if the user has never set a password $passwordUpdate = $user->getAttribute('passwordUpdate'); @@ -2710,7 +2710,7 @@ Http::patch('/v1/account/phone') $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]); - $target = $auth->skip(fn () => $dbForProject->findOne('targets', [ + $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), ])); @@ -2741,7 +2741,7 @@ Http::patch('/v1/account/phone') $oldTarget = $user->find('identifier', $oldPhone, 'targets'); if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) { - $auth->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone))); + $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone))); } $dbForProject->purgeCachedDocument('users', $user->getId()); } catch (Duplicate $th) { @@ -2856,14 +2856,14 @@ Http::post('/v1/account/recovery') ->inject('locale') ->inject('queueForMails') ->inject('queueForEvents') - ->inject('auth') - ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, Authorization $auth) { + ->inject('authorization') + ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, Authorization $authorization) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); } - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -2897,7 +2897,7 @@ Http::post('/v1/account/recovery') 'ip' => $request->getIP(), ]); - $auth->addRole(Role::user($profile->getId())->toString()); + $authorization->addRole(Role::user($profile->getId())->toString()); $recovery = $dbForProject->createDocument('tokens', $recovery ->setAttribute('$permissions', [ @@ -3034,8 +3034,8 @@ Http::put('/v1/account/recovery') ->inject('project') ->inject('queueForEvents') ->inject('hooks') - ->inject('auth') - ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, Authorization $auth) { + ->inject('authorization') + ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, Authorization $authorization) { $profile = $dbForProject->getDocument('users', $userId); if ($profile->isEmpty()) { @@ -3049,7 +3049,7 @@ Http::put('/v1/account/recovery') throw new Exception(Exception::USER_INVALID_TOKEN); } - $auth->addRole(Role::user($profile->getId())->toString()); + $authorization->addRole(Role::user($profile->getId())->toString()); $newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS); @@ -3119,8 +3119,8 @@ Http::post('/v1/account/verification') ->inject('locale') ->inject('queueForEvents') ->inject('queueForMails') - ->inject('auth') - ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $auth) { + ->inject('authorization') + ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) { if (empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled'); @@ -3130,7 +3130,7 @@ Http::post('/v1/account/verification') throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED); } - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $verificationSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_VERIFICATION); @@ -3147,7 +3147,7 @@ Http::post('/v1/account/verification') 'ip' => $request->getIP(), ]); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $verification = $dbForProject->createDocument('tokens', $verification ->setAttribute('$permissions', [ @@ -3278,10 +3278,10 @@ Http::put('/v1/account/verification') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('auth') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $auth) { + ->inject('authorization') + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { - $profile = $auth->skip(fn () => $dbForProject->getDocument('users', $userId)); + $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId)); if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); @@ -3294,7 +3294,7 @@ Http::put('/v1/account/verification') throw new Exception(Exception::USER_INVALID_TOKEN); } - $auth->addRole(Role::user($profile->getId())->toString()); + $authorization->addRole(Role::user($profile->getId())->toString()); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true)); @@ -3342,8 +3342,8 @@ Http::post('/v1/account/verification/phone') ->inject('queueForMessaging') ->inject('project') ->inject('locale') - ->inject('auth') - ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, Authorization $auth) { + ->inject('authorization') + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, Authorization $authorization) { if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -3356,7 +3356,7 @@ Http::post('/v1/account/verification/phone') throw new Exception(Exception::USER_PHONE_ALREADY_VERIFIED); } - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); $secret = Auth::codeGenerator(); @@ -3373,7 +3373,7 @@ Http::post('/v1/account/verification/phone') 'ip' => $request->getIP(), ]); - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $verification = $dbForProject->createDocument('tokens', $verification ->setAttribute('$permissions', [ @@ -3452,10 +3452,10 @@ Http::put('/v1/account/verification/phone') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('auth') - ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $auth) { + ->inject('authorization') + ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { - $profile = $auth->skip(fn () => $dbForProject->getDocument('users', $userId)); + $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId)); if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); @@ -3467,7 +3467,7 @@ Http::put('/v1/account/verification/phone') throw new Exception(Exception::USER_INVALID_TOKEN); } - $auth->addRole(Role::user($profile->getId())->toString()); + $authorization->addRole(Role::user($profile->getId())->toString()); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true)); @@ -4153,13 +4153,13 @@ Http::post('/v1/account/targets/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('auth') - ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $auth) { + ->inject('authorization') + ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) { $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; - $provider = $auth->skip(fn () => $dbForProject->getDocument('providers', $providerId)); + $provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId)); - $target = $auth->skip(fn () => $dbForProject->getDocument('targets', $targetId)); + $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); if (!$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); @@ -4226,10 +4226,10 @@ Http::put('/v1/account/targets/:targetId/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('auth') - ->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $auth) { + ->inject('authorization') + ->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) { - $target = $auth->skip(fn () => $dbForProject->getDocument('targets', $targetId)); + $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); if ($target->isEmpty()) { throw new Exception(Exception::USER_TARGET_NOT_FOUND); @@ -4282,9 +4282,9 @@ Http::delete('/v1/account/targets/:targetId/push') ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('auth') - ->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $auth) { - $target = $auth->skip(fn () => $dbForProject->getDocument('targets', $targetId)); + ->inject('authorization') + ->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) { + $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId)); if ($target->isEmpty()) { throw new Exception(Exception::USER_TARGET_NOT_FOUND); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 900c0193c7..77052aba2a 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -17,6 +17,8 @@ use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Audit\Audit; use Utopia\Cache\Cache; use Utopia\Config\Config; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; @@ -79,9 +81,9 @@ Http::post('/v1/projects') ->inject('dbForConsole') ->inject('cache') ->inject('pools') - ->inject('auth') + ->inject('authorization') ->inject('connections') - ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Cache $cache, Group $pools, Authorization $auth, Connections $connections) { + ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Cache $cache, array $pools, Authorization $authorization, Connections $connections) { $team = $dbForConsole->getDocument('teams', $teamId); @@ -181,19 +183,27 @@ Http::post('/v1/projects') throw new Exception(Exception::PROJECT_ALREADY_EXISTS); } - $connection = $pools->get($database)->pop(); - $connections->add($connection); - $dbForProject = new Database($connection->getResource(), $cache); - $dbForProject->setAuthorization($auth); + $pool = $pools['pools-database-'.$project->getAttribute('database')]['pool']; + $dsn = $pools['pools-database-'.$project->getAttribute('database')]['dsn']; + $connection = $pool->get(); + $connections->add($connection, $pool); + $adapter = match ($dsn->getScheme()) { + 'mariadb' => new MariaDB($connection), + 'mysql' => new MySQL($connection), + default => null + }; + + $adapter->setDatabase($dsn->getPath()); + + $dbForProject = new Database($adapter, $cache); + $dbForProject->setAuthorization($authorization); $dbForProject->setNamespace("_{$project->getInternalId()}"); $dbForProject->create(); - - $audit = new Audit($dbForProject, $auth); + + $audit = new Audit($dbForProject, $authorization); $audit->setup(); - - $adapter = new TimeLimit('', 0, 1, $dbForProject, $auth); + $adapter = new TimeLimit('', 0, 1, $dbForProject, $authorization); $adapter->setup(); - /** @var array $collections */ $collections = Config::getParam('collections', [])['projects'] ?? []; diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 5c5013d98c..0dd1922f05 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -64,16 +64,16 @@ Http::post('/v1/teams') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('auth') - ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $auth) { + ->inject('authorization') + ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { - $isPrivilegedUser = Auth::isPrivilegedUser($auth->getRoles()); - $isAppUser = Auth::isAppUser($auth->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAppUser = Auth::isAppUser($authorization->getRoles()); $teamId = $teamId == 'unique()' ? ID::unique() : $teamId; try { - $team = $auth->skip(fn () => $dbForProject->createDocument('teams', new Document([ + $team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([ '$id' => $teamId, '$permissions' => [ Permission::read(Role::team($teamId)), @@ -398,10 +398,10 @@ Http::post('/v1/teams/:teamId/memberships') ->inject('queueForMails') ->inject('queueForMessaging') ->inject('queueForEvents') - ->inject('auth') - ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, Authorization $auth) { - $isAPIKey = Auth::isAppUser($auth->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($auth->getRoles()); + ->inject('authorization') + ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, Authorization $authorization) { + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if (empty($url)) { if (!$isAPIKey && !$isPrivilegedUser) { @@ -412,8 +412,8 @@ Http::post('/v1/teams/:teamId/memberships') if (empty($userId) && empty($email) && empty($phone)) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required'); } - $isPrivilegedUser = Auth::isPrivilegedUser($auth->getRoles()); - $isAppUser = Auth::isAppUser($auth->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAppUser = Auth::isAppUser($authorization->getRoles()); if (!$isPrivilegedUser && !$isAppUser && empty(System::getEnv('_APP_SMTP_HOST'))) { throw new Exception(Exception::GENERAL_SMTP_DISABLED); @@ -473,7 +473,7 @@ Http::post('/v1/teams/:teamId/memberships') try { $userId = ID::unique(); - $invitee = $auth->skip(fn () => $dbForProject->createDocument('users', new Document([ + $invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$permissions' => [ Permission::read(Role::any()), @@ -509,7 +509,7 @@ Http::post('/v1/teams/:teamId/memberships') } } - $isOwner = $auth->isRole('team:' . $team->getId() . '/owner'); + $isOwner = $authorization->isRole('team:' . $team->getId() . '/owner'); if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team'); @@ -541,12 +541,12 @@ Http::post('/v1/teams/:teamId/memberships') if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership try { - $membership = $auth->skip(fn () => $dbForProject->createDocument('memberships', $membership)); + $membership = $authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership)); } catch (Duplicate $th) { throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS); } - $auth->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); + $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); $dbForProject->purgeCachedDocument('users', $invitee->getId()); } else { @@ -866,8 +866,8 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId') ->inject('user') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('auth') - ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $auth) { + ->inject('authorization') + ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -884,9 +884,9 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId') throw new Exception(Exception::USER_NOT_FOUND); } - $isPrivilegedUser = Auth::isPrivilegedUser($auth->getRoles()); - $isAppUser = Auth::isAppUser($auth->getRoles()); - $isOwner = $auth->isRole('team:' . $team->getId() . '/owner'); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAppUser = Auth::isAppUser($authorization->getRoles()); + $isOwner = $authorization->isRole('team:' . $team->getId() . '/owner'); if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles'); @@ -942,8 +942,8 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('project') ->inject('geodb') ->inject('queueForEvents') - ->inject('auth') - ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Authorization $auth) { + ->inject('authorization') + ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Authorization $authorization) { $protocol = $request->getProtocol(); $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -952,7 +952,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') throw new Exception(Exception::MEMBERSHIP_NOT_FOUND); } - $team = $auth->skip(fn () => $dbForProject->getDocument('teams', $teamId)); + $team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId)); if ($team->isEmpty()) { throw new Exception(Exception::TEAM_NOT_FOUND); @@ -987,11 +987,11 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->setAttribute('confirm', true) ; - $auth->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true))); + $authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true))); // Log user in - $auth->addRole(Role::user($user->getId())->toString()); + $authorization->addRole(Role::user($user->getId())->toString()); $detector = new Detector($request->getUserAgent('UNKNOWN')); $record = $geodb->get($request->getIP()); @@ -1021,13 +1021,13 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status') $dbForProject->purgeCachedDocument('users', $user->getId()); - $auth->addRole(Role::user($userId)->toString()); + $authorization->addRole(Role::user($userId)->toString()); $membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership); $dbForProject->purgeCachedDocument('users', $user->getId()); - $auth->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); + $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); $queueForEvents ->setParam('teamId', $team->getId()) @@ -1072,8 +1072,8 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->inject('auth') - ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $auth) { + ->inject('authorization') + ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) { $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -1108,7 +1108,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId') $dbForProject->purgeCachedDocument('users', $user->getId()); if ($membership->getAttribute('confirm')) { // Count only confirmed members - $auth->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0)); + $authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0)); } $queueForEvents @@ -1137,8 +1137,8 @@ Http::get('/v1/teams/:teamId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('auth') - ->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $auth) { + ->inject('authorization') + ->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { $team = $dbForProject->getDocument('teams', $teamId); @@ -1156,7 +1156,7 @@ Http::get('/v1/teams/:teamId/logs') $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; $offset = $grouped['offset'] ?? 0; - $audit = new Audit($dbForProject, $auth); + $audit = new Audit($dbForProject, $authorization); $resource = 'team/' . $team->getId(); $logs = $audit->getLogsByResource($resource, $limit, $offset); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 1849e84c56..fc870045b6 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -769,8 +769,8 @@ Http::get('/v1/users/:userId/logs') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->inject('auth') - ->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $auth) { + ->inject('authorization') + ->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) { $user = $dbForProject->getDocument('users', $userId); @@ -788,7 +788,7 @@ Http::get('/v1/users/:userId/logs') $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; $offset = $grouped['offset'] ?? 0; - $audit = new Audit($dbForProject, $auth); + $audit = new Audit($dbForProject, $authorization); $logs = $audit->getLogsByUser($user->getInternalId(), $limit, $offset); @@ -2105,8 +2105,8 @@ Http::get('/v1/users/usage') ->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->inject('auth') - ->action(function (string $range, Response $response, Database $dbForProject, Authorization $auth) { + ->inject('authorization') + ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) { $periods = Config::getParam('usage', []); $stats = $usage = []; @@ -2116,7 +2116,7 @@ Http::get('/v1/users/usage') METRIC_SESSIONS, ]; - $auth->skip(function () use ($dbForProject, $days, $metrics, &$stats) { + $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) { foreach ($metrics as $count => $metric) { $result = $dbForProject->findOne('stats', [ Query::equal('metric', [$metric]), diff --git a/app/controllers/general.php b/app/controllers/general.php index 6feab6e2f7..3855791bac 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -5,6 +5,7 @@ use Appwrite\Event\Event; use Appwrite\Event\Usage; use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Network\Validator\Origin; +use Appwrite\Utopia\Queue\Connections; use Appwrite\Utopia\Request; use Appwrite\Utopia\Request\Filters\V16 as RequestV16; use Appwrite\Utopia\Request\Filters\V17 as RequestV17; @@ -635,7 +636,8 @@ Http::error() ->inject('logger') ->inject('log') ->inject('authorization') - ->action(function (Throwable $error, Document $user, ?Route $route, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Authorization $authorization) { + ->inject('connections') + ->action(function (Throwable $error, Document $user, ?Route $route, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Authorization $authorization, Connections $connections) { $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); if(is_null($route)) { @@ -691,6 +693,7 @@ Http::error() $trace = $error->getTrace(); if (php_sapi_name() === 'cli') { + Console::error('[Error] ------------------'); Console::error('[Error] Timestamp: ' . date('c', time())); if ($route) { @@ -728,7 +731,7 @@ Http::error() /** Wrap all exceptions inside Appwrite\Extend\Exception */ if (!($error instanceof AppwriteException)) { - $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error); + $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, (int)$code, $error); } switch ($code) { // Don't show 500 errors! @@ -748,7 +751,7 @@ Http::error() break; default: $code = 500; // All other errors get the generic 500 server error status code - $message = 'Server Error'; + $message = (Http::getMode() === Http::MODE_TYPE_DEVELOPMENT) ? $message : 'Server Error'; } //$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised @@ -794,6 +797,8 @@ Http::error() $response->html($layout->render()); } + $connections->reclaim(); + $response->dynamic( new Document($output), Http::isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR @@ -884,4 +889,20 @@ foreach (Config::getParam('services', []) as $service) { //include_once $service['controller']; } +include_once 'shared/api.php'; +include_once 'shared/api/auth.php'; +include_once 'api/account.php'; +include_once 'api/avatars.php'; +//include_once 'api/database.php'; +//include_once 'api/functions.php'; +//include_once 'api/graphql.php'; +//include_once 'api/health.php'; include_once 'api/locale.php'; +//include_once 'api/messaging.php'; +//include_once 'api/migrations.php'; +include_once 'api/projects.php'; +//include_once 'api/proxy.php'; +//include_once 'api/storage.php'; +include_once 'api/teams.php'; +include_once 'api/users.php'; +//include_once 'api/vcs.php'; diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index bf5c3323c7..39926b7182 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -29,6 +29,7 @@ use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Authorization\Input; use Utopia\System\System; use Utopia\Http\Http; +use Utopia\Http\Route; use Utopia\Http\Validator\WhiteList; $parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) { @@ -152,7 +153,7 @@ $databaseListener = function (string $event, Document $document, Document $proje Http::init() ->groups(['api']) - ->inject('utopia') + ->inject('route') ->inject('request') ->inject('dbForConsole') ->inject('project') @@ -160,10 +161,8 @@ Http::init() ->inject('session') ->inject('servers') ->inject('mode') - ->inject('auth') - ->action(function (Http $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Authorization $auth) { - $route = $utopia->getRoute(); - + ->inject('authorization') + ->action(function (Route $route, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Authorization $authorization) { if ($project->isEmpty()) { throw new Exception(Exception::PROJECT_NOT_FOUND); } @@ -225,8 +224,8 @@ Http::init() throw new Exception(Exception::PROJECT_KEY_EXPIRED); } - $auth->addRole(Auth::USER_ROLE_APPS); - $auth->setDefaultStatus(false); // Cancel security segmentation for API keys. + $authorization->addRole(Auth::USER_ROLE_APPS); + $authorization->setDefaultStatus(false); // Cancel security segmentation for API keys. $accessedAt = $key->getAttribute('accessedAt', ''); if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCCESS)) > $accessedAt) { @@ -252,10 +251,10 @@ Http::init() } } - $auth->addRole($role); + $authorization->addRole($role); - foreach (Auth::getRoles($user, $auth) as $authRole) { - $auth->addRole($authRole); + foreach (Auth::getRoles($user, $authorization) as $authRole) { + $authorization->addRole($authRole); } $service = $route->getLabel('sdk.namespace', ''); @@ -263,7 +262,7 @@ Http::init() if ( array_key_exists($service, $project->getAttribute('services', [])) && !$project->getAttribute('services', [])[$service] - && !(Auth::isPrivilegedUser($auth->getRoles()) || Auth::isAppUser($auth->getRoles())) + && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) ) { throw new Exception(Exception::GENERAL_SERVICE_DISABLED); } @@ -302,7 +301,7 @@ Http::init() Http::init() ->groups(['api']) - ->inject('utopia') + ->inject('route') ->inject('request') ->inject('response') ->inject('project') @@ -316,15 +315,12 @@ Http::init() ->inject('queueForUsage') ->inject('dbForProject') ->inject('mode') - ->inject('auth') - ->action(function (Http $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode, Authorization $auth) use ($databaseListener) { - - $route = $utopia->getRoute(); - + ->inject('authorization') + ->action(function (Route $route, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode, Authorization $authorization) use ($databaseListener) { if ( array_key_exists('rest', $project->getAttribute('apis', [])) && !$project->getAttribute('apis', [])['rest'] - && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles())) + && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles())) ) { throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED); } @@ -340,7 +336,7 @@ Http::init() foreach ($abuseKeyLabel as $abuseKey) { $start = $request->getContentRangeStart(); $end = $request->getContentRangeEnd(); - $timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject, $auth); + $timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject, $authorization); $timeLimit ->setParam('{projectId}', $project->getId()) ->setParam('{userId}', $user->getId()) @@ -354,7 +350,7 @@ Http::init() $closestLimit = null; - $roles = $auth->getRoles(); + $roles = $authorization->getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); @@ -420,7 +416,7 @@ Http::init() $useCache = $route->getLabel('cache', false); if ($useCache) { $key = md5($request->getURI() . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); - $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); + $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key)); $cache = new Cache( new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId()) ); @@ -434,17 +430,17 @@ Http::init() if ($type === 'bucket') { $bucketId = $parts[1] ?? null; - $bucket = $auth->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); + $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); - $isAPIKey = Auth::isAppUser($auth->getRoles()); - $isPrivilegedUser = Auth::isPrivilegedUser($auth->getRoles()); + $isAPIKey = Auth::isAppUser($authorization->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $fileSecurity = $bucket->getAttribute('fileSecurity', false); - $valid = $auth->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); + $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead())); if (!$fileSecurity && !$valid) { throw new Exception(Exception::USER_UNAUTHORIZED); } @@ -455,7 +451,7 @@ Http::init() if ($fileSecurity && !$valid) { $file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $file = $auth->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($file->isEmpty()) { @@ -529,7 +525,7 @@ Http::shutdown() Http::shutdown() ->groups(['api']) - ->inject('utopia') + ->inject('route') ->inject('request') ->inject('response') ->inject('project') @@ -545,9 +541,9 @@ Http::shutdown() ->inject('queueForFunctions') ->inject('mode') ->inject('dbForConsole') - ->inject('auth') + ->inject('authorization') ->action(function ( - Http $utopia, + Route $route, Request $request, Response $response, Document $project, @@ -563,10 +559,10 @@ Http::shutdown() Func $queueForFunctions, string $mode, Database $dbForConsole, - Authorization $auth, + Authorization $authorization, ) use ($parseLabel) { if (!empty($user) && !$user->isEmpty() && empty($user->getInternalId())) { - $user = $auth->skip(fn () => $dbForProject->getDocument('users', $user->getId())); + $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $user->getId())); } $responsePayload = $response->getPayload(); @@ -626,7 +622,6 @@ Http::shutdown() } } - $route = $utopia->getRoute(); $requestParams = $route->getParamsValues(); /** @@ -696,11 +691,11 @@ Http::shutdown() $key = md5($request->getURI() . '*' . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER; $signature = md5($data['payload']); - $cacheLog = $auth->skip(fn () => $dbForProject->getDocument('cache', $key)); + $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key)); $accessedAt = $cacheLog->getAttribute('accessedAt', ''); $now = DateTime::now(); if ($cacheLog->isEmpty()) { - $auth->skip(fn () => $dbForProject->createDocument('cache', new Document([ + $authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([ '$id' => $key, 'resource' => $resource, 'resourceType' => $resourceType, @@ -710,7 +705,7 @@ Http::shutdown() ]))); } elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) { $cacheLog->setAttribute('accessedAt', $now); - $auth->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog)); + $authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog)); } if ($signature !== $cacheLog->getAttribute('signature')) { diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php index 8a7aa30951..32d5d9b1e5 100644 --- a/app/controllers/shared/api/auth.php +++ b/app/controllers/shared/api/auth.php @@ -8,6 +8,7 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Http\Http; +use Utopia\Http\Route; use Utopia\System\System; Http::init() @@ -31,12 +32,12 @@ Http::init() Http::init() ->groups(['auth']) - ->inject('utopia') + ->inject('route') ->inject('request') ->inject('project') ->inject('geodb') - ->inject('auth') - ->action(function (Http $utopia, Request $request, Document $project, Reader $geodb, Authorization $auth) { + ->inject('authorization') + ->action(function (Route $route, Request $request, Document $project, Reader $geodb, Authorization $authorization) { $denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', ''); if (!empty($denylist && $project->getId() === 'console')) { $countries = explode(',', $denylist); @@ -47,10 +48,8 @@ Http::init() } } - $route = $utopia->match($request); - - $isPrivilegedUser = Auth::isPrivilegedUser($auth->getRoles()); - $isAppUser = Auth::isAppUser($auth->getRoles()); + $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles()); + $isAppUser = Auth::isAppUser($authorization->getRoles()); if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs return; diff --git a/app/init2.php b/app/init2.php index 16329276e3..af0f0d0a3c 100644 --- a/app/init2.php +++ b/app/init2.php @@ -78,39 +78,12 @@ use Utopia\System\System; use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub; use Utopia\Cache\Adapter\None; +Http::setMode(System::getEnv('_APP_ENV', Http::MODE_TYPE_PRODUCTION)); -function getAdapter($type, $scheme, $resource) { - switch ($type) { - case 'database': - $adapter = match ($scheme) { - 'mariadb' => new MariaDB($resource), - 'mysql' => new MySQL($resource), - default => null - }; - - $adapter->setDatabase($scheme); - break; - case 'pubsub': - $adapter = $resource(); - break; - case 'queue': - $adapter = match ($scheme) { - //'redis' => new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()), - default => null - }; - break; - case 'cache': - $adapter = match ($scheme) { - 'redis' => new RedisCache($resource), - default => null - }; - break; - - default: - throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation."); - } - - return $adapter; +if (!Http::isProduction()) { + // Allow specific domains to skip public domain validation in dev environment + // Useful for existing tests involving webhooks + PublicDomain::allow(['request-catcher']); } $global = new Registry(); @@ -140,6 +113,10 @@ $global->set('geodb', function () { return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-02.mmdb'); }); +$global->set('hooks', function () { + return new Hooks(); +}); + $global->set('pools', (function () { $fallbackForDB = 'db_main=' . URL::unparse([ 'scheme' => 'mariadb', @@ -199,9 +176,12 @@ $global->set('pools', (function () { $multipe = $connection['multiple'] ?? false; $schemes = $connection['schemes'] ?? []; $dsns = explode(',', $connection['dsns'] ?? ''); + $config = []; + foreach ($dsns as &$dsn) { $dsn = explode('=', $dsn); $name = ($multipe) ? $dsn[0] : 'main'; + $config[] = $name; $dsn = $dsn[1] ?? ''; if (empty($dsn)) { @@ -267,6 +247,8 @@ $global->set('pools', (function () { 'dsn' => $dsn, ]; } + + Config::setParam('pools-' . $key, $config); } return function () use ($pools): array { @@ -401,6 +383,34 @@ $user }); $container->set($user); +$session = new Dependency(); +$session + ->setName('session') + ->inject('user') + ->inject('project') + ->setCallback(function (Document $user, Document $project) { + if ($user->isEmpty()) { + return; + } + + $sessions = $user->getAttribute('sessions', []); + $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret, $authDuration); + + if (!$sessionId) { + return; + } + + foreach ($sessions as $session) { + if ($sessionId === $session->getId()) { + return $session; + } + } + + return; + }); +$container->set($session); + $console = new Dependency(); $console ->setName('console') @@ -498,6 +508,8 @@ $dbForProject 'mysql' => new MySQL($connection), default => null }; + + $adapter->setDatabase($dsn->getPath()); $database = new Database($adapter, $cache); $database->setAuthorization($authorization); @@ -531,7 +543,7 @@ $dbForConsole default => null }; - $adapter->setDatabase('appwrite'); + $adapter->setDatabase($dsn->getPath()); $database = new Database($adapter, $cache); $database->setAuthorization($authorization); @@ -788,6 +800,21 @@ $clients }); $container->set($clients); +$servers = new Dependency(); +$servers + ->setName('servers') + ->setCallback(function () { + $platforms = Config::getParam('platforms'); + $server = $platforms[APP_PLATFORM_SERVER]; + + $languages = array_map(function ($language) { + return strtolower($language['name']); + }, $server['sdks']); + + return $languages; + }); +$container->set($servers); + $geodb = new Dependency(); $geodb ->setName('geodb') @@ -795,4 +822,44 @@ $geodb ->setCallback(function (Registry $register) { return $register->get('geodb'); }); -$container->set($geodb); \ No newline at end of file +$container->set($geodb); + +$passwordsDictionary = new Dependency(); +$passwordsDictionary + ->setName('passwordsDictionary') + ->setCallback(function () { + $content = file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); + $content = explode("\n", $content); + $content = array_flip($content); + return $content; + }); + +$container->set($passwordsDictionary); + +$hooks = new Dependency(); +$hooks + ->setName('hooks') + ->inject('registry') + ->setCallback(function (Registry $registry) { + return $registry->get('hooks'); + }); + +$container->set($hooks); + +$requestTimestamp = new Dependency(); +$requestTimestamp + ->setName('requestTimestamp') + ->inject('request') + ->setCallback(function ($request) { + $timestampHeader = $request->getHeader('x-appwrite-timestamp'); + $requestTimestamp = null; + if (!empty($timestampHeader)) { + try { + $requestTimestamp = new \DateTime($timestampHeader); + } catch (\Throwable $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value'); + } + } + return $requestTimestamp; + }); +$container->set($requestTimestamp); diff --git a/composer.lock b/composer.lock index fcb2ae8659..d7ab08b87e 100644 --- a/composer.lock +++ b/composer.lock @@ -1825,12 +1825,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "aef4e9a7bcb3ba21b993f2daad0364657c2fe1aa" + "reference": "d600ae234780e083c600ab62a8348b7ea506cd1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/aef4e9a7bcb3ba21b993f2daad0364657c2fe1aa", - "reference": "aef4e9a7bcb3ba21b993f2daad0364657c2fe1aa", + "url": "https://api.github.com/repos/utopia-php/http/zipball/d600ae234780e083c600ab62a8348b7ea506cd1d", + "reference": "d600ae234780e083c600ab62a8348b7ea506cd1d", "shasum": "" }, "require": { @@ -1867,7 +1867,7 @@ "issues": "https://github.com/utopia-php/http/issues", "source": "https://github.com/utopia-php/http/tree/feat-di-upgrade" }, - "time": "2024-04-13T17:20:36+00:00" + "time": "2024-04-14T16:41:37+00:00" }, { "name": "utopia-php/image", diff --git a/src/Appwrite/Utopia/Queue/Connections.php b/src/Appwrite/Utopia/Queue/Connections.php index 2aa4d0f69b..e873373566 100644 --- a/src/Appwrite/Utopia/Queue/Connections.php +++ b/src/Appwrite/Utopia/Queue/Connections.php @@ -2,41 +2,20 @@ namespace Appwrite\Utopia\Queue; -use Utopia\Pools\Connection; - class Connections { /** - * @var Connection[] + * @var array */ protected array $connections = []; /** - * @param Connection $pool + * @param mixed $connection * @return self */ - public function add(Connection $connection): self + public function add(mixed $connection, $pool): self { - $this->connections[$connection->getID()] = $connection; - return $this; - } - - /** - * @param string $id - * @return Connection - */ - public function get(string $id): Connection - { - return $this->connections[$id] ?? throw new \Exception("Connection '{$id}' not found"); - } - - /** - * @param string $id - * @return self - */ - public function remove(string $id): self - { - unset($this->connections[$id]); + $this->connections[] = ['connection' => $connection, 'pool' => $pool]; return $this; } @@ -45,8 +24,11 @@ class Connections */ public function reclaim(): self { - foreach ($this->connections as $connection) { - $connection->reclaim(); + foreach ($this->connections as $id => $resource) { + $pool = $resource['pool']; + $connection = $resource['connection']; + $pool->put($connection); + unset($this->connections[$id]); } return $this;