mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Resolve merge conflicts
This commit is contained in:
@@ -1185,6 +1185,39 @@ return [
|
||||
'default' => false,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('personalAccessToken'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('personalAccessTokenExpiry'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('personalRefreshToken'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['encrypt'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
|
||||
|
||||
@@ -875,6 +875,11 @@ return [
|
||||
'description' => 'Variable with the same ID already exists in this project. Try again with a different ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::VARIABLE_CANNOT_UNSET_SECRET => [
|
||||
'name' => Exception::VARIABLE_CANNOT_UNSET_SECRET,
|
||||
'description' => 'Secret variables cannot be marked as non-secret. Please re-create the variable if this is your intention.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GRAPHQL_NO_QUERY => [
|
||||
'name' => Exception::GRAPHQL_NO_QUERY,
|
||||
'description' => 'Param "query" is not optional.',
|
||||
|
||||
@@ -4761,7 +4761,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"weight": 305,
|
||||
"weight": 306,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4846,7 +4846,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"weight": 304,
|
||||
"weight": 305,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -4960,7 +4960,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"weight": 306,
|
||||
"weight": 307,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5033,7 +5033,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"weight": 330,
|
||||
"weight": 331,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
@@ -5084,7 +5084,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
@@ -5543,7 +5543,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 375,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5625,7 +5625,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5699,7 +5699,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"weight": 207,
|
||||
"weight": 208,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5784,7 +5784,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"weight": 206,
|
||||
"weight": 207,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"deprecated": false,
|
||||
@@ -5881,7 +5881,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"weight": 208,
|
||||
"weight": 209,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5952,7 +5952,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"weight": 213,
|
||||
"weight": 214,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6040,7 +6040,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"weight": 214,
|
||||
"weight": 215,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6106,7 +6106,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"weight": 210,
|
||||
"weight": 211,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6172,7 +6172,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"weight": 209,
|
||||
"weight": 210,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6388,7 +6388,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"weight": 211,
|
||||
"weight": 212,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6461,7 +6461,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"weight": 218,
|
||||
"weight": 219,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6536,7 +6536,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"weight": 217,
|
||||
"weight": 218,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6620,7 +6620,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 219,
|
||||
"weight": 220,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6681,7 +6681,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"weight": 221,
|
||||
"weight": 222,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6754,7 +6754,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 223,
|
||||
"weight": 224,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6817,7 +6817,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"weight": 225,
|
||||
"weight": 226,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6902,7 +6902,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"weight": 224,
|
||||
"weight": 225,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7012,7 +7012,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"weight": 226,
|
||||
"weight": 227,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7083,7 +7083,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"weight": 227,
|
||||
"weight": 228,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7169,7 +7169,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"weight": 229,
|
||||
"weight": 230,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7242,7 +7242,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"weight": 228,
|
||||
"weight": 229,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7339,7 +7339,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"weight": 220,
|
||||
"weight": 221,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7399,7 +7399,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"weight": 222,
|
||||
"weight": 223,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -37571,7 +37571,7 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, react-native-android, react-native-ios and unity.",
|
||||
"description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, and unity.",
|
||||
"x-example": "web"
|
||||
},
|
||||
"key": {
|
||||
|
||||
@@ -4927,7 +4927,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"weight": 305,
|
||||
"weight": 306,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5009,7 +5009,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"weight": 304,
|
||||
"weight": 305,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5127,7 +5127,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"weight": 306,
|
||||
"weight": 307,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5198,7 +5198,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"weight": 330,
|
||||
"weight": 331,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
@@ -5271,7 +5271,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
@@ -5768,7 +5768,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 375,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5852,7 +5852,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5924,7 +5924,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"weight": 207,
|
||||
"weight": 208,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6006,7 +6006,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"weight": 206,
|
||||
"weight": 207,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"deprecated": false,
|
||||
@@ -6097,7 +6097,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"weight": 208,
|
||||
"weight": 209,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6166,7 +6166,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"weight": 213,
|
||||
"weight": 214,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6254,7 +6254,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"weight": 214,
|
||||
"weight": 215,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6325,7 +6325,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"weight": 210,
|
||||
"weight": 211,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6396,7 +6396,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"weight": 209,
|
||||
"weight": 210,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6595,7 +6595,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"weight": 211,
|
||||
"weight": 212,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
@@ -6666,7 +6666,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"weight": 218,
|
||||
"weight": 219,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6740,7 +6740,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"weight": 217,
|
||||
"weight": 218,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6831,7 +6831,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 219,
|
||||
"weight": 220,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6892,7 +6892,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"weight": 221,
|
||||
"weight": 222,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6966,7 +6966,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 223,
|
||||
"weight": 224,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7029,7 +7029,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"weight": 225,
|
||||
"weight": 226,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7111,7 +7111,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"weight": 224,
|
||||
"weight": 225,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7225,7 +7225,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"weight": 226,
|
||||
"weight": 227,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7294,7 +7294,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"weight": 227,
|
||||
"weight": 228,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7379,7 +7379,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"weight": 229,
|
||||
"weight": 230,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7450,7 +7450,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"weight": 228,
|
||||
"weight": 229,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7545,7 +7545,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"weight": 220,
|
||||
"weight": 221,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -7605,7 +7605,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"weight": 222,
|
||||
"weight": 223,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -38160,7 +38160,7 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, react-native-android, react-native-ios and unity.",
|
||||
"description": "Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, and unity.",
|
||||
"x-example": "web"
|
||||
},
|
||||
"key": {
|
||||
|
||||
@@ -7,4 +7,5 @@ return [
|
||||
"gif" => "image/gif",
|
||||
"png" => "image/png",
|
||||
"heic" => "image/heic",
|
||||
"webp" => "image/webp",
|
||||
];
|
||||
|
||||
@@ -21,7 +21,6 @@ use Appwrite\Event\Usage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -60,6 +59,7 @@ use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -1188,8 +1188,8 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
||||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
|
||||
->param('success', '', fn ($hostnames, $schemes) => new Redirect($hostnames, $schemes), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['hostnames', 'schemes'])
|
||||
->param('failure', '', fn ($hostnames, $schemes) => new Redirect($hostnames, $schemes), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['hostnames', 'schemes'])
|
||||
->param('success', '', fn ($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('failure', '', fn ($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -1784,8 +1784,8 @@ App::get('/v1/account/tokens/oauth2/:provider')
|
||||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
|
||||
->param('success', '', fn ($hostnames, $schemes) => new Redirect($hostnames, $schemes), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['hostnames', 'schemes'])
|
||||
->param('failure', '', fn ($hostnames, $schemes) => new Redirect($hostnames, $schemes), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['hostnames', 'schemes'])
|
||||
->param('success', '', fn ($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('failure', '', fn ($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -1864,7 +1864,7 @@ App::post('/v1/account/tokens/magic-url')
|
||||
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. 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('email', '', new Email(), 'User email.')
|
||||
->param('url', '', fn ($hostnames, $schemes) => new Redirect($hostnames, $schemes), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['hostnames', 'schemes'])
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -3157,7 +3157,7 @@ App::post('/v1/account/recovery')
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('url', '', fn ($hostnames, $schemes) => new Redirect($hostnames, $schemes), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['hostnames', 'schemes'])
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
@@ -3432,7 +3432,7 @@ App::post('/v1/account/verification')
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},userId:{userId}')
|
||||
->param('url', '', fn ($hostnames, $schemes) => new Redirect($hostnames, $schemes), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['hostnames', 'schemes']) // TODO add built-in confirm page
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add built-in confirm page
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
|
||||
@@ -1585,7 +1585,7 @@ App::post('/v1/functions/:functionId/variables')
|
||||
->param('functionId', '', new UID(), 'Function unique ID.', false)
|
||||
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
|
||||
->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true)
|
||||
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -1729,10 +1729,11 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
|
||||
->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, ?bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -1749,9 +1750,14 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
throw new Exception(Exception::VARIABLE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($variable->getAttribute('secret') === true && $secret === false) {
|
||||
throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET);
|
||||
}
|
||||
|
||||
$variable
|
||||
->setAttribute('key', $key)
|
||||
->setAttribute('value', $value ?? $variable->getAttribute('value'))
|
||||
->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
|
||||
->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function']));
|
||||
|
||||
try {
|
||||
|
||||
@@ -388,7 +388,7 @@ App::post('/v1/project/variables')
|
||||
))
|
||||
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
|
||||
->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true)
|
||||
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -509,19 +509,25 @@ App::put('/v1/project/variables/:variableId')
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
|
||||
->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
->action(function (string $variableId, string $key, ?string $value, ?bool $secret, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
$variable = $dbForProject->getDocument('variables', $variableId);
|
||||
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') {
|
||||
throw new Exception(Exception::VARIABLE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($variable->getAttribute('secret') === true && $secret === false) {
|
||||
throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET);
|
||||
}
|
||||
|
||||
$variable
|
||||
->setAttribute('key', $key)
|
||||
->setAttribute('value', $value ?? $variable->getAttribute('value'))
|
||||
->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
|
||||
->setAttribute('search', implode(' ', [$variableId, $key, 'project']));
|
||||
|
||||
try {
|
||||
|
||||
@@ -1741,7 +1741,7 @@ App::post('/v1/projects/:projectId/platforms')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY, Origin::CLIENT_TYPE_REACT_NATIVE_IOS, Origin::CLIENT_TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
|
||||
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY, Origin::CLIENT_TYPE_REACT_NATIVE_IOS, Origin::CLIENT_TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
|
||||
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
|
||||
|
||||
@@ -11,7 +11,6 @@ use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\Platform\Workers\Deletes;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -50,6 +49,7 @@ use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -455,7 +455,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
|
||||
->param('url', '', fn ($hostnames, $schemes) => new Redirect($hostnames, $schemes), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['hostnames', 'schemes']) // TODO add our own built-in confirm page
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
|
||||
@@ -316,8 +316,8 @@ App::get('/v1/vcs/github/authorize')
|
||||
type: MethodType::WEBAUTH,
|
||||
hide: true,
|
||||
))
|
||||
->param('success', '', fn ($hostnames) => new Host($hostnames), 'URL to redirect back to console after a successful installation attempt.', true, ['hostnames'])
|
||||
->param('failure', '', fn ($hostnames) => new Host($hostnames), 'URL to redirect back to console after a failed installation attempt.', true, ['hostnames'])
|
||||
->param('success', '', fn ($clients) => new Host($clients), 'URL to redirect back to console after a successful installation attempt.', true, ['clients'])
|
||||
->param('failure', '', fn ($clients) => new Host($clients), 'URL to redirect back to console after a failed installation attempt.', true, ['clients'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
|
||||
@@ -130,7 +130,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
};
|
||||
}
|
||||
|
||||
if ($type === 'function' || $type === 'site') {
|
||||
if ($type === 'function' || $type === 'site' || $type === 'deployment') {
|
||||
$method = $utopia->getRoute()?->getLabel('sdk', null);
|
||||
|
||||
if (empty($method)) {
|
||||
@@ -592,7 +592,7 @@ App::init()
|
||||
->inject('getProjectDB')
|
||||
->inject('locale')
|
||||
->inject('localeCodes')
|
||||
->inject('hostnames')
|
||||
->inject('clients')
|
||||
->inject('geodb')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForEvents')
|
||||
@@ -600,8 +600,7 @@ App::init()
|
||||
->inject('queueForFunctions')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('previewHostname')
|
||||
->inject('platforms')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $hostnames, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked, string $previewHostname, array $platforms) {
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked, string $previewHostname) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -718,7 +717,7 @@ App::init()
|
||||
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
|
||||
|
||||
$refDomainOrigin = 'localhost';
|
||||
$validator = new Hostname($hostnames);
|
||||
$validator = new Hostname($clients);
|
||||
if ($validator->isValid($origin)) {
|
||||
$refDomainOrigin = $origin;
|
||||
}
|
||||
@@ -809,7 +808,7 @@ App::init()
|
||||
* Skip this check for non-web platforms which are not required to send an origin header
|
||||
*/
|
||||
$origin = $request->getOrigin($request->getReferer(''));
|
||||
$originValidator = new Origin($platforms);
|
||||
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
|
||||
|
||||
if (
|
||||
!$originValidator->isValid($origin)
|
||||
@@ -1119,6 +1118,7 @@ App::error()
|
||||
->setParam('trace', $output['trace'] ?? []);
|
||||
|
||||
$response->html($layout->render());
|
||||
return;
|
||||
}
|
||||
|
||||
$response->dynamic(
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
global $utopia, $request, $response;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
@@ -15,6 +14,7 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
@@ -26,7 +26,7 @@ App::get('/v1/mock/tests/general/oauth2')
|
||||
->label('docs', false)
|
||||
->label('mock', true)
|
||||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('redirect_uri', '', new Redirect(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
|
||||
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
|
||||
->param('scope', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('state', '', new Text(1024), 'OAuth2 state.')
|
||||
->inject('response')
|
||||
@@ -44,7 +44,7 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
||||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('client_secret', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('grant_type', 'authorization_code', new WhiteList(['refresh_token', 'authorization_code']), 'OAuth2 Grant Type.', true)
|
||||
->param('redirect_uri', '', new Redirect(['localhost']), 'OAuth2 Redirect URI.', true)
|
||||
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.', true)
|
||||
->param('code', '', new Text(100), 'OAuth2 state.', true)
|
||||
->param('refresh_token', '', new Text(100), 'OAuth2 refresh token.', true)
|
||||
->inject('response')
|
||||
|
||||
+46
-34
@@ -1213,46 +1213,58 @@ App::setResource('queueForCertificates', function (Queue\Publisher $publisher) {
|
||||
App::setResource('queueForMigrations', function (Queue\Publisher $publisher) {
|
||||
return new Migration($publisher);
|
||||
}, ['publisher']);
|
||||
App::setResource('platforms', function (Document $project, Document $console) {
|
||||
return [
|
||||
...$project->getAttribute('platforms', []),
|
||||
...$console->getAttribute('platforms', []),
|
||||
];
|
||||
}, ['project', 'console']);
|
||||
App::setResource('hostnames', function (array $platforms) {
|
||||
// Allow environment configured hostnames
|
||||
$hostnames = [];
|
||||
App::setResource('clients', function ($request, $console, $project) {
|
||||
$console->setAttribute('platforms', [ // Always allow current host
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Current Host',
|
||||
'type' => Origin::CLIENT_TYPE_WEB,
|
||||
'hostname' => $request->getHostname(),
|
||||
], Document::SET_TYPE_APPEND);
|
||||
|
||||
$hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', ''));
|
||||
$validator = new Hostname();
|
||||
foreach (explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', '')) as $hostname) {
|
||||
foreach ($hostnames as $hostname) {
|
||||
$hostname = trim($hostname);
|
||||
if ($validator->isValid($hostname)) {
|
||||
$hostnames[] = $hostname;
|
||||
if (!$validator->isValid($hostname)) {
|
||||
continue;
|
||||
}
|
||||
$console->setAttribute('platforms', [
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'type' => Origin::CLIENT_TYPE_WEB,
|
||||
'name' => $hostname,
|
||||
'hostname' => $hostname,
|
||||
], Document::SET_TYPE_APPEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get All verified client URLs for both console and current projects
|
||||
* + Filter for duplicated entries
|
||||
*/
|
||||
$clientsConsole = \array_map(
|
||||
fn ($node) => $node['hostname'],
|
||||
\array_filter(
|
||||
$console->getAttribute('platforms', []),
|
||||
fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname']))
|
||||
)
|
||||
);
|
||||
|
||||
$clients = $clientsConsole;
|
||||
$platforms = $project->getAttribute('platforms', []);
|
||||
|
||||
foreach ($platforms as $node) {
|
||||
if (
|
||||
isset($node['type']) &&
|
||||
($node['type'] === Origin::CLIENT_TYPE_WEB ||
|
||||
$node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) &&
|
||||
!empty($node['hostname'])
|
||||
) {
|
||||
$clients[] = $node['hostname'];
|
||||
}
|
||||
}
|
||||
|
||||
// Add database configured hostnames
|
||||
foreach ($platforms as $platform) {
|
||||
if (!empty($platform['hostname']) && in_array($platform['type'], [
|
||||
Origin::CLIENT_TYPE_WEB,
|
||||
Origin::CLIENT_TYPE_FLUTTER_WEB,
|
||||
])) {
|
||||
$hostnames[] = $platform['hostname'];
|
||||
}
|
||||
}
|
||||
return \array_unique($clients);
|
||||
}, ['request', 'console', 'project']);
|
||||
|
||||
return \array_unique($hostnames);
|
||||
}, ['platforms']);
|
||||
App::setResource('schemes', function (array $platforms, Document $project) {
|
||||
// Allow expo development scheme by default
|
||||
$schemes = ['exp'];
|
||||
|
||||
// Allow `appwrite-callback-${projectId}` scheme by default
|
||||
if (!empty($project) && $project->getId() !== 'console') {
|
||||
$schemes[] = 'appwrite-callback-' . $project->getId();
|
||||
}
|
||||
|
||||
return \array_unique($schemes);
|
||||
}, ['platforms', 'project']);
|
||||
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
|
||||
Generated
+12
-12
@@ -4490,16 +4490,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/queue",
|
||||
"version": "0.8.5",
|
||||
"version": "0.8.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/queue.git",
|
||||
"reference": "b8cd9553a01ec8a8089ce285f9a56e895d5b8ec8"
|
||||
"reference": "b713b997285c29d120bbcbe3d6e93762d850f87c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/b8cd9553a01ec8a8089ce285f9a56e895d5b8ec8",
|
||||
"reference": "b8cd9553a01ec8a8089ce285f9a56e895d5b8ec8",
|
||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/b713b997285c29d120bbcbe3d6e93762d850f87c",
|
||||
"reference": "b713b997285c29d120bbcbe3d6e93762d850f87c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4549,9 +4549,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/queue/issues",
|
||||
"source": "https://github.com/utopia-php/queue/tree/0.8.5"
|
||||
"source": "https://github.com/utopia-php/queue/tree/0.8.6"
|
||||
},
|
||||
"time": "2025-02-07T10:55:04+00:00"
|
||||
"time": "2025-02-10T03:35:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/registry",
|
||||
@@ -4607,16 +4607,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/storage",
|
||||
"version": "0.18.8",
|
||||
"version": "0.18.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/storage.git",
|
||||
"reference": "84737afa634e6a833fc4f8b0c967553234d3f215"
|
||||
"reference": "1cf455404e8700b3093fd73d74a38d41cdced90c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/84737afa634e6a833fc4f8b0c967553234d3f215",
|
||||
"reference": "84737afa634e6a833fc4f8b0c967553234d3f215",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/1cf455404e8700b3093fd73d74a38d41cdced90c",
|
||||
"reference": "1cf455404e8700b3093fd73d74a38d41cdced90c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4656,9 +4656,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/storage/issues",
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.18.8"
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.18.9"
|
||||
},
|
||||
"time": "2024-12-04T08:30:35+00:00"
|
||||
"time": "2025-02-11T13:10:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/swoole",
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ services:
|
||||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: appwrite/console:5.3.0-sites-rc.2
|
||||
image: appwrite/console:5.3.0-sites-rc.5
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
@@ -258,6 +258,7 @@ class Exception extends \Exception
|
||||
/** Variables */
|
||||
public const VARIABLE_NOT_FOUND = 'variable_not_found';
|
||||
public const VARIABLE_ALREADY_EXISTS = 'variable_already_exists';
|
||||
public const VARIABLE_CANNOT_UNSET_SECRET = 'variable_cannot_unset_secret';
|
||||
|
||||
/** Platform */
|
||||
public const PLATFORM_NOT_FOUND = 'platform_not_found';
|
||||
|
||||
@@ -266,7 +266,6 @@ class Mapper
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
case 'Utopia\Validator\Domain':
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
case 'Appwrite\Network\Validator\Redirect':
|
||||
case 'Appwrite\Event\Validator\Event':
|
||||
case 'Appwrite\Event\Validator\FunctionEvent':
|
||||
case 'Utopia\Validator\HexColor':
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
|
||||
use Utopia\Validator\Host;
|
||||
|
||||
/**
|
||||
* Redirect
|
||||
*
|
||||
* Validate that URL has an allowed host for redirect
|
||||
*
|
||||
* @package Utopia\Validator
|
||||
*/
|
||||
class Redirect extends Host
|
||||
{
|
||||
protected array $schemes = [];
|
||||
|
||||
/**
|
||||
* @param array<string> $hostnames Allow list of allowed hostnames
|
||||
* @param array<string> $schemes Allow list of allowed schemes
|
||||
*/
|
||||
public function __construct(array $hostnames = [], array $schemes = [])
|
||||
{
|
||||
$this->schemes = $schemes;
|
||||
parent::__construct($hostnames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
$schemes = '';
|
||||
if (!empty($this->schemes)) {
|
||||
$schemes = "URL scheme must be one of the following: " . implode(", ", array_map(function ($scheme) {
|
||||
return "`$scheme`://";
|
||||
}, $this->schemes));
|
||||
}
|
||||
|
||||
$hostnames = '';
|
||||
if (!empty($this->hostnames)) {
|
||||
$hostnames = "URL hostname must be one of the following: " . implode(", ", array_map(function ($hostname) {
|
||||
return "http://`$hostname`";
|
||||
}, $this->hostnames));
|
||||
}
|
||||
|
||||
return $schemes . ($schemes && $hostnames ? " or " : "") . $hostnames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid
|
||||
*
|
||||
* Validation will pass when $value is a valid URL and the host is allowed
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (empty($value) || !\is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Then check for scheme
|
||||
$scheme = '';
|
||||
if (preg_match('/^([a-z][a-z0-9+\.-]*):\/+/i', $value, $matches)) {
|
||||
$scheme = strtolower($matches[1]);
|
||||
}
|
||||
|
||||
// These are dangerous schemes, may expose XSS vulnerabilities
|
||||
if (in_array($scheme, ["javascript", "data", "blob", "file"])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When the scheme is in the allowed list, the URL is valid.
|
||||
if (!empty($this->schemes) && in_array($scheme, $this->schemes)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::isValid($value);
|
||||
}
|
||||
}
|
||||
@@ -56,14 +56,13 @@ class Create extends Base
|
||||
->param('siteId', '', new UID(), 'Site unique ID.', false)
|
||||
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
|
||||
->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true)
|
||||
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only sites can read them during build and runtime.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $key, string $value, bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform)
|
||||
public function action(string $siteId, string $key, string $value, bool $secret, Response $response, Database $dbForProject)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Base
|
||||
@@ -52,12 +53,13 @@ class Update extends Base
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
|
||||
->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only sites can read them during build and runtime.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject)
|
||||
public function action(string $siteId, string $variableId, string $key, ?string $value, ?bool $secret, Response $response, Database $dbForProject)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
|
||||
@@ -74,9 +76,14 @@ class Update extends Base
|
||||
throw new Exception(Exception::VARIABLE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($variable->getAttribute('secret') === true && $secret === false) {
|
||||
throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET);
|
||||
}
|
||||
|
||||
$variable
|
||||
->setAttribute('key', $key)
|
||||
->setAttribute('value', $value ?? $variable->getAttribute('value'))
|
||||
->setAttribute('secret', $secret ?? $variable->getAttribute('secret'))
|
||||
->setAttribute('search', implode(' ', [$variableId, $site->getId(), $key, 'site']));
|
||||
|
||||
try {
|
||||
|
||||
@@ -378,7 +378,6 @@ class OpenAPI3 extends Format
|
||||
$node['schema']['format'] = 'email';
|
||||
$node['schema']['x-example'] = 'email@example.com';
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Redirect':
|
||||
case 'Utopia\Validator\Host':
|
||||
case 'Utopia\Validator\URL':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
|
||||
@@ -396,7 +396,6 @@ class Swagger2 extends Format
|
||||
$node['format'] = 'email';
|
||||
$node['x-example'] = 'email@example.com';
|
||||
break;
|
||||
case 'Appwrite\Network\Validator\Redirect':
|
||||
case 'Utopia\Validator\Host':
|
||||
case 'Utopia\Validator\URL':
|
||||
$node['type'] = $validator->getType();
|
||||
|
||||
@@ -41,7 +41,7 @@ class Platform extends Model
|
||||
])
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, react-native-android, react-native-ios and unity.',
|
||||
'description' => 'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, ios, android, and unity.',
|
||||
'default' => '',
|
||||
'example' => 'web',
|
||||
])
|
||||
|
||||
@@ -82,6 +82,46 @@ trait FunctionsBase
|
||||
return $variable;
|
||||
}
|
||||
|
||||
protected function getVariable(string $functionId, string $variableId): mixed
|
||||
{
|
||||
$variable = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
return $variable;
|
||||
}
|
||||
|
||||
protected function updateVariable(string $functionId, string $variableId, mixed $params): mixed
|
||||
{
|
||||
$variable = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), $params);
|
||||
|
||||
return $variable;
|
||||
}
|
||||
|
||||
protected function listVariables(string $functionId, mixed $params = []): mixed
|
||||
{
|
||||
$variables = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), $params);
|
||||
|
||||
return $variables;
|
||||
}
|
||||
|
||||
protected function deleteVariable(string $functionId, string $variableId): mixed
|
||||
{
|
||||
$variable = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
return $variable;
|
||||
}
|
||||
|
||||
protected function getFunction(string $functionId): mixed
|
||||
{
|
||||
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
|
||||
|
||||
@@ -110,7 +110,8 @@ class FunctionsConsoleClientTest extends Scope
|
||||
$data['functionId'],
|
||||
[
|
||||
'key' => 'APP_TEST',
|
||||
'value' => 'TESTINGVALUE'
|
||||
'value' => 'TESTINGVALUE',
|
||||
'secret' => false
|
||||
]
|
||||
);
|
||||
|
||||
@@ -131,6 +132,7 @@ class FunctionsConsoleClientTest extends Scope
|
||||
$this->assertEquals(201, $variable['headers']['status-code']);
|
||||
$this->assertEquals('APP_TEST_1', $variable['body']['key']);
|
||||
$this->assertEmpty($variable['body']['value']);
|
||||
$this->assertTrue($variable['body']['secret']);
|
||||
|
||||
$secretVariableId = $variable['body']['$id'];
|
||||
|
||||
@@ -142,7 +144,8 @@ class FunctionsConsoleClientTest extends Scope
|
||||
$data['functionId'],
|
||||
[
|
||||
'key' => 'APP_TEST',
|
||||
'value' => 'ANOTHERTESTINGVALUE'
|
||||
'value' => 'ANOTHERTESTINGVALUE',
|
||||
'secret' => false
|
||||
]
|
||||
);
|
||||
|
||||
@@ -320,6 +323,41 @@ class FunctionsConsoleClientTest extends Scope
|
||||
$this->assertEquals("APP_TEST_UPDATE_2", $variable['body']['key']);
|
||||
$this->assertEquals("TESTINGVALUEUPDATED", $variable['body']['value']);
|
||||
|
||||
// convert non-secret variable to secret
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'key' => 'APP_TEST_UPDATE_2',
|
||||
'secret' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals("APP_TEST_UPDATE_2", $response['body']['key']);
|
||||
$this->assertEmpty($response['body']['value']);
|
||||
$this->assertTrue($response['body']['secret']);
|
||||
|
||||
$variable = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $variable['headers']['status-code']);
|
||||
$this->assertEquals("APP_TEST_UPDATE_2", $variable['body']['key']);
|
||||
$this->assertEmpty($variable['body']['value']);
|
||||
$this->assertTrue($variable['body']['secret']);
|
||||
|
||||
// convert secret variable to non-secret
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'key' => 'APP_TEST_UPDATE',
|
||||
'secret' => false
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
@@ -410,4 +448,53 @@ class FunctionsConsoleClientTest extends Scope
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function testVariableE2E(): void
|
||||
{
|
||||
$function = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'runtime' => 'node-18.0',
|
||||
'name' => 'Variable E2E Test',
|
||||
'entrypoint' => 'index.js',
|
||||
'logging' => false,
|
||||
'execute' => ['any']
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$this->assertFalse($function['body']['logging']);
|
||||
$this->assertNotEmpty($function['body']['$id']);
|
||||
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
// create variable
|
||||
$variable = $this->createVariable($functionId, [
|
||||
'key' => 'CUSTOM_VARIABLE',
|
||||
'value' => 'a_secret_value',
|
||||
'secret' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $variable['headers']['status-code']);
|
||||
$this->assertNotEmpty($variable['body']['$id']);
|
||||
$this->assertEquals('CUSTOM_VARIABLE', $variable['body']['key']);
|
||||
$this->assertEquals('', $variable['body']['value']);
|
||||
$this->assertEquals(true, $variable['body']['secret']);
|
||||
|
||||
$deploymentId = $this->setupDeployment($functionId, [
|
||||
'entrypoint' => 'index.js',
|
||||
'code' => $this->packageFunction('node'),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$execution = $this->createExecution($functionId);
|
||||
|
||||
$this->assertEquals(201, $execution['headers']['status-code']);
|
||||
$this->assertEmpty($execution['body']['logs']);
|
||||
$this->assertEmpty($execution['body']['errors']);
|
||||
$body = json_decode($execution['body']['responseBody']);
|
||||
$this->assertEquals('a_secret_value', $body->CUSTOM_VARIABLE);
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
'timeout' => 10,
|
||||
]);
|
||||
|
||||
$functionId = $functionId = $function['body']['$id'] ?? '';
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$dateValidator = new DatetimeValidator();
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
@@ -356,7 +356,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$this->assertNotEmpty($function['body']['$id']);
|
||||
|
||||
$functionId = $functionId = $function['body']['$id'] ?? '';
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$deployments = $this->listDeployments($functionId);
|
||||
|
||||
@@ -1898,7 +1898,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertFalse($function['body']['logging']);
|
||||
$this->assertNotEmpty($function['body']['$id']);
|
||||
|
||||
$functionId = $functionId = $function['body']['$id'] ?? '';
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('node'),
|
||||
|
||||
@@ -3923,10 +3923,14 @@ class ProjectsConsoleClientTest extends Scope
|
||||
'x-appwrite-mode' => 'admin',
|
||||
], $this->getHeaders()), [
|
||||
'key' => 'APP_TEST',
|
||||
'value' => 'TESTINGVALUE'
|
||||
'value' => 'TESTINGVALUE',
|
||||
'secret' => false
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $variable['headers']['status-code']);
|
||||
$this->assertEquals('APP_TEST', $variable['body']['key']);
|
||||
$this->assertEquals('TESTINGVALUE', $variable['body']['value']);
|
||||
$this->assertFalse($variable['body']['secret']);
|
||||
$variableId = $variable['body']['$id'];
|
||||
|
||||
// test for secret variable
|
||||
@@ -4047,6 +4051,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals("APP_TEST_1", $response['body']['key']);
|
||||
$this->assertEmpty($response['body']['value']);
|
||||
$this->assertTrue($response['body']['secret']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
@@ -4118,6 +4123,17 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals("APP_TEST_UPDATE_1", $variable['body']['key']);
|
||||
$this->assertEmpty($variable['body']['value']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['secretVariableId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $data['projectId'],
|
||||
'x-appwrite-mode' => 'admin',
|
||||
], $this->getHeaders()), [
|
||||
'key' => 'APP_TEST_UPDATE_1',
|
||||
'secret' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/project/variables', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $data['projectId'],
|
||||
|
||||
@@ -6,6 +6,7 @@ use Appwrite\Tests\Async;
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
trait SitesBase
|
||||
{
|
||||
@@ -103,6 +104,46 @@ trait SitesBase
|
||||
return $variable;
|
||||
}
|
||||
|
||||
protected function getVariable(string $siteId, string $variableId): mixed
|
||||
{
|
||||
$variable = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/variables/' . $variableId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
return $variable;
|
||||
}
|
||||
|
||||
protected function listVariables(string $siteId, mixed $params = []): mixed
|
||||
{
|
||||
$variables = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/variables', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), $params);
|
||||
|
||||
return $variables;
|
||||
}
|
||||
|
||||
protected function updateVariable(string $siteId, string $variableId, mixed $params): mixed
|
||||
{
|
||||
$variable = $this->client->call(Client::METHOD_PUT, '/sites/' . $siteId . '/variables/' . $variableId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), $params);
|
||||
|
||||
return $variable;
|
||||
}
|
||||
|
||||
protected function deleteVariable(string $siteId, string $variableId): mixed
|
||||
{
|
||||
$variable = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId . '/variables/' . $variableId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
return $variable;
|
||||
}
|
||||
|
||||
protected function getSite(string $siteId): mixed
|
||||
{
|
||||
$site = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId, array_merge([
|
||||
@@ -215,4 +256,26 @@ trait SitesBase
|
||||
|
||||
return $site;
|
||||
}
|
||||
|
||||
protected function getSiteDomain(string $siteId): string
|
||||
{
|
||||
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('resourceId', [$siteId])->toString(),
|
||||
Query::equal('resourceType', ['site'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertGreaterThanOrEqual(1, $rules['body']['total']);
|
||||
$this->assertGreaterThanOrEqual(1, \count($rules['body']['rules']));
|
||||
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
|
||||
|
||||
$domain = $rules['body']['rules'][0]['domain'];
|
||||
|
||||
return $domain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,193 @@ class SitesCustomServerTest extends Scope
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testVariables(): void
|
||||
{
|
||||
$site = $this->createSite([
|
||||
'buildRuntime' => 'ssr-22',
|
||||
'fallbackFile' => null,
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
'siteId' => ID::unique()
|
||||
]);
|
||||
|
||||
$siteId = $site['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEquals(201, $site['headers']['status-code']);
|
||||
$this->assertNotEmpty($site['body']['$id']);
|
||||
$this->assertEquals('Test Site', $site['body']['name']);
|
||||
|
||||
$variable = $this->createVariable($siteId, [
|
||||
'key' => 'siteKey1',
|
||||
'value' => 'siteValue1',
|
||||
'secret' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $variable['headers']['status-code']);
|
||||
$this->assertNotEmpty($variable['body']['$id']);
|
||||
$this->assertEquals('siteKey1', $variable['body']['key']);
|
||||
$this->assertEquals('siteValue1', $variable['body']['value']);
|
||||
$this->assertEquals(false, $variable['body']['secret']);
|
||||
|
||||
$variable2 = $this->createVariable($siteId, [
|
||||
'key' => 'siteKey2',
|
||||
'value' => 'siteValue2',
|
||||
'secret' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $variable2['headers']['status-code']);
|
||||
$this->assertNotEmpty($variable2['body']['$id']);
|
||||
$this->assertEquals('siteKey2', $variable2['body']['key']);
|
||||
$this->assertEquals('siteValue2', $variable2['body']['value']);
|
||||
$this->assertEquals(false, $variable2['body']['secret']);
|
||||
|
||||
$secretVariable = $this->createVariable($siteId, [
|
||||
'key' => 'siteKey3',
|
||||
'value' => 'siteValue3',
|
||||
'secret' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $secretVariable['headers']['status-code']);
|
||||
$this->assertNotEmpty($secretVariable['body']['$id']);
|
||||
$this->assertEquals('siteKey3', $secretVariable['body']['key']);
|
||||
$this->assertEquals('', $secretVariable['body']['value']);
|
||||
$this->assertEquals(true, $secretVariable['body']['secret']);
|
||||
|
||||
$variable = $this->getVariable($siteId, $variable['body']['$id']);
|
||||
|
||||
$this->assertEquals(200, $variable['headers']['status-code']);
|
||||
$this->assertNotEmpty($variable['body']['$id']);
|
||||
$this->assertEquals('siteKey1', $variable['body']['key']);
|
||||
$this->assertEquals('siteValue1', $variable['body']['value']);
|
||||
$this->assertEquals(false, $variable['body']['secret']);
|
||||
|
||||
$secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']);
|
||||
|
||||
$this->assertEquals(200, $secretVariable['headers']['status-code']);
|
||||
$this->assertNotEmpty($secretVariable['body']['$id']);
|
||||
$this->assertEquals('siteKey3', $secretVariable['body']['key']);
|
||||
$this->assertEquals('', $secretVariable['body']['value']);
|
||||
$this->assertEquals(true, $secretVariable['body']['secret']);
|
||||
|
||||
$variable = $this->updateVariable($siteId, $variable['body']['$id'], [
|
||||
'key' => 'siteKey1Updated',
|
||||
'value' => 'siteValue1Updated',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $variable['headers']['status-code']);
|
||||
$this->assertNotEmpty($variable['body']['$id']);
|
||||
$this->assertEquals('siteKey1Updated', $variable['body']['key']);
|
||||
$this->assertEquals('siteValue1Updated', $variable['body']['value']);
|
||||
$this->assertEquals(false, $variable['body']['secret']);
|
||||
|
||||
$variable = $this->updateVariable($siteId, $variable['body']['$id'], [
|
||||
'key' => 'siteKey1Updated',
|
||||
'secret' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $variable['headers']['status-code']);
|
||||
$this->assertNotEmpty($variable['body']['$id']);
|
||||
$this->assertEquals('siteKey1Updated', $variable['body']['key']);
|
||||
$this->assertEquals('', $variable['body']['value']);
|
||||
$this->assertEquals(true, $variable['body']['secret']);
|
||||
|
||||
$secretVariable = $this->updateVariable($siteId, $secretVariable['body']['$id'], [
|
||||
'key' => 'siteKey3',
|
||||
'value' => 'siteValue3Updated',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $secretVariable['headers']['status-code']);
|
||||
$this->assertNotEmpty($secretVariable['body']['$id']);
|
||||
$this->assertEquals('siteKey3', $secretVariable['body']['key']);
|
||||
$this->assertEquals('', $secretVariable['body']['value']);
|
||||
$this->assertEquals(true, $secretVariable['body']['secret']);
|
||||
|
||||
$response = $this->updateVariable($siteId, $secretVariable['body']['$id'], [
|
||||
'key' => 'siteKey3',
|
||||
'secret' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']);
|
||||
|
||||
$this->assertEquals(200, $secretVariable['headers']['status-code']);
|
||||
$this->assertNotEmpty($secretVariable['body']['$id']);
|
||||
$this->assertEquals('siteKey3', $secretVariable['body']['key']);
|
||||
$this->assertEquals('', $secretVariable['body']['value']);
|
||||
$this->assertEquals(true, $secretVariable['body']['secret']);
|
||||
|
||||
$variables = $this->listVariables($siteId);
|
||||
|
||||
$this->assertEquals(200, $variables['headers']['status-code']);
|
||||
$this->assertCount(3, $variables['body']['variables']);
|
||||
|
||||
$response = $this->deleteVariable($siteId, $variable['body']['$id']);
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
$response = $this->deleteVariable($siteId, $variable2['body']['$id']);
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
$response = $this->deleteVariable($siteId, $secretVariable['body']['$id']);
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
$variables = $this->listVariables($siteId);
|
||||
|
||||
$this->assertEquals(200, $variables['headers']['status-code']);
|
||||
$this->assertCount(0, $variables['body']['variables']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testVariablesE2E(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Astro site',
|
||||
'framework' => 'astro',
|
||||
'adapter' => 'ssr',
|
||||
'buildRuntime' => 'ssr-22',
|
||||
'outputDirectory' => './dist',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'fallbackFile' => '',
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$secretVariable = $this->createVariable($siteId, [
|
||||
'key' => 'name',
|
||||
'value' => 'Appwrite',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $secretVariable['headers']['status-code']);
|
||||
$this->assertNotEmpty($secretVariable['body']['$id']);
|
||||
$this->assertEquals('name', $secretVariable['body']['key']);
|
||||
$this->assertEquals('', $secretVariable['body']['value']);
|
||||
$this->assertEquals(true, $secretVariable['body']['secret']);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('astro'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$domain = $this->getSiteDomain($siteId);
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Env variable is Appwrite", $response['body']);
|
||||
$this->assertStringNotContainsString("Variable not found", $response['body']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testListSites(): void
|
||||
{
|
||||
/**
|
||||
@@ -940,6 +1127,155 @@ class SitesCustomServerTest extends Scope
|
||||
$this->assertArrayHasKey('adapters', $framework);
|
||||
}
|
||||
|
||||
public function testSiteTemplate(): void
|
||||
{
|
||||
$template = $this->getTemplate('astro-starter');
|
||||
$this->assertEquals(200, $template['headers']['status-code']);
|
||||
|
||||
$template = $template['body'];
|
||||
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Template site',
|
||||
'framework' => $template['frameworks'][0]['key'],
|
||||
'adapter' => $template['frameworks'][0]['adapter'],
|
||||
'buildRuntime' => $template['frameworks'][0]['buildRuntime'],
|
||||
'outputDirectory' => $template['frameworks'][0]['outputDirectory'],
|
||||
'buildCommand' => $template['frameworks'][0]['buildCommand'],
|
||||
'installCommand' => $template['frameworks'][0]['installCommand'],
|
||||
'fallbackFile' => $template['frameworks'][0]['fallbackFile'],
|
||||
'templateRepository' => $template['providerRepositoryId'],
|
||||
'templateOwner' => $template['providerOwner'],
|
||||
'templateRootDirectory' => $template['frameworks'][0]['providerRootDirectory'],
|
||||
'templateVersion' => $template['providerVersion'],
|
||||
]);
|
||||
|
||||
$this->assertEventually(function () use ($siteId) {
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertNotEmpty($site['body']['deploymentId']);
|
||||
}, 50000, 500);
|
||||
|
||||
$domain = $this->getSiteDomain($siteId);
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Astro Blog", $response['body']);
|
||||
$this->assertStringContainsString("Hello, Astronaut!", $response['body']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/about', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Astro Blog", $response['body']);
|
||||
$this->assertStringContainsString("About Me", $response['body']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testSiteDomainReclaiming(): void
|
||||
{
|
||||
$subdomain = 'startup' . \uniqid();
|
||||
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Startup site',
|
||||
'framework' => 'other',
|
||||
'adapter' => 'static',
|
||||
'buildRuntime' => 'static-1',
|
||||
'outputDirectory' => './',
|
||||
'buildCommand' => '',
|
||||
'installCommand' => '',
|
||||
'fallbackFile' => '',
|
||||
'subdomain' => $subdomain
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$domain = $this->getSiteDomain($siteId);
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringNotContainsString("This domain is not connected to any Appwrite resource yet", $response['body']);
|
||||
|
||||
$site = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Startup 2 site',
|
||||
'framework' => 'other',
|
||||
'adapter' => 'static',
|
||||
'buildRuntime' => 'static-1',
|
||||
'outputDirectory' => './',
|
||||
'buildCommand' => '',
|
||||
'installCommand' => '',
|
||||
'fallbackFile' => '',
|
||||
'subdomain' => $subdomain
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
$this->assertStringContainsString("Subdomain already exists.", $site['body']['message']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
|
||||
$this->assertEventually(function () use ($domain) {
|
||||
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('domain', [$domain])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(0, $rules['body']['total']);
|
||||
}, 50000, 500);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("This domain is not connected to any Appwrite resource yet", $response['body']);
|
||||
|
||||
$site = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Startup 2 site',
|
||||
'framework' => 'other',
|
||||
'adapter' => 'static',
|
||||
'buildRuntime' => 'static-1',
|
||||
'outputDirectory' => './',
|
||||
'buildCommand' => '',
|
||||
'installCommand' => '',
|
||||
'fallbackFile' => '',
|
||||
'subdomain' => $subdomain
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $site['headers']['status-code']);
|
||||
|
||||
$this->cleanupSite($site['body']['$id']);
|
||||
}
|
||||
|
||||
public function testConsoleAvailabilityEndpoint(): void
|
||||
{
|
||||
$site = $this->createSite([
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import node from '@astrojs/node';
|
||||
import 'dotenv/config';
|
||||
|
||||
export default defineConfig({
|
||||
output: 'server',
|
||||
adapter: node({
|
||||
mode: 'standalone'
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "my-astro-app",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.0.2",
|
||||
"astro": "^5.2.5",
|
||||
"dotenv": "^16.4.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
const value = import.meta.env.name || 'Variable not found';
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Astro SSR</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Env variable is {value}</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Network\Validators;
|
||||
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class RedirectTest extends TestCase
|
||||
{
|
||||
public function redirectsProvider(): array
|
||||
{
|
||||
return [
|
||||
"localhost" => [["localhost"], [], "http://localhost", true],
|
||||
"localhost-no-scheme" => [["localhost"], [], "localhost", false],
|
||||
"expo scheme" => [[], ["exp"], "exp://192.168.0.1", true],
|
||||
"custom scheme" => [[], ["myapp"], "myapp://", true],
|
||||
"custom scheme triple slash" => [[], ["myapp"], "myapp:///", true],
|
||||
"scheme with special chars" => [[], ["my-app+custom.123"], "my-app+custom.123://", true],
|
||||
"url https" => [["example.com"], [], "https://example.com", true],
|
||||
"url http" => [["example.com"], [], "http://example.com", true],
|
||||
"malformed scheme" => [[], [], "http:/example.com", false],
|
||||
"invalid url" => [[], [], "example.com", false],
|
||||
"invalid host" => [["notexample.com"], [], "https://example.com", false],
|
||||
"javascript scheme" => [[], [], "javascript://alert(1)", false],
|
||||
"javascript scheme with different case" => [[], [], "JaVaScRiPt://alert(1)", false],
|
||||
"empty string" => [[], [], "", false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider redirectsProvider
|
||||
*/
|
||||
public function testIsValid(
|
||||
array $hostnames,
|
||||
array $schemes,
|
||||
string $value,
|
||||
bool $expected
|
||||
): void {
|
||||
$validator = new Redirect($hostnames, $schemes);
|
||||
|
||||
$this->assertEquals($expected, $validator->isValid($value));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user