From d238ec1d860b80e1f15133732f63076979432bc5 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh <77877486+everly-gif@users.noreply.github.com> Date: Fri, 18 Mar 2022 01:54:50 +0530 Subject: [PATCH 01/54] fix spelling of default values for s3 and spaces regions --- app/config/variables.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/variables.php b/app/config/variables.php index 5cae4aa564..f11cfedaf3 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -457,7 +457,7 @@ return [ 'name' => '_APP_STORAGE_S3_REGION', 'description' => 'AWS S3 storage region. Required when storage adapter is set to S3. You can find your region info for your bucket from AWS console.', 'introduction' => '0.13.0', - 'default' => 'us-eas-1', + 'default' => 'us-east-1', 'required' => false, 'question' => '', ], @@ -489,7 +489,7 @@ return [ 'name' => '_APP_STORAGE_DO_SPACES_REGION', 'description' => 'DigitalOcean spaces region. Required when storage adapter is set to DOSpaces. You can find your region info for your space from DigitalOcean console.', 'introduction' => '0.13.0', - 'default' => 'us-eas-1', + 'default' => 'us-east-1', 'required' => false, 'question' => '', ], From ee46e141032048ce4784899743d8dc2f22061713 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 1 Apr 2022 16:04:56 +0530 Subject: [PATCH 02/54] Create Auth0 OAuth Adapter --- app/config/providers.php | 10 + app/views/console/users/oauth/auth0.phtml | 12 ++ public/images/users/auth0.png | Bin 0 -> 2512 bytes public/scripts/views/forms/oauth-custom.js | 4 + src/Appwrite/Auth/OAuth2/Auth0.php | 206 +++++++++++++++++++++ 5 files changed, 232 insertions(+) create mode 100644 app/views/console/users/oauth/auth0.phtml create mode 100644 public/images/users/auth0.png create mode 100644 src/Appwrite/Auth/OAuth2/Auth0.php diff --git a/app/config/providers.php b/app/config/providers.php index 9a6b5fb50f..aa3877005c 100644 --- a/app/config/providers.php +++ b/app/config/providers.php @@ -21,6 +21,16 @@ return [ // Ordered by ABC. 'beta' => true, 'mock' => false, ], + 'auth0' => [ + 'name' => 'Auth0', + 'developers' => 'https://auth0.com/developers', + 'icon' => 'icon-auth0', + 'enabled' => true, + 'sandbox' => false, + 'form' => 'auth0.phtml', + 'beta' => false, + 'mock' => false, + ], 'bitbucket' => [ 'name' => 'BitBucket', 'developers' => 'https://developer.atlassian.com/bitbucket', diff --git a/app/views/console/users/oauth/auth0.phtml b/app/views/console/users/oauth/auth0.phtml new file mode 100644 index 0000000000..504eca6bc1 --- /dev/null +++ b/app/views/console/users/oauth/auth0.phtml @@ -0,0 +1,12 @@ +getParam('provider', ''); +?> + + + + + + + + + \ No newline at end of file diff --git a/public/images/users/auth0.png b/public/images/users/auth0.png new file mode 100644 index 0000000000000000000000000000000000000000..d0800c6294456910029132f3edf02cbbdbefc6fb GIT binary patch literal 2512 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 literal 0 HcmV?d00001 diff --git a/public/scripts/views/forms/oauth-custom.js b/public/scripts/views/forms/oauth-custom.js index 323c874411..22ab85f872 100644 --- a/public/scripts/views/forms/oauth-custom.js +++ b/public/scripts/views/forms/oauth-custom.js @@ -16,6 +16,10 @@ "keyId": "oauth2AppleKeyId", "teamId": "oauth2AppleTeamId", "p8": "oauth2AppleP8" + }, + "Auth0": { + "clientSecret": "oauth2Auth0ClientSecret", + "auth0Domain": "oauth2Auth0Domain" } } let provider = element.getAttribute("data-forms-oauth-custom"); diff --git a/src/Appwrite/Auth/OAuth2/Auth0.php b/src/Appwrite/Auth/OAuth2/Auth0.php new file mode 100644 index 0000000000..43a4038aa1 --- /dev/null +++ b/src/Appwrite/Auth/OAuth2/Auth0.php @@ -0,0 +1,206 @@ +getAuth0Domain().'/authorize?'.\http_build_query([ + 'client_id' => $this->appID, + 'redirect_uri' => $this->callback, + 'state'=> \json_encode($this->state), + 'scope'=> \implode(' ', $this->getScopes()), + 'response_type' => 'code' + ]); + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokens(string $code): array + { + if(empty($this->tokens)) { + $headers = ['Content-Type: application/x-www-form-urlencoded']; + $this->tokens = \json_decode($this->request( + 'POST', + 'https://'.$this->getAuth0Domain().'/oauth/token', + $headers, + \http_build_query([ + 'code' => $code, + 'client_id' => $this->appID, + 'client_secret' => $this->getClientSecret(), + 'redirect_uri' => $this->callback, + 'scope' => \implode(' ', $this->getScopes()), + 'grant_type' => 'authorization_code' + ]) + ), true); + } + + return $this->tokens; + } + + + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + $headers = ['Content-Type: application/x-www-form-urlencoded']; + $this->tokens = \json_decode($this->request( + 'POST', + 'https://'.$this->getAuth0Domain().'/oauth/token', + $headers, + \http_build_query([ + 'refresh_token' => $refreshToken, + 'client_id' => $this->appID, + 'client_secret' => $this->getClientSecret(), + 'grant_type' => 'refresh_token' + ]) + ), true); + + if(empty($this->tokens['refresh_token'])) { + $this->tokens['refresh_token'] = $refreshToken; + } + + return $this->tokens; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserID(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['sub'])) { + return $user['sub']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserEmail(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['email'])) { + return $user['email']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserName(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['name'])) { + return $user['name']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return array + */ + protected function getUser(string $accessToken) + { + if (empty($this->user)) { + $headers = ['Authorization: Bearer '. \urlencode($accessToken)]; + $user = $this->request('GET', 'https://'.$this->getAuth0Domain().'/userinfo', $headers); + $this->user = \json_decode($user, true); + } + + return $this->user; + } + + /** + * Extracts the Client Secret from the JSON stored in appSecret + * @return string + */ + protected function getClientSecret(): string + { + $secret = $this->decodeJson(); + + return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : ''; + } + + /** + * Extracts the Auth0 Domain from the JSON stored in appSecret. Defaults to 'common' as a fallback + * @return string + */ + protected function getAuth0Domain(): string + { + $secret = $this->decodeJson(); + return (isset($secret['auth0Domain'])) ? $secret['auth0Domain'] : ''; + } + + /** + * Decode the JSON stored in appSecret + * @return array + */ + protected function decodeJson(): array + { + try { + $secret = \json_decode($this->appSecret, true); + } catch (\Throwable $th) { + throw new Exception('Invalid secret'); + } + return $secret; + } +} \ No newline at end of file From 4400d4b8079454f825639267d0a2f271af113b7a Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 1 Apr 2022 16:54:58 +0530 Subject: [PATCH 03/54] Add refresh token support --- src/Appwrite/Auth/OAuth2/Auth0.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/OAuth2/Auth0.php b/src/Appwrite/Auth/OAuth2/Auth0.php index 43a4038aa1..0709217b5b 100644 --- a/src/Appwrite/Auth/OAuth2/Auth0.php +++ b/src/Appwrite/Auth/OAuth2/Auth0.php @@ -15,7 +15,8 @@ class Auth0 extends OAuth2 protected $scopes = [ 'openid', 'profile', - 'email' + 'email', + 'offline_access' ]; /** From 0cacfd17cbff94e8b80ab8f825a0822b0f247085 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Fri, 1 Apr 2022 19:43:29 +0530 Subject: [PATCH 04/54] Update Auth0 domain info tip for better clarity --- app/views/console/users/oauth/auth0.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/console/users/oauth/auth0.phtml b/app/views/console/users/oauth/auth0.phtml index 504eca6bc1..8509a582b5 100644 --- a/app/views/console/users/oauth/auth0.phtml +++ b/app/views/console/users/oauth/auth0.phtml @@ -6,7 +6,7 @@ $provider = $this->getParam('provider', ''); - + \ No newline at end of file From 0784981c4209b4d583fc1384d9fa98d4da19b153 Mon Sep 17 00:00:00 2001 From: Aditya Oberai <31401437+adityaoberai@users.noreply.github.com> Date: Sun, 3 Apr 2022 19:17:16 +0530 Subject: [PATCH 05/54] Update src/Appwrite/Auth/OAuth2/Auth0.php Co-authored-by: Christy Jacob --- src/Appwrite/Auth/OAuth2/Auth0.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/OAuth2/Auth0.php b/src/Appwrite/Auth/OAuth2/Auth0.php index 0709217b5b..9186f7c1cd 100644 --- a/src/Appwrite/Auth/OAuth2/Auth0.php +++ b/src/Appwrite/Auth/OAuth2/Auth0.php @@ -84,7 +84,7 @@ class Auth0 extends OAuth2 * * @return array */ - public function refreshTokens(string $refreshToken):array + public function refreshTokens(string $refreshToken): array { $headers = ['Content-Type: application/x-www-form-urlencoded']; $this->tokens = \json_decode($this->request( From e7fd6a81ac873283fe99175114d28b6c3da77416 Mon Sep 17 00:00:00 2001 From: Aditya Oberai <31401437+adityaoberai@users.noreply.github.com> Date: Sun, 3 Apr 2022 19:17:29 +0530 Subject: [PATCH 06/54] Update src/Appwrite/Auth/OAuth2/Auth0.php Co-authored-by: Christy Jacob --- src/Appwrite/Auth/OAuth2/Auth0.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/OAuth2/Auth0.php b/src/Appwrite/Auth/OAuth2/Auth0.php index 9186f7c1cd..b7e6071417 100644 --- a/src/Appwrite/Auth/OAuth2/Auth0.php +++ b/src/Appwrite/Auth/OAuth2/Auth0.php @@ -195,7 +195,7 @@ class Auth0 extends OAuth2 * Decode the JSON stored in appSecret * @return array */ - protected function decodeJson(): array + protected function getAppSecret(): array { try { $secret = \json_decode($this->appSecret, true); From 0c09d73a20d35b841746bf1729df58616bc5afbf Mon Sep 17 00:00:00 2001 From: Aditya Oberai <31401437+adityaoberai@users.noreply.github.com> Date: Sun, 3 Apr 2022 21:57:15 +0530 Subject: [PATCH 07/54] Update src/Appwrite/Auth/OAuth2/Auth0.php Co-authored-by: Christy Jacob --- src/Appwrite/Auth/OAuth2/Auth0.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/OAuth2/Auth0.php b/src/Appwrite/Auth/OAuth2/Auth0.php index b7e6071417..7195bce83e 100644 --- a/src/Appwrite/Auth/OAuth2/Auth0.php +++ b/src/Appwrite/Auth/OAuth2/Auth0.php @@ -200,7 +200,7 @@ class Auth0 extends OAuth2 try { $secret = \json_decode($this->appSecret, true); } catch (\Throwable $th) { - throw new Exception('Invalid secret'); + throw new \Exception('Invalid secret'); } return $secret; } From 3397036224f9e3f3a23fc670baad79349072be44 Mon Sep 17 00:00:00 2001 From: Aditya Oberai <31401437+adityaoberai@users.noreply.github.com> Date: Sun, 3 Apr 2022 21:57:28 +0530 Subject: [PATCH 08/54] Update src/Appwrite/Auth/OAuth2/Auth0.php Co-authored-by: Christy Jacob --- src/Appwrite/Auth/OAuth2/Auth0.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/OAuth2/Auth0.php b/src/Appwrite/Auth/OAuth2/Auth0.php index 7195bce83e..0558bfc24c 100644 --- a/src/Appwrite/Auth/OAuth2/Auth0.php +++ b/src/Appwrite/Auth/OAuth2/Auth0.php @@ -198,7 +198,7 @@ class Auth0 extends OAuth2 protected function getAppSecret(): array { try { - $secret = \json_decode($this->appSecret, true); + $secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR); } catch (\Throwable $th) { throw new \Exception('Invalid secret'); } From 90e55f4c34a2d43aa111ecf86a2cfcba23695213 Mon Sep 17 00:00:00 2001 From: Aditya Oberai <31401437+adityaoberai@users.noreply.github.com> Date: Sun, 3 Apr 2022 21:57:40 +0530 Subject: [PATCH 09/54] Update src/Appwrite/Auth/OAuth2/Auth0.php Co-authored-by: Christy Jacob --- src/Appwrite/Auth/OAuth2/Auth0.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/OAuth2/Auth0.php b/src/Appwrite/Auth/OAuth2/Auth0.php index 0558bfc24c..75616e41c8 100644 --- a/src/Appwrite/Auth/OAuth2/Auth0.php +++ b/src/Appwrite/Auth/OAuth2/Auth0.php @@ -159,7 +159,7 @@ class Auth0 extends OAuth2 * * @return array */ - protected function getUser(string $accessToken) + protected function getUser(string $accessToken): array { if (empty($this->user)) { $headers = ['Authorization: Bearer '. \urlencode($accessToken)]; From 528b4eb947260e1c89800249ff8399253b324226 Mon Sep 17 00:00:00 2001 From: adityaoberai Date: Sun, 3 Apr 2022 22:07:49 +0530 Subject: [PATCH 10/54] Add suggested changes --- src/Appwrite/Auth/OAuth2/Auth0.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Auth/OAuth2/Auth0.php b/src/Appwrite/Auth/OAuth2/Auth0.php index 75616e41c8..b1c9c8ce1f 100644 --- a/src/Appwrite/Auth/OAuth2/Auth0.php +++ b/src/Appwrite/Auth/OAuth2/Auth0.php @@ -172,27 +172,30 @@ class Auth0 extends OAuth2 /** * Extracts the Client Secret from the JSON stored in appSecret + * * @return string */ protected function getClientSecret(): string { - $secret = $this->decodeJson(); + $secret = $this->getAppSecret(); return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : ''; } /** - * Extracts the Auth0 Domain from the JSON stored in appSecret. Defaults to 'common' as a fallback + * Extracts the Auth0 Domain from the JSON stored in appSecret + * * @return string */ protected function getAuth0Domain(): string { - $secret = $this->decodeJson(); + $secret = $this->getAppSecret(); return (isset($secret['auth0Domain'])) ? $secret['auth0Domain'] : ''; } /** * Decode the JSON stored in appSecret + * * @return array */ protected function getAppSecret(): array From f6e8f082c945cf235bd45a7f0e677a7409d81a1f Mon Sep 17 00:00:00 2001 From: Wen Yu Ge Date: Mon, 18 Apr 2022 16:15:22 -0400 Subject: [PATCH 11/54] Updates create collection permission description to be clearer --- app/controllers/api/database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 1cf78b07e3..653097d12f 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -150,7 +150,7 @@ App::post('/v1/database/collections') ->label('sdk.response.model', Response::MODEL_COLLECTION) ->param('collectionId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.') - ->param('permission', null, new WhiteList(['document', 'collection']), 'Permissions type model to use for reading documents in this collection. You can use collection-level permission set once on the collection using the `read` and `write` params, or you can set document-level permission where each document read and write params will decide who has access to read and write to each document individually. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.') + ->param('permission', null, new WhiteList(['document', 'collection']), 'Specifies the permissions model used in this collection, which accepts either \'collection\' or \'document\'. For \'collection\' level permission, the permissions specified in read and write params are applied to all documents in the collection. For \'document\' level permissions, read and write permissions are specified in each document. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.') ->param('read', null, new Permissions(), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.') ->param('write', null, new Permissions(), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.') ->inject('response') From f3252fc3d3ba46c2c9338d8c05bcf9e290df0a22 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Wed, 20 Apr 2022 16:19:50 +0000 Subject: [PATCH 12/54] cleanup code for locale --- app/controllers/api/locale.php | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index 34018f6e90..d3a0916960 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -20,12 +20,8 @@ App::get('/v1/locale') ->inject('response') ->inject('locale') ->inject('geodb') - ->action(function ($request, $response, $locale, $geodb) { - /** @var Appwrite\Utopia\Request $request */ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Locale\Locale $locale */ - /** @var MaxMind\Db\Reader $geodb */ - + ->action(function (Request $request, Response $response, Locale $locale, Reader $geodb) { + $eu = Config::getParam('locale-eu'); $currencies = Config::getParam('locale-currencies'); $output = []; @@ -82,10 +78,8 @@ App::get('/v1/locale/countries') ->label('sdk.response.model', Response::MODEL_COUNTRY_LIST) ->inject('response') ->inject('locale') - ->action(function ($response, $locale) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Locale\Locale $locale */ - + ->action(function (Response $response, Locale $locale) { + $list = Config::getParam('locale-countries'); /* @var $list array */ $output = []; @@ -116,9 +110,7 @@ App::get('/v1/locale/countries/eu') ->label('sdk.response.model', Response::MODEL_COUNTRY_LIST) ->inject('response') ->inject('locale') - ->action(function ($response, $locale) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Locale\Locale $locale */ + ->action(function (Response $response, Locale $locale) { $eu = Config::getParam('locale-eu'); $output = []; @@ -152,10 +144,8 @@ App::get('/v1/locale/countries/phones') ->label('sdk.response.model', Response::MODEL_PHONE_LIST) ->inject('response') ->inject('locale') - ->action(function ($response, $locale) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Locale\Locale $locale */ - + ->action(function (Response $response, Locale $locale) { + $list = Config::getParam('locale-phones'); /* @var $list array */ $output = []; @@ -187,9 +177,7 @@ App::get('/v1/locale/continents') ->label('sdk.response.model', Response::MODEL_CONTINENT_LIST) ->inject('response') ->inject('locale') - ->action(function ($response, $locale) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Locale\Locale $locale */ + ->action(function (Response $response,Locale $locale) { $list = Config::getParam('locale-continents'); /* @var $list array */ @@ -219,8 +207,7 @@ App::get('/v1/locale/currencies') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_CURRENCY_LIST) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $list = Config::getParam('locale-currencies'); @@ -242,8 +229,7 @@ App::get('/v1/locale/languages') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LANGUAGE_LIST) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $list = Config::getParam('locale-languages'); From 277897eefca5c0989ee1f775348a8eff136acfba Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Wed, 20 Apr 2022 16:44:34 +0000 Subject: [PATCH 13/54] push changes --- app/controllers/api/locale.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index d3a0916960..e97c50b16d 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -2,8 +2,11 @@ use Utopia\Database\Document; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Request; use Utopia\App; use Utopia\Config\Config; +use Utopia\Locale\Locale; +use MaxMind\Db\Reader; App::get('/v1/locale') ->desc('Get User Locale') From f5ca72b6122b26e8274d4ec8fdd83d61c6a1983a Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Thu, 21 Apr 2022 02:47:51 +0000 Subject: [PATCH 14/54] add space --- app/controllers/api/locale.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index e97c50b16d..6c8f7797a0 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -180,7 +180,7 @@ App::get('/v1/locale/continents') ->label('sdk.response.model', Response::MODEL_CONTINENT_LIST) ->inject('response') ->inject('locale') - ->action(function (Response $response,Locale $locale) { + ->action(function (Response $response, Locale $locale) { $list = Config::getParam('locale-continents'); /* @var $list array */ From e0a8ef8b3aaa76f59c7568bbf4f9d9faae42e6f2 Mon Sep 17 00:00:00 2001 From: Tanay Pant Date: Sat, 23 Apr 2022 20:14:32 +0200 Subject: [PATCH 15/54] Create Okta OAuth Adapter --- app/config/providers.php | 10 + app/views/console/users/oauth/okta.phtml | 12 ++ public/images/users/okta.png | Bin 0 -> 8762 bytes public/scripts/views/forms/oauth-custom.js | 4 + src/Appwrite/Auth/OAuth2/Okta.php | 210 +++++++++++++++++++++ 5 files changed, 236 insertions(+) create mode 100644 app/views/console/users/oauth/okta.phtml create mode 100644 public/images/users/okta.png create mode 100644 src/Appwrite/Auth/OAuth2/Okta.php diff --git a/app/config/providers.php b/app/config/providers.php index 9a6b5fb50f..27f63b88fb 100644 --- a/app/config/providers.php +++ b/app/config/providers.php @@ -141,6 +141,16 @@ return [ // Ordered by ABC. 'beta' => false, 'mock' => false, ], + 'okta' => [ + 'name' => 'Okta', + 'developers' => 'https://developer.okta.com/', + 'icon' => 'icon-okta', + 'enabled' => true, + 'sandbox' => false, + 'form' => 'okta.phtml', + 'beta' => false, + 'mock' => false, + ], 'paypal' => [ 'name' => 'PayPal', 'developers' => 'https://developer.paypal.com/docs/api/overview/', diff --git a/app/views/console/users/oauth/okta.phtml b/app/views/console/users/oauth/okta.phtml new file mode 100644 index 0000000000..44c47c6550 --- /dev/null +++ b/app/views/console/users/oauth/okta.phtml @@ -0,0 +1,12 @@ +getParam('provider', ''); +?> + + + + + + + + + \ No newline at end of file diff --git a/public/images/users/okta.png b/public/images/users/okta.png new file mode 100644 index 0000000000000000000000000000000000000000..c66b273cfdc5618f207f022507c0657ee6a2ed5c GIT binary patch literal 8762 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 literal 0 HcmV?d00001 diff --git a/public/scripts/views/forms/oauth-custom.js b/public/scripts/views/forms/oauth-custom.js index fd8bd855d0..fabb2b17ee 100644 --- a/public/scripts/views/forms/oauth-custom.js +++ b/public/scripts/views/forms/oauth-custom.js @@ -16,6 +16,10 @@ "keyID": "oauth2AppleKeyId", "teamID": "oauth2AppleTeamId", "p8": "oauth2AppleP8" + }, + "Okta": { + "clientSecret": "oauth2OktaClientSecret", + "oktaDomain": "oauth2OktaDomain" } } let provider = element.getAttribute("data-forms-oauth-custom"); diff --git a/src/Appwrite/Auth/OAuth2/Okta.php b/src/Appwrite/Auth/OAuth2/Okta.php new file mode 100644 index 0000000000..70ea0d4b13 --- /dev/null +++ b/src/Appwrite/Auth/OAuth2/Okta.php @@ -0,0 +1,210 @@ +getOktaDomain().'/oauth2/default/v1/authorize?'.\http_build_query([ + 'client_id' => $this->appID, + 'redirect_uri' => $this->callback, + 'state'=> \json_encode($this->state), + 'scope'=> \implode(' ', $this->getScopes()), + 'response_type' => 'code' + ]); + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokens(string $code): array + { + if(empty($this->tokens)) { + $headers = ['Content-Type: application/x-www-form-urlencoded']; + $this->tokens = \json_decode($this->request( + 'POST', + 'https://'.$this->getOktaDomain().'/oauth2/default/v1/token', + $headers, + \http_build_query([ + 'code' => $code, + 'client_id' => $this->appID, + 'client_secret' => $this->getClientSecret(), + 'redirect_uri' => $this->callback, + 'scope' => \implode(' ', $this->getScopes()), + 'grant_type' => 'authorization_code' + ]) + ), true); + } + + return $this->tokens; + } + + + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken): array + { + $headers = ['Content-Type: application/x-www-form-urlencoded']; + $this->tokens = \json_decode($this->request( + 'POST', + 'https://'.$this->getOktaDomain().'/oauth2/default/v1/token', + $headers, + \http_build_query([ + 'refresh_token' => $refreshToken, + 'client_id' => $this->appID, + 'client_secret' => $this->getClientSecret(), + 'grant_type' => 'refresh_token' + ]) + ), true); + + if(empty($this->tokens['refresh_token'])) { + $this->tokens['refresh_token'] = $refreshToken; + } + + return $this->tokens; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserID(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['sub'])) { + return $user['sub']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserEmail(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['email'])) { + return $user['email']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserName(string $accessToken): string + { + $user = $this->getUser($accessToken); + + if (isset($user['name'])) { + return $user['name']; + } + + return ''; + } + + /** + * @param string $accessToken + * + * @return array + */ + protected function getUser(string $accessToken): array + { + if (empty($this->user)) { + $headers = ['Authorization: Bearer '. \urlencode($accessToken)]; + $user = $this->request('GET', 'https://'.$this->getOktaDomain().'/v1/userinfo', $headers); + $this->user = \json_decode($user, true); + } + + return $this->user; + } + + /** + * Extracts the Client Secret from the JSON stored in appSecret + * + * @return string + */ + protected function getClientSecret(): string + { + $secret = $this->getAppSecret(); + + return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : ''; + } + + /** + * Extracts the Okta Domain from the JSON stored in appSecret + * + * @return string + */ + protected function getOktaDomain(): string + { + $secret = $this->getAppSecret(); + return (isset($secret['oktaDomain'])) ? $secret['oktaDomain'] : ''; + } + + /** + * Decode the JSON stored in appSecret + * + * @return array + */ + protected function getAppSecret(): array + { + try { + $secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR); + } catch (\Throwable $th) { + throw new \Exception('Invalid secret'); + } + return $secret; + } +} \ No newline at end of file From 8942610219b9559a0f8645af9eac150d3b1c0e12 Mon Sep 17 00:00:00 2001 From: Tanay Pant Date: Sat, 23 Apr 2022 23:29:26 +0200 Subject: [PATCH 16/54] Fix typo --- public/scripts/views/forms/oauth-custom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/views/forms/oauth-custom.js b/public/scripts/views/forms/oauth-custom.js index fabb2b17ee..965296656f 100644 --- a/public/scripts/views/forms/oauth-custom.js +++ b/public/scripts/views/forms/oauth-custom.js @@ -23,7 +23,7 @@ } } let provider = element.getAttribute("data-forms-oauth-custom"); - if (!provider || !providers.hasOwnProperty(provider)) { console.error("Provider for custom form not set or unkown") } + if (!provider || !providers.hasOwnProperty(provider)) { console.error("Provider for custom form not set or unknown") } let config = providers[provider]; // Add Change Listeners for element From adce803d9e0ec79714dc72418117b2a417c4de30 Mon Sep 17 00:00:00 2001 From: Tanay Pant Date: Sat, 23 Apr 2022 23:44:50 +0200 Subject: [PATCH 17/54] Minor fix --- src/Appwrite/Auth/OAuth2/Okta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/OAuth2/Okta.php b/src/Appwrite/Auth/OAuth2/Okta.php index 70ea0d4b13..3277420a3b 100644 --- a/src/Appwrite/Auth/OAuth2/Okta.php +++ b/src/Appwrite/Auth/OAuth2/Okta.php @@ -163,7 +163,7 @@ class Okta extends OAuth2 { if (empty($this->user)) { $headers = ['Authorization: Bearer '. \urlencode($accessToken)]; - $user = $this->request('GET', 'https://'.$this->getOktaDomain().'/v1/userinfo', $headers); + $user = $this->request('GET', 'https://'.$this->getOktaDomain().'oauth2/default/v1/userinfo', $headers); $this->user = \json_decode($user, true); } From ea4d3fb76b41f959e02b5f0a6d17ca52c0c3d171 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Sun, 24 Apr 2022 04:51:28 +0000 Subject: [PATCH 18/54] regroup namespaces --- app/controllers/api/locale.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index 6c8f7797a0..bd3891a893 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -1,12 +1,12 @@ desc('Get User Locale') From f435649acbe33e7f9ac3a1fd992e7761e3c5651d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 25 Apr 2022 07:54:17 +0000 Subject: [PATCH 19/54] Fix 1-element enum --- app/views/console/database/collection.phtml | 2 +- composer.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/console/database/collection.phtml b/app/views/console/database/collection.phtml index 1bdef71d18..3651f05358 100644 --- a/app/views/console/database/collection.phtml +++ b/app/views/console/database/collection.phtml @@ -1040,7 +1040,7 @@ $logs = $this->getParam('logs', null);
- +
diff --git a/composer.lock b/composer.lock index 3084527702..ca66336799 100644 --- a/composer.lock +++ b/composer.lock @@ -2250,16 +2250,16 @@ }, { "name": "utopia-php/framework", - "version": "0.19.9", + "version": "0.19.20", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "4af9fc866edce1b8cff94731fb26c27599118e87" + "reference": "65ced168db8f6e188ceeb0d101f57552c3d8b2af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/4af9fc866edce1b8cff94731fb26c27599118e87", - "reference": "4af9fc866edce1b8cff94731fb26c27599118e87", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/65ced168db8f6e188ceeb0d101f57552c3d8b2af", + "reference": "65ced168db8f6e188ceeb0d101f57552c3d8b2af", "shasum": "" }, "require": { @@ -2293,9 +2293,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.19.9" + "source": "https://github.com/utopia-php/framework/tree/0.19.20" }, - "time": "2022-04-14T15:39:47+00:00" + "time": "2022-04-14T15:42:37+00:00" }, { "name": "utopia-php/image", From d981c8f5e5d5c796517b60312080722535c120d9 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 25 Apr 2022 10:12:58 +0100 Subject: [PATCH 20/54] Rename 'stdout' in executions to 'response' --- app/config/collections.php | 2 +- app/controllers/api/functions.php | 4 +- app/executor.php | 4 +- app/workers/functions.php | 4 +- .../Utopia/Response/Model/Execution.php | 4 +- .../Functions/FunctionsCustomClientTest.php | 4 +- .../Functions/FunctionsCustomServerTest.php | 52 +++++++++---------- 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 3e3de4c7bd..a48eb99c31 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -2121,7 +2121,7 @@ $collections = [ 'filters' => [], ], [ - '$id' => 'stdout', + '$id' => 'response', 'type' => Database::VAR_STRING, 'format' => '', 'size' => 16384, diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index da65d4deb4..2a80c9bb99 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -880,7 +880,7 @@ App::post('/v1/functions/:functionId/executions') 'trigger' => 'http', // http / schedule / event 'status' => 'waiting', // waiting / processing / completed / failed 'statusCode' => 0, - 'stdout' => '', + 'response' => '', 'stderr' => '', 'time' => 0.0, 'search' => implode(' ', [$functionId, $executionId]), @@ -956,7 +956,7 @@ App::post('/v1/functions/:functionId/executions') /** Update execution status */ $execution->setAttribute('status', $executionResponse['status']); $execution->setAttribute('statusCode', $executionResponse['statusCode']); - $execution->setAttribute('stdout', $executionResponse['stdout']); + $execution->setAttribute('response', $executionResponse['response']); $execution->setAttribute('stderr', $executionResponse['stderr']); $execution->setAttribute('time', $executionResponse['time']); } catch (\Throwable $th) { diff --git a/app/executor.php b/app/executor.php index e9f4c21391..104f3db557 100644 --- a/app/executor.php +++ b/app/executor.php @@ -279,7 +279,7 @@ App::post('/v1/runtimes') $endTime = \time(); $container = array_merge($container, [ 'status' => 'ready', - 'stdout' => \utf8_encode($stdout), + 'response' => \utf8_encode($stdout), 'stderr' => \utf8_encode($stderr), 'startTime' => $startTime, 'endTime' => $endTime, @@ -512,7 +512,7 @@ App::post('/v1/execution') $execution = [ 'status' => $functionStatus, 'statusCode' => $statusCode, - 'stdout' => \utf8_encode(\mb_substr($stdout, -16384)), + 'response' => \utf8_encode(\mb_substr($stdout, -16384)), 'stderr' => \utf8_encode(\mb_substr($stderr, -16384)), 'time' => $executionTime, ]; diff --git a/app/workers/functions.php b/app/workers/functions.php index 66464a9d23..96f12fddf7 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -254,7 +254,7 @@ class FunctionsV1 extends Worker 'trigger' => $trigger, 'status' => 'waiting', 'statusCode' => 0, - 'stdout' => '', + 'response' => '', 'stderr' => '', 'time' => 0.0, 'search' => implode(' ', [$functionId, $executionId]), @@ -303,7 +303,7 @@ class FunctionsV1 extends Worker /** Update execution status */ $execution->setAttribute('status', $executionResponse['status']); $execution->setAttribute('statusCode', $executionResponse['statusCode']); - $execution->setAttribute('stdout', $executionResponse['stdout']); + $execution->setAttribute('response', $executionResponse['response']); $execution->setAttribute('stderr', $executionResponse['stderr']); $execution->setAttribute('time', $executionResponse['time']); } catch (\Throwable $th) { diff --git a/src/Appwrite/Utopia/Response/Model/Execution.php b/src/Appwrite/Utopia/Response/Model/Execution.php index 31d32ffbb0..a8031bb0cd 100644 --- a/src/Appwrite/Utopia/Response/Model/Execution.php +++ b/src/Appwrite/Utopia/Response/Model/Execution.php @@ -53,9 +53,9 @@ class Execution extends Model 'default' => 0, 'example' => 0, ]) - ->addRule('stdout', [ + ->addRule('response', [ 'type' => self::TYPE_STRING, - 'description' => 'The script stdout output string. Logs the last 4,000 characters of the execution stdout output.', + 'description' => 'The script response output string. Logs the last 4,000 characters of the execution response output.', 'default' => '', 'example' => '', ]) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index d2443783c0..a895995659 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -208,7 +208,7 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-key' => $apikey, ]); - $output = json_decode($executions['body']['stdout'], true); + $output = json_decode($executions['body']['response'], true); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); $this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']); @@ -383,7 +383,7 @@ class FunctionsCustomClientTest extends Scope 'async' => false ]); - $output = json_decode($execution['body']['stdout'], true); + $output = json_decode($execution['body']['response'], true); $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']); diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index e26f93ad34..3431e13ca9 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -490,7 +490,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($data['functionId'], $execution['body']['functionId']); $this->assertEquals('waiting', $execution['body']['status']); $this->assertEquals(0, $execution['body']['statusCode']); - $this->assertEquals('', $execution['body']['stdout']); + $this->assertEquals('', $execution['body']['response']); $this->assertEquals('', $execution['body']['stderr']); $this->assertEquals(0, $execution['body']['time']); @@ -507,13 +507,13 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($data['functionId'], $execution['body']['functionId']); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['statusCode']); - $this->assertStringContainsString($execution['body']['functionId'], $execution['body']['stdout']); - $this->assertStringContainsString($data['deploymentId'], $execution['body']['stdout']); - $this->assertStringContainsString('Test1', $execution['body']['stdout']); - $this->assertStringContainsString('http', $execution['body']['stdout']); - $this->assertStringContainsString('PHP', $execution['body']['stdout']); - $this->assertStringContainsString('8.0', $execution['body']['stdout']); - // $this->assertStringContainsString('êä', $execution['body']['stdout']); // tests unknown utf-8 chars + $this->assertStringContainsString($execution['body']['functionId'], $execution['body']['response']); + $this->assertStringContainsString($data['deploymentId'], $execution['body']['response']); + $this->assertStringContainsString('Test1', $execution['body']['response']); + $this->assertStringContainsString('http', $execution['body']['response']); + $this->assertStringContainsString('PHP', $execution['body']['response']); + $this->assertStringContainsString('8.0', $execution['body']['response']); + // $this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars $this->assertEquals('', $execution['body']['stderr']); $this->assertLessThan(0.500, $execution['body']['time']); @@ -596,11 +596,11 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); - $this->assertStringContainsString($data['deploymentId'], $execution['body']['stdout']); - $this->assertStringContainsString('Test1', $execution['body']['stdout']); - $this->assertStringContainsString('http', $execution['body']['stdout']); - $this->assertStringContainsString('PHP', $execution['body']['stdout']); - $this->assertStringContainsString('8.0', $execution['body']['stdout']); + $this->assertStringContainsString($data['deploymentId'], $execution['body']['response']); + $this->assertStringContainsString('Test1', $execution['body']['response']); + $this->assertStringContainsString('http', $execution['body']['response']); + $this->assertStringContainsString('PHP', $execution['body']['response']); + $this->assertStringContainsString('8.0', $execution['body']['response']); // $this->assertStringContainsString('êä', $execution['body']['sdtout']); // tests unknown utf-8 chars $this->assertLessThan(0.500, $execution['body']['time']); @@ -764,7 +764,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($executions['body']['executions'][0]['statusCode'], 124); $this->assertGreaterThan(2, $executions['body']['executions'][0]['time']); $this->assertLessThan(3, $executions['body']['executions'][0]['time']); - $this->assertEquals($executions['body']['executions'][0]['stdout'], ''); + $this->assertEquals($executions['body']['executions'][0]['response'], ''); $this->assertEquals($executions['body']['executions'][0]['stderr'], 'Execution timed out.'); // Cleanup : Delete function @@ -847,7 +847,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $output = json_decode($executions['body']['stdout'], true); + $output = json_decode($executions['body']['response'], true); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); @@ -875,7 +875,7 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(1, $executions['body']['executions']); $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId); $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http'); - $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); + $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']); // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ @@ -952,7 +952,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $output = json_decode($executions['body']['stdout'], true); + $output = json_decode($executions['body']['response'], true); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); @@ -981,7 +981,7 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(1, $executions['body']['executions']); $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId); $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http'); - $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); + $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']); // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ @@ -1057,7 +1057,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $output = json_decode($executions['body']['stdout'], true); + $output = json_decode($executions['body']['response'], true); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); @@ -1086,7 +1086,7 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(1, $executions['body']['executions']); $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId); $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http'); - $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); + $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']); // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ @@ -1162,7 +1162,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $output = json_decode($executions['body']['stdout'], true); + $output = json_decode($executions['body']['response'], true); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); @@ -1191,7 +1191,7 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(1, $executions['body']['executions']); $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId); $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http'); - $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); + $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']); // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ @@ -1267,7 +1267,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $output = json_decode($executions['body']['stdout'], true); + $output = json_decode($executions['body']['response'], true); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); @@ -1296,7 +1296,7 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(1, $executions['body']['executions']); $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId); $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http'); - $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); + $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']); // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ @@ -1372,7 +1372,7 @@ class FunctionsCustomServerTest extends Scope // 'x-appwrite-project' => $this->getProject()['$id'], // ], $this->getHeaders())); - // $output = json_decode($executions['body']['stdout'], true); + // $output = json_decode($executions['body']['response'], true); // $this->assertEquals(200, $executions['headers']['status-code']); // $this->assertEquals('completed', $executions['body']['status']); @@ -1401,7 +1401,7 @@ class FunctionsCustomServerTest extends Scope // $this->assertCount(1, $executions['body']['executions']); // $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId); // $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http'); - // $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); + // $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']); // // Cleanup : Delete function // $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ From f6084098f0629938ace67a4735bda83c52840f39 Mon Sep 17 00:00:00 2001 From: INFERN04 <77529288+INFERN04@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:02:10 +0530 Subject: [PATCH 21/54] Update GETTING_STARTED.md I fixed a preposition --- docs/sdks/python/GETTING_STARTED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sdks/python/GETTING_STARTED.md b/docs/sdks/python/GETTING_STARTED.md index 4473088b36..46a3001ab9 100644 --- a/docs/sdks/python/GETTING_STARTED.md +++ b/docs/sdks/python/GETTING_STARTED.md @@ -1,7 +1,7 @@ ## Getting Started ### Init your SDK -Initialize your SDK with your Appwrite server API endpoint and project ID which can be found in your project settings page and your new API secret Key from project's API keys section. +Initialize your SDK with your Appwrite server API endpoint and project ID which can be found on your project settings page and your new API secret Key from project's API keys section. ```python from appwrite.client import Client From 5d86acf15f799a844118254a50dde02f1132983f Mon Sep 17 00:00:00 2001 From: Tanay Pant Date: Mon, 25 Apr 2022 15:45:50 +0200 Subject: [PATCH 22/54] Fix image size --- public/images/users/okta.png | Bin 8762 -> 7632 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/users/okta.png b/public/images/users/okta.png index c66b273cfdc5618f207f022507c0657ee6a2ed5c..61941c58dc636896581402b159294609b9133372 100644 GIT binary patch delta 7152 zcmV004R>004l5008;`004mK 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 From 1bca3b3ca851b8026375e49c98ca381b815d629d Mon Sep 17 00:00:00 2001 From: Tanay Pant <7481165+tanay1337@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:23:45 +0200 Subject: [PATCH 23/54] Fix missing slash --- src/Appwrite/Auth/OAuth2/Okta.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Auth/OAuth2/Okta.php b/src/Appwrite/Auth/OAuth2/Okta.php index 3277420a3b..61c710e8ad 100644 --- a/src/Appwrite/Auth/OAuth2/Okta.php +++ b/src/Appwrite/Auth/OAuth2/Okta.php @@ -163,7 +163,7 @@ class Okta extends OAuth2 { if (empty($this->user)) { $headers = ['Authorization: Bearer '. \urlencode($accessToken)]; - $user = $this->request('GET', 'https://'.$this->getOktaDomain().'oauth2/default/v1/userinfo', $headers); + $user = $this->request('GET', 'https://'.$this->getOktaDomain().'/oauth2/default/v1/userinfo', $headers); $this->user = \json_decode($user, true); } @@ -207,4 +207,4 @@ class Okta extends OAuth2 } return $secret; } -} \ No newline at end of file +} From 487d38db958e57fdbf5cff858b49a33ebb27fa50 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 26 Apr 2022 12:07:33 +0200 Subject: [PATCH 24/54] fix(user): search integrity --- app/controllers/api/users.php | 14 +++- tests/e2e/Services/Account/AccountBase.php | 14 ++-- .../Account/AccountCustomClientTest.php | 83 ++++++++++++++++++- .../Account/AccountCustomServerTest.php | 2 +- tests/e2e/Services/Users/UsersBase.php | 76 +++++++++++++++++ 5 files changed, 177 insertions(+), 12 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 6bb14c5003..a38c8cad89 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -453,7 +453,12 @@ App::patch('/v1/users/:userId/name') throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } - $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('name', $name)); + $user + ->setAttribute('name', $name) + ->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email'), $name])); + ; + + $user = $dbForProject->updateDocument('users', $user->getId(), $user); $audits ->setParam('userId', $user->getId()) @@ -542,8 +547,13 @@ App::patch('/v1/users/:userId/email') $email = \strtolower($email); + $user + ->setAttribute('email', $email) + ->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name')])) + ; + try { - $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('email', $email)); + $user = $dbForProject->updateDocument('users', $user->getId(), $user); } catch(Duplicate $th) { throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS); } diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 2fbe65a0e5..3aa368baad 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -445,7 +445,7 @@ trait AccountBase { $email = $data['email'] ?? ''; $session = $data['session'] ?? ''; - $newName = 'New Name'; + $newName = 'Lorem'; /** * Test for SUCCESS @@ -477,7 +477,7 @@ trait AccountBase ])); $this->assertEquals($response['headers']['status-code'], 401); - + $response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -485,7 +485,7 @@ trait AccountBase 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, ]), [ ]); - + $this->assertEquals($response['headers']['status-code'], 400); $response = $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([ @@ -496,7 +496,7 @@ trait AccountBase ]), [ 'name' => 'ocSRq1d3QphHivJyUmYY7WMnrxyjdk5YvVwcDqx2zS0coxESN8RmsQwLWw5Whnf0WbVohuFWTRAaoKgCOO0Y0M7LwgFnZmi8881Y72222222222222222222222222222' ]); - + $this->assertEquals($response['headers']['status-code'], 400); $data['name'] = $newName; @@ -532,7 +532,6 @@ trait AccountBase $this->assertNotEmpty($response['body']['$id']); $this->assertIsNumeric($response['body']['registration']); $this->assertEquals($response['body']['email'], $email); - $this->assertEquals($response['body']['name'], 'New Name'); $response = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ 'origin' => 'http://localhost', @@ -625,7 +624,6 @@ trait AccountBase $this->assertNotEmpty($response['body']['$id']); $this->assertIsNumeric($response['body']['registration']); $this->assertEquals($response['body']['email'], $newEmail); - $this->assertEquals($response['body']['name'], 'New Name'); /** * Test for FAILURE @@ -637,7 +635,7 @@ trait AccountBase ])); $this->assertEquals($response['headers']['status-code'], 401); - + $response = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -645,7 +643,7 @@ trait AccountBase 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, ]), [ ]); - + $this->assertEquals($response['headers']['status-code'], 400); // Test if we can create a new account with the old email diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 2b06828a6e..30a01b31b9 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -510,4 +510,85 @@ class AccountCustomClientTest extends Scope $this->assertEquals($response['headers']['status-code'], 404); } -} \ No newline at end of file + + /** + * @depends testUpdateAccountName + */ + public function testUpdateAccountNameSearch($data): void + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + $newName = 'Lorem'; + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'search' => $newName + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertCount(1, $response['body']['users']); + $this->assertEquals($response['body']['users'][0]['email'], $email); + + $response = $this->client->call(Client::METHOD_GET, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'search' => $id + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertCount(1, $response['body']['users']); + $this->assertEquals($response['body']['users'][0]['email'], $email); + } + + /** + * @depends testUpdateAccountEmail + */ + public function testUpdateAccountEmailSearch($data): void + { + $id = $data['id'] ?? ''; + $email = $data['email'] ?? ''; + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'search' => '"' . $email . '"' + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertCount(1, $response['body']['users']); + $this->assertEquals($response['body']['users'][0]['email'], $email); + + $response = $this->client->call(Client::METHOD_GET, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'search' => $id + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertCount(1, $response['body']['users']); + $this->assertEquals($response['body']['users'][0]['email'], $email); + } +} diff --git a/tests/e2e/Services/Account/AccountCustomServerTest.php b/tests/e2e/Services/Account/AccountCustomServerTest.php index fabbc5b77f..832dd34389 100644 --- a/tests/e2e/Services/Account/AccountCustomServerTest.php +++ b/tests/e2e/Services/Account/AccountCustomServerTest.php @@ -33,7 +33,7 @@ class AccountCustomServerTest extends Scope ]); $this->assertEquals(401, $response['headers']['status-code']); - + return []; } } \ No newline at end of file diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 2e61917d2d..1f5f2922d8 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -281,6 +281,44 @@ trait UsersBase return $data; } + /** + * @depends testUpdateUserName + */ + public function testUpdateUserNameSearch($data): void + { + $id = $data['userId'] ?? ''; + $newName = 'Updated name'; + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'search' => $newName + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertCount(1, $response['body']['users']); + $this->assertEquals($response['body']['users'][0]['$id'], $id); + + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'search' => $id + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertCount(1, $response['body']['users']); + $this->assertEquals($response['body']['users'][0]['$id'], $id); + } + /** * @depends testGetUser */ @@ -310,6 +348,44 @@ trait UsersBase return $data; } + /** + * @depends testUpdateUserEmail + */ + public function testUpdateUserEmailSearch($data): void + { + $id = $data['userId'] ?? ''; + $newEmail = '"users.service@updated.com"'; + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'search' => $newEmail + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertCount(1, $response['body']['users']); + $this->assertEquals($response['body']['users'][0]['$id'], $id); + + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'search' => $id + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertCount(1, $response['body']['users']); + $this->assertEquals($response['body']['users'][0]['$id'], $id); + } + /** * @depends testUpdateUserEmail */ From 56c184eff3602d160860916cb7e446d056b606ae Mon Sep 17 00:00:00 2001 From: Tanay Pant Date: Tue, 26 Apr 2022 12:15:42 +0200 Subject: [PATCH 25/54] Add field for custom authorization server ID --- app/views/console/users/oauth/okta.phtml | 2 ++ public/scripts/views/forms/oauth-custom.js | 3 ++- src/Appwrite/Auth/OAuth2/Okta.php | 19 +++++++++++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/views/console/users/oauth/okta.phtml b/app/views/console/users/oauth/okta.phtml index 44c47c6550..2459e1543c 100644 --- a/app/views/console/users/oauth/okta.phtml +++ b/app/views/console/users/oauth/okta.phtml @@ -8,5 +8,7 @@ $provider = $this->getParam('provider', ''); + + \ No newline at end of file diff --git a/public/scripts/views/forms/oauth-custom.js b/public/scripts/views/forms/oauth-custom.js index 965296656f..67fb213215 100644 --- a/public/scripts/views/forms/oauth-custom.js +++ b/public/scripts/views/forms/oauth-custom.js @@ -19,7 +19,8 @@ }, "Okta": { "clientSecret": "oauth2OktaClientSecret", - "oktaDomain": "oauth2OktaDomain" + "oktaDomain": "oauth2OktaDomain", + "authorizationServerId": "oauth2OktaAuthorizationServerId" } } let provider = element.getAttribute("data-forms-oauth-custom"); diff --git a/src/Appwrite/Auth/OAuth2/Okta.php b/src/Appwrite/Auth/OAuth2/Okta.php index 61c710e8ad..7b1b0d19e1 100644 --- a/src/Appwrite/Auth/OAuth2/Okta.php +++ b/src/Appwrite/Auth/OAuth2/Okta.php @@ -42,7 +42,7 @@ class Okta extends OAuth2 */ public function getLoginURL(): string { - return 'https://'.$this->getOktaDomain().'/oauth2/default/v1/authorize?'.\http_build_query([ + return 'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/authorize?'.\http_build_query([ 'client_id' => $this->appID, 'redirect_uri' => $this->callback, 'state'=> \json_encode($this->state), @@ -62,7 +62,7 @@ class Okta extends OAuth2 $headers = ['Content-Type: application/x-www-form-urlencoded']; $this->tokens = \json_decode($this->request( 'POST', - 'https://'.$this->getOktaDomain().'/oauth2/default/v1/token', + 'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/token', $headers, \http_build_query([ 'code' => $code, @@ -89,7 +89,7 @@ class Okta extends OAuth2 $headers = ['Content-Type: application/x-www-form-urlencoded']; $this->tokens = \json_decode($this->request( 'POST', - 'https://'.$this->getOktaDomain().'/oauth2/default/v1/token', + 'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/token', $headers, \http_build_query([ 'refresh_token' => $refreshToken, @@ -163,7 +163,7 @@ class Okta extends OAuth2 { if (empty($this->user)) { $headers = ['Authorization: Bearer '. \urlencode($accessToken)]; - $user = $this->request('GET', 'https://'.$this->getOktaDomain().'/oauth2/default/v1/userinfo', $headers); + $user = $this->request('GET', 'https://'.$this->getOktaDomain().'/oauth2/'.$this->getAuthorizationServerId().'/v1/userinfo', $headers); $this->user = \json_decode($user, true); } @@ -193,6 +193,17 @@ class Okta extends OAuth2 return (isset($secret['oktaDomain'])) ? $secret['oktaDomain'] : ''; } + /** + * Extracts the Okta Authorization Server ID from the JSON stored in appSecret + * + * @return string + */ + protected function getAuthorizationServerId(): string + { + $secret = $this->getAppSecret(); + return (isset($secret['authorizationServerId'])) ? $secret['authorizationServerId'] : 'default'; + } + /** * Decode the JSON stored in appSecret * From fde7278f43d638d06e196713c147a116c248c273 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 26 Apr 2022 12:26:07 +0200 Subject: [PATCH 26/54] fix(ui): document crud redirect --- app/views/console/database/document.phtml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/views/console/database/document.phtml b/app/views/console/database/document.phtml index 8324c680d0..a48d533830 100644 --- a/app/views/console/database/document.phtml +++ b/app/views/console/database/document.phtml @@ -55,17 +55,26 @@ $logs = $this->getParam('logs', null); data-analytics-activity data-analytics-event="submit" data-analytics-category="console" - data-analytics-label="Update Database Document" data-service="{{|documentAction}}" data-name="project-document" data-scope="sdk" data-event="submit" - data-success="trigger,redirect" - data-success-param-trigger-events="database.updateDocument" - data-success-param-redirect-url="/console/database/document?id={{serviceData.$id}}&collection={{project-collection.$id}}&project={{router.params.project}}" data-failure="alert" - data-failure-param-alert-text="Failed to update document" - data-failure-param-alert-classname="error"> + data-failure-param-alert-classname="error" + + data-analytics-label="Create Database Document" + data-success="trigger,redirect" + data-success-param-trigger-events="database.createDocument" + data-success-param-redirect-url="/console/database/collection?id={{project-collection.$id}}&project={{router.params.project}}" + data-failure-param-alert-text="Failed to create document" + + data-analytics-label="Update Database Document" + data-success="trigger,alert" + data-success-param-trigger-events="database.updateDocument" + data-success-param-alert-text="Your document was updated" + data-failure-param-alert-text="Failed to update document" + + > From c7540b03f78755bc89319df708ac0600894c751f Mon Sep 17 00:00:00 2001 From: Tanay Pant Date: Tue, 26 Apr 2022 16:27:17 +0200 Subject: [PATCH 27/54] Update image --- public/images/users/okta.png | Bin 7632 -> 6945 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/users/okta.png b/public/images/users/okta.png index 61941c58dc636896581402b159294609b9133372..8016323b20dd17c5c34361dc6154f3bea64ef231 100644 GIT binary patch literal 6945 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 delta 7223 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 Date: Tue, 26 Apr 2022 16:30:35 +0200 Subject: [PATCH 28/54] Update image --- public/images/users/okta.png | Bin 6945 -> 7408 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/users/okta.png b/public/images/users/okta.png index 8016323b20dd17c5c34361dc6154f3bea64ef231..bdf7907f46be98e29e0da289e699d2dd1b2421be 100644 GIT binary patch delta 5982 zcmbW*Wna^c+6XDS0SUPe{lOfryIBK*b$}B%Gw7LJ&!asE`cQSxm?o0&x<9 zIZDbP#ALGg2?v27`IcQA9ELR=b(N>Vxcjyt!Oy0f@1L}`=4pv%BvKKmI`L&imPT$^ zKK=alYufE!zZZqF;Xk$-o9(T!;iFdJ=$Yr^pKGvgY@dxhYJ?~fN+0tj4O5job*!ezmWX`3oW5%wt`Hx*_VN4tn*gjJX?uXJm3?k=c=hDPu ze`-qJ$ytzv6-Nq(?YDc?32C zX)c`qyJbZV%^rdK@cR%n-pq1EvB8lo`gp={RO5fffd8%`A}m~_<^#NX?K1H478_*6VN_D50=T8>qf|RJws3MPzBkJ%_ z^jAIQih5~lBV)El2QRssCc;1&5e`sO$p|FzY>bR1dhS6{qd zDpY_ah+QF$8KN>22DS}3D>`a8@4_dPODWi`9#4C58^6`A4{?cy)vyjRK1hJHbW{>$ zX`%kc2MH@Xs*<&}eP2mW=|i#VdXE+sygB>GH%0x{uC-*1F3^Qw$#Oa-^gxMr4dsli z0Xk^MP2TV+7{^#2TtDpaSxJEPN?N5@4>96D+z@*#076)^9APo~15iCiY}RP?BoCAF z3)xq1sA;1F+Uk&C4b8X8Dl^Z6;5IO@XN#v;CJj6$JTMUJFpUns7@8!?fXd;9vbM$l z6VbjPv z8<&C^F=fydDZJ!Sm)h^1iGn!PC9%iLx_zd{TUw=#>-D|M_Mw zYjOsk1d<}o%r|B1ITbI2>#pxl$TFy`Dp9olg8j8A9uhozr#eb88@^5LC4bddb+uyo z4z+6HonQIM5BiP7af`8AcYJpV^7kj*l3=uSqVGhct28e4P(6>749y$5k|r}%>>Cq< zhN2&x?Rf_=Gcg8j9HVN(DkpmY;=Ywwky|rf!m2^F#Collp;jt24YfD((UZ=Q6^Xcd zlSh}ht$mDoWogKq$g;ztb~!15tv#C9!JA~#+zX+^JcB6N=19pj#d6(f-boH0toAC{ zRHS*k4jOK6t>dg{6W@t`cyd*L?m!giS-Ihm88>D13h{(Ceu4RjOuiQ|9^btPkh7GO zJyI>T%iQv7p9nDc@NyGopsny3!|m{P)F-H&R7jCyh91|#GAUhP@?y}^TEF1bsV_Z6 z-R~hEjne~_bVNzLm#yGuq!=RK+$O5EoaeE^X!; zHb)xC*r{YvKFMZtU3dy4mxjz;jHDZ(Dm7ZQ4Rl2Jlwaz}opDrmCj|xlJtF${MivBj z=0#?fG1|-=I4B?fsVE zpVDDiL0U0qe;yhd$JAyhq{(5Xh(sQ{`k?wQlsu<1gsnlsY;37pR1Te9_p=Uc!|C$J3opkA92uolNnhN%_^xNq_@g6cDIvk|+ zT9G=BNNH#0QU>mZ`_dmvn-k7|^%c+3)PxO%KD$_|kvN>37gl>~Pi=PJ06VcWum0Fo z7;9tH-zXH)x-%wLKU~mA=+*u-scDw!csgE;06v3e34`whFI;tKws@ni>9amnN+BJ;${@`JYN3(NAmCIf~3PS{23NeZYHs^eiO3Kxn_~DqlsS0SAonm z8`B$l#?44}uGYCdo<^7-j5@?!!q%UL&u@aBa8A?b`l~i1yPLYVjPl}}W8?kIm zECCKb$l!S%wKg7(!MET_yhXi(|M&!neMh%z#?lu#Bv+d>c7j*CU%tr%d39w^6FV71 zY#qc*jiGSL?T?uhkmfml|A^y{T?jwrcLR^zPR zWyC!js_ScSHH-G>Z@)S4kot|90Yrk6_OFQWCls?&Qk>+kdrS>qfscJBh|9Y&5Yi=6 z9G(Lc1so`306CvL7jb`cp*A8Ht=L7Lq?H|bby~di9aMGK5EaA~JScDqet*hLP{w}W z@lQVG*Kg7G7hu6{q4~3whJyP956nM!XqB?9^tox@ukMF=z)(iyr*i^B zx|Cj#dvwS!kQ0t9fp- zR?|_U!X?N*4iD8L`utP?Lp<}38?Zl9!4T0KhBKC$LV3M6Bl)49k;Y2lQ|kPSQ4KHj zi&|2!i@5`r&EjD5#I+l{&>sXRXG+oJz;{9}Q(8P#0y8P*L> zp%=2=pOB>e(}lAtb}xzbf8J4iFf>vN%#lnHFw7{eafg3EI<} z7xRPhP}B;c1Oa=XsvLI-KZYYOGeq)zaW^h(S=S8%y-bkSP7t8UpI)b&*>^MmQT&-C ztoLX!rI|9f_h&i-9jFp;74zP`qN0y)%KD~{Z$ieawc+hzFhdf`qCA0E*+B;?tRUqe& zW=3UQ0(+2Kc!u%*dQ109S43)~gC3PQ=*|tE>T1}ol}Z^n*-TxZ@wjR%ZO)DD_|dv) zG<9o|tun+_FZ$tY&mNk;m5FDOfVadeG7r;sU@Gb!>UQZ!3FW4$eV^22Mr z&F@o=Y6aEnf>O8fT~R~f5*=~_w(jbvjpu8*k1xp^$tWJmWrZ3fA+oLv%b7n=)1;MY zw6SMzfirYPA57M~>T>kYx9gF5l8+8rN4zQGUqb_Eo{R@%9OAf^@dFJE{E@z`U0$I_ z^W`TKnSpfPWoW%q!X)N&lRxHd*}}CR$R|l&zW)858~Hwu zCsD-&cT$u-W%mj>;;HQ=8Lc>*c2)mv{%BGH1K{3&QlEbZGHSEn(pH3x6FW+9KSCqryiR)#cLr#VUgDTE zajh5#u%x2y&K>_{*h(KmP%gWNpKbf6A^Nxi=FHf^J|$m zFR3rQ=A_q}FBI!+cVM~0^17^=#no;6UL0Jx#_~mrOB6@H{Ddm%*of0qH=h)-3X`q< z5yGPwQAL*>mYSj7EnsP3vYd+b!T^X;KhBXN-Z2Qn3{wRWZEBhe3z5e&SI@F$Q}g2s zU$?IYB|9b+4?h$KpiG*(NwlbH7W6y5l;cU+%HaFx+gp%QuTAEL7061^S_fsrG3Z{` zPp(?_;RF#kCzJL6iUj#*-S;;+T%Gb9%N=)BfQ@zb@wXkWpF=ty2MaNk)dOwxsoS6bX?p2O&luEJ9oMLp z&`3@Jb5{Q0r{lES5rLvx_w&p4343)iN_-AO4+~TZnj4N;8Y3%GwO6lt$QhFZqyo>a z8!%J@bPMs(mOgx!pKycj94R8s$^`Z2*$sK3$EWY`pA8lt(ZiyKUd!`3!wY&zBr z{(Ob)w_zIUh`ZgD`3(AzvV*C=gb;}G2MhI0OSrwRfb!Va+Ppf>`~vGQ9;gVg%^}2| z{35l=-M9pz?eo+X$cWbrO4vpaQE zX5)@&!yqJgRyN;AR)4(6MfWJqU$x+4dfZ6;yw?Im3c#3Yy%8~%YB75H%6m%*lB4c; zsnwJ@%Cb^EcDQM4V5)vkDW>!F#LRI^ClibSRQo=E+Z74O%(-n+)md`U=Tc?-lU}%3 zV?4U@Th@@J*G)9r*ah=7XtRBt4cS}5a~N$9wH0Lk+^g=*tk~3i_E@GrK8};&nsbIq z!XgsCQ3;&TTTNQrm9u}HvR7YTlc-Dk5$D5NaU$^3^3cQ;1-{jaTC9 z7yhlxmw`j?TPde>cz90*1f{Lom6QQIzTuhWyk!dW%y5=m)N~rjiO8Xj3xC?ZVPU(7GQO*A03%n60 ze#W+nTcWDNt>-fi-A_jUX3--aq!b1;kpXRO!ELHS85PA)8qzHzF@H&L?P}>=fqiY9 zlHkO}g89{gEQc5H{79-=| zg-5CsnZ}nb#FP2*iyT z?e(S=ULS39EOliH+I4@l{DN zG5UW7Ajh#tz|rf#Z_9WaihC|eemWjdu&YDcf4*BL>ZZ!GXjS@U;MZr(YGBzkYGaf` z^n^o3h%hfK>YGnC`ucDmldiThOO};Z39mo7lKXx%q7~2l)904SQH57*OLq7kNWhIn z>$Y;G`v8L9PUu5hlD*bdyg|<&84dO~i;}=)WQf-@#H+a#9S3=CV?Cpz6@=Wq)>N7k zR2M$DHt`X0K`hAl{OxrJ0FCSF{8aRfTe~tobYcVu13jj*%bZ$i9_1T-=s89qDl+q> zrf^?2#FgA{pyzckbJ7tehX*Q)S4mbO*y7?gSK6AWxmL-?aLg5;OJ`d7KKrJ@?z&{0 z;GU{({@d51R&*5*_?{+a7SwsESi;($vu6eKIjp zmZu6mfIRi&EF3hQ;CFHhi45-zX5*F42nj&`IGqk-A>>)He7>JDAFtc?Y?T2V$K%bY z#g26|qN7Z$5}W^7tY|{Q>C;7t94A7f=L#^qv&O}AJJaEUTT)}Uu4Mbe(?x)$SpKIb zR3q8Yp$mTBnd2yc?@TX1Hou;`Wh@J&S~>cq%rYPG5dBE06nc6?oyn^UM~%rSOJ<~& z4H)-R-Npx2zMNZ9s&@4h$k7Q6d=Y=a_Mo@h&M4MMwO_D$!-EP$*{Pjqcq!U4keUz0q+a1x3J=pl4*OlC8VBStic^rlVn~UaRU5{eMKlly?9C delta 5515 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 Date: Wed, 27 Apr 2022 11:06:53 +0000 Subject: [PATCH 29/54] Implement user tokens subcollection --- app/config/collections.php | 89 +++++++++++++++++++++++++++++++-- app/controllers/api/account.php | 81 ++++++++++++------------------ app/controllers/api/teams.php | 2 +- app/controllers/api/users.php | 4 +- app/init.php | 12 +++++ app/workers/deletes.php | 5 ++ 6 files changed, 139 insertions(+), 54 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 3e3de4c7bd..b9221250b9 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1065,9 +1065,9 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, - 'default' => [], - 'array' => true, - 'filters' => ['json'], + 'default' => null, + 'array' => false, + 'filters' => ['subQueryTokens'], ], [ '$id' => 'memberships', @@ -1128,6 +1128,89 @@ $collections = [ ], ], + 'tokens' => [ + '$collection' => Database::METADATA, + '$id' => 'tokens', + 'name' => 'Tokens', + 'attributes' => [ + [ + '$id' => 'userId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'type', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'secret', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 512, // https://www.tutorialspoint.com/how-long-is-the-sha256-hash-in-mysql (512 for encryption) + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['encrypt'], + ], + [ + '$id' => 'expire', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'userAgent', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'ip', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 45, // https://stackoverflow.com/a/166157/2299554 + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ] + ], + 'indexes' => [ + [ + '$id' => '_key_user', + 'type' => Database::INDEX_KEY, + 'attributes' => ['userId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + ], + ], + 'sessions' => [ '$collection' => Database::METADATA, '$id' => 'sessions', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 200b845987..eba5958fc7 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -105,7 +105,7 @@ App::post('/v1/account') 'name' => $name, 'prefs' => new \stdClass(), 'sessions' => [], - 'tokens' => [], + 'tokens' => null, 'memberships' => [], 'search' => implode(' ', [$userId, $email, $name]), 'deleted' => false @@ -506,7 +506,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'name' => $name, 'prefs' => new \stdClass(), 'sessions' => [], - 'tokens' => [], + 'tokens' => null, 'memberships' => [], 'search' => implode(' ', [$userId, $email, $name]), 'deleted' => false @@ -680,7 +680,7 @@ App::post('/v1/account/sessions/magic-url') 'reset' => false, 'prefs' => new \stdClass(), 'sessions' => [], - 'tokens' => [], + 'tokens' => null, 'memberships' => [], 'search' => implode(' ', [$userId, $email]), 'deleted' => false @@ -706,13 +706,12 @@ App::post('/v1/account/sessions/magic-url') Authorization::setRole('user:'.$user->getId()); - $user->setAttribute('tokens', $token, Document::SET_TYPE_APPEND); + $token = $dbForProject->createDocument('tokens', $token + ->setAttribute('$read', ['user:'.$user->getId()]) + ->setAttribute('$write', ['user:'.$user->getId()]) + ); - $user = $dbForProject->updateDocument('users', $user->getId(), $user); - - if (false === $user) { - throw new Exception('Failed to save user to DB', 500, Exception::GENERAL_SERVER_ERROR); - } + $dbForProject->deleteCachedDocument('users', $user->getId()); if(empty($url)) { $url = $request->getProtocol().'://'.$request->getHostname().'/auth/magic-url'; @@ -786,7 +785,7 @@ App::put('/v1/account/sessions/magic-url') /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ - $user = $dbForProject->getDocument('users', $userId); + $user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId)); if ($user->isEmpty() || $user->getAttribute('deleted')) { throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); @@ -825,24 +824,12 @@ App::put('/v1/account/sessions/magic-url') ->setAttribute('$write', ['user:' . $user->getId()]) ); - $tokens = $user->getAttribute('tokens', []); - /** * We act like we're updating and validating * the recovery token but actually we don't need it anymore. */ - foreach ($tokens as $key => $singleToken) { - if ($token === $singleToken->getId()) { - unset($tokens[$key]); - } - } - - $user - ->setAttribute('sessions', $session, Document::SET_TYPE_APPEND) - ->setAttribute('tokens', $tokens); - - - $user = $dbForProject->updateDocument('users', $user->getId(), $user); + $dbForProject->deleteDocument('tokens', $token); + $dbForProject->deleteCachedDocument('users', $user->getId()); if (false === $user) { throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR); @@ -952,7 +939,7 @@ App::post('/v1/account/sessions/anonymous') 'name' => null, 'prefs' => new \stdClass(), 'sessions' => [], - 'tokens' => [], + 'tokens' => null, 'memberships' => [], 'search' => $userId, 'deleted' => false @@ -1906,9 +1893,12 @@ App::post('/v1/account/recovery') Authorization::setRole('user:' . $profile->getId()); - $profile->setAttribute('tokens', $recovery, Document::SET_TYPE_APPEND); + $recovery = $dbForProject->createDocument('tokens', $recovery + ->setAttribute('$read', ['user:'.$profile->getId()]) + ->setAttribute('$write', ['user:'.$profile->getId()]) + ); - $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile); + $dbForProject->deleteCachedDocument('users', $profile->getId()); $url = Template::parseURL($url); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]); @@ -2003,18 +1993,14 @@ App::put('/v1/account/recovery') ->setAttribute('emailVerification', true) ); + $recoveryDocument = $dbForProject->getDocument('tokens', $recovery); + /** * We act like we're updating and validating * the recovery token but actually we don't need it anymore. */ - foreach ($tokens as $key => $token) { - if ($recovery === $token->getId()) { - $recovery = $token; - unset($tokens[$key]); - } - } - - $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens)); + $dbForProject->deleteDocument('tokens', $recovery); + $dbForProject->deleteCachedDocument('users', $profile->getId()); $audits ->setParam('userId', $profile->getId()) @@ -2025,7 +2011,7 @@ App::put('/v1/account/recovery') $usage ->setParam('users.update', 1) ; - $response->dynamic($recovery, Response::MODEL_TOKEN); + $response->dynamic($recoveryDocument, Response::MODEL_TOKEN); }); App::post('/v1/account/verification') @@ -2089,9 +2075,12 @@ App::post('/v1/account/verification') Authorization::setRole('user:' . $user->getId()); - $user->setAttribute('tokens', $verification, Document::SET_TYPE_APPEND); + $verification = $dbForProject->createDocument('tokens', $verification + ->setAttribute('$read', ['user:'.$user->getId()]) + ->setAttribute('$write', ['user:'.$user->getId()]) + ); - $user = $dbForProject->updateDocument('users', $user->getId(), $user); + $dbForProject->deleteCachedDocument('users', $user->getId()); $url = Template::parseURL($url); $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]); @@ -2161,7 +2150,7 @@ App::put('/v1/account/verification') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - $profile = $dbForProject->getDocument('users', $userId); + $profile = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId)); if ($profile->isEmpty()) { throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); @@ -2177,19 +2166,15 @@ App::put('/v1/account/verification') Authorization::setRole('user:' . $profile->getId()); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true)); + + $verificationDocument = $dbForProject->getDocument('tokens', $verification); /** * We act like we're updating and validating * the verification token but actually we don't need it anymore. */ - foreach ($tokens as $key => $token) { - if ($token->getId() === $verification) { - $verification = $token; - unset($tokens[$key]); - } - } - - $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens)); + $dbForProject->deleteDocument('tokens', $verification); + $dbForProject->deleteCachedDocument('users', $profile->getId()); $audits ->setParam('userId', $profile->getId()) @@ -2200,5 +2185,5 @@ App::put('/v1/account/verification') $usage ->setParam('users.update', 1) ; - $response->dynamic($verification, Response::MODEL_TOKEN); + $response->dynamic($verificationDocument, Response::MODEL_TOKEN); }); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 22705d3bf8..7d9d6f38fd 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -339,7 +339,7 @@ App::post('/v1/teams/:teamId/memberships') 'name' => $name, 'prefs' => new \stdClass(), 'sessions' => [], - 'tokens' => [], + 'tokens' => null, 'memberships' => [], 'search' => implode(' ', [$userId, $email, $name]), ]))); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 6bb14c5003..97cbc8e33c 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -64,7 +64,7 @@ App::post('/v1/users') 'name' => $name, 'prefs' => new \stdClass(), 'sessions' => [], - 'tokens' => [], + 'tokens' => null, 'memberships' => [], 'search' => implode(' ', [$userId, $email, $name]), 'deleted' => false @@ -739,7 +739,7 @@ App::delete('/v1/users/:userId') ->setAttribute("email", null) ->setAttribute("password", null) ->setAttribute("deleted", true) - ->setAttribute("tokens", []) + ->setAttribute("tokens", null) ->setAttribute("search", null) ; diff --git a/app/init.php b/app/init.php index fd509297f6..c64e199464 100644 --- a/app/init.php +++ b/app/init.php @@ -301,6 +301,18 @@ Database::addFilter('subQueryWebhooks', } ); +Database::addFilter('subQueryTokens', + function($value) { + return null; + }, + function($value, Document $document, Database $database) { + return $database + ->find('tokens', [ + new Query('userId', Query::TYPE_EQUAL, [$document->getId()]) + ], $database->getIndexLimit(), 0, []); + } +); + Database::addFilter('encrypt', function($value) { $key = App::getEnv('_APP_OPENSSL_KEY_V1'); diff --git a/app/workers/deletes.php b/app/workers/deletes.php index b8a44ebb82..0166ae43e2 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -232,6 +232,11 @@ class DeletesV1 extends Worker } } }); + + // Delete tokens + $this->deleteByGroup('tokens', [ + new Query('userId', Query::TYPE_EQUAL, [$userId]) + ], $this->getProjectDB($projectId)); } /** From 92d6ed6089c34739fa672f0e081cdb0bf7f1ff47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 27 Apr 2022 12:44:47 +0000 Subject: [PATCH 30/54] Implement memberships subquery --- app/config/collections.php | 4 ++-- app/controllers/api/account.php | 8 +++---- app/controllers/api/teams.php | 39 +++++++-------------------------- app/controllers/api/users.php | 2 +- app/init.php | 12 ++++++++++ 5 files changed, 27 insertions(+), 38 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 3e3de4c7bd..dbff598476 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1077,8 +1077,8 @@ $collections = [ 'signed' => true, 'required' => false, 'default' => [], - 'array' => true, - 'filters' => ['json'], + 'array' => false, + 'filters' => ['subQueryMemberships'], ], [ '$id' => 'search', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 200b845987..31bf7bf421 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -106,7 +106,7 @@ App::post('/v1/account') 'prefs' => new \stdClass(), 'sessions' => [], 'tokens' => [], - 'memberships' => [], + 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), 'deleted' => false ]))); @@ -507,7 +507,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'prefs' => new \stdClass(), 'sessions' => [], 'tokens' => [], - 'memberships' => [], + 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), 'deleted' => false ]))); @@ -681,7 +681,7 @@ App::post('/v1/account/sessions/magic-url') 'prefs' => new \stdClass(), 'sessions' => [], 'tokens' => [], - 'memberships' => [], + 'memberships' => null, 'search' => implode(' ', [$userId, $email]), 'deleted' => false ]))); @@ -953,7 +953,7 @@ App::post('/v1/account/sessions/anonymous') 'prefs' => new \stdClass(), 'sessions' => [], 'tokens' => [], - 'memberships' => [], + 'memberships' => null, 'search' => $userId, 'deleted' => false ]))); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 22705d3bf8..591bb82b1b 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -79,10 +79,7 @@ App::post('/v1/teams') ]); $membership = $dbForProject->createDocument('memberships', $membership); - - // Attach user to team - $user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND); - $user = $dbForProject->updateDocument('users', $user->getId(), $user); + $dbForProject->deleteCachedDocument('users', $user->getId()); } if (!empty($user->getId())) { @@ -340,7 +337,7 @@ App::post('/v1/teams/:teamId/memberships') 'prefs' => new \stdClass(), 'sessions' => [], 'tokens' => [], - 'memberships' => [], + 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), ]))); } catch (Duplicate $th) { @@ -380,10 +377,7 @@ App::post('/v1/teams/:teamId/memberships') $team->setAttribute('total', $team->getAttribute('total', 0) + 1); $team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team)); - // Attach user to team - $invitee->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND); - - $invitee = Authorization::skip(fn() => $dbForProject->updateDocument('users', $invitee->getId(), $invitee)); + $dbForProject->deleteCachedDocument('users', $invitee->getId()); } else { try { $membership = $dbForProject->createDocument('memberships', $membership); @@ -604,13 +598,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') /** * Replace membership on profile */ - $memberships = array_filter($profile->getAttribute('memberships'), fn (Document $m) => $m->getId() !== $membership->getId()); - - $profile - ->setAttribute('memberships', $memberships) - ->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND); - - Authorization::skip(fn () => $dbForProject->updateDocument('users', $profile->getId(), $profile)); + + $dbForProject->deleteCachedDocument('users', $profile->getId()); $audits ->setParam('userId', $user->getId()) @@ -700,7 +689,6 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $user ->setAttribute('emailVerification', true) - ->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND) ; // Log user in @@ -734,6 +722,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $user = $dbForProject->updateDocument('users', $user->getId(), $user); $membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership); + + $dbForProject->deleteCachedDocument('users', $user->getId()); $team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('total', $team->getAttribute('total', 0) + 1))); @@ -813,20 +803,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') throw new Exception('Failed to remove membership from DB', 500, Exception::GENERAL_SERVER_ERROR); } - $memberships = $user->getAttribute('memberships', []); - - foreach ($memberships as $key => $child) { - /** @var Document $child */ - - if ($membershipId == $child->getId()) { - unset($memberships[$key]); - break; - } - } - - $user->setAttribute('memberships', $memberships); - - Authorization::skip(fn() => $dbForProject->updateDocument('users', $user->getId(), $user)); + $dbForProject->deleteCachedDocument('users', $user->getId()); if ($membership->getAttribute('confirm')) { // Count only confirmed members $team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0)); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 6bb14c5003..bfb7d4d17a 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -65,7 +65,7 @@ App::post('/v1/users') 'prefs' => new \stdClass(), 'sessions' => [], 'tokens' => [], - 'memberships' => [], + 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), 'deleted' => false ])); diff --git a/app/init.php b/app/init.php index fd509297f6..cac7a9701b 100644 --- a/app/init.php +++ b/app/init.php @@ -301,6 +301,18 @@ Database::addFilter('subQueryWebhooks', } ); +Database::addFilter('subQueryMemberships', + function($value) { + return null; + }, + function($value, Document $document, Database $database) { + return Authorization::skip(fn() => $database + ->find('memberships', [ + new Query('userId', Query::TYPE_EQUAL, [$document->getId()]) + ], $database->getIndexLimit(), 0, [])); + } +); + Database::addFilter('encrypt', function($value) { $key = App::getEnv('_APP_OPENSSL_KEY_V1'); From 4bc8ec11e0a79e4e811c604b5657042a1f301c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 27 Apr 2022 12:57:23 +0000 Subject: [PATCH 31/54] Test fix --- app/workers/deletes.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index b8a44ebb82..f261002a3c 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -214,9 +214,6 @@ class DeletesV1 extends Worker new Query('userId', Query::TYPE_EQUAL, [$userId]) ], $this->getProjectDB($projectId)); - $user->setAttribute('sessions', []); - $updated = $this->getProjectDB($projectId)->updateDocument('users', $userId, $user); - // Delete Memberships and decrement team membership counts $this->deleteByGroup('memberships', [ new Query('userId', Query::TYPE_EQUAL, [$userId]) @@ -437,6 +434,7 @@ class DeletesV1 extends Worker */ protected function deleteById(Document $document, Database $database, callable $callback = null): bool { + Authorization::disable(); if ($database->deleteDocument($document->getCollection(), $document->getId())) { Console::success('Deleted document "' . $document->getId() . '" successfully'); From 2ab9c40b439b5e8939e8d823353aaee1e7fb3e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 27 Apr 2022 13:17:27 +0000 Subject: [PATCH 32/54] Code quality improvements --- app/workers/deletes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index f261002a3c..52a5b16516 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -19,6 +19,7 @@ use Utopia\Audit\Audit; require_once __DIR__ . '/../init.php'; Authorization::disable(); +Authorization::setDefaultStatus(false); Console::title('Deletes V1 Worker'); Console::success(APP_NAME . ' deletes worker v1 has started' . "\n"); From 72f745982a5eb2119a4f06840202387eb249c079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 27 Apr 2022 13:22:41 +0000 Subject: [PATCH 33/54] Code cleanup --- app/workers/deletes.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 52a5b16516..6ec1acc7cd 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -435,7 +435,6 @@ class DeletesV1 extends Worker */ protected function deleteById(Document $document, Database $database, callable $callback = null): bool { - Authorization::disable(); if ($database->deleteDocument($document->getCollection(), $document->getId())) { Console::success('Deleted document "' . $document->getId() . '" successfully'); From 2c93cf664b6212f50969e1cc10640b7c41c5d914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 27 Apr 2022 14:11:12 +0000 Subject: [PATCH 34/54] Fix tests --- app/controllers/api/account.php | 2 ++ app/init.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index eba5958fc7..963c6b5d14 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -831,6 +831,8 @@ App::put('/v1/account/sessions/magic-url') $dbForProject->deleteDocument('tokens', $token); $dbForProject->deleteCachedDocument('users', $user->getId()); + $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)); + if (false === $user) { throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR); } diff --git a/app/init.php b/app/init.php index c64e199464..586ce54a86 100644 --- a/app/init.php +++ b/app/init.php @@ -306,10 +306,10 @@ Database::addFilter('subQueryTokens', return null; }, function($value, Document $document, Database $database) { - return $database + return Authorization::skip(fn() => $database ->find('tokens', [ new Query('userId', Query::TYPE_EQUAL, [$document->getId()]) - ], $database->getIndexLimit(), 0, []); + ], $database->getIndexLimit(), 0, [])); } ); From 6b5a6d552b00218df087ea338dfe074380d81403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Fri, 29 Apr 2022 08:28:17 +0000 Subject: [PATCH 35/54] Fix bug related to tests --- app/workers/deletes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 0166ae43e2..52654e1512 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -19,6 +19,7 @@ use Utopia\Audit\Audit; require_once __DIR__ . '/../init.php'; Authorization::disable(); +Authorization::setDefaultStatus(false); Console::title('Deletes V1 Worker'); Console::success(APP_NAME . ' deletes worker v1 has started' . "\n"); From d2008d33a5c49efeb39be158d6d826f03b97ef09 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Mon, 2 May 2022 14:28:36 +0000 Subject: [PATCH 36/54] refactor health.php --- app/controllers/api/health.php | 48 ++++++++++++---------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index bc57aa8ccb..b805958c14 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -1,6 +1,8 @@ label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $output = [ 'status' => 'pass', @@ -40,8 +41,7 @@ App::get('/v1/health/version') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_VERSION) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $response->dynamic(new Document([ 'version' => APP_VERSION_STABLE ]), Response::MODEL_HEALTH_VERSION); }); @@ -59,9 +59,7 @@ App::get('/v1/health/db') ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') ->inject('utopia') - ->action(function ($response, $utopia) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\App $utopia */ + ->action(function (Response $response, App $utopia) { $checkStart = \microtime(true); @@ -99,10 +97,7 @@ App::get('/v1/health/cache') ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') ->inject('utopia') - ->action(function ($response, $utopia) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\App $utopia */ - /** @var Redis */ + ->action(function (Response $response, App $utopia) { $checkStart = \microtime(true); @@ -132,8 +127,7 @@ App::get('/v1/health/time') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_TIME) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { /* * Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server @@ -190,8 +184,7 @@ App::get('/v1/health/queue/webhooks') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $response->dynamic(new Document([ 'size' => Resque::size(Event::WEBHOOK_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); @@ -208,8 +201,7 @@ App::get('/v1/health/queue/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $response->dynamic(new Document([ 'size' => Resque::size(Event::AUDITS_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); @@ -226,8 +218,7 @@ App::get('/v1/health/queue/usage') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $response->dynamic(new Document([ 'size' => Resque::size(Event::USAGE_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); @@ -244,8 +235,7 @@ App::get('/v1/health/queue/certificates') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $response->dynamic(new Document([ 'size' => Resque::size(Event::CERTIFICATES_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); @@ -262,8 +252,7 @@ App::get('/v1/health/queue/functions') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $response->dynamic(new Document([ 'size' => Resque::size(Event::FUNCTIONS_QUEUE_NAME) ]), Response::MODEL_HEALTH_QUEUE); }, ['response']); @@ -280,8 +269,7 @@ App::get('/v1/health/storage/local') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $checkStart = \microtime(true); @@ -322,8 +310,7 @@ App::get('/v1/health/anti-virus') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_HEALTH_ANTIVIRUS) ->inject('response') - ->action(function ($response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function (Response $response) { $output = [ 'status' => '', @@ -359,10 +346,7 @@ App::get('/v1/health/stats') // Currently only used internally ->inject('response') ->inject('register') ->inject('deviceFiles') - ->action(function ($response, $register, $deviceFiles) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Registry\Registry $register */ - /** @var Utopia\Storage\Device $deviceFiles */ + ->action(function (Response $response, Registry $register, Device $deviceFiles) { $cache = $register->get('cache'); From fd3a802a305c80993bd66b4dda04f699d13348e4 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Mon, 2 May 2022 14:34:01 +0000 Subject: [PATCH 37/54] regroup namespaces --- app/controllers/api/health.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index b805958c14..d51ed6a458 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -1,15 +1,15 @@ desc('Get HTTP') From 2729237edcbea31ec6cc9270be68a0dccf71cb80 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Mon, 2 May 2022 15:04:14 +0000 Subject: [PATCH 38/54] retrigger build --- app/controllers/api/health.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index d51ed6a458..2ca66e24ba 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -9,7 +9,7 @@ use Utopia\Database\Document; use Utopia\Registry\Registry; use Utopia\Storage\Device; use Utopia\Storage\Device\Local; -use Utopia\Storage\Storage; +use Utopia\Storage\Storage; App::get('/v1/health') ->desc('Get HTTP') From 03b28fdb918c474a4aeac616aea40624f2a0e616 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Mon, 2 May 2022 18:06:12 +0000 Subject: [PATCH 39/54] refactor avatars.php --- app/controllers/api/avatars.php | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 9d483bf2b7..d501e791c8 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -1,5 +1,7 @@ param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000.', true) ->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000.', true) ->inject('response') - ->action(function ($url, $width, $height, $response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function ($url, $width, $height, Response $response) { $quality = 80; $output = 'png'; @@ -215,8 +214,7 @@ App::get('/v1/avatars/favicon') ->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE) ->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.') ->inject('response') - ->action(function ($url, $response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function ($url, Response $response) { $width = 56; $height = 56; @@ -371,8 +369,7 @@ App::get('/v1/avatars/qr') ->param('margin', 1, new Range(0, 10), 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true) ->param('download', false, new Boolean(true), 'Return resulting image with \'Content-Disposition: attachment \' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.', true) ->inject('response') - ->action(function ($text, $size, $margin, $download, $response) { - /** @var Appwrite\Utopia\Response $response */ + ->action(function ($text, $size, $margin, $download, Response $response) { $download = ($download === '1' || $download === 'true' || $download === 1 || $download === true); $options = new QROptions([ @@ -416,9 +413,7 @@ App::get('/v1/avatars/initials') ->param('background', '', new HexColor(), 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true) ->inject('response') ->inject('user') - ->action(function ($name, $width, $height, $color, $background, $response, $user) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $user */ + ->action(function ($name, $width, $height, $color, $background, Response $response, Document $user) { $themes = [ ['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET From 5950289083caf05ab89ffbd9afc7202727e98a45 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Mon, 2 May 2022 18:30:13 +0000 Subject: [PATCH 40/54] retrigger build --- app/controllers/api/avatars.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index d501e791c8..e3b56cf588 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -16,7 +16,7 @@ use Utopia\Validator\Boolean; use Utopia\Validator\HexColor; use Utopia\Validator\Range; use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; +use Utopia\Validator\WhiteList; $avatarCallback = function ($type, $code, $width, $height, $quality, Response $response) { From 76cd8251126ae81845cdbe8a7af99a8f9f29fe1d Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Tue, 3 May 2022 11:57:26 +0000 Subject: [PATCH 41/54] refactor teams.php --- app/controllers/api/teams.php | 66 ++++++++--------------------------- 1 file changed, 15 insertions(+), 51 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 22705d3bf8..9d2723f3e3 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -2,13 +2,16 @@ use Appwrite\Auth\Auth; use Appwrite\Detector\Detector; +use Appwrite\Event\Event; +use Appwrite\Extend\Exception; use Appwrite\Network\Validator\Email; use Appwrite\Network\Validator\Host; use Appwrite\Template\Template; use Appwrite\Utopia\Database\Validator\CustomId; +use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; +use MaxMind\Db\Reader; use Utopia\App; -use Appwrite\Extend\Exception; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -42,11 +45,7 @@ App::post('/v1/teams') ->inject('user') ->inject('dbForProject') ->inject('events') - ->action(function ($teamId, $name, $roles, $response, $user, $dbForProject, $events) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $user */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $events */ + ->action(function ($teamId, $name, $roles, Response $response, Document $user, Database $dbForProject, Event $events) { $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); $isAppUser = Auth::isAppUser(Authorization::getRoles()); @@ -112,9 +111,7 @@ App::get('/v1/teams') ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForProject') - ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ + ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, Response $response, Database $dbForProject) { if (!empty($cursor)) { $cursorTeam = $dbForProject->getDocument('teams', $cursor); @@ -153,9 +150,7 @@ App::get('/v1/teams/:teamId') ->param('teamId', '', new UID(), 'Team ID.') ->inject('response') ->inject('dbForProject') - ->action(function ($teamId, $response, $dbForProject) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ + ->action(function ($teamId, Response $response, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -182,9 +177,7 @@ App::put('/v1/teams/:teamId') ->param('name', null, new Text(128), 'New team name. Max length: 128 chars.') ->inject('response') ->inject('dbForProject') - ->action(function ($teamId, $name, $response, $dbForProject) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ + ->action(function ($teamId, $name, Response $response, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -216,11 +209,7 @@ App::delete('/v1/teams/:teamId') ->inject('dbForProject') ->inject('events') ->inject('deletes') - ->action(function ($teamId, $response, $dbForProject, $events, $deletes) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $deletes */ + ->action(function ($teamId, Response $response, Database $dbForProject, Event $events, Event $deletes) { $team = $dbForProject->getDocument('teams', $teamId); @@ -281,13 +270,7 @@ App::post('/v1/teams/:teamId/memberships') ->inject('locale') ->inject('audits') ->inject('mails') - ->action(function ($teamId, $email, $roles, $url, $name, $response, $project, $user, $dbForProject, $locale, $audits, $mails) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $project */ - /** @var Utopia\Database\Document $user */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Event\Event $mails */ + ->action(function ($teamId, $email, $roles, $url, $name, Response $response, Document $project, Document $user, Database $dbForProject, $locale, Event $audits, Event $mails) { if(empty(App::getEnv('_APP_SMTP_HOST'))) { throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); @@ -445,9 +428,7 @@ App::get('/v1/teams/:teamId/memberships') ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForProject') - ->action(function ($teamId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ + ->action(function ($teamId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, Response $response, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -519,9 +500,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId') ->param('membershipId', '', new UID(), 'Membership ID.') ->inject('response') ->inject('dbForProject') - ->action(function ($teamId, $membershipId, $response, $dbForProject) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ + ->action(function ($teamId, $membershipId, Response $response, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -565,12 +544,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ->inject('user') ->inject('dbForProject') ->inject('audits') - ->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForProject, $audits) { - /** @var Appwrite\Utopia\Request $request */ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $user */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + ->action(function ($teamId, $membershipId, $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $audits) { $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -647,13 +621,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('dbForProject') ->inject('geodb') ->inject('audits') - ->action(function ($teamId, $membershipId, $userId, $secret, $request, $response, $user, $dbForProject, $geodb, $audits) { - /** @var Appwrite\Utopia\Request $request */ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $user */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var MaxMind\Db\Reader $geodb */ - /** @var Appwrite\Event\Event $audits */ + ->action(function ($teamId, $membershipId, $userId, $secret, Request $request, Response $response, Document $user, Database $dbForProject, Reader $geodb, Event $audits) { $protocol = $request->getProtocol(); @@ -777,11 +745,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') ->inject('dbForProject') ->inject('audits') ->inject('events') - ->action(function ($teamId, $membershipId, $response, $dbForProject, $audits, $events) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Event\Event $events */ + ->action(function ($teamId, $membershipId, Response $response, Database $dbForProject, Event $audits, Event $events) { $membership = $dbForProject->getDocument('memberships', $membershipId); From 20b4119ee988f5bf94ef673b91fa2e36f0e8a171 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Tue, 3 May 2022 13:21:25 +0000 Subject: [PATCH 42/54] add primitive types to params --- app/controllers/api/teams.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 9d2723f3e3..22ac091e6f 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -45,7 +45,7 @@ App::post('/v1/teams') ->inject('user') ->inject('dbForProject') ->inject('events') - ->action(function ($teamId, $name, $roles, Response $response, Document $user, Database $dbForProject, Event $events) { + ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $events) { $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); $isAppUser = Auth::isAppUser(Authorization::getRoles()); @@ -111,7 +111,7 @@ App::get('/v1/teams') ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForProject') - ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, Response $response, Database $dbForProject) { + ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { if (!empty($cursor)) { $cursorTeam = $dbForProject->getDocument('teams', $cursor); @@ -150,7 +150,7 @@ App::get('/v1/teams/:teamId') ->param('teamId', '', new UID(), 'Team ID.') ->inject('response') ->inject('dbForProject') - ->action(function ($teamId, Response $response, Database $dbForProject) { + ->action(function (string $teamId, Response $response, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -177,7 +177,7 @@ App::put('/v1/teams/:teamId') ->param('name', null, new Text(128), 'New team name. Max length: 128 chars.') ->inject('response') ->inject('dbForProject') - ->action(function ($teamId, $name, Response $response, Database $dbForProject) { + ->action(function (string $teamId, string $name, Response $response, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -209,7 +209,7 @@ App::delete('/v1/teams/:teamId') ->inject('dbForProject') ->inject('events') ->inject('deletes') - ->action(function ($teamId, Response $response, Database $dbForProject, Event $events, Event $deletes) { + ->action(function (string $teamId, Response $response, Database $dbForProject, Event $events, Event $deletes) { $team = $dbForProject->getDocument('teams', $teamId); @@ -270,7 +270,7 @@ App::post('/v1/teams/:teamId/memberships') ->inject('locale') ->inject('audits') ->inject('mails') - ->action(function ($teamId, $email, $roles, $url, $name, Response $response, Document $project, Document $user, Database $dbForProject, $locale, Event $audits, Event $mails) { + ->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, $locale, Event $audits, Event $mails) { if(empty(App::getEnv('_APP_SMTP_HOST'))) { throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); @@ -428,7 +428,7 @@ App::get('/v1/teams/:teamId/memberships') ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForProject') - ->action(function ($teamId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, Response $response, Database $dbForProject) { + ->action(function (string $teamId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -500,7 +500,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId') ->param('membershipId', '', new UID(), 'Membership ID.') ->inject('response') ->inject('dbForProject') - ->action(function ($teamId, $membershipId, Response $response, Database $dbForProject) { + ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -544,7 +544,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ->inject('user') ->inject('dbForProject') ->inject('audits') - ->action(function ($teamId, $membershipId, $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $audits) { + ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $audits) { $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -621,7 +621,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('dbForProject') ->inject('geodb') ->inject('audits') - ->action(function ($teamId, $membershipId, $userId, $secret, Request $request, Response $response, Document $user, Database $dbForProject, Reader $geodb, Event $audits) { + ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Reader $geodb, Event $audits) { $protocol = $request->getProtocol(); @@ -745,7 +745,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') ->inject('dbForProject') ->inject('audits') ->inject('events') - ->action(function ($teamId, $membershipId, Response $response, Database $dbForProject, Event $audits, Event $events) { + ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $audits, Event $events) { $membership = $dbForProject->getDocument('memberships', $membershipId); From 65a246bd218896ea3f4690855f6c00946e73bcf9 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Wed, 4 May 2022 09:51:48 +0000 Subject: [PATCH 43/54] add type for locale --- app/controllers/api/teams.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 22ac091e6f..f956d7e2ef 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -21,6 +21,7 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Key; use Utopia\Database\Validator\UID; +use Utopia\Locale\Locale; use Utopia\Validator\Text; use Utopia\Validator\Range; use Utopia\Validator\ArrayList; @@ -270,7 +271,7 @@ App::post('/v1/teams/:teamId/memberships') ->inject('locale') ->inject('audits') ->inject('mails') - ->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, $locale, Event $audits, Event $mails) { + ->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $audits, Event $mails) { if(empty(App::getEnv('_APP_SMTP_HOST'))) { throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); From 4e8d9a1da791e07742a938c98b6232fd5d8d282b Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Wed, 4 May 2022 12:23:34 +0000 Subject: [PATCH 44/54] refactor storage.php --- app/controllers/api/storage.php | 111 ++++++-------------------------- 1 file changed, 19 insertions(+), 92 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 856e898113..3c307a7c71 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -2,8 +2,10 @@ use Appwrite\Auth\Auth; use Appwrite\ClamAV\Network; +use Appwrite\Event\Event; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\OpenSSL\OpenSSL; +use Appwrite\Stats\Stats; use Appwrite\Utopia\Response; use Utopia\App; use Utopia\Cache\Adapter\Filesystem; @@ -21,6 +23,7 @@ use Utopia\Database\Validator\UID; use Appwrite\Extend\Exception; use Utopia\Image\Image; use Utopia\Storage\Compression\Algorithms\GZIP; +use Utopia\Storage\Device; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; use Utopia\Storage\Validator\File; @@ -34,6 +37,7 @@ use Utopia\Validator\Integer; use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; +use Utopia\Swoole\Request; App::post('/v1/storage/buckets') ->desc('Create bucket') @@ -61,11 +65,7 @@ App::post('/v1/storage/buckets') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($bucketId, $name, $permission, $read, $write, $enabled, $maximumFileSize, $allowedFileExtensions, $encryption, $antivirus, $response, $dbForProject, $audits, $usage) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Stats\Stats $usage */ + ->action(function (string $bucketId, string $name, string $permission, array $read, array $write, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage) { $bucketId = $bucketId === 'unique()' ? $dbForProject->getId() : $bucketId; try { @@ -157,10 +157,7 @@ App::get('/v1/storage/buckets') ->inject('response') ->inject('dbForProject') ->inject('usage') - ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject, $usage) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Stats\Stats $usage */ + ->action(function (array $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) { $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : []; @@ -195,10 +192,7 @@ App::get('/v1/storage/buckets/:bucketId') ->inject('response') ->inject('dbForProject') ->inject('usage') - ->action(function ($bucketId, $response, $dbForProject, $usage) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Stats\Stats $usage */ + ->action(function (string $bucketId, Response $response, Database $dbForProject, Stats $usage) { $bucket = $dbForProject->getDocument('buckets', $bucketId); @@ -237,11 +231,7 @@ App::put('/v1/storage/buckets/:bucketId') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($bucketId, $name, $permission, $read, $write, $enabled, $maximumFileSize, $allowedFileExtensions, $encryption, $antivirus, $response, $dbForProject, $audits, $usage) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Stats\Stats $usage */ + ->action(function (string $bucketId, string $name, string $permission, array $read, array $write, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage) { $bucket = $dbForProject->getDocument('buckets', $bucketId); @@ -298,13 +288,7 @@ App::delete('/v1/storage/buckets/:bucketId') ->inject('deletes') ->inject('events') ->inject('usage') - ->action(function ($bucketId, $response, $dbForProject, $audits, $deletes, $events, $usage) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Event\Event $deletes */ - /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Stats\Stats $usage */ + ->action(function (string $bucketId, Response $response, Database $dbForProject, Event $audits, Event $deletes, Event $events, Stats $usage) { $bucket = $dbForProject->getDocument('buckets', $bucketId); @@ -366,17 +350,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->inject('mode') ->inject('deviceFiles') ->inject('deviceLocal') - ->action(function ($bucketId, $fileId, $file, $read, $write, $request, $response, $dbForProject, $user, $audits, $usage, $events, $mode, $deviceFiles, $deviceLocal) { - /** @var Utopia\Swoole\Request $request */ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Utopia\Database\Document $user */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Stats\Stats $usage */ - /** @var Utopia\Storage\Device $deviceFiles */ - /** @var Utopia\Storage\Device $deviceLocal */ - /** @var string $mode */ + ->action(function (string $bucketId, string $fileId, string $file, array $read, array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -711,11 +685,7 @@ App::get('/v1/storage/buckets/:bucketId/files') ->inject('dbForProject') ->inject('usage') ->inject('mode') - ->action(function ($bucketId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject, $usage, $mode) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Stats\Stats $usage */ - /** @var string $mode */ + ->action(function (string $bucketId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage, string $mode) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -791,11 +761,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('dbForProject') ->inject('usage') ->inject('mode') - ->action(function ($bucketId, $fileId, $response, $dbForProject, $usage, $mode) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Stats\Stats $usage */ - /** @var string $mode */ + ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Stats $usage, string $mode) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -863,15 +829,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->inject('mode') ->inject('deviceFiles') ->inject('deviceLocal') - ->action(function ($bucketId, $fileId, $width, $height, $gravity, $quality, $borderWidth, $borderColor, $borderRadius, $opacity, $rotation, $background, $output, $request, $response, $project, $dbForProject, $usage, $mode, $deviceFiles, $deviceLocal) { - /** @var Utopia\Swoole\Request $request */ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $project */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Stats\Stats $usage */ - /** @var Utopia\Storage\Device $deviceFiles */ - /** @var Utopia\Storage\Device $deviceLocal */ - /** @var string $mode */ + ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, Stats $usage, string $mode, Device $deviceFiles, Device $deviceLocal) { if (!\extension_loaded('imagick')) { throw new Exception('Imagick extension is missing', 500, Exception::GENERAL_SERVER_ERROR); @@ -1041,13 +999,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->inject('usage') ->inject('mode') ->inject('deviceFiles') - ->action(function ($bucketId, $fileId, $request, $response, $dbForProject, $usage, $mode, $deviceFiles) { - /** @var Utopia\Swoole\Request $request */ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Stats\Stats $usage */ - /** @var Utopia\Storage\Device $deviceFiles */ - /** @var string $mode */ + ->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, Stats $usage, string $mode, Device $deviceFiles) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -1184,13 +1136,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->inject('usage') ->inject('mode') ->inject('deviceFiles') - ->action(function ($bucketId, $fileId, $response, $request, $dbForProject, $usage, $mode, $deviceFiles) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Swoole\Request $request */ - /** @var Utopia\Database\Database $dbForInternal */ - /** @var Appwrite\Stats\Stats $usage */ - /** @var Utopia\Storage\Device $deviceFiles */ - /** @var string $mode */ + ->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, Stats $usage, string $mode, Device $deviceFiles) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -1344,14 +1290,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('usage') ->inject('mode') ->inject('events') - ->action(function ($bucketId, $fileId, $read, $write, $response, $dbForProject, $user, $audits, $usage, $mode, $events) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Utopia\Database\Document $user */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Stats\Stats $usage */ - /** @var Appwrite\Event\Event $events */ - /** @var string $mode */ + ->action(function (string $bucketId, string $fileId, array $read, array $write,Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, string $mode, Event $events) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user @@ -1444,15 +1383,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('mode') ->inject('deviceFiles') ->inject('project') - ->action(function ($bucketId, $fileId, $response, $dbForProject, $events, $audits, $usage, $mode, $deviceFiles, $project) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Stats\Stats $usage */ - /** @var Utopia\Storage\Device $deviceFiles */ - /** @var string $mode */ + ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $events, Event $audits, Stats $usage, string $mode, Device $deviceFiles, Document $project) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -1539,9 +1470,7 @@ App::get('/v1/storage/usage') ->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->action(function ($range, $response, $dbForProject) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ + ->action(function (string $range, Response $response, Database $dbForProject) { $usage = []; if (App::getEnv('_APP_USAGE_STATS', 'enabled') === 'enabled') { @@ -1651,9 +1580,7 @@ App::get('/v1/storage/:bucketId/usage') ->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true) ->inject('response') ->inject('dbForProject') - ->action(function ($bucketId, $range, $response, $dbForProject) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForProject */ + ->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) { $bucket = $dbForProject->getDocument('buckets', $bucketId); From 5b72d9d48d1a555c128d74d218d95ff88116b294 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Wed, 4 May 2022 12:49:35 +0000 Subject: [PATCH 45/54] change file type --- app/controllers/api/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 3c307a7c71..b14e841b7a 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -350,7 +350,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->inject('mode') ->inject('deviceFiles') ->inject('deviceLocal') - ->action(function (string $bucketId, string $fileId, string $file, array $read, array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { + ->action(function (string $bucketId, string $fileId, File $file, array $read, array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); From ceea134579e8f80b8c79cafe71d71f9508d39dbe Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Wed, 4 May 2022 13:00:12 +0000 Subject: [PATCH 46/54] fix file param --- app/controllers/api/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index b14e841b7a..dfd3b3a066 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -350,7 +350,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->inject('mode') ->inject('deviceFiles') ->inject('deviceLocal') - ->action(function (string $bucketId, string $fileId, File $file, array $read, array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { + ->action(function (string $bucketId, string $fileId, array $file, array $read, array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); From cc1ddc87d919d1d167ddb219f42cb75be1b3ea68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Fri, 6 May 2022 11:03:41 +0000 Subject: [PATCH 47/54] Fix tema user creation --- app/controllers/api/teams.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 22705d3bf8..9768f23c32 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -342,6 +342,7 @@ App::post('/v1/teams/:teamId/memberships') 'tokens' => [], 'memberships' => [], 'search' => implode(' ', [$userId, $email, $name]), + 'deleted' => false ]))); } catch (Duplicate $th) { throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS); From 8c4b035dd741980d701c78dcf3341284f850cb24 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Fri, 6 May 2022 13:33:44 +0000 Subject: [PATCH 48/54] update storage.php with optional parameters --- app/controllers/api/storage.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index dfd3b3a066..d8fcd3a95e 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -65,7 +65,7 @@ App::post('/v1/storage/buckets') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function (string $bucketId, string $name, string $permission, array $read, array $write, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage) { + ->action(function (string $bucketId, string $name, string $permission, ?array $read, ?array $write, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage) { $bucketId = $bucketId === 'unique()' ? $dbForProject->getId() : $bucketId; try { @@ -157,7 +157,7 @@ App::get('/v1/storage/buckets') ->inject('response') ->inject('dbForProject') ->inject('usage') - ->action(function (array $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) { + ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) { $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : []; @@ -231,7 +231,7 @@ App::put('/v1/storage/buckets/:bucketId') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function (string $bucketId, string $name, string $permission, array $read, array $write, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage) { + ->action(function (string $bucketId, string $name, string $permission, ?array $read, ?array $write, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage) { $bucket = $dbForProject->getDocument('buckets', $bucketId); @@ -350,7 +350,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->inject('mode') ->inject('deviceFiles') ->inject('deviceLocal') - ->action(function (string $bucketId, string $fileId, array $file, array $read, array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { + ->action(function (string $bucketId, string $fileId, array $file, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -1290,7 +1290,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('usage') ->inject('mode') ->inject('events') - ->action(function (string $bucketId, string $fileId, array $read, array $write,Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, string $mode, Event $events) { + ->action(function (string $bucketId, string $fileId, ?array $read, ?array $write,Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, string $mode, Event $events) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user From a2091a25725d96fcf97ddf77c9ea96d522425162 Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Fri, 6 May 2022 15:12:11 +0000 Subject: [PATCH 49/54] retrigger build --- app/controllers/api/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index d8fcd3a95e..8d047f2e21 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -37,7 +37,7 @@ use Utopia\Validator\Integer; use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; -use Utopia\Swoole\Request; +use Utopia\Swoole\Request; App::post('/v1/storage/buckets') ->desc('Create bucket') From f3608dd4fe721c152cf394c403db168af429077c Mon Sep 17 00:00:00 2001 From: Everly Precia Suresh Date: Fri, 6 May 2022 16:51:02 +0000 Subject: [PATCH 50/54] refactor projects.php --- app/controllers/api/projects.php | 131 ++++++++----------------------- 1 file changed, 33 insertions(+), 98 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index f7100a88a2..8c2392846f 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -2,6 +2,7 @@ use Appwrite\Auth\Auth; use Appwrite\Auth\Validator\Password; +use Appwrite\Event\Event; use Appwrite\Network\Validator\CNAME; use Appwrite\Network\Validator\Domain as DomainValidator; use Appwrite\Network\Validator\Origin; @@ -18,6 +19,7 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; +use Utopia\Registry\Registry; use Appwrite\Extend\Exception; use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; @@ -26,8 +28,7 @@ use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; -App::init(function ($project) { - /** @var Utopia\Database\Document $project */ +App::init(function (Document $project) { if ($project->getId() !== 'console') { throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN); @@ -59,10 +60,7 @@ App::post('/v1/projects') ->inject('response') ->inject('dbForConsole') ->inject('dbForProject') - ->action(function ($projectId, $name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole, $dbForProject) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ - /** @var Utopia\Database\Database $dbForProject */ + ->action(function (string $projectId, string $name, string $teamId, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Database $dbForProject) { $team = $dbForConsole->getDocument('teams', $teamId); @@ -170,9 +168,7 @@ App::get('/v1/projects') ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForConsole') - ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForConsole) { if (!empty($cursor)) { $cursorProject = $dbForConsole->getDocument('projects', $cursor); @@ -210,9 +206,7 @@ App::get('/v1/projects/:projectId') ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -239,11 +233,7 @@ App::get('/v1/projects/:projectId/usage') ->inject('dbForConsole') ->inject('dbForProject') ->inject('register') - ->action(function ($projectId, $range, $response, $dbForConsole, $dbForProject, $register) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Utopia\Registry\Registry $register */ + ->action(function (string $projectId, string $range, Response $response, Database $dbForConsole, Database $dbForProject, Registry $register) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -360,9 +350,7 @@ App::patch('/v1/projects/:projectId') ->param('legalTaxId', '', new Text(256), 'Project legal tax ID. Max length: 256 chars.', true) ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -402,10 +390,7 @@ App::patch('/v1/projects/:projectId/service') ->param('status', null, new Boolean(), 'Service status.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $service, $status, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ - /** @var Boolean $status */ + ->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -437,9 +422,7 @@ App::patch('/v1/projects/:projectId/oauth2') ->param('secret', '', new text(512), 'Provider secret key. Max length: 512 chars.', true) ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $provider, $appId, $secret, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $provider, string $appId, string $secret, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -470,9 +453,7 @@ App::patch('/v1/projects/:projectId/auth/limit') ->param('limit', false, new Range(0, APP_LIMIT_USERS), 'Set the max number of users allowed in this project. Use 0 for unlimited.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $limit, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -505,9 +486,7 @@ App::patch('/v1/projects/:projectId/auth/:method') ->param('status', false, new Boolean(true), 'Set the status of this auth method.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $method, $status, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $method, bool $status, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); $auth = Config::getParam('auth')[$method] ?? []; @@ -541,11 +520,7 @@ App::delete('/v1/projects/:projectId') ->inject('user') ->inject('dbForConsole') ->inject('deletes') - ->action(function ($projectId, $password, $response, $user, $dbForConsole, $deletes) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $user */ - /** @var Utopia\Database\Database $dbForConsole */ - /** @var Appwrite\Event\Event $deletes */ + ->action(function (string $projectId, string $password, Response $response, Document $user, Database $dbForConsole, Event $deletes) { if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); @@ -594,9 +569,7 @@ App::post('/v1/projects/:projectId/webhooks') ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true) ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $name, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -640,9 +613,7 @@ App::get('/v1/projects/:projectId/webhooks') ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -674,9 +645,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ->param('webhookId', null, new UID(), 'Webhook unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $webhookId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -716,9 +685,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true) ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $webhookId, string $name, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -766,9 +733,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->param('webhookId', null, new UID(), 'Webhook unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $webhookId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -809,9 +774,7 @@ App::post('/v1/projects/:projectId/keys') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true)), 'Key scopes list.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $name, $scopes, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $name, array $scopes, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -850,9 +813,7 @@ App::get('/v1/projects/:projectId/keys') ->param('projectId', null, new UID(), 'Project unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -884,9 +845,7 @@ App::get('/v1/projects/:projectId/keys/:keyId') ->param('keyId', null, new UID(), 'Key unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $keyId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -922,9 +881,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true)), 'Key scopes list') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $keyId, $name, $scopes, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $keyId, string $name, array $scopes, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -966,9 +923,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId') ->param('keyId', null, new UID(), 'Key unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $keyId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1012,9 +967,7 @@ App::post('/v1/projects/:projectId/platforms') ->param('hostname', '', new Text(256), 'Platform client hostname. Max length: 256 chars.', true) ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $type, $name, $key, $store, $hostname, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $type, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1057,9 +1010,7 @@ App::get('/v1/projects/:projectId/platforms') ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1091,9 +1042,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ->param('platformId', null, new UID(), 'Platform unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $platformId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1131,9 +1080,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->param('hostname', '', new Text(256), 'Platform client URL. Max length: 256 chars.', true) ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $platformId, $name, $key, $store, $hostname, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $platformId, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1178,9 +1125,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ->param('platformId', null, new UID(), 'Platform unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $platformId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1220,9 +1165,7 @@ App::post('/v1/projects/:projectId/domains') ->param('domain', null, new DomainValidator(), 'Domain name.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $domain, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $domain, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1281,9 +1224,7 @@ App::get('/v1/projects/:projectId/domains') ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1315,9 +1256,7 @@ App::get('/v1/projects/:projectId/domains/:domainId') ->param('domainId', null, new UID(), 'Domain unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $domainId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1351,9 +1290,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') ->param('domainId', null, new UID(), 'Domain unique ID.') ->inject('response') ->inject('dbForConsole') - ->action(function ($projectId, $domainId, $response, $dbForConsole) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1413,9 +1350,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ->inject('response') ->inject('dbForConsole') ->inject('deletes') - ->action(function ($projectId, $domainId, $response, $dbForConsole, $deletes) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForConsole */ + ->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole, $deletes) { $project = $dbForConsole->getDocument('projects', $projectId); From 95317d87b813affed8def950afb48b957eebf38c Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 16:31:32 +0200 Subject: [PATCH 51/54] fix: event validator --- src/Appwrite/Event/Validator/Event.php | 13 ++++++++----- tests/unit/Event/Validator/EventValidatorTest.php | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php index d5ad85475d..ce0f3e0067 100644 --- a/src/Appwrite/Event/Validator/Event.php +++ b/src/Appwrite/Event/Validator/Event.php @@ -87,11 +87,14 @@ class Event extends Validator } if ($attribute ?? false) { - if ( - !\array_key_exists($attribute, $events[$type][$action]) || - (($subType ?? false) && !\array_key_exists($attribute, $events[$type][$subType][$action])) - ) { - return false; + if (($subType ?? false)) { + if (!\array_key_exists($attribute, $events[$type][$subType][$action])) { + return false; + } + } else { + if (!\array_key_exists($attribute, $events[$type][$action])) { + return false; + } } } diff --git a/tests/unit/Event/Validator/EventValidatorTest.php b/tests/unit/Event/Validator/EventValidatorTest.php index f0886ae5a7..e523999599 100644 --- a/tests/unit/Event/Validator/EventValidatorTest.php +++ b/tests/unit/Event/Validator/EventValidatorTest.php @@ -44,6 +44,7 @@ class EventValidatorTest extends TestCase $this->assertTrue($this->object->isValid('buckets.*')); $this->assertTrue($this->object->isValid('teams.*')); $this->assertTrue($this->object->isValid('users.*')); + $this->assertTrue($this->object->isValid('teams.*.memberships.*.update.status')); /** * Test for FAILURE @@ -57,5 +58,6 @@ class EventValidatorTest extends TestCase $this->assertFalse($this->object->isValid('collections.*.documents.*.unknown')); $this->assertFalse($this->object->isValid('users.torsten.unknown')); $this->assertFalse($this->object->isValid('users.torsten.delete.email')); + $this->assertFalse($this->object->isValid('teams.*.memberships.*.update.unknown')); } } From 773e6d0415108e48dc85e5e0cb87aa6e4addce93 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 17:10:56 +0200 Subject: [PATCH 52/54] style: fix some styles --- app/controllers/api/avatars.php | 50 +++++++++++++++------------------ app/controllers/api/locale.php | 37 ++++++++++-------------- app/controllers/api/storage.php | 2 +- 3 files changed, 38 insertions(+), 51 deletions(-) diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index e3b56cf588..198f379171 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -16,9 +16,9 @@ use Utopia\Validator\Boolean; use Utopia\Validator\HexColor; use Utopia\Validator\Range; use Utopia\Validator\Text; -use Utopia\Validator\WhiteList; +use Utopia\Validator\WhiteList; -$avatarCallback = function ($type, $code, $width, $height, $quality, Response $response) { +$avatarCallback = function (string $type, string $code, int $width, int $height, int $quality, Response $response) { $code = \strtolower($code); $type = \strtolower($type); @@ -38,7 +38,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, Response $r $output = 'png'; $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache - $key = \md5('/v1/avatars/'.$type.'/:code-' . $code . $width . $height . $quality . $output); + $key = \md5('/v1/avatars/' . $type . '/:code-' . $code . $width . $height . $quality . $output); $path = $set[$code]; $type = 'png'; @@ -56,8 +56,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality, Response $r ->setContentType('image/png') ->addHeader('Expires', $date) ->addHeader('X-Appwrite-Cache', 'hit') - ->send($data) - ; + ->send($data); } $image = new Image(\file_get_contents($path)); @@ -95,7 +94,7 @@ App::get('/v1/avatars/credit-cards/:code') ->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) ->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->inject('response') - ->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response)); + ->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response)); App::get('/v1/avatars/browsers/:code') ->desc('Get Browser Icon') @@ -113,7 +112,7 @@ App::get('/v1/avatars/browsers/:code') ->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) ->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->inject('response') - ->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response)); + ->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response)); App::get('/v1/avatars/flags/:code') ->desc('Get Country Flag') @@ -131,7 +130,7 @@ App::get('/v1/avatars/flags/:code') ->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) ->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->inject('response') - ->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response)); + ->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response)); App::get('/v1/avatars/image') ->desc('Get Image from URL') @@ -148,7 +147,7 @@ App::get('/v1/avatars/image') ->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000.', true) ->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000.', true) ->inject('response') - ->action(function ($url, $width, $height, Response $response) { + ->action(function (string $url, int $width, int $height, Response $response) { $quality = 80; $output = 'png'; @@ -163,8 +162,7 @@ App::get('/v1/avatars/image') ->setContentType('image/png') ->addHeader('Expires', $date) ->addHeader('X-Appwrite-Cache', 'hit') - ->send($data) - ; + ->send($data); } if (!\extension_loaded('imagick')) { @@ -179,7 +177,7 @@ App::get('/v1/avatars/image') try { $image = new Image($fetch); - } catch (\Exception$exception) { + } catch (\Exception $exception) { throw new Exception('Unable to parse image', 500, Exception::GENERAL_SERVER_ERROR); } @@ -195,8 +193,7 @@ App::get('/v1/avatars/image') ->setContentType('image/png') ->addHeader('Expires', $date) ->addHeader('X-Appwrite-Cache', 'miss') - ->send($data); - ; + ->send($data);; unset($image); }); @@ -214,7 +211,7 @@ App::get('/v1/avatars/favicon') ->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE) ->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.') ->inject('response') - ->action(function ($url, Response $response) { + ->action(function (string $url, Response $response) { $width = 56; $height = 56; @@ -231,8 +228,7 @@ App::get('/v1/avatars/favicon') ->setContentType('image/png') ->addHeader('Expires', $date) ->addHeader('X-Appwrite-Cache', 'hit') - ->send($data) - ; + ->send($data); } if (!\extension_loaded('imagick')) { @@ -246,7 +242,8 @@ App::get('/v1/avatars/favicon') CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 3, CURLOPT_URL => $url, - CURLOPT_USERAGENT => \sprintf(APP_USERAGENT, + CURLOPT_USERAGENT => \sprintf( + APP_USERAGENT, App::getEnv('_APP_VERSION', 'UNKNOWN'), App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) ), @@ -324,8 +321,7 @@ App::get('/v1/avatars/favicon') ->setContentType('image/x-icon') ->addHeader('Expires', $date) ->addHeader('X-Appwrite-Cache', 'miss') - ->send($data) - ; + ->send($data); } $fetch = @\file_get_contents($outputHref, false); @@ -369,7 +365,7 @@ App::get('/v1/avatars/qr') ->param('margin', 1, new Range(0, 10), 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true) ->param('download', false, new Boolean(true), 'Return resulting image with \'Content-Disposition: attachment \' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.', true) ->inject('response') - ->action(function ($text, $size, $margin, $download, Response $response) { + ->action(function (string $text, int $size, int $margin, bool $download, Response $response) { $download = ($download === '1' || $download === 'true' || $download === 1 || $download === true); $options = new QROptions([ @@ -391,8 +387,7 @@ App::get('/v1/avatars/qr') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->setContentType('image/png') - ->send($image->output('png', 9)) - ; + ->send($image->output('png', 9)); }); App::get('/v1/avatars/initials') @@ -413,7 +408,7 @@ App::get('/v1/avatars/initials') ->param('background', '', new HexColor(), 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true) ->inject('response') ->inject('user') - ->action(function ($name, $width, $height, $color, $background, Response $response, Document $user) { + ->action(function (string $name, int $width, int $height, string $color, string $background, Response $response, Document $user) { $themes = [ ['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET @@ -433,8 +428,8 @@ App::get('/v1/avatars/initials') $name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', '')); $words = \explode(' ', \strtoupper($name)); // if there is no space, try to split by `_` underscore - $words = (count($words) == 1 ) ? \explode('_', \strtoupper($name)) : $words; - + $words = (count($words) == 1) ? \explode('_', \strtoupper($name)) : $words; + $initials = null; $code = 0; @@ -473,6 +468,5 @@ App::get('/v1/avatars/initials') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->setContentType('image/png') - ->send($image->getImageBlob()) - ; + ->send($image->getImageBlob()); }); diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index bd3891a893..cf7c49566e 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -24,7 +24,6 @@ App::get('/v1/locale') ->inject('locale') ->inject('geodb') ->action(function (Request $request, Response $response, Locale $locale, Reader $geodb) { - $eu = Config::getParam('locale-eu'); $currencies = Config::getParam('locale-currencies'); $output = []; @@ -39,8 +38,8 @@ App::get('/v1/locale') if ($record) { $output['countryCode'] = $record['country']['iso_code']; - $output['country'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown')); - $output['continent'] = $locale->getText('continents.'.strtolower($record['continent']['code']), $locale->getText('locale.country.unknown')); + $output['country'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown')); + $output['continent'] = $locale->getText('continents.' . strtolower($record['continent']['code']), $locale->getText('locale.country.unknown')); $output['continent'] = (isset($continents[$record['continent']['code']])) ? $continents[$record['continent']['code']] : $locale->getText('locale.country.unknown'); $output['continentCode'] = $record['continent']['code']; $output['eu'] = (\in_array($record['country']['iso_code'], $eu)) ? true : false; @@ -62,8 +61,8 @@ App::get('/v1/locale') } $response - ->addHeader('Cache-Control', 'public, max-age='.$time) - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache + ->addHeader('Cache-Control', 'public, max-age=' . $time) + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache ; $response->dynamic(new Document($output), Response::MODEL_LOCALE); }); @@ -82,13 +81,12 @@ App::get('/v1/locale/countries') ->inject('response') ->inject('locale') ->action(function (Response $response, Locale $locale) { - $list = Config::getParam('locale-countries'); /* @var $list array */ $output = []; foreach ($list as $value) { $output[] = new Document([ - 'name' => $locale->getText('countries.'.strtolower($value)), + 'name' => $locale->getText('countries.' . strtolower($value)), 'code' => $value, ]); } @@ -114,14 +112,13 @@ App::get('/v1/locale/countries/eu') ->inject('response') ->inject('locale') ->action(function (Response $response, Locale $locale) { - $eu = Config::getParam('locale-eu'); $output = []; foreach ($eu as $code) { - if ($locale->getText('countries.'.strtolower($code), false) !== false) { + if ($locale->getText('countries.' . strtolower($code), false) !== false) { $output[] = new Document([ - 'name' => $locale->getText('countries.'.strtolower($code)), + 'name' => $locale->getText('countries.' . strtolower($code)), 'code' => $code, ]); } @@ -148,18 +145,17 @@ App::get('/v1/locale/countries/phones') ->inject('response') ->inject('locale') ->action(function (Response $response, Locale $locale) { - $list = Config::getParam('locale-phones'); /* @var $list array */ $output = []; \asort($list); foreach ($list as $code => $name) { - if ($locale->getText('countries.'.strtolower($code), false) !== false) { + if ($locale->getText('countries.' . strtolower($code), false) !== false) { $output[] = new Document([ - 'code' => '+'.$list[$code], + 'code' => '+' . $list[$code], 'countryCode' => $code, - 'countryName' => $locale->getText('countries.'.strtolower($code)), + 'countryName' => $locale->getText('countries.' . strtolower($code)), ]); } } @@ -181,12 +177,11 @@ App::get('/v1/locale/continents') ->inject('response') ->inject('locale') ->action(function (Response $response, Locale $locale) { + $list = Config::getParam('locale-continents'); - $list = Config::getParam('locale-continents'); /* @var $list array */ - - foreach ($list as $key => $value) { + foreach ($list as $value) { $output[] = new Document([ - 'name' => $locale->getText('continents.'.strtolower($value)), + 'name' => $locale->getText('continents.' . strtolower($value)), 'code' => $value, ]); } @@ -211,10 +206,9 @@ App::get('/v1/locale/currencies') ->label('sdk.response.model', Response::MODEL_CURRENCY_LIST) ->inject('response') ->action(function (Response $response) { - $list = Config::getParam('locale-currencies'); - $list = array_map(fn($node) => new Document($node), $list); + $list = array_map(fn ($node) => new Document($node), $list); $response->dynamic(new Document(['currencies' => $list, 'total' => \count($list)]), Response::MODEL_CURRENCY_LIST); }); @@ -233,10 +227,9 @@ App::get('/v1/locale/languages') ->label('sdk.response.model', Response::MODEL_LANGUAGE_LIST) ->inject('response') ->action(function (Response $response) { - $list = Config::getParam('locale-languages'); $list = array_map(fn ($node) => new Document($node), $list); $response->dynamic(new Document(['languages' => $list, 'total' => \count($list)]), Response::MODEL_LANGUAGE_LIST); - }); \ No newline at end of file + }); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 8d047f2e21..9ad25bda69 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -159,7 +159,7 @@ App::get('/v1/storage/buckets') ->inject('usage') ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) { - $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : []; + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; if (!empty($cursor)) { $cursorBucket = $dbForProject->getDocument('buckets', $cursor); From 18379c25b8b7950a3178634f297e2488cec237d5 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 17:49:17 +0200 Subject: [PATCH 53/54] fix: merge issues --- app/controllers/api/account.php | 8 ++++++-- app/controllers/api/storage.php | 2 +- app/workers/deletes.php | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2bf31a001a..b5a7e30679 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -826,7 +826,11 @@ App::put('/v1/account/sessions/magic-url') $dbForProject->deleteDocument('tokens', $token); $dbForProject->deleteCachedDocument('users', $user->getId()); - $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)); + $user + ->setAttribute('emailVerification', true) + ->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); + + $user = $dbForProject->updateDocument('users', $user->getId(), $user); if (false === $user) { throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR); @@ -1962,7 +1966,7 @@ App::put('/v1/account/recovery') $events ->setParam('userId', $profile->getId()) - ->setParam('tokenId', $recovery->getId()) + ->setParam('tokenId', $recoveryDocument->getId()) ; $response->dynamic($recoveryDocument, Response::MODEL_TOKEN); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 0c06c75f56..4ac1e63905 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -238,7 +238,7 @@ App::put('/v1/storage/buckets/:bucketId') ->inject('audits') ->inject('usage') ->inject('events') - ->action(function (string $bucketId, string $name, string $permission, ?array $read, ?array $write, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage, Event $events) { + ->action(function (string $bucketId, string $name, string $permission, ?array $read, ?array $write, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Audit $audits, Stats $usage, Event $events) { $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 5867ad15b8..b06f2ffa0e 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -208,7 +208,6 @@ class DeletesV1 extends Worker */ $userId = $document->getId(); - $user = $this->getProjectDB($projectId)->getDocument('users', $userId); // Delete all sessions of this user from the sessions table and update the sessions field of the user record $this->deleteByGroup('sessions', [ From bf2301ee997131a9836e3e8707ab6bc6c55a5016 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 17:51:38 +0200 Subject: [PATCH 54/54] chore: build js --- public/dist/scripts/app-all.js | 4 ++-- public/dist/scripts/app.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js index 10fd74576c..a973062efb 100644 --- a/public/dist/scripts/app-all.js +++ b/public/dist/scripts/app-all.js @@ -3838,8 +3838,8 @@ list["filters-"+filter.key]=params[key][i];}}}} return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');} -console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"}} -let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unkown")} +console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"}} +let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unknown")} let config=providers[provider];element.addEventListener('change',sync);let elements={};for(const key in config){if(Object.hasOwnProperty.call(config,key)){elements[key]=document.getElementById(config[key]);elements[key].addEventListener('change',update);}} function update(){let json={};for(const key in elements){if(Object.hasOwnProperty.call(elements,key)){json[key]=elements[key].value}} element.value=JSON.stringify(json);} diff --git a/public/dist/scripts/app.js b/public/dist/scripts/app.js index 7240e243c9..454c8586e3 100644 --- a/public/dist/scripts/app.js +++ b/public/dist/scripts/app.js @@ -785,8 +785,8 @@ list["filters-"+filter.key]=params[key][i];}}}} return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');} -console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"}} -let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unkown")} +console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"}} +let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unknown")} let config=providers[provider];element.addEventListener('change',sync);let elements={};for(const key in config){if(Object.hasOwnProperty.call(config,key)){elements[key]=document.getElementById(config[key]);elements[key].addEventListener('change',update);}} function update(){let json={};for(const key in elements){if(Object.hasOwnProperty.call(elements,key)){json[key]=elements[key].value}} element.value=JSON.stringify(json);}