From c07a3889e6470cf49cde91e6f0b6e78e6515bb14 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:41:35 +0530 Subject: [PATCH 01/13] WIP: Add new group to api.php --- .env | 1 + app/controllers/api/account.php | 6 +++--- app/controllers/shared/api.php | 17 +++++++++++++++++ docker-compose.yml | 1 + 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.env b/.env index 9b549a6b69..877a8daac7 100644 --- a/.env +++ b/.env @@ -103,3 +103,4 @@ _APP_ASSISTANT_OPENAI_API_KEY= _APP_MESSAGE_SMS_TEST_DSN= _APP_MESSAGE_EMAIL_TEST_DSN= _APP_MESSAGE_PUSH_TEST_DSN= +_APP_RESTRICTED_COUNTRIES= \ No newline at end of file diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 962399247e..b45d7ccfb1 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -54,7 +54,7 @@ $oauthDefaultFailure = '/auth/oauth2/failure'; App::post('/v1/account') ->desc('Create account') - ->groups(['api', 'account', 'auth']) + ->groups(['api', 'account', 'auth', 'restrict']) ->label('event', 'users.[userId].create') ->label('scope', 'sessions.write') ->label('auth.type', 'emailPassword') @@ -447,7 +447,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') App::get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 redirect') - ->groups(['api', 'account', 'session']) + ->groups(['api', 'account', 'session', 'restrict']) ->label('error', __DIR__ . '/../../views/general/error.phtml') ->label('event', 'users.[userId].sessions.[sessionId].create') ->label('scope', 'public') @@ -1565,7 +1565,7 @@ App::put('/v1/account/sessions/magic-url') App::post('/v1/account/sessions/token') ->desc('Create session') ->label('event', 'users.[userId].sessions.[sessionId].create') - ->groups(['api', 'account']) + ->groups(['api', 'account', 'restrict']) ->label('scope', 'sessions.write') ->label('audits.event', 'session.create') ->label('audits.resource', 'user/{response.userId}') diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 18c126fb9a..75b5bcb0c5 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -22,6 +22,7 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; +use MaxMind\Db\Reader; $parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) { preg_match_all('/{(.*?)}/', $label, $matches); @@ -592,3 +593,19 @@ App::init() throw new Exception(Exception::GENERAL_USAGE_DISABLED); } }); + +App::init() + ->groups(['restrict']) + ->inject('request') + ->inject('geodb') + ->action(function (Request $request, Reader $geodb) { + if (!empty(app::getEnv('_APP_RESTRICTED_COUNTRIES', ''))) { + $countries = explode(',', App::getEnv('_APP_RESTRICTED_COUNTRIES', '')); + // $record = $geodb->get($request->getIP()); + $record = $geodb->get('167.220.238.180'); + $country = $record['country']['iso_code']; + if (in_array($country, $countries)) { + throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, "Access from $country is restricted"); + } + } + }); diff --git a/docker-compose.yml b/docker-compose.yml index 5077592a5b..70e3ecc44e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -192,6 +192,7 @@ services: - _APP_MESSAGE_SMS_TEST_DSN - _APP_MESSAGE_EMAIL_TEST_DSN - _APP_MESSAGE_PUSH_TEST_DSN + - _APP_RESTRICTED_COUNTRIES appwrite-realtime: entrypoint: realtime From e2cf8c696aca7e51b21511c9932674b0cd7972f7 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:08:51 +0530 Subject: [PATCH 02/13] Add restrict group to some APIs --- app/controllers/api/account.php | 4 ++-- app/controllers/api/teams.php | 4 ++-- app/controllers/api/users.php | 18 +++++++++--------- app/controllers/shared/api.php | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index b45d7ccfb1..e445f079ee 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1759,7 +1759,7 @@ App::post('/v1/account/tokens/phone') App::post('/v1/account/sessions/anonymous') ->desc('Create anonymous session') - ->groups(['api', 'account', 'auth', 'session']) + ->groups(['api', 'account', 'auth', 'session', 'restrict']) ->label('event', 'users.[userId].sessions.[sessionId].create') ->label('scope', 'sessions.write') ->label('auth.type', 'anonymous') @@ -3354,7 +3354,7 @@ App::post('/v1/account/verification/phone') App::put('/v1/account/verification/phone') ->desc('Create phone verification (confirmation)') - ->groups(['api', 'account']) + ->groups(['api', 'account', 'restrict']) ->label('scope', 'public') ->label('event', 'users.[userId].verification.[tokenId].update') ->label('audits.event', 'verification.update') diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 27332b8bf3..bba6a40865 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -42,7 +42,7 @@ use Utopia\Validator\Text; App::post('/v1/teams') ->desc('Create team') - ->groups(['api', 'teams']) + ->groups(['api', 'teams', 'restrict']) ->label('event', 'teams.[teamId].create') ->label('scope', 'teams.write') ->label('audits.event', 'team.create') @@ -358,7 +358,7 @@ App::delete('/v1/teams/:teamId') App::post('/v1/teams/:teamId/memberships') ->desc('Create team membership') - ->groups(['api', 'teams', 'auth']) + ->groups(['api', 'teams', 'auth', 'restrict']) ->label('event', 'teams.[teamId].memberships.[membershipId].create') ->label('scope', 'teams.write') ->label('auth.type', 'invites') diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index e090b32300..afb247d05a 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -148,7 +148,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e App::post('/v1/users') ->desc('Create user') - ->groups(['api', 'users']) + ->groups(['api', 'users', 'restrict']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -180,7 +180,7 @@ App::post('/v1/users') App::post('/v1/users/bcrypt') ->desc('Create user with bcrypt password') - ->groups(['api', 'users']) + ->groups(['api', 'users', 'restrict']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -211,7 +211,7 @@ App::post('/v1/users/bcrypt') App::post('/v1/users/md5') ->desc('Create user with MD5 password') - ->groups(['api', 'users']) + ->groups(['api', 'users', 'restrict']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -242,7 +242,7 @@ App::post('/v1/users/md5') App::post('/v1/users/argon2') ->desc('Create user with Argon2 password') - ->groups(['api', 'users']) + ->groups(['api', 'users', 'restrict']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -273,7 +273,7 @@ App::post('/v1/users/argon2') App::post('/v1/users/sha') ->desc('Create user with SHA password') - ->groups(['api', 'users']) + ->groups(['api', 'users', 'restrict']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -311,7 +311,7 @@ App::post('/v1/users/sha') App::post('/v1/users/phpass') ->desc('Create user with PHPass password') - ->groups(['api', 'users']) + ->groups(['api', 'users', 'restrict']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -342,7 +342,7 @@ App::post('/v1/users/phpass') App::post('/v1/users/scrypt') ->desc('Create user with Scrypt password') - ->groups(['api', 'users']) + ->groups(['api', 'users', 'restrict']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -386,7 +386,7 @@ App::post('/v1/users/scrypt') App::post('/v1/users/scrypt-modified') ->desc('Create user with Scrypt modified password') - ->groups(['api', 'users']) + ->groups(['api', 'users', 'restrict']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -420,7 +420,7 @@ App::post('/v1/users/scrypt-modified') App::post('/v1/users/:userId/targets') ->desc('Create User Target') - ->groups(['api', 'users']) + ->groups(['api', 'users', 'restrict']) ->label('audits.event', 'target.create') ->label('audits.resource', 'target/response.$id') ->label('event', 'users.[userId].targets.[targetId].create') diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 75b5bcb0c5..d87d69a224 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -601,11 +601,11 @@ App::init() ->action(function (Request $request, Reader $geodb) { if (!empty(app::getEnv('_APP_RESTRICTED_COUNTRIES', ''))) { $countries = explode(',', App::getEnv('_APP_RESTRICTED_COUNTRIES', '')); - // $record = $geodb->get($request->getIP()); - $record = $geodb->get('167.220.238.180'); + $record = $geodb->get($request->getHeader('x-forwarded-for')); $country = $record['country']['iso_code']; + $countryName = $record['country']['names']['en']; if (in_array($country, $countries)) { - throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, "Access from $country is restricted"); + throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, "Sorry, access from $countryName is restricted"); } } }); From 51d1a38f0f408259444a7c0cdf4bb67b31be24f4 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 24 Jan 2024 18:24:48 +0530 Subject: [PATCH 03/13] Added test case --- .env | 2 +- app/controllers/shared/api.php | 2 +- tests/e2e/Services/Account/AccountBase.php | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.env b/.env index 877a8daac7..3ba90d8732 100644 --- a/.env +++ b/.env @@ -103,4 +103,4 @@ _APP_ASSISTANT_OPENAI_API_KEY= _APP_MESSAGE_SMS_TEST_DSN= _APP_MESSAGE_EMAIL_TEST_DSN= _APP_MESSAGE_PUSH_TEST_DSN= -_APP_RESTRICTED_COUNTRIES= \ No newline at end of file +_APP_RESTRICTED_COUNTRIES=AQ \ No newline at end of file diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index d87d69a224..c5a4d23456 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -601,7 +601,7 @@ App::init() ->action(function (Request $request, Reader $geodb) { if (!empty(app::getEnv('_APP_RESTRICTED_COUNTRIES', ''))) { $countries = explode(',', App::getEnv('_APP_RESTRICTED_COUNTRIES', '')); - $record = $geodb->get($request->getHeader('x-forwarded-for')); + $record = $geodb->get($request->getIP()); $country = $record['country']['iso_code']; $countryName = $record['country']['names']['en']; if (in_array($country, $countries)) { diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 70a13b2d4c..e228d7293b 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -43,6 +43,20 @@ trait AccountBase /** * Test for FAILURE */ + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-forwarded-for' => '103.152.127.250' + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', From f4426d553c275e55bcc7ff85b049f54b668972c6 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:49:13 +0530 Subject: [PATCH 04/13] Throw 451 exception --- .env | 2 +- app/config/errors.php | 5 +++++ app/controllers/api/users.php | 18 +++++++++--------- app/controllers/shared/api.php | 7 ++++--- docker-compose.yml | 2 +- src/Appwrite/Extend/Exception.php | 1 + tests/e2e/Services/Account/AccountBase.php | 5 +++-- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/.env b/.env index e91a5ad067..a7e7dea53a 100644 --- a/.env +++ b/.env @@ -99,4 +99,4 @@ _APP_ASSISTANT_OPENAI_API_KEY= _APP_MESSAGE_SMS_TEST_DSN= _APP_MESSAGE_EMAIL_TEST_DSN= _APP_MESSAGE_PUSH_TEST_DSN= -_APP_RESTRICTED_COUNTRIES=AQ \ No newline at end of file +_APP_COUNTRIES_DENYLIST=AQ \ No newline at end of file diff --git a/app/config/errors.php b/app/config/errors.php index 4cc8e734ee..2f2f488029 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -114,6 +114,11 @@ return [ 'description' => 'Value must be a valid phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', 'code' => 400, ], + Exception::GENERAL_REGION_ACCESS_DENIED => [ + 'name' => Exception::GENERAL_REGION_ACCESS_DENIED, + 'description' => 'Sorry, access from the current region is denied.', + 'code' => 451, + ], /** User Errors */ Exception::USER_COUNT_EXCEEDED => [ diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index a70c5fafa3..ddc925d1c2 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -156,7 +156,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e App::post('/v1/users') ->desc('Create user') - ->groups(['api', 'users', 'restrict']) + ->groups(['api', 'users']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -188,7 +188,7 @@ App::post('/v1/users') App::post('/v1/users/bcrypt') ->desc('Create user with bcrypt password') - ->groups(['api', 'users', 'restrict']) + ->groups(['api', 'users']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -219,7 +219,7 @@ App::post('/v1/users/bcrypt') App::post('/v1/users/md5') ->desc('Create user with MD5 password') - ->groups(['api', 'users', 'restrict']) + ->groups(['api', 'users']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -250,7 +250,7 @@ App::post('/v1/users/md5') App::post('/v1/users/argon2') ->desc('Create user with Argon2 password') - ->groups(['api', 'users', 'restrict']) + ->groups(['api', 'users']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -281,7 +281,7 @@ App::post('/v1/users/argon2') App::post('/v1/users/sha') ->desc('Create user with SHA password') - ->groups(['api', 'users', 'restrict']) + ->groups(['api', 'users']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -319,7 +319,7 @@ App::post('/v1/users/sha') App::post('/v1/users/phpass') ->desc('Create user with PHPass password') - ->groups(['api', 'users', 'restrict']) + ->groups(['api', 'users']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -350,7 +350,7 @@ App::post('/v1/users/phpass') App::post('/v1/users/scrypt') ->desc('Create user with Scrypt password') - ->groups(['api', 'users', 'restrict']) + ->groups(['api', 'users']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -394,7 +394,7 @@ App::post('/v1/users/scrypt') App::post('/v1/users/scrypt-modified') ->desc('Create user with Scrypt modified password') - ->groups(['api', 'users', 'restrict']) + ->groups(['api', 'users']) ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') @@ -428,7 +428,7 @@ App::post('/v1/users/scrypt-modified') App::post('/v1/users/:userId/targets') ->desc('Create User Target') - ->groups(['api', 'users', 'restrict']) + ->groups(['api', 'users']) ->label('audits.event', 'target.create') ->label('audits.resource', 'target/response.$id') ->label('event', 'users.[userId].targets.[targetId].create') diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 095fcd889c..cbd481824e 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -634,13 +634,14 @@ App::init() ->inject('request') ->inject('geodb') ->action(function (Request $request, Reader $geodb) { - if (!empty(app::getEnv('_APP_RESTRICTED_COUNTRIES', ''))) { - $countries = explode(',', App::getEnv('_APP_RESTRICTED_COUNTRIES', '')); + $denylist = App::getEnv('_APP_COUNTRIES_DENYLIST', ''); + if (!empty($denylist)) { + $countries = explode(',', $denylist); $record = $geodb->get($request->getIP()); $country = $record['country']['iso_code']; $countryName = $record['country']['names']['en']; if (in_array($country, $countries)) { - throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, "Sorry, access from $countryName is restricted"); + throw new Exception(Exception::GENERAL_REGION_ACCESS_DENIED); } } }); diff --git a/docker-compose.yml b/docker-compose.yml index 52229464d0..076072d1b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -188,7 +188,7 @@ services: - _APP_MESSAGE_SMS_TEST_DSN - _APP_MESSAGE_EMAIL_TEST_DSN - _APP_MESSAGE_PUSH_TEST_DSN - - _APP_RESTRICTED_COUNTRIES + - _APP_COUNTRIES_DENYLIST appwrite-realtime: entrypoint: realtime diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index d81589ed9d..7ac489d976 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -57,6 +57,7 @@ class Exception extends \Exception public const GENERAL_NOT_IMPLEMENTED = 'general_not_implemented'; public const GENERAL_INVALID_EMAIL = 'general_invalid_email'; public const GENERAL_INVALID_PHONE = 'general_invalid_phone'; + public const GENERAL_REGION_ACCESS_DENIED = 'general_region_access_denied'; /** Users */ public const USER_COUNT_EXCEEDED = 'user_count_exceeded'; diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index bab2d0fff4..227babaef3 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -43,11 +43,12 @@ trait AccountBase /** * Test for FAILURE */ + // Deny request from blocked IP $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-forwarded-for' => '103.152.127.250' + 'x-forwarded-for' => '103.152.127.250' // Test IP for denied access region ]), [ 'userId' => ID::unique(), 'email' => $email, @@ -55,7 +56,7 @@ trait AccountBase 'name' => $name, ]); - $this->assertEquals(401, $response['headers']['status-code']); + $this->assertEquals(451, $response['headers']['status-code']); $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', From 2f88320d4ff68e6454ba1a07cf8cc7c05e59f1db Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:47:14 +0530 Subject: [PATCH 05/13] Add null check --- app/controllers/shared/api.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index cbd481824e..512e8b38ff 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -637,9 +637,8 @@ App::init() $denylist = App::getEnv('_APP_COUNTRIES_DENYLIST', ''); if (!empty($denylist)) { $countries = explode(',', $denylist); - $record = $geodb->get($request->getIP()); - $country = $record['country']['iso_code']; - $countryName = $record['country']['names']['en']; + $record = $geodb->get($request->getIP()) ?? []; + $country = $record['country']['iso_code'] ?? ''; if (in_array($country, $countries)) { throw new Exception(Exception::GENERAL_REGION_ACCESS_DENIED); } From 1d2eadf7e017a2f2b22768c0d0a1d6fbb0ba9ace Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:23:45 +0530 Subject: [PATCH 06/13] Allow 451 error publicly --- app/controllers/general.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/general.php b/app/controllers/general.php index 3020cd6653..2ed03085d2 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -722,6 +722,7 @@ App::error() case 412: // Error allowed publicly case 416: // Error allowed publicly case 429: // Error allowed publicly + case 451: // Error allowed publicly case 501: // Error allowed publicly case 503: // Error allowed publicly break; From 8df8a5a1e1f21169a0b9b1fa1e6f65dad7d2afce Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:38:47 +0530 Subject: [PATCH 07/13] composer update --- composer.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 0cbc3edd2e..2cdc366a2e 100644 --- a/composer.lock +++ b/composer.lock @@ -2473,16 +2473,16 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", "shasum": "" }, "require": { @@ -2514,9 +2514,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.2" + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" }, - "time": "2023-09-27T20:04:15+00:00" + "time": "2024-01-30T19:34:25+00:00" }, { "name": "doctrine/instantiator", @@ -5174,5 +5174,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } From 851e5c515447da59524a1d5f46486d4c29d27c91 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:40:41 +0530 Subject: [PATCH 08/13] Remove restrict group and add logic to auth group --- app/controllers/api/account.php | 10 ++++---- app/controllers/api/teams.php | 4 ++-- app/controllers/shared/api.php | 28 +++++++++------------- app/controllers/shared/api/auth.php | 13 +++++++++- tests/e2e/Services/Account/AccountBase.php | 2 +- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4e9d6d2146..8589c6a747 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -55,7 +55,7 @@ $oauthDefaultFailure = '/auth/oauth2/failure'; App::post('/v1/account') ->desc('Create account') - ->groups(['api', 'account', 'auth', 'restrict']) + ->groups(['api', 'account', 'auth']) ->label('event', 'users.[userId].create') ->label('scope', 'sessions.write') ->label('auth.type', 'emailPassword') @@ -452,7 +452,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') App::get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 redirect') - ->groups(['api', 'account', 'session', 'restrict']) + ->groups(['api', 'account', 'session']) ->label('error', __DIR__ . '/../../views/general/error.phtml') ->label('event', 'users.[userId].sessions.[sessionId].create') ->label('scope', 'public') @@ -1569,7 +1569,7 @@ App::put('/v1/account/sessions/magic-url') App::post('/v1/account/sessions/token') ->desc('Create session') ->label('event', 'users.[userId].sessions.[sessionId].create') - ->groups(['api', 'account', 'restrict']) + ->groups(['api', 'account']) ->label('scope', 'sessions.write') ->label('audits.event', 'session.create') ->label('audits.resource', 'user/{response.userId}') @@ -1763,7 +1763,7 @@ App::post('/v1/account/tokens/phone') App::post('/v1/account/sessions/anonymous') ->desc('Create anonymous session') - ->groups(['api', 'account', 'auth', 'session', 'restrict']) + ->groups(['api', 'account', 'auth', 'session']) ->label('event', 'users.[userId].sessions.[sessionId].create') ->label('scope', 'sessions.write') ->label('auth.type', 'anonymous') @@ -3357,7 +3357,7 @@ App::post('/v1/account/verification/phone') App::put('/v1/account/verification/phone') ->desc('Create phone verification (confirmation)') - ->groups(['api', 'account', 'restrict']) + ->groups(['api', 'account']) ->label('scope', 'public') ->label('event', 'users.[userId].verification.[tokenId].update') ->label('audits.event', 'verification.update') diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index abca065d34..1dce2b3b88 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -42,7 +42,7 @@ use Utopia\Validator\Text; App::post('/v1/teams') ->desc('Create team') - ->groups(['api', 'teams', 'restrict']) + ->groups(['api', 'teams']) ->label('event', 'teams.[teamId].create') ->label('scope', 'teams.write') ->label('audits.event', 'team.create') @@ -358,7 +358,7 @@ App::delete('/v1/teams/:teamId') App::post('/v1/teams/:teamId/memberships') ->desc('Create team membership') - ->groups(['api', 'teams', 'auth', 'restrict']) + ->groups(['api', 'teams', 'auth']) ->label('event', 'teams.[teamId].memberships.[membershipId].create') ->label('scope', 'teams.write') ->label('auth.type', 'invites') diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index ddff19b1d6..a80fa90674 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -320,7 +320,17 @@ App::init() ->inject('utopia') ->inject('request') ->inject('project') - ->action(function (App $utopia, Request $request, Document $project) { + ->inject('geodb') + ->action(function (App $utopia, Request $request, Document $project, Reader $geodb) { + $denylist = App::getEnv('_APP_COUNTRIES_DENYLIST', ''); + if (!empty($denylist)) { + $countries = explode(',', $denylist); + $record = $geodb->get($request->getIP()) ?? []; + $country = $record['country']['iso_code'] ?? ''; + if (in_array($country, $countries)) { + throw new Exception(Exception::GENERAL_REGION_ACCESS_DENIED); + } + } $route = $utopia->getRoute(); @@ -628,19 +638,3 @@ App::init() throw new Exception(Exception::GENERAL_USAGE_DISABLED); } }); - -App::init() - ->groups(['restrict']) - ->inject('request') - ->inject('geodb') - ->action(function (Request $request, Reader $geodb) { - $denylist = App::getEnv('_APP_COUNTRIES_DENYLIST', ''); - if (!empty($denylist)) { - $countries = explode(',', $denylist); - $record = $geodb->get($request->getIP()) ?? []; - $country = $record['country']['iso_code'] ?? ''; - if (in_array($country, $countries)) { - throw new Exception(Exception::GENERAL_REGION_ACCESS_DENIED); - } - } - }); diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php index b603726be0..f9b0071f92 100644 --- a/app/controllers/shared/api/auth.php +++ b/app/controllers/shared/api/auth.php @@ -6,13 +6,24 @@ use Utopia\App; use Appwrite\Extend\Exception; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; +use MaxMind\Db\Reader; App::init() ->groups(['auth']) ->inject('utopia') ->inject('request') ->inject('project') - ->action(function (App $utopia, Request $request, Document $project) { + ->inject('geodb') + ->action(function (App $utopia, Request $request, Document $project, Reader $geodb) { + $denylist = App::getEnv('_APP_COUNTRIES_DENYLIST', ''); + if (!empty($denylist)) { + $countries = explode(',', $denylist); + $record = $geodb->get($request->getIP()) ?? []; + $country = $record['country']['iso_code'] ?? ''; + if (in_array($country, $countries)) { + throw new Exception(Exception::GENERAL_REGION_ACCESS_DENIED); + } + } $route = $utopia->match($request); diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 9deae301d7..2911ad4776 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -57,7 +57,7 @@ trait AccountBase 'name' => $name, ]); - $this->assertEquals(451, $response['headers']['status-code']); + // $this->assertEquals(451, $response['headers']['status-code']); $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', From 1f0dc6cc1e46c568749506d885fb809b09ee5ff6 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:50:27 +0530 Subject: [PATCH 09/13] Update swoole version --- composer.json | 2 +- composer.lock | 32 +++++++++++----------- tests/e2e/Services/Account/AccountBase.php | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index b6951e9a43..d3afb874c5 100644 --- a/composer.json +++ b/composer.json @@ -65,7 +65,7 @@ "utopia-php/queue": "0.6.*", "utopia-php/registry": "0.5.*", "utopia-php/storage": "0.18.*", - "utopia-php/swoole": "0.5.*", + "utopia-php/swoole": "0.8.*", "utopia-php/vcs": "0.6.*", "utopia-php/websocket": "0.1.*", "matomo/device-detector": "6.1.*", diff --git a/composer.lock b/composer.lock index 4ae4fd3742..8c30f5da72 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": "4b9b6ff602a179493e0196636d961e9c", + "content-hash": "f146300fe86e42d31f9cd99dd0807bbe", "packages": [ { "name": "adhocore/jwt", @@ -1195,16 +1195,16 @@ }, { "name": "utopia-php/database", - "version": "0.48.0", + "version": "0.48.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2651f41b9d3909dc123d26becfb6a3a44fb63077" + "reference": "52abe057180a76fe354a516300344b33f268f6ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2651f41b9d3909dc123d26becfb6a3a44fb63077", - "reference": "2651f41b9d3909dc123d26becfb6a3a44fb63077", + "url": "https://api.github.com/repos/utopia-php/database/zipball/52abe057180a76fe354a516300344b33f268f6ea", + "reference": "52abe057180a76fe354a516300344b33f268f6ea", "shasum": "" }, "require": { @@ -1245,9 +1245,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.48.0" + "source": "https://github.com/utopia-php/database/tree/0.48.1" }, - "time": "2024-01-19T08:17:22+00:00" + "time": "2024-02-02T04:54:13+00:00" }, { "name": "utopia-php/domains", @@ -2084,28 +2084,28 @@ }, { "name": "utopia-php/swoole", - "version": "0.5.0", + "version": "0.8.2", "source": { "type": "git", "url": "https://github.com/utopia-php/swoole.git", - "reference": "c2a3a4f944a2f22945af3cbcb95b13f0769628b1" + "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/swoole/zipball/c2a3a4f944a2f22945af3cbcb95b13f0769628b1", - "reference": "c2a3a4f944a2f22945af3cbcb95b13f0769628b1", + "url": "https://api.github.com/repos/utopia-php/swoole/zipball/5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", + "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", "shasum": "" }, "require": { "ext-swoole": "*", "php": ">=8.0", - "utopia-php/framework": "0.*.*" + "utopia-php/framework": "0.33.*" }, "require-dev": { "laravel/pint": "1.2.*", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.3", - "swoole/ide-helper": "4.8.3", - "vimeo/psalm": "4.15.0" + "swoole/ide-helper": "5.0.2" }, "type": "library", "autoload": { @@ -2129,9 +2129,9 @@ ], "support": { "issues": "https://github.com/utopia-php/swoole/issues", - "source": "https://github.com/utopia-php/swoole/tree/0.5.0" + "source": "https://github.com/utopia-php/swoole/tree/0.8.2" }, - "time": "2022-10-19T22:19:07+00:00" + "time": "2024-02-01T14:54:12+00:00" }, { "name": "utopia-php/system", diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 2911ad4776..9deae301d7 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -57,7 +57,7 @@ trait AccountBase 'name' => $name, ]); - // $this->assertEquals(451, $response['headers']['status-code']); + $this->assertEquals(451, $response['headers']['status-code']); $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', From d96d84201e4fa644e9ea1e26f8ce74ab079fe5ec Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:03:20 +0530 Subject: [PATCH 10/13] Add auth group to create email token endpoint --- app/controllers/api/account.php | 4 ++-- app/controllers/shared/api.php | 6 ++++++ app/controllers/shared/api/auth.php | 6 ++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index b7b92cd9c6..0eb79e1fb4 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1212,9 +1212,9 @@ App::post('/v1/account/tokens/magic-url') App::post('/v1/account/tokens/email') ->desc('Create email token (OTP)') - ->groups(['api', 'account']) + ->groups(['api', 'account', 'auth']) ->label('scope', 'sessions.write') - ->label('auth.type', 'email') + ->label('auth.type', 'email-otp') ->label('audits.event', 'session.create') ->label('audits.resource', 'user/{response.userId}') ->label('audits.userId', '{response.userId}') diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index a80fa90674..698de76fa3 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -379,6 +379,12 @@ App::init() } break; + case 'email-otp': + if (($auths['emailOTP'] ?? true) === false) { + throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email OTP authentication is disabled for this project'); + } + break; + default: throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route'); break; diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php index f9b0071f92..2fbcfb64d3 100644 --- a/app/controllers/shared/api/auth.php +++ b/app/controllers/shared/api/auth.php @@ -72,6 +72,12 @@ App::init() } break; + case 'email-otp': + if (($auths['emailOTP'] ?? true) === false) { + throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email OTP authentication is disabled for this project'); + } + break; + default: throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route'); break; From 52c91096856d45a12959515a81fadec6994eb27a Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 2 Feb 2024 19:35:35 +0530 Subject: [PATCH 11/13] Improve error message --- app/config/errors.php | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index e0c708d5ac..79a092ec99 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -116,7 +116,7 @@ return [ ], Exception::GENERAL_REGION_ACCESS_DENIED => [ 'name' => Exception::GENERAL_REGION_ACCESS_DENIED, - 'description' => 'Sorry, access from the current region is denied.', + 'description' => 'Your location is not supported due to legal requirements.', 'code' => 451, ], diff --git a/composer.lock b/composer.lock index c90c5f760f..28d4f4072a 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": "3b43bf6f0fca50a3a2834e1bbaa90d63", + "content-hash": "e6e0d6874f4d718d96396d71a66864da", "packages": [ { "name": "adhocore/jwt", From d40a51b50a0db3c48d904f397a36bed6e9389ada Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 2 Feb 2024 20:01:54 +0530 Subject: [PATCH 12/13] Only disallow console requests --- .env | 4 ++-- app/controllers/shared/api.php | 4 ++-- app/controllers/shared/api/auth.php | 4 ++-- docker-compose.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.env b/.env index e681d97f8f..c8c0fccb2b 100644 --- a/.env +++ b/.env @@ -4,6 +4,7 @@ _APP_WORKER_PER_CORE=6 _APP_CONSOLE_WHITELIST_ROOT=disabled _APP_CONSOLE_WHITELIST_EMAILS= _APP_CONSOLE_WHITELIST_IPS= +_APP_CONSOLE_COUNTRIES_DENYLIST=AQ _APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io _APP_SYSTEM_EMAIL_NAME=Appwrite _APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io @@ -99,5 +100,4 @@ _APP_ASSISTANT_OPENAI_API_KEY= _APP_MESSAGE_SMS_TEST_DSN= _APP_MESSAGE_EMAIL_TEST_DSN= _APP_MESSAGE_PUSH_TEST_DSN= -_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10 -_APP_COUNTRIES_DENYLIST=AQ \ No newline at end of file +_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10 \ No newline at end of file diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 698de76fa3..85a74ecca8 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -322,8 +322,8 @@ App::init() ->inject('project') ->inject('geodb') ->action(function (App $utopia, Request $request, Document $project, Reader $geodb) { - $denylist = App::getEnv('_APP_COUNTRIES_DENYLIST', ''); - if (!empty($denylist)) { + $denylist = App::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', ''); + if (!empty($denylist) && $project->getId() === 'console') { $countries = explode(',', $denylist); $record = $geodb->get($request->getIP()) ?? []; $country = $record['country']['iso_code'] ?? ''; diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php index 2fbcfb64d3..0b24f8b3a1 100644 --- a/app/controllers/shared/api/auth.php +++ b/app/controllers/shared/api/auth.php @@ -15,8 +15,8 @@ App::init() ->inject('project') ->inject('geodb') ->action(function (App $utopia, Request $request, Document $project, Reader $geodb) { - $denylist = App::getEnv('_APP_COUNTRIES_DENYLIST', ''); - if (!empty($denylist)) { + $denylist = App::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', ''); + if (!empty($denylist && $project->getId() === 'console')) { $countries = explode(',', $denylist); $record = $geodb->get($request->getIP()) ?? []; $country = $record['country']['iso_code'] ?? ''; diff --git a/docker-compose.yml b/docker-compose.yml index 9d61c86987..72d81cc8a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -188,7 +188,7 @@ services: - _APP_MESSAGE_SMS_TEST_DSN - _APP_MESSAGE_EMAIL_TEST_DSN - _APP_MESSAGE_PUSH_TEST_DSN - - _APP_COUNTRIES_DENYLIST + - _APP_CONSOLE_COUNTRIES_DENYLIST appwrite-realtime: entrypoint: realtime From 0f1df4b5bb367587422cb4f6f16cd344c265603c Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:25:52 +0530 Subject: [PATCH 13/13] Fixed tests --- tests/e2e/Services/Account/AccountBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 24773f8e1c..ebb9ea644c 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -48,7 +48,7 @@ trait AccountBase $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => 'console', 'x-forwarded-for' => '103.152.127.250' // Test IP for denied access region ]), [ 'userId' => ID::unique(),