Merge branch '0.16.x' into fix-unit-tests

This commit is contained in:
Eldad A. Fux
2022-08-13 12:28:33 +03:00
committed by GitHub
67 changed files with 9059 additions and 7462 deletions
+1
View File
@@ -22,6 +22,7 @@ ports:
vscode:
extensions:
- ms-azuretools.vscode-docker
- zobo.php-intellisense
github:
# https://www.gitpod.io/docs/prebuilds#github-specific-configuration
+30
View File
@@ -31,6 +31,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false,
],
'authentik' => [
'name' => 'Authentik',
'developers' => 'https://goauthentik.io/docs/',
'icon' => 'icon-authentik',
'enabled' => true,
'sandbox' => false,
'form' => 'authentik.phtml',
'beta' => false,
'mock' => false,
],
'autodesk' => [
'name' => 'Autodesk',
'developers' => 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/',
@@ -91,6 +101,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false,
],
'disqus' => [
'name' => 'Disqus',
'developers' => 'https://disqus.com/api/docs/auth/',
'icon' => 'icon-disqus',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
],
'dropbox' => [
'name' => 'Dropbox',
'developers' => 'https://www.dropbox.com/developers/documentation',
@@ -201,6 +221,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false
],
'podio' => [
'name' => 'Podio',
'developers' => 'https://developers.podio.com/doc/oauth-authorization',
'icon' => 'icon-podio',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => false,
],
'salesforce' => [
'name' => 'Salesforce',
'developers' => 'https://developer.salesforce.com/docs/',
+3 -3
View File
@@ -1443,7 +1443,7 @@ App::patch('/v1/account/name')
$user = $dbForProject->updateDocument('users', $user->getId(), $user
->setAttribute('name', $name)
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email')])));
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email', ''), $user->getAttribute('phone', '')])));
$audits
->setResource('user/' . $user->getId())
@@ -1539,7 +1539,7 @@ App::patch('/v1/account/email')
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', ''))
->setAttribute('email', $email)
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $email, $user->getAttribute('phone', '')]));
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
@@ -1592,7 +1592,7 @@ App::patch('/v1/account/phone')
$user
->setAttribute('phone', $phone)
->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $phone]));
try {
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
+38 -30
View File
@@ -882,11 +882,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
->label('sdk.namespace', 'databases')
->label('sdk.method', 'createStringAttribute')
->label('sdk.description', '/docs/references/databases/create-string-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_STRING)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.')
->param('required', null, new Boolean(), 'Is attribute required?')
@@ -915,6 +915,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
'array' => $array,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
});
@@ -928,11 +929,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createEmailAttribute')
->label('sdk.description', '/docs/references/databases/create-email-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_EMAIL)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Email(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@@ -955,6 +956,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
'format' => APP_DATABASE_ATTRIBUTE_EMAIL,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL);
});
@@ -968,11 +970,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createEnumAttribute')
->label('sdk.description', '/docs/references/databases/create-attribute-enum.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_ENUM)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('elements', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.')
->param('required', null, new Boolean(), 'Is attribute required?')
@@ -1011,6 +1013,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
'formatOptions' => ['elements' => $elements],
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM);
});
@@ -1024,11 +1027,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createIpAttribute')
->label('sdk.description', '/docs/references/databases/create-ip-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_IP)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new IP(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@@ -1051,6 +1054,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
'format' => APP_DATABASE_ATTRIBUTE_IP,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP);
});
@@ -1064,11 +1068,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createUrlAttribute')
->label('sdk.description', '/docs/references/databases/create-url-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_URL)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new URL(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@@ -1091,6 +1095,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
'format' => APP_DATABASE_ATTRIBUTE_URL,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL);
});
@@ -1104,11 +1109,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createIntegerAttribute')
->label('sdk.description', '/docs/references/databases/create-integer-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_INTEGER)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new Integer(), 'Minimum value to enforce on new documents', true)
@@ -1160,6 +1165,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
$attribute->setAttribute('max', \intval($formatOptions['max']));
}
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER);
});
@@ -1173,11 +1179,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createFloatAttribute')
->label('sdk.description', '/docs/references/databases/create-float-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_FLOAT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents', true)
@@ -1232,6 +1238,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
$attribute->setAttribute('max', \floatval($formatOptions['max']));
}
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT);
});
@@ -1245,11 +1252,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.method', 'createBooleanAttribute')
->label('sdk.description', '/docs/references/databases/create-boolean-attribute.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_BOOLEAN)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Boolean(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@@ -1271,6 +1278,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea
'array' => $array,
]), $response, $dbForProject, $database, $audits, $events, $usage);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN);
});
@@ -1287,7 +1295,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->inject('response')
->inject('dbForProject')
->inject('usage')
@@ -1337,7 +1345,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
Response::MODEL_ATTRIBUTE_IP,
Response::MODEL_ATTRIBUTE_STRING,])// needs to be last, since its condition would dominate any other string attribute
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->inject('response')
->inject('dbForProject')
@@ -1400,7 +1408,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->inject('response')
->inject('dbForProject')
@@ -1491,11 +1499,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
->label('sdk.namespace', 'databases')
->label('sdk.method', 'createIndex')
->label('sdk.description', '/docs/references/databases/create-index.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INDEX)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', null, new Key(), 'Index Key.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.')
->param('attributes', null, new ArrayList(new Key(true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.')
@@ -1633,7 +1641,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
->setPayload($index->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($index, Response::MODEL_INDEX);
});
@@ -1650,7 +1658,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INDEX_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->inject('response')
->inject('dbForProject')
->inject('usage')
@@ -1692,7 +1700,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INDEX)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', null, new Key(), 'Index Key.')
->inject('response')
->inject('dbForProject')
@@ -1743,7 +1751,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Index Key.')
->inject('response')
->inject('dbForProject')
@@ -1820,7 +1828,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('documentId', '', new CustomId(), 'Document 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('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection). Make sure to define attributes before creating documents.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('read', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
@@ -1941,8 +1949,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/database#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. By default will return maximum 25 results. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the document used as the starting point for the query, excluding the document itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
@@ -2055,7 +2063,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', null, new UID(), 'Document ID.')
->inject('response')
->inject('dbForProject')
@@ -2358,7 +2366,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', null, new UID(), 'Document ID.')
->inject('response')
->inject('dbForProject')
+3 -3
View File
@@ -453,7 +453,7 @@ App::post('/v1/functions/:functionId/deployments')
->label('sdk.description', '/docs/references/functions/create-deployment.md')
->label('sdk.packaging', true)
->label('sdk.request.type', 'multipart/form-data')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
->param('functionId', '', new UID(), 'Function ID.')
@@ -622,7 +622,7 @@ App::post('/v1/functions/:functionId/deployments')
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
});
@@ -906,7 +906,7 @@ App::post('/v1/functions/:functionId/executions')
$event->trigger();
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
return $response->dynamic($execution, Response::MODEL_EXECUTION);
}
+8 -6
View File
@@ -31,12 +31,14 @@ use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
App::init(function (Document $project) {
if ($project->getId() !== 'console') {
throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN);
}
}, ['project'], 'projects');
App::init()
->groups(['projects'])
->inject('project')
->action(function (Document $project) {
if ($project->getId() !== 'console') {
throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN);
}
});
App::post('/v1/projects')
->desc('Create Project')
+2 -2
View File
@@ -427,8 +427,8 @@ App::post('/v1/teams/:teamId/memberships')
$response->dynamic(
$membership
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email')),
->setAttribute('userName', $invitee->getAttribute('name'))
->setAttribute('userEmail', $invitee->getAttribute('email')),
Response::MODEL_MEMBERSHIP
);
});
+3 -2
View File
@@ -506,7 +506,7 @@ App::patch('/v1/users/:userId/name')
$user
->setAttribute('name', $name)
->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email'), $name]));
->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email', ''), $name, $user->getAttribute('phone', '')]));
;
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
@@ -596,7 +596,7 @@ App::patch('/v1/users/:userId/email')
$user
->setAttribute('email', $email)
->setAttribute('emailVerification', false)
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name')]))
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name', ''), $user->getAttribute('phone', '')]))
;
try {
@@ -646,6 +646,7 @@ App::patch('/v1/users/:userId/phone')
$user
->setAttribute('phone', $number)
->setAttribute('phoneVerification', false)
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $number]));
;
try {
+464 -443
View File
@@ -35,485 +35,506 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
App::init(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients) {
App::init()
->inject('utopia')
->inject('request')
->inject('response')
->inject('console')
->inject('project')
->inject('dbForConsole')
->inject('user')
->inject('locale')
->inject('clients')
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients) {
/*
* Request format
*/
$route = $utopia->match($request);
Request::setRoute($route);
/*
* Request format
*/
$route = $utopia->match($request);
Request::setRoute($route);
$requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($requestFormat) {
switch ($requestFormat) {
case version_compare($requestFormat, '0.12.0', '<'):
Request::setFilter(new RequestV12());
break;
case version_compare($requestFormat, '0.13.0', '<'):
Request::setFilter(new RequestV13());
break;
case version_compare($requestFormat, '0.14.0', '<'):
Request::setFilter(new RequestV14());
break;
default:
Request::setFilter(null);
}
} else {
Request::setFilter(null);
}
$domain = $request->getHostname();
$domains = Config::getParam('domains', []);
if (!array_key_exists($domain, $domains)) {
$domain = new Domain(!empty($domain) ? $domain : '');
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
$domains[$domain->get()] = false;
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
Console::warning('Skipping SSL certificates generation on ACME challenge.');
} else {
Authorization::disable();
$envDomain = App::getEnv('_APP_DOMAIN', '');
$mainDomain = null;
if (!empty($envDomain) && $envDomain !== 'localhost') {
$mainDomain = $envDomain;
} else {
$domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
$requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($requestFormat) {
switch ($requestFormat) {
case version_compare($requestFormat, '0.12.0', '<'):
Request::setFilter(new RequestV12());
break;
case version_compare($requestFormat, '0.13.0', '<'):
Request::setFilter(new RequestV13());
break;
case version_compare($requestFormat, '0.14.0', '<'):
Request::setFilter(new RequestV14());
break;
default:
Request::setFilter(null);
}
} else {
Request::setFilter(null);
}
if ($mainDomain !== $domain->get()) {
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
$domain = $request->getHostname();
$domains = Config::getParam('domains', []);
if (!array_key_exists($domain, $domains)) {
$domain = new Domain(!empty($domain) ? $domain : '');
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
$domains[$domain->get()] = false;
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
Console::warning('Skipping SSL certificates generation on ACME challenge.');
} else {
$domainDocument = $dbForConsole->findOne('domains', [
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
]);
Authorization::disable();
if (!$domainDocument) {
$domainDocument = new Document([
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
'verification' => false,
'certificateId' => null,
$envDomain = App::getEnv('_APP_DOMAIN', '');
$mainDomain = null;
if (!empty($envDomain) && $envDomain !== 'localhost') {
$mainDomain = $envDomain;
} else {
$domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
}
if ($mainDomain !== $domain->get()) {
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
} else {
$domainDocument = $dbForConsole->findOne('domains', [
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
]);
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
if (!$domainDocument) {
$domainDocument = new Document([
'domain' => $domain->get(),
'tld' => $domain->getSuffix(),
'registerable' => $domain->getRegisterable(),
'verification' => false,
'certificateId' => null,
]);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
(new Certificate())
->setDomain($domainDocument)
->trigger();
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
(new Certificate())
->setDomain($domainDocument)
->trigger();
}
}
$domains[$domain->get()] = true;
Authorization::reset(); // ensure authorization is re-enabled
}
$domains[$domain->get()] = true;
Authorization::reset(); // ensure authorization is re-enabled
}
Config::setParam('domains', $domains);
}
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
if (\in_array($localeParam, Config::getParam('locale-codes'))) {
$locale->setDefault($localeParam);
}
if ($project->isEmpty()) {
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
}
if (!empty($route->getLabel('sdk.auth', [])) && $project->isEmpty() && ($route->getLabel('scope', '') !== 'public')) {
throw new AppwriteException('Missing or unknown project ID', 400, AppwriteException::PROJECT_UNKNOWN);
}
$referrer = $request->getReferer();
$origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST);
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
$refDomainOrigin = 'localhost';
$validator = new Hostname($clients);
if ($validator->isValid($origin)) {
$refDomainOrigin = $origin;
}
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
$refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
? $refDomain
: (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
$selfDomain = new Domain($request->getHostname());
$endDomain = new Domain((string)$origin);
Config::setParam(
'domainVerification',
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
$endDomain->getRegisterable() !== ''
);
Config::setParam('cookieDomain', (
$request->getHostname() === 'localhost' ||
$request->getHostname() === 'localhost:' . $request->getPort() ||
(\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false)
)
? null
: '.' . $request->getHostname());
/*
* Response format
*/
$responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($responseFormat) {
switch ($responseFormat) {
case version_compare($responseFormat, '0.11.2', '<='):
Response::setFilter(new ResponseV11());
break;
case version_compare($responseFormat, '0.12.4', '<='):
Response::setFilter(new ResponseV12());
break;
case version_compare($responseFormat, '0.13.4', '<='):
Response::setFilter(new ResponseV13());
break;
case version_compare($responseFormat, '0.14.0', '<='):
Response::setFilter(new ResponseV14());
break;
default:
Response::setFilter(null);
}
} else {
Response::setFilter(null);
}
/*
* Security Headers
*
* As recommended at:
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException('Method unsupported over HTTP.', 500, AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED);
}
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
Config::setParam('domains', $domains);
}
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
}
$response
->addHeader('Server', 'Appwrite')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $refDomain)
->addHeader('Access-Control-Allow-Credentials', 'true')
;
/*
* Validate Client Domain - Check to avoid CSRF attack
* Adding Appwrite API domains to allow XDOMAIN communication
* Skip this check for non-web platforms which are not required to send an origin header
*/
$origin = $request->getOrigin($request->getReferer(''));
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
if (
!$originValidator->isValid($origin)
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
&& $route->getLabel('origin', false) !== '*'
&& empty($request->getHeader('x-appwrite-key', ''))
) {
throw new AppwriteException($originValidator->getDescription(), 403, AppwriteException::GENERAL_UNKNOWN_ORIGIN);
}
/*
* ACL Check
*/
$role = ($user->isEmpty()) ? Auth::USER_ROLE_GUEST : Auth::USER_ROLE_MEMBER;
// Add user roles
$memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships');
if ($memberships) {
foreach ($memberships->getAttribute('roles', []) as $memberRole) {
switch ($memberRole) {
case 'owner':
$role = Auth::USER_ROLE_OWNER;
break;
case 'admin':
$role = Auth::USER_ROLE_ADMIN;
break;
case 'developer':
$role = Auth::USER_ROLE_DEVELOPER;
break;
}
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
if (\in_array($localeParam, Config::getParam('locale-codes'))) {
$locale->setDefault($localeParam);
}
}
$roles = Config::getParam('roles', []);
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
$authKey = $request->getHeader('x-appwrite-key', '');
if (!empty($authKey)) { // API Key authentication
// Check if given key match project API keys
$key = $project->find('secret', $authKey, 'keys');
/*
* Try app auth when we have project key and no user
* Mock user to app and grant API key scopes in addition to default app scopes
*/
if ($key && $user->isEmpty()) {
$user = new Document([
'$id' => '',
'status' => true,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);
$role = Auth::USER_ROLE_APP;
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
$expire = $key->getAttribute('expire', 0);
if (!empty($expire) && $expire < \time()) {
throw new AppwriteException('Project key expired', 401, AppwriteException:: PROJECT_KEY_EXPIRED);
}
Authorization::setRole('role:' . Auth::USER_ROLE_APP);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
}
}
Authorization::setRole('role:' . $role);
foreach (Auth::getRoles($user) as $authRole) {
Authorization::setRole($authRole);
}
$service = $route->getLabel('sdk.namespace', '');
if (!empty($service)) {
if (
array_key_exists($service, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$service]
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
}
}
if (!\in_array($scope, $scopes)) {
if ($project->isEmpty()) { // Check if permission is denied because project is missing
if ($project->isEmpty()) {
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
}
throw new AppwriteException($user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')', 401, AppwriteException::GENERAL_UNAUTHORIZED_SCOPE);
}
if (false === $user->getAttribute('status')) { // Account is blocked
throw new AppwriteException('Invalid credentials. User is blocked', 401, AppwriteException::USER_BLOCKED);
}
if ($user->getAttribute('reset')) {
throw new AppwriteException('Password reset is required', 412, AppwriteException::USER_PASSWORD_RESET_REQUIRED);
}
}, ['utopia', 'request', 'response', 'console', 'project', 'dbForConsole', 'user', 'locale', 'clients']);
App::options(function (Request $request, Response $response) {
$origin = $request->getOrigin();
$response
->addHeader('Server', 'Appwrite')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
->noContent();
}, ['request', 'response']);
App::error(function (Throwable $error, App $utopia, Request $request, Response $response, View $layout, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->match($request);
/** Delegate PDO exceptions to the global handler so the database connection can be returned to the pool */
if ($error instanceof PDOException) {
throw $error;
}
if ($logger) {
if ($error->getCode() >= 500 || $error->getCode() === 0) {
try {
/** @var Utopia\Database\Document $user */
$user = $utopia->getResource('user');
} catch (\Throwable $th) {
// All good, user is optional information for logger
}
$log = new Utopia\Logger\Log();
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId());
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::$roles);
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: ' . $responseCode);
}
}
$code = $error->getCode();
$message = $error->getMessage();
$file = $error->getFile();
$line = $error->getLine();
$trace = $error->getTrace();
if (php_sapi_name() === 'cli') {
Console::error('[Error] Timestamp: ' . date('c', time()));
if ($route) {
Console::error('[Error] Method: ' . $route->getMethod());
Console::error('[Error] URL: ' . $route->getPath());
if (!empty($route->getLabel('sdk.auth', [])) && $project->isEmpty() && ($route->getLabel('scope', '') !== 'public')) {
throw new AppwriteException('Missing or unknown project ID', 400, AppwriteException::PROJECT_UNKNOWN);
}
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $message);
Console::error('[Error] File: ' . $file);
Console::error('[Error] Line: ' . $line);
}
$referrer = $request->getReferer();
$origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST);
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
/** Handle Utopia Errors */
if ($error instanceof Utopia\Exception) {
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
switch ($code) {
case 400:
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
break;
case 404:
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
break;
$refDomainOrigin = 'localhost';
$validator = new Hostname($clients);
if ($validator->isValid($origin)) {
$refDomainOrigin = $origin;
}
}
/** Wrap all exceptions inside Appwrite\Extend\Exception */
if (!($error instanceof AppwriteException)) {
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
}
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
switch ($code) { // Don't show 500 errors!
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 416: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
case 503: // Error allowed publicly
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
$message = 'Server Error';
}
$refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
? $refDomain
: (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
$selfDomain = new Domain($request->getHostname());
$endDomain = new Domain((string)$origin);
$type = $error->getType();
Config::setParam(
'domainVerification',
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
$endDomain->getRegisterable() !== ''
);
$output = ((App::isDevelopment())) ? [
'message' => $message,
'code' => $code,
'file' => $file,
'line' => $line,
'trace' => $trace,
'version' => $version,
'type' => $type,
] : [
'message' => $message,
'code' => $code,
'version' => $version,
'type' => $type,
];
Config::setParam('cookieDomain', (
$request->getHostname() === 'localhost' ||
$request->getHostname() === 'localhost:' . $request->getPort() ||
(\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false)
)
? null
: '.' . $request->getHostname());
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
->addHeader('Pragma', 'no-cache')
->setStatusCode($code)
;
/*
* Response format
*/
$responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($responseFormat) {
switch ($responseFormat) {
case version_compare($responseFormat, '0.11.2', '<='):
Response::setFilter(new ResponseV11());
break;
case version_compare($responseFormat, '0.12.4', '<='):
Response::setFilter(new ResponseV12());
break;
case version_compare($responseFormat, '0.13.4', '<='):
Response::setFilter(new ResponseV13());
break;
case version_compare($responseFormat, '0.14.0', '<='):
Response::setFilter(new ResponseV14());
break;
default:
Response::setFilter(null);
}
} else {
Response::setFilter(null);
}
$template = ($route) ? $route->getLabel('error', null) : null;
/*
* Security Headers
*
* As recommended at:
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException('Method unsupported over HTTP.', 500, AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED);
}
if ($template) {
$comp = new View($template);
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
}
$comp
->setParam('development', App::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $error->getMessage())
->setParam('code', $code)
->setParam('trace', $trace)
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
}
$response
->addHeader('Server', 'Appwrite')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $refDomain)
->addHeader('Access-Control-Allow-Credentials', 'true')
;
$layout
->setParam('title', $project->getAttribute('name') . ' - Error')
->setParam('description', 'No Description')
->setParam('body', $comp)
->setParam('version', $version)
->setParam('litespeed', false)
/*
* Validate Client Domain - Check to avoid CSRF attack
* Adding Appwrite API domains to allow XDOMAIN communication
* Skip this check for non-web platforms which are not required to send an origin header
*/
$origin = $request->getOrigin($request->getReferer(''));
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
if (
!$originValidator->isValid($origin)
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
&& $route->getLabel('origin', false) !== '*'
&& empty($request->getHeader('x-appwrite-key', ''))
) {
throw new AppwriteException($originValidator->getDescription(), 403, AppwriteException::GENERAL_UNKNOWN_ORIGIN);
}
/*
* ACL Check
*/
$role = ($user->isEmpty()) ? Auth::USER_ROLE_GUEST : Auth::USER_ROLE_MEMBER;
// Add user roles
$memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships');
if ($memberships) {
foreach ($memberships->getAttribute('roles', []) as $memberRole) {
switch ($memberRole) {
case 'owner':
$role = Auth::USER_ROLE_OWNER;
break;
case 'admin':
$role = Auth::USER_ROLE_ADMIN;
break;
case 'developer':
$role = Auth::USER_ROLE_DEVELOPER;
break;
}
}
}
$roles = Config::getParam('roles', []);
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
$authKey = $request->getHeader('x-appwrite-key', '');
if (!empty($authKey)) { // API Key authentication
// Check if given key match project API keys
$key = $project->find('secret', $authKey, 'keys');
/*
* Try app auth when we have project key and no user
* Mock user to app and grant API key scopes in addition to default app scopes
*/
if ($key && $user->isEmpty()) {
$user = new Document([
'$id' => '',
'status' => true,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);
$role = Auth::USER_ROLE_APP;
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
$expire = $key->getAttribute('expire', 0);
if (!empty($expire) && $expire < \time()) {
throw new AppwriteException('Project key expired', 401, AppwriteException:: PROJECT_KEY_EXPIRED);
}
Authorization::setRole('role:' . Auth::USER_ROLE_APP);
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
}
}
Authorization::setRole('role:' . $role);
foreach (Auth::getRoles($user) as $authRole) {
Authorization::setRole($authRole);
}
$service = $route->getLabel('sdk.namespace', '');
if (!empty($service)) {
if (
array_key_exists($service, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$service]
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
}
}
if (!\in_array($scope, $scopes)) {
if ($project->isEmpty()) { // Check if permission is denied because project is missing
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
}
throw new AppwriteException($user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')', 401, AppwriteException::GENERAL_UNAUTHORIZED_SCOPE);
}
if (false === $user->getAttribute('status')) { // Account is blocked
throw new AppwriteException('Invalid credentials. User is blocked', 401, AppwriteException::USER_BLOCKED);
}
if ($user->getAttribute('reset')) {
throw new AppwriteException('Password reset is required', 412, AppwriteException::USER_PASSWORD_RESET_REQUIRED);
}
});
App::options()
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$origin = $request->getOrigin();
$response
->addHeader('Server', 'Appwrite')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
->noContent();
});
App::error()
->inject('error')
->inject('utopia')
->inject('request')
->inject('response')
->inject('layout')
->inject('project')
->inject('logger')
->inject('loggerBreadcrumbs')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, View $layout, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->match($request);
/** Delegate PDO exceptions to the global handler so the database connection can be returned to the pool */
if ($error instanceof PDOException) {
throw $error;
}
if ($logger) {
if ($error->getCode() >= 500 || $error->getCode() === 0) {
try {
/** @var Utopia\Database\Document $user */
$user = $utopia->getResource('user');
} catch (\Throwable $th) {
// All good, user is optional information for logger
}
$log = new Utopia\Logger\Log();
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId());
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::$roles);
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
$log->addBreadcrumb($loggerBreadcrumb);
}
$responseCode = $logger->addLog($log);
Console::info('Log pushed with status code: ' . $responseCode);
}
}
$code = $error->getCode();
$message = $error->getMessage();
$file = $error->getFile();
$line = $error->getLine();
$trace = $error->getTrace();
if (php_sapi_name() === 'cli') {
Console::error('[Error] Timestamp: ' . date('c', time()));
if ($route) {
Console::error('[Error] Method: ' . $route->getMethod());
Console::error('[Error] URL: ' . $route->getPath());
}
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $message);
Console::error('[Error] File: ' . $file);
Console::error('[Error] Line: ' . $line);
}
/** Handle Utopia Errors */
if ($error instanceof Utopia\Exception) {
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
switch ($code) {
case 400:
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
break;
case 404:
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
break;
}
}
/** Wrap all exceptions inside Appwrite\Extend\Exception */
if (!($error instanceof AppwriteException)) {
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
}
switch ($code) { // Don't show 500 errors!
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 416: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
case 503: // Error allowed publicly
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
$message = 'Server Error';
}
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
$type = $error->getType();
$output = ((App::isDevelopment())) ? [
'message' => $message,
'code' => $code,
'file' => $file,
'line' => $line,
'trace' => $trace,
'version' => $version,
'type' => $type,
] : [
'message' => $message,
'code' => $code,
'version' => $version,
'type' => $type,
];
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
->addHeader('Pragma', 'no-cache')
->setStatusCode($code)
;
$response->html($layout->render());
}
$template = ($route) ? $route->getLabel('error', null) : null;
$response->dynamic(
new Document($output),
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
);
}, ['error', 'utopia', 'request', 'response', 'layout', 'project', 'logger', 'loggerBreadcrumbs']);
if ($template) {
$comp = new View($template);
$comp
->setParam('development', App::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $error->getMessage())
->setParam('code', $code)
->setParam('trace', $trace)
;
$layout
->setParam('title', $project->getAttribute('name') . ' - Error')
->setParam('description', 'No Description')
->setParam('body', $comp)
->setParam('version', $version)
->setParam('litespeed', false)
;
$response->html($layout->render());
}
$response->dynamic(
new Document($output),
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
);
});
App::get('/manifest.json')
->desc('Progressive app manifest file')
+21 -16
View File
@@ -214,7 +214,7 @@ App::get('/v1/mock/tests/general/download')
->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->send("Download test passed.")
->send("GET:/v1/mock/tests/general/download:passed")
;
});
@@ -558,24 +558,29 @@ App::get('/v1/mock/tests/general/oauth2/failure')
]);
});
App::shutdown(function (App $utopia, Response $response, Request $request) {
App::shutdown()
->groups(['mock'])
->inject('utopia')
->inject('response')
->inject('request')
->action(function (App $utopia, Response $response, Request $request) {
$result = [];
$route = $utopia->match($request);
$path = APP_STORAGE_CACHE . '/tests.json';
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
$result = [];
$route = $utopia->match($request);
$path = APP_STORAGE_CACHE . '/tests.json';
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
if (!\is_array($tests)) {
throw new Exception('Failed to read results', 500, Exception::GENERAL_MOCK);
}
if (!\is_array($tests)) {
throw new Exception('Failed to read results', 500, Exception::GENERAL_MOCK);
}
$result[$route->getMethod() . ':' . $route->getPath()] = true;
$result[$route->getMethod() . ':' . $route->getPath()] = true;
$tests = \array_merge($tests, $result);
$tests = \array_merge($tests, $result);
if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
throw new Exception('Failed to save results', 500, Exception::GENERAL_MOCK);
}
if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
throw new Exception('Failed to save results', 500, Exception::GENERAL_MOCK);
}
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
}, ['utopia', 'response', 'request'], 'mock');
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
});
+248 -215
View File
@@ -19,234 +19,267 @@ use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry;
App::init(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
App::init()
->groups(['api'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
->inject('user')
->inject('events')
->inject('audits')
->inject('mails')
->inject('usage')
->inject('deletes')
->inject('database')
->inject('dbForProject')
->inject('mode')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
$route = $utopia->match($request);
$route = $utopia->match($request);
if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
throw new Exception('Missing or unknown project ID', 400, Exception::PROJECT_UNKNOWN);
}
if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
throw new Exception('Missing or unknown project ID', 400, Exception::PROJECT_UNKNOWN);
}
/*
* Abuse Check
*/
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$timeLimitArray = [];
/*
* Abuse Check
*/
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$timeLimitArray = [];
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
foreach ($abuseKeyLabel as $abuseKey) {
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname() . $route->getPath());
$timeLimitArray[] = $timeLimit;
}
foreach ($abuseKeyLabel as $abuseKey) {
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getHostname() . $route->getPath());
$timeLimitArray[] = $timeLimit;
}
$closestLimit = null;
$closestLimit = null;
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if (!empty($value)) {
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if (!empty($value)) {
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
}
}
$abuse = new Abuse($timeLimit);
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
$closestLimit = $timeLimit->remaining();
$response
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
;
}
if (
(App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled' // Route is rate-limited
&& $abuse->check()) // Abuse is not disabled
&& (!$isAppUser && !$isPrivilegedUser)
) { // User is not an admin or API key
throw new Exception('Too many requests', 429, Exception::GENERAL_RATE_LIMIT_EXCEEDED);
}
}
$abuse = new Abuse($timeLimit);
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
$closestLimit = $timeLimit->remaining();
$response
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
;
}
if (
(App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled' // Route is rate-limited
&& $abuse->check()) // Abuse is not disabled
&& (!$isAppUser && !$isPrivilegedUser)
) { // User is not an admin or API key
throw new Exception('Too many requests', 429, Exception::GENERAL_RATE_LIMIT_EXCEEDED);
}
}
/*
* Background Jobs
*/
$events
->setEvent($route->getLabel('event', ''))
->setProject($project)
->setUser($user)
;
$mails
->setProject($project)
->setUser($user)
;
$audits
->setMode($mode)
->setUserAgent($request->getUserAgent(''))
->setIP($request->getIP())
->setEvent($route->getLabel('event', ''))
->setProject($project)
->setUser($user)
;
$usage
->setParam('projectId', $project->getId())
->setParam('httpRequest', 1)
->setParam('httpUrl', $request->getHostname() . $request->getURI())
->setParam('httpMethod', $request->getMethod())
->setParam('httpPath', $route->getPath())
->setParam('networkRequestSize', 0)
->setParam('networkResponseSize', 0)
->setParam('storage', 0)
;
$deletes->setProject($project);
$database->setProject($project);
}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'mails', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api');
App::init(function (App $utopia, Request $request, Document $project) {
$route = $utopia->match($request);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
}
$auths = $project->getAttribute('auths', []);
switch ($route->getLabel('auth.type', '')) {
case 'emailPassword':
if (($auths['emailPassword'] ?? true) === false) {
throw new Exception('Email / Password authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'magic-url':
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
throw new Exception('Magic URL authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'anonymous':
if (($auths['anonymous'] ?? true) === false) {
throw new Exception('Anonymous authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'invites':
if (($auths['invites'] ?? true) === false) {
throw new Exception('Invites authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'jwt':
if (($auths['JWT'] ?? true) === false) {
throw new Exception('JWT authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
default:
throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
break;
}
}, ['utopia', 'request', 'project'], 'auth');
App::shutdown(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) {
if (!empty($events->getEvent())) {
if (empty($events->getPayload())) {
$events->setPayload($response->getPayload());
}
/**
* Trigger functions.
*/
/*
* Background Jobs
*/
$events
->setClass(Event::FUNCTIONS_CLASS_NAME)
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
->trigger();
->setEvent($route->getLabel('event', ''))
->setProject($project)
->setUser($user)
;
/**
* Trigger webhooks.
*/
$events
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->trigger();
$mails
->setProject($project)
->setUser($user)
;
/**
* Trigger realtime.
*/
if ($project->getId() !== 'console') {
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
$payload = new Document($events->getPayload());
$audits
->setMode($mode)
->setUserAgent($request->getUserAgent(''))
->setIP($request->getIP())
->setEvent($route->getLabel('event', ''))
->setProject($project)
->setUser($user)
;
$db = $events->getContext('database');
$collection = $events->getContext('collection');
$bucket = $events->getContext('bucket');
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $payload,
project: $project,
database: $db,
collection: $collection,
bucket: $bucket,
);
Realtime::send(
projectId: $target['projectId'] ?? $project->getId(),
payload: $events->getPayload(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
options: [
'permissionsChanged' => $target['permissionsChanged'],
'userId' => $events->getParam('userId')
]
);
}
}
if (!empty($audits->getResource())) {
foreach ($events->getParams() as $key => $value) {
$audits->setParam($key, $value);
}
$audits->trigger();
}
if (!empty($deletes->getType())) {
$deletes->trigger();
}
if (!empty($database->getType())) {
$database->trigger();
}
$route = $utopia->match($request);
if (
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
&& $project->getId()
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
&& !empty($route->getLabel('sdk.namespace', null))
) { // Don't calculate console usage on admin mode
$usage
->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
->setParam('networkResponseSize', $response->getSize())
->submit();
}
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode', 'dbForProject'], 'api');
->setParam('projectId', $project->getId())
->setParam('httpRequest', 1)
->setParam('httpUrl', $request->getHostname() . $request->getURI())
->setParam('httpMethod', $request->getMethod())
->setParam('httpPath', $route->getPath())
->setParam('networkRequestSize', 0)
->setParam('networkResponseSize', 0)
->setParam('storage', 0)
;
$deletes->setProject($project);
$database->setProject($project);
});
App::init()
->groups(['auth'])
->inject('utopia')
->inject('request')
->inject('project')
->action(function (App $utopia, Request $request, Document $project) {
$route = $utopia->match($request);
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
}
$auths = $project->getAttribute('auths', []);
switch ($route->getLabel('auth.type', '')) {
case 'emailPassword':
if (($auths['emailPassword'] ?? true) === false) {
throw new Exception('Email / Password authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'magic-url':
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
throw new Exception('Magic URL authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'anonymous':
if (($auths['anonymous'] ?? true) === false) {
throw new Exception('Anonymous authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'invites':
if (($auths['invites'] ?? true) === false) {
throw new Exception('Invites authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
case 'jwt':
if (($auths['JWT'] ?? true) === false) {
throw new Exception('JWT authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
}
break;
default:
throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
break;
}
});
App::shutdown()
->groups(['api'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
->inject('events')
->inject('audits')
->inject('usage')
->inject('deletes')
->inject('database')
->inject('mode')
->inject('dbForProject')
->action(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) {
if (!empty($events->getEvent())) {
if (empty($events->getPayload())) {
$events->setPayload($response->getPayload());
}
/**
* Trigger functions.
*/
$events
->setClass(Event::FUNCTIONS_CLASS_NAME)
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
->trigger();
/**
* Trigger webhooks.
*/
$events
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->trigger();
/**
* Trigger realtime.
*/
if ($project->getId() !== 'console') {
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
$payload = new Document($events->getPayload());
$db = $events->getContext('database');
$collection = $events->getContext('collection');
$bucket = $events->getContext('bucket');
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $payload,
project: $project,
database: $db,
collection: $collection,
bucket: $bucket,
);
Realtime::send(
projectId: $target['projectId'] ?? $project->getId(),
payload: $events->getPayload(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles'],
options: [
'permissionsChanged' => $target['permissionsChanged'],
'userId' => $events->getParam('userId')
]
);
}
}
if (!empty($audits->getResource())) {
foreach ($events->getParams() as $key => $value) {
$audits->setParam($key, $value);
}
$audits->trigger();
}
if (!empty($deletes->getType())) {
$deletes->trigger();
}
if (!empty($database->getType())) {
$database->trigger();
}
$route = $utopia->match($request);
if (
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
&& $project->getId()
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
&& !empty($route->getLabel('sdk.namespace', null))
) { // Don't calculate console usage on admin mode
$usage
->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
->setParam('networkResponseSize', $response->getSize())
->submit();
}
});
+48 -43
View File
@@ -6,54 +6,59 @@ use Appwrite\Utopia\Response;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\View;
App::init(function (App $utopia, Request $request, Response $response, View $layout) {
App::init()
->groups(['web'])
->inject('utopia')
->inject('request')
->inject('response')
->inject('layout')
->action(function (App $utopia, Request $request, Response $response, View $layout) {
/* AJAX check */
if (!empty($request->getQuery('version', ''))) {
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml');
}
/* AJAX check */
if (!empty($request->getQuery('version', ''))) {
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml');
}
$port = $request->getPort();
$protocol = $request->getProtocol();
$domain = $request->getHostname();
$port = $request->getPort();
$protocol = $request->getProtocol();
$domain = $request->getHostname();
$layout
->setParam('title', APP_NAME)
->setParam('protocol', $protocol)
->setParam('domain', $domain)
->setParam('endpoint', $protocol . '://' . $domain . ($port != 80 && $port != 443 ? ':' . $port : ''))
->setParam('home', App::getEnv('_APP_HOME'))
->setParam('setup', App::getEnv('_APP_SETUP'))
->setParam('class', 'unknown')
->setParam('icon', '/images/favicon.png')
->setParam('roles', [
['type' => 'owner', 'label' => 'Owner'],
['type' => 'developer', 'label' => 'Developer'],
['type' => 'admin', 'label' => 'Admin'],
])
->setParam('runtimes', Config::getParam('runtimes'))
->setParam('mode', App::getMode())
;
$layout
->setParam('title', APP_NAME)
->setParam('protocol', $protocol)
->setParam('domain', $domain)
->setParam('endpoint', $protocol . '://' . $domain . ($port != 80 && $port != 443 ? ':' . $port : ''))
->setParam('home', App::getEnv('_APP_HOME'))
->setParam('setup', App::getEnv('_APP_SETUP'))
->setParam('class', 'unknown')
->setParam('icon', '/images/favicon.png')
->setParam('roles', [
['type' => 'owner', 'label' => 'Owner'],
['type' => 'developer', 'label' => 'Developer'],
['type' => 'admin', 'label' => 'Admin'],
])
->setParam('runtimes', Config::getParam('runtimes'))
->setParam('mode', App::getMode())
;
$time = (60 * 60 * 24 * 45); // 45 days cache
$time = (60 * 60 * 24 * 45); // 45 days cache
$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('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
;
$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('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
;
$route = $utopia->match($request);
$route = $utopia->match($request);
$route->label('error', __DIR__ . '/../../views/general/error.phtml');
$route->label('error', __DIR__ . '/../../views/general/error.phtml');
$scope = $route->getLabel('scope', '');
$scope = $route->getLabel('scope', '');
$layout
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
->setParam('isDev', App::isDevelopment())
->setParam('class', $scope)
;
}, ['utopia', 'request', 'response', 'layout'], 'web');
$layout
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
->setParam('isDev', App::isDevelopment())
->setParam('class', $scope)
;
});
+26 -21
View File
@@ -9,31 +9,36 @@ use Utopia\Domains\Domain;
use Utopia\Database\Validator\UID;
use Utopia\Storage\Storage;
App::init(function (View $layout) {
App::init()
->groups(['console'])
->inject('layout')
->action(function (View $layout) {
$layout
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
->setParam('analytics', 'UA-26264668-5')
;
});
$layout
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
->setParam('analytics', 'UA-26264668-5')
;
}, ['layout'], 'console');
App::shutdown()
->groups(['console'])
->inject('response')
->inject('layout')
->action(function (Response $response, View $layout) {
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
App::shutdown(function (Response $response, View $layout) {
$footer
->setParam('home', App::getEnv('_APP_HOME', ''))
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
$layout
->setParam('header', [$header])
->setParam('footer', [$footer])
;
$footer
->setParam('home', App::getEnv('_APP_HOME', ''))
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$layout
->setParam('header', [$header])
->setParam('footer', [$footer])
;
$response->html($layout->render());
}, ['response', 'layout'], 'console');
$response->html($layout->render());
});
App::get('/error/:code')
->groups(['web', 'console'])
+25 -20
View File
@@ -7,29 +7,34 @@ use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
App::init(function (View $layout) {
App::init()
->groups(['home'])
->inject('layout')
->action(function (View $layout) {
$header = new View(__DIR__ . '/../../views/home/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/home/comps/footer.phtml');
$header = new View(__DIR__ . '/../../views/home/comps/header.phtml');
$footer = new View(__DIR__ . '/../../views/home/comps/footer.phtml');
$footer
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$footer
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$layout
->setParam('title', APP_NAME)
->setParam('description', '')
->setParam('class', 'home')
->setParam('platforms', Config::getParam('platforms'))
->setParam('header', [$header])
->setParam('footer', [$footer])
;
});
$layout
->setParam('title', APP_NAME)
->setParam('description', '')
->setParam('class', 'home')
->setParam('platforms', Config::getParam('platforms'))
->setParam('header', [$header])
->setParam('footer', [$footer])
;
}, ['layout'], 'home');
App::shutdown(function (Response $response, View $layout) {
$response->html($layout->render());
}, ['response', 'layout'], 'home');
App::shutdown()
->groups(['home'])
->inject('response')
->inject('layout')
->action(function (Response $response, View $layout) {
$response->html($layout->render());
});
App::get('/')
->groups(['web', 'home'])
+52 -45
View File
@@ -581,57 +581,64 @@ App::setResource('orchestrationPool', fn() => $orchestrationPool);
App::setResource('activeRuntimes', fn() => $activeRuntimes);
/** Set callbacks */
App::error(function ($utopia, $error, $request, $response) {
$route = $utopia->match($request);
logError($error, "httpError", $route);
App::error()
->inject('utopia')
->inject('error')
->inject('request')
->inject('response')
->action(function (App $utopia, throwable $error, Request $request, Response $response) {
$route = $utopia->match($request);
logError($error, "httpError", $route);
switch ($error->getCode()) {
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 406: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 425: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
case 503: // Error allowed publicly
$code = $error->getCode();
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
}
switch ($error->getCode()) {
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 406: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 425: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
case 503: // Error allowed publicly
$code = $error->getCode();
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
}
$output = [
'message' => $error->getMessage(),
'code' => $error->getCode(),
'file' => $error->getFile(),
'line' => $error->getLine(),
'trace' => $error->getTrace(),
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
];
$output = [
'message' => $error->getMessage(),
'code' => $error->getCode(),
'file' => $error->getFile(),
'line' => $error->getLine(),
'trace' => $error->getTrace(),
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
];
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
->addHeader('Pragma', 'no-cache')
->setStatusCode($code);
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
->addHeader('Pragma', 'no-cache')
->setStatusCode($code);
$response->json($output);
}, ['utopia', 'error', 'request', 'response']);
$response->json($output);
});
App::init(function ($request, $response) {
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
if (empty($secretKey)) {
throw new Exception('Missing executor key', 401);
}
App::init()
->inject('request')
->action(function (Request $request) {
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
if (empty($secretKey)) {
throw new Exception('Missing executor key', 401);
}
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
throw new Exception('Missing executor key', 401);
}
}, ['request', 'response']);
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
throw new Exception('Missing executor key', 401);
}
});
$http->on('start', function ($http) {
+1 -1
View File
@@ -123,7 +123,7 @@
data-analytics-category="console/navigation"
data-analytics-label="Users Link">
<i class="icon-users"></i>
Users
Authentication
</a>
</li>
<li>
@@ -583,6 +583,11 @@ $logs = $this->getParam('logs', null);
<input id="id" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-collection.$id}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<label>Database ID</label>
<div class="input-copy margin-bottom">
<input type="text" autocomplete="off" placeholder="" data-ls-bind="{{router.params.databaseId}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"
@@ -348,6 +348,11 @@ $logs = $this->getParam('logs', null);
<input type="text" autocomplete="off" placeholder="" data-ls-bind="{{router.params.collection}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<label>Database ID</label>
<div class="input-copy margin-bottom">
<input type="text" autocomplete="off" placeholder="" data-ls-bind="{{router.params.databaseId}}" disabled data-forms-copy class="margin-bottom-no" />
</div>
<ul class="margin-bottom-large text-fade text-size-small" data-ls-if="({{project-document.$id}})">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i>
<button data-ls-ui-trigger="open-json"
+1 -1
View File
@@ -11,7 +11,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
<a data-ls-attrs="href=/console/home?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Home</a>
<br />
<span>Users</span>
<span>Authentication</span>
</h1>
</div>
@@ -0,0 +1,12 @@
<?php
$provider = $this->getParam('provider', '');
?>
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Client ID<span class="tooltip" data-tooltip="Provided in the Provider you created in authentik"><i class="icon-info-circled"></i></span></label>
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Client ID" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Provided in the Provider you created in authentik"><i class="icon-info-circled"></i></span></label>
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain">authentik Base-Domain<span class="tooltip" data-tooltip="Your authentik Base-Domain (without 'https://')"><i class="icon-info-circled"></i></span></label>
<input name="authentikDomain" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain" type="text" autocomplete="off" placeholder="auth.example.com" />
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />
+2 -2
View File
@@ -18,8 +18,8 @@
data-scope="console"
data-event="submit"
data-success="alert,redirect"
data-success-param-alert="Password Reset Completed"
data-success-param-url="/auth/signin"
data-success-param-alert-text="Password Reset Completed"
data-success-param-redirect-url="/auth/signin"
data-failure="alert"
data-failure-param-alert-text="Password Reset Failed"
data-failure-param-alert-classname="error">
+2 -2
View File
@@ -595,7 +595,7 @@ services:
- _APP_REDIS_PASS
mariadb:
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
restart: unless-stopped
@@ -611,7 +611,7 @@ services:
command: 'mysqld --innodb-flush-method=fsync'
redis:
image: redis:6.2-alpine
image: redis:7.0.4-alpine
container_name: appwrite-redis
<<: *x-logging
restart: unless-stopped
-5
View File
@@ -20,11 +20,6 @@ Console::success(APP_NAME . ' deletes worker v1 has started' . "\n");
class DeletesV1 extends Worker
{
/**
* @var Database
*/
protected $consoleDB = null;
public function getName(): string
{
return "deletes";
+2 -2
View File
@@ -43,7 +43,7 @@
"ext-sockets": "*",
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.10.*",
"utopia-php/framework": "0.19.*",
"utopia-php/framework": "0.20.*",
"utopia-php/logger": "0.3.*",
"utopia-php/abuse": "0.7.*",
"utopia-php/analytics": "0.2.*",
@@ -77,7 +77,7 @@
}
],
"require-dev": {
"appwrite/sdk-generator": "0.19.5",
"appwrite/sdk-generator": "0.20.0",
"phpunit/phpunit": "9.5.20",
"squizlabs/php_codesniffer": "^3.6",
"swoole/ide-helper": "4.8.9",
Generated
+28 -28
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "677b1b47c8567f0b7b05645e2bbc7bc7",
"content-hash": "993486075710ab0cdbba6c33f0b09218",
"packages": [
{
"name": "adhocore/jwt",
@@ -1894,16 +1894,16 @@
},
{
"name": "utopia-php/cache",
"version": "0.6.0",
"version": "0.6.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cache.git",
"reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3"
"reference": "9889235a6d3da6cbb1f435201529da4d27c30e79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/cache/zipball/8ea1353a4bbab617e23c865a7c97b60d8074aee3",
"reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3",
"url": "https://api.github.com/repos/utopia-php/cache/zipball/9889235a6d3da6cbb1f435201529da4d27c30e79",
"reference": "9889235a6d3da6cbb1f435201529da4d27c30e79",
"shasum": ""
},
"require": {
@@ -1941,9 +1941,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/cache/issues",
"source": "https://github.com/utopia-php/cache/tree/0.6.0"
"source": "https://github.com/utopia-php/cache/tree/0.6.1"
},
"time": "2022-04-04T12:30:05+00:00"
"time": "2022-08-10T08:12:46+00:00"
},
{
"name": "utopia-php/cli",
@@ -2169,16 +2169,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.19.21",
"version": "0.20.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d"
"reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/3b7bd8e4acf84fd7d560ced8e0142221d302575d",
"reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/beb5e861c7d0a6256a1272e6b9d70b060ca8629a",
"reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a",
"shasum": ""
},
"require": {
@@ -2212,9 +2212,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.19.21"
"source": "https://github.com/utopia-php/framework/tree/0.20.0"
},
"time": "2022-05-12T18:42:28+00:00"
"time": "2022-07-30T09:55:28+00:00"
},
{
"name": "utopia-php/image",
@@ -2828,29 +2828,29 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.19.5",
"version": "0.20.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "04de540cf683e2b08b3192c137dde7f2c37003d9"
"reference": "af7dd08848a78a0d38befa3e63083eb0186806f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/04de540cf683e2b08b3192c137dde7f2c37003d9",
"reference": "04de540cf683e2b08b3192c137dde7f2c37003d9",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/af7dd08848a78a0d38befa3e63083eb0186806f7",
"reference": "af7dd08848a78a0d38befa3e63083eb0186806f7",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"matthiasmullie/minify": "^1.3",
"matthiasmullie/minify": "^1.3.68",
"php": ">=7.0.0",
"twig/twig": "^3.3"
"twig/twig": "^3.4.1"
},
"require-dev": {
"brianium/paratest": "^6.4",
"phpunit/phpunit": "^9.5.13"
"phpunit/phpunit": "^9.5.21"
},
"type": "library",
"autoload": {
@@ -2872,9 +2872,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.19.5"
"source": "https://github.com/appwrite/sdk-generator/tree/0.20.0"
},
"time": "2022-07-06T11:05:57+00:00"
"time": "2022-08-02T10:09:48+00:00"
},
{
"name": "doctrine/instantiator",
@@ -5271,16 +5271,16 @@
},
{
"name": "twig/twig",
"version": "v3.4.1",
"version": "v3.4.2",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "e939eae92386b69b49cfa4599dd9bead6bf4a342"
"reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/e939eae92386b69b49cfa4599dd9bead6bf4a342",
"reference": "e939eae92386b69b49cfa4599dd9bead6bf4a342",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077",
"reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077",
"shasum": ""
},
"require": {
@@ -5331,7 +5331,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.4.1"
"source": "https://github.com/twigphp/Twig/tree/v3.4.2"
},
"funding": [
{
@@ -5343,7 +5343,7 @@
"type": "tidelift"
}
],
"time": "2022-05-17T05:48:52+00:00"
"time": "2022-08-12T06:47:24+00:00"
}
],
"aliases": [],
+2 -2
View File
@@ -638,7 +638,7 @@ services:
- _APP_REDIS_PASS
mariadb:
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
<<: *x-logging
networks:
@@ -668,7 +668,7 @@ services:
# - SMARTHOST_PORT=587
redis:
image: redis:6.2-alpine
image: redis:7.0.4-alpine
<<: *x-logging
container_name: appwrite-redis
command: >
+1 -1
View File
@@ -1,3 +1,3 @@
You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.
You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard.
When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.
@@ -1 +1 @@
Create a new Collection. Before using this route, you should create a new database resource using either a [server integration](/docs/server/database#databaseCreateCollection) API or directly from your database console.
Create a new Collection. Before using this route, you should create a new database resource using either a [server integration](/docs/server/databases#databasesCreateCollection) API or directly from your database console.
+1 -1
View File
@@ -1 +1 @@
Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](/docs/server/database#databaseCreateCollection) API or directly from your database console.
Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](/docs/server/databases#databasesCreateCollection) API or directly from your database console.
+1 -1
View File
@@ -1,4 +1,4 @@
Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](/docs/server/database#storageCreateBucket) API or directly from your Appwrite console.
Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](/docs/server/storage#storageCreateBucket) API or directly from your Appwrite console.
Larger files should be uploaded using multiple requests with the [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.
+1 -1
View File
@@ -2,6 +2,6 @@ The Databases service allows you to create structured collections of documents,
All data returned by the Databases service are represented as structured JSON documents.
The Databases service can contain multiple databases, each database can contain multiple collections. A collection is a group of similarly structured documents. The accepted structure of documents is defined by [collection attributes](/docs/database#attributes). The collection attributes help you ensure all your user-submitted data is validated and stored according to the collection structure.
The Databases service can contain multiple databases, each database can contain multiple collections. A collection is a group of similarly structured documents. The accepted structure of documents is defined by [collection attributes](/docs/databases#attributes). The collection attributes help you ensure all your user-submitted data is validated and stored according to the collection structure.
Using Appwrite permissions architecture, you can assign read or write access to each collection or document in your project for either a specific user, team, user role, or even grant it with public access (`role:all`). You can learn more about [how Appwrite handles permissions and access control](/docs/permissions).
+1 -1
View File
@@ -1,3 +1,3 @@
The Locale service allows you to customize your app based on your users' location. Using this service, you can get your users' location, IP address, list of countries and continents names, phone codes, currencies, and more.
The Locale service allows you to customize your app based on your users' location. Using this service, you can get your users' location, IP address, list of countries and continents names, phone codes, currencies, and more. Country codes returned follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard.
The user service supports multiple locales. This feature allows you to fetch countries and continents information in your app language. To switch locales, all you need to do is pass the 'X-Appwrite-Locale' header or set the 'setLocale' method using any of our available SDKs. [View here the list of available locales](https://github.com/appwrite/appwrite/blob/master/app/config/locale/codes.php).
+33 -30
View File
@@ -9,8 +9,8 @@
"version": "0.1.0",
"license": "BSD-3-Clause",
"dependencies": {
"chart.js": "^3.8.0",
"markdown-it": "^12.3.2",
"chart.js": "^3.8.2",
"markdown-it": "^13.0.1",
"pell": "^1.0.6",
"prismjs": "^1.28.0",
"turndown": "^7.1.1"
@@ -549,9 +549,9 @@
}
},
"node_modules/chart.js": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz",
"integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg=="
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.2.tgz",
"integrity": "sha512-7rqSlHWMUKFyBDOJvmFGW2lxULtcwaPLegDjX/Nu5j6QybY+GCiQkEY+6cqHw62S5tcwXMD8Y+H5OBGoR7d+ZQ=="
},
"node_modules/chokidar": {
"version": "2.1.8",
@@ -1067,9 +1067,12 @@
}
},
"node_modules/entities": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
@@ -2606,9 +2609,9 @@
}
},
"node_modules/linkify-it": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
"integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
"integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
"dependencies": {
"uc.micro": "^1.0.1"
}
@@ -2862,13 +2865,13 @@
}
},
"node_modules/markdown-it": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz",
"integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
"dependencies": {
"argparse": "^2.0.1",
"entities": "~2.1.0",
"linkify-it": "^3.0.1",
"entities": "~3.0.1",
"linkify-it": "^4.0.1",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
},
@@ -5484,9 +5487,9 @@
"dev": true
},
"chart.js": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz",
"integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg=="
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.2.tgz",
"integrity": "sha512-7rqSlHWMUKFyBDOJvmFGW2lxULtcwaPLegDjX/Nu5j6QybY+GCiQkEY+6cqHw62S5tcwXMD8Y+H5OBGoR7d+ZQ=="
},
"chokidar": {
"version": "2.1.8",
@@ -5924,9 +5927,9 @@
}
},
"entities": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q=="
},
"errno": {
"version": "0.1.8",
@@ -7178,9 +7181,9 @@
}
},
"linkify-it": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
"integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
"integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
"requires": {
"uc.micro": "^1.0.1"
}
@@ -7413,13 +7416,13 @@
}
},
"markdown-it": {
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz",
"integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
"requires": {
"argparse": "^2.0.1",
"entities": "~2.1.0",
"linkify-it": "^3.0.1",
"entities": "~3.0.1",
"linkify-it": "^4.0.1",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
}
+2 -2
View File
@@ -17,8 +17,8 @@
"gulp-less": "^5.0.0"
},
"dependencies": {
"chart.js": "^3.8.0",
"markdown-it": "^12.3.2",
"chart.js": "^3.8.2",
"markdown-it": "^13.0.1",
"pell": "^1.0.6",
"prismjs": "^1.28.0",
"turndown": "^7.1.1"
+480 -283
View File
File diff suppressed because one or more lines are too long
+473 -277
View File
File diff suppressed because it is too large Load Diff
+7 -6
View File
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -57,7 +57,7 @@ window.addEventListener("load", async () => {
const realtime = window.ls.container.get('realtime');
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
let current = {};
window.ls.container.get('console').subscribe(['project', 'console'], response => {
window.ls.container.get('console').client.subscribe(['project', 'console'], response => {
if (response.events.includes('stats.connections')) {
for (let project in response.payload) {
current[project] = response.payload[project] ?? 0;
+15 -3
View File
@@ -2,16 +2,28 @@
"use strict";
window.ls.container.set('console', function (window) {
var sdk = new window.Appwrite();
var client = new Appwrite.Client();
var endpoint = window.location.origin + '/v1';
sdk
client
.setEndpoint(endpoint)
.setProject('console')
.setLocale(APP_ENV.LOCALE)
;
return sdk;
return {
client: client,
account: new Appwrite.Account(client),
avatars: new Appwrite.Avatars(client),
databases: new Appwrite.Databases(client),
functions: new Appwrite.Functions(client),
health: new Appwrite.Health(client),
locale: new Appwrite.Locale(client),
projects: new Appwrite.Projects(client),
storage: new Appwrite.Storage(client),
teams: new Appwrite.Teams(client),
users: new Appwrite.Users(client)
}
}, true);
})(window);
+14 -3
View File
@@ -2,17 +2,28 @@
"use strict";
window.ls.container.set('sdk', function (window, router) {
var sdk = new window.Appwrite();
var client = new Appwrite.Client();
var endpoint = window.location.origin + '/v1';
sdk
client
.setEndpoint(endpoint)
.setProject(router.params.project || '')
.setLocale(APP_ENV.LOCALE)
.setMode('admin')
;
return sdk;
return {
client: client,
account: new Appwrite.Account(client),
avatars: new Appwrite.Avatars(client),
databases: new Appwrite.Databases(client),
functions: new Appwrite.Functions(client),
health: new Appwrite.Health(client),
locale: new Appwrite.Locale(client),
storage: new Appwrite.Storage(client),
teams: new Appwrite.Teams(client),
users: new Appwrite.Users(client)
}
}, false);
})(window);
@@ -26,6 +26,10 @@
"clientSecret": "oauth2Auth0ClientSecret",
"auth0Domain": "oauth2Auth0Domain"
},
"Authentik": {
"clientSecret": "oauth2AuthentikClientSecret",
"authentikDomain": "oauth2AuthentikDomain"
},
"Gitlab": {
"endpoint": "oauth2GitlabEndpoint",
"clientSecret": "oauth2GitlabClientSecret",
+7 -1
View File
@@ -203,6 +203,12 @@
let params = [];
let match;
let indexOfArguments = functionAsString.indexOf('(');
if (indexOfArguments !== -1) {
functionAsString = functionAsString.slice(indexOfArguments, -1);
}
functionAsString = functionAsString.replaceAll('={}', "");
functionAsString = functionAsString.replaceAll('=[]', "");
functionAsString = functionAsString.replace(REGEX_COMMENTS, "");
@@ -259,7 +265,7 @@
let args = getParams(target);
return target.apply(
target,
container.get(scope),
args.map(function(value) {
let result = getValue(value, prefix, data);
+227
View File
@@ -0,0 +1,227 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://goauthentik.io/docs/providers/oauth2/
class Authentik extends OAuth2
{
/**
* @var array
*/
protected array $scopes = [
'openid',
'profile',
'email',
'offline_access'
];
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @return string
*/
public function getName(): string
{
return 'authentik';
}
/**
* @return string
*/
public function getLoginURL(): string
{
return 'https://' . $this->getAuthentikDomain() . '/application/o/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->getAuthentikDomain() . '/application/o/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->getAuthentikDomain() . '/application/o/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 '';
}
/**
* Check if the User email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
if ($user['email_verified'] ?? false) {
return true;
}
return false;
}
/**
* @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->getAuthentikDomain() . '/application/o/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 $secret['clientSecret'] ?? '';
}
/**
* Extracts the authentik Domain from the JSON stored in appSecret
*
* @return string
*/
protected function getAuthentikDomain(): string
{
$secret = $this->getAppSecret();
return $secret['authentikDomain'] ?? '';
}
/**
* 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;
}
}
+188
View File
@@ -0,0 +1,188 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://disqus.com/api/docs/auth/
class Disqus extends OAuth2
{
/**
* @var string
*/
private string $endpoint = 'https://disqus.com/api/';
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = [
'read',
'email',
];
/**
* @return string
*/
public function getName(): string
{
return 'disqus';
}
/**
* @return string
*/
public function getLoginURL(): string
{
$url = $this->endpoint . 'oauth/2.0/authorize/?' .
\http_build_query([
'response_type' => 'code',
'client_id' => $this->appID,
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback,
'scope' => \implode(',', $this->getScopes())
]);
return $url;
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . 'oauth/2.0/access_token/',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'authorization_code',
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
'redirect_uri' => $this->callback,
'code' => $code,
'scope' => \implode(' ', $this->getScopes()),
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
$this->endpoint . 'oauth/2.0/access_token/?',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
])
), true);
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
/**
* @param string $token
*
* @return string
*/
public function getUserID(string $accessToken): string
{
$user = $this->getUser($accessToken);
$userId = $user['id'];
return $userId;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
$userEmail = $user['email'];
return $userEmail;
}
/**
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
// Look out for the change in their enpoint.
// It's in Beta so they may provide a parameter in the future.
// https://disqus.com/api/docs/users/details/
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
$username = $user['name'] ?? '';
return $username;
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = $this->request(
'GET',
$this->endpoint . '3.0/users/details.json?' . \http_build_query([
'access_token' => $accessToken,
'api_key' => $this->appID,
'api_secret' => $this->appSecret
]),
);
$this->user = \json_decode($user, true)['response'];
}
return $this->user;
}
}
-2
View File
@@ -80,7 +80,6 @@ class Linkedin extends OAuth2
])
), true);
}
return $this->tokens;
}
@@ -107,7 +106,6 @@ class Linkedin extends OAuth2
if (empty($this->tokens['refresh_token'])) {
$this->tokens['refresh_token'] = $refreshToken;
}
return $this->tokens;
}
+199
View File
@@ -0,0 +1,199 @@
<?php
namespace Appwrite\Auth\OAuth2;
use Appwrite\Auth\OAuth2;
// Reference Material
// https://developers.podio.com/doc/oauth-authorization
class Podio extends OAuth2
{
/**
* Endpoint used for initiating OAuth flow
*
* @var string
*/
private string $endpoint = 'https://podio.com/oauth';
/**
* Endpoint for communication with API server
*
* @var string
*/
private string $apiEndpoint = 'https://api.podio.com';
/**
* @var array
*/
protected array $user = [];
/**
* @var array
*/
protected array $tokens = [];
/**
* @var array
*/
protected array $scopes = []; // No scopes required
/**
* @return string
*/
public function getName(): string
{
return 'podio';
}
/**
* @return string
*/
public function getLoginURL(): string
{
$url = $this->endpoint . '/authorize?' .
\http_build_query([
'client_id' => $this->appID,
'state' => \json_encode($this->state),
'redirect_uri' => $this->callback
]);
return $url;
}
/**
* @param string $code
*
* @return array
*/
protected function getTokens(string $code): array
{
if (empty($this->tokens)) {
$this->tokens = \json_decode($this->request(
'POST',
$this->apiEndpoint . '/oauth/token',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->callback,
'client_id' => $this->appID,
'client_secret' => $this->appSecret
])
), true);
}
return $this->tokens;
}
/**
* @param string $refreshToken
*
* @return array
*/
public function refreshTokens(string $refreshToken): array
{
$this->tokens = \json_decode($this->request(
'POST',
$this->apiEndpoint . '/oauth/token',
['Content-Type: application/x-www-form-urlencoded'],
\http_build_query([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $this->appID,
'client_secret' => $this->appSecret,
])
), 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);
return \strval($user['user_id']) ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserEmail(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['mail'] ?? '';
}
/**
* Check if the OAuth email is verified
*
* @param string $accessToken
*
* @return bool
*/
public function isEmailVerified(string $accessToken): bool
{
$user = $this->getUser($accessToken);
$mails = $user['mails'];
$mainMailIndex = \array_search($user['mail'], \array_map(fn($m) => $m['mail'], $mails));
$mainMain = $mails[$mainMailIndex];
if ($mainMain['verified'] ?? false) {
return true;
}
return false;
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserName(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['name'] ?? '';
}
/**
* @param string $accessToken
*
* @return array
*/
protected function getUser(string $accessToken): array
{
if (empty($this->user)) {
$user = \json_decode($this->request(
'GET',
$this->apiEndpoint . '/user',
['Authorization: Bearer ' . \urlencode($accessToken)]
), true);
$profile = \json_decode($this->request(
'GET',
$this->apiEndpoint . '/user/profile',
['Authorization: Bearer ' . \urlencode($accessToken)]
), true);
$this->user = $user;
$this->user['name'] = $profile['name'];
}
return $this->user;
}
}
@@ -27,8 +27,10 @@ class AttributeEmail extends Attribute
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_EMAIL,
'example' => APP_DATABASE_ATTRIBUTE_EMAIL,
'default' => 'email',
'example' => 'email',
'array' => false,
'require' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,
@@ -34,8 +34,10 @@ class AttributeEnum extends Attribute
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_ENUM,
'example' => APP_DATABASE_ATTRIBUTE_ENUM,
'default' => 'enum',
'example' => 'enum',
'array' => false,
'require' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,
@@ -27,8 +27,10 @@ class AttributeIP extends Attribute
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_IP,
'example' => APP_DATABASE_ATTRIBUTE_IP,
'default' => 'ip',
'example' => 'ip',
'array' => false,
'require' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,
@@ -27,8 +27,10 @@ class AttributeURL extends Attribute
->addRule('format', [
'type' => self::TYPE_STRING,
'description' => 'String format.',
'default' => APP_DATABASE_ATTRIBUTE_URL,
'example' => APP_DATABASE_ATTRIBUTE_URL,
'default' => 'url',
'example' => 'url',
'array' => false,
'required' => true,
])
->addRule('default', [
'type' => self::TYPE_STRING,
@@ -66,9 +66,9 @@ class Deployment extends Model
])
->addRule('status', [
'type' => self::TYPE_STRING,
'description' => 'The deployment status.',
'description' => 'The deployment status. Possible values are "processing", "building", "pending", "ready", and "failed".',
'default' => '',
'example' => 'enabled',
'example' => 'ready',
])
->addRule('buildStdout', [
'type' => self::TYPE_STRING,
+28 -28
View File
@@ -165,23 +165,23 @@ trait DatabasesBase
'array' => true,
]);
$this->assertEquals($title['headers']['status-code'], 201);
$this->assertEquals($title['headers']['status-code'], 202);
$this->assertEquals($title['body']['key'], 'title');
$this->assertEquals($title['body']['type'], 'string');
$this->assertEquals($title['body']['size'], 256);
$this->assertEquals($title['body']['required'], true);
$this->assertEquals($releaseYear['headers']['status-code'], 201);
$this->assertEquals($releaseYear['headers']['status-code'], 202);
$this->assertEquals($releaseYear['body']['key'], 'releaseYear');
$this->assertEquals($releaseYear['body']['type'], 'integer');
$this->assertEquals($releaseYear['body']['required'], true);
$this->assertEquals($duration['headers']['status-code'], 201);
$this->assertEquals($duration['headers']['status-code'], 202);
$this->assertEquals($duration['body']['key'], 'duration');
$this->assertEquals($duration['body']['type'], 'integer');
$this->assertEquals($duration['body']['required'], false);
$this->assertEquals($actors['headers']['status-code'], 201);
$this->assertEquals($actors['headers']['status-code'], 202);
$this->assertEquals($actors['body']['key'], 'actors');
$this->assertEquals($actors['body']['type'], 'string');
$this->assertEquals($actors['body']['size'], 256);
@@ -318,7 +318,7 @@ trait DatabasesBase
'default' => true,
]);
$this->assertEquals(201, $string['headers']['status-code']);
$this->assertEquals(202, $string['headers']['status-code']);
$this->assertEquals('string', $string['body']['key']);
$this->assertEquals('string', $string['body']['type']);
$this->assertEquals(false, $string['body']['required']);
@@ -326,7 +326,7 @@ trait DatabasesBase
$this->assertEquals(16, $string['body']['size']);
$this->assertEquals('default', $string['body']['default']);
$this->assertEquals(201, $email['headers']['status-code']);
$this->assertEquals(202, $email['headers']['status-code']);
$this->assertEquals('email', $email['body']['key']);
$this->assertEquals('string', $email['body']['type']);
$this->assertEquals(false, $email['body']['required']);
@@ -334,7 +334,7 @@ trait DatabasesBase
$this->assertEquals('email', $email['body']['format']);
$this->assertEquals('default@example.com', $email['body']['default']);
$this->assertEquals(201, $enum['headers']['status-code']);
$this->assertEquals(202, $enum['headers']['status-code']);
$this->assertEquals('enum', $enum['body']['key']);
$this->assertEquals('string', $enum['body']['type']);
$this->assertEquals(false, $enum['body']['required']);
@@ -344,7 +344,7 @@ trait DatabasesBase
$this->assertIsArray($enum['body']['elements']);
$this->assertEquals(['yes', 'no', 'maybe'], $enum['body']['elements']);
$this->assertEquals(201, $ip['headers']['status-code']);
$this->assertEquals(202, $ip['headers']['status-code']);
$this->assertEquals('ip', $ip['body']['key']);
$this->assertEquals('string', $ip['body']['type']);
$this->assertEquals(false, $ip['body']['required']);
@@ -352,7 +352,7 @@ trait DatabasesBase
$this->assertEquals('ip', $ip['body']['format']);
$this->assertEquals('192.0.2.0', $ip['body']['default']);
$this->assertEquals(201, $url['headers']['status-code']);
$this->assertEquals(202, $url['headers']['status-code']);
$this->assertEquals('url', $url['body']['key']);
$this->assertEquals('string', $url['body']['type']);
$this->assertEquals(false, $url['body']['required']);
@@ -360,7 +360,7 @@ trait DatabasesBase
$this->assertEquals('url', $url['body']['format']);
$this->assertEquals('http://example.com', $url['body']['default']);
$this->assertEquals(201, $integer['headers']['status-code']);
$this->assertEquals(202, $integer['headers']['status-code']);
$this->assertEquals('integer', $integer['body']['key']);
$this->assertEquals('integer', $integer['body']['type']);
$this->assertEquals(false, $integer['body']['required']);
@@ -369,7 +369,7 @@ trait DatabasesBase
$this->assertEquals(5, $integer['body']['max']);
$this->assertEquals(3, $integer['body']['default']);
$this->assertEquals(201, $float['headers']['status-code']);
$this->assertEquals(202, $float['headers']['status-code']);
$this->assertEquals('float', $float['body']['key']);
$this->assertEquals('double', $float['body']['type']);
$this->assertEquals(false, $float['body']['required']);
@@ -378,7 +378,7 @@ trait DatabasesBase
$this->assertEquals(5.5, $float['body']['max']);
$this->assertEquals(3.5, $float['body']['default']);
$this->assertEquals(201, $boolean['headers']['status-code']);
$this->assertEquals(202, $boolean['headers']['status-code']);
$this->assertEquals('boolean', $boolean['body']['key']);
$this->assertEquals('boolean', $boolean['body']['type']);
$this->assertEquals(false, $boolean['body']['required']);
@@ -705,7 +705,7 @@ trait DatabasesBase
'attributes' => ['title'],
]);
$this->assertEquals(201, $titleIndex['headers']['status-code']);
$this->assertEquals(202, $titleIndex['headers']['status-code']);
$this->assertEquals('titleIndex', $titleIndex['body']['key']);
$this->assertEquals('fulltext', $titleIndex['body']['type']);
$this->assertCount(1, $titleIndex['body']['attributes']);
@@ -721,7 +721,7 @@ trait DatabasesBase
'attributes' => ['releaseYear'],
]);
$this->assertEquals(201, $releaseYearIndex['headers']['status-code']);
$this->assertEquals(202, $releaseYearIndex['headers']['status-code']);
$this->assertEquals('releaseYear', $releaseYearIndex['body']['key']);
$this->assertEquals('key', $releaseYearIndex['body']['type']);
$this->assertCount(1, $releaseYearIndex['body']['attributes']);
@@ -737,7 +737,7 @@ trait DatabasesBase
'attributes' => ['releaseYear', '$createdAt', '$updatedAt'],
]);
$this->assertEquals(201, $releaseWithDate['headers']['status-code']);
$this->assertEquals(202, $releaseWithDate['headers']['status-code']);
$this->assertEquals('releaseYearDated', $releaseWithDate['body']['key']);
$this->assertEquals('key', $releaseWithDate['body']['type']);
$this->assertCount(3, $releaseWithDate['body']['attributes']);
@@ -1689,15 +1689,15 @@ trait DatabasesBase
'default' => 'NORTH'
]);
$this->assertEquals(201, $email['headers']['status-code']);
$this->assertEquals(201, $ip['headers']['status-code']);
$this->assertEquals(201, $url['headers']['status-code']);
$this->assertEquals(201, $range['headers']['status-code']);
$this->assertEquals(201, $floatRange['headers']['status-code']);
$this->assertEquals(201, $probability['headers']['status-code']);
$this->assertEquals(201, $upperBound['headers']['status-code']);
$this->assertEquals(201, $lowerBound['headers']['status-code']);
$this->assertEquals(201, $enum['headers']['status-code']);
$this->assertEquals(202, $email['headers']['status-code']);
$this->assertEquals(202, $ip['headers']['status-code']);
$this->assertEquals(202, $url['headers']['status-code']);
$this->assertEquals(202, $range['headers']['status-code']);
$this->assertEquals(202, $floatRange['headers']['status-code']);
$this->assertEquals(202, $probability['headers']['status-code']);
$this->assertEquals(202, $upperBound['headers']['status-code']);
$this->assertEquals(202, $lowerBound['headers']['status-code']);
$this->assertEquals(202, $enum['headers']['status-code']);
$this->assertEquals(400, $invalidRange['headers']['status-code']);
$this->assertEquals(400, $defaultArray['headers']['status-code']);
$this->assertEquals(400, $defaultRequired['headers']['status-code']);
@@ -2144,7 +2144,7 @@ trait DatabasesBase
'required' => true,
]);
$this->assertEquals(201, $attribute['headers']['status-code'], 201);
$this->assertEquals(202, $attribute['headers']['status-code'], 202);
$this->assertEquals('attribute', $attribute['body']['key']);
// wait for db to add attribute
@@ -2160,7 +2160,7 @@ trait DatabasesBase
'attributes' => [$attribute['body']['key']],
]);
$this->assertEquals(201, $index['headers']['status-code']);
$this->assertEquals(202, $index['headers']['status-code']);
$this->assertEquals('key_attribute', $index['body']['key']);
// wait for db to add attribute
@@ -2292,7 +2292,7 @@ trait DatabasesBase
'attributes' => ['title'],
]);
$this->assertEquals($uniqueIndex['headers']['status-code'], 201);
$this->assertEquals($uniqueIndex['headers']['status-code'], 202);
sleep(2);
@@ -2464,7 +2464,7 @@ trait DatabasesBase
'required' => true,
]);
$this->assertEquals($title['headers']['status-code'], 201);
$this->assertEquals($title['headers']['status-code'], 202);
// wait for database worker to create attributes
sleep(2);
@@ -63,7 +63,7 @@ class DatabasesCustomClientTest extends Scope
'size' => 255,
'required' => true,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals(202, $response['headers']['status-code']);
// Wait for database worker to finish creating attributes
sleep(2);
@@ -618,8 +618,8 @@ class DatabasesCustomServerTest extends Scope
'required' => true,
]);
$this->assertEquals(201, $attribute1['headers']['status-code']);
$this->assertEquals(201, $attribute2['headers']['status-code']);
$this->assertEquals(202, $attribute1['headers']['status-code']);
$this->assertEquals(202, $attribute2['headers']['status-code']);
$this->assertEquals('attribute1', $attribute1['body']['key']);
$this->assertEquals('attribute2', $attribute2['body']['key']);
@@ -646,8 +646,8 @@ class DatabasesCustomServerTest extends Scope
'attributes' => ['attribute2'],
]);
$this->assertEquals(201, $index1['headers']['status-code']);
$this->assertEquals(201, $index2['headers']['status-code']);
$this->assertEquals(202, $index1['headers']['status-code']);
$this->assertEquals(202, $index2['headers']['status-code']);
$this->assertEquals('index1', $index1['body']['key']);
$this->assertEquals('index2', $index2['body']['key']);
@@ -742,8 +742,8 @@ class DatabasesCustomServerTest extends Scope
'required' => true,
]);
$this->assertEquals(201, $attribute1['headers']['status-code']);
$this->assertEquals(201, $attribute2['headers']['status-code']);
$this->assertEquals(202, $attribute1['headers']['status-code']);
$this->assertEquals(202, $attribute2['headers']['status-code']);
$this->assertEquals('attribute1', $attribute1['body']['key']);
$this->assertEquals('attribute2', $attribute2['body']['key']);
@@ -770,8 +770,8 @@ class DatabasesCustomServerTest extends Scope
'attributes' => ['attribute2'],
]);
$this->assertEquals(201, $index1['headers']['status-code']);
$this->assertEquals(201, $index2['headers']['status-code']);
$this->assertEquals(202, $index1['headers']['status-code']);
$this->assertEquals(202, $index2['headers']['status-code']);
$this->assertEquals('index1', $index1['body']['key']);
$this->assertEquals('index2', $index2['body']['key']);
@@ -980,7 +980,7 @@ class DatabasesCustomServerTest extends Scope
'required' => true,
]);
$this->assertEquals($attribute['headers']['status-code'], 201);
$this->assertEquals($attribute['headers']['status-code'], 202);
}
sleep(5);
@@ -1043,7 +1043,7 @@ class DatabasesCustomServerTest extends Scope
'required' => true,
]);
$this->assertEquals($attribute['headers']['status-code'], 201);
$this->assertEquals($attribute['headers']['status-code'], 202);
}
sleep(20);
@@ -1080,7 +1080,7 @@ class DatabasesCustomServerTest extends Scope
'attributes' => ["attribute{$i}"],
]);
$this->assertEquals(201, $index['headers']['status-code']);
$this->assertEquals(202, $index['headers']['status-code']);
$this->assertEquals("key_attribute{$i}", $index['body']['key']);
}
@@ -77,7 +77,7 @@ class DatabasesPermissionsMemberTest extends Scope
'size' => 256,
'required' => true,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals(202, $response['headers']['status-code']);
$private = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $this->getServerHeader(), [
'collectionId' => 'unique()',
@@ -95,7 +95,7 @@ class DatabasesPermissionsMemberTest extends Scope
'size' => 256,
'required' => true,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals(202, $response['headers']['status-code']);
sleep(2);
@@ -89,7 +89,7 @@ class FunctionsCustomClientTest extends Scope
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Wait for deployment to be built.
sleep(10);
@@ -118,7 +118,7 @@ class FunctionsCustomClientTest extends Scope
'async' => true,
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [
@@ -179,7 +179,7 @@ class FunctionsCustomClientTest extends Scope
// Wait for deployment to be built.
sleep(5);
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
$function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
'content-type' => 'application/json',
@@ -196,7 +196,7 @@ class FunctionsCustomClientTest extends Scope
'data' => 'foobar',
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@@ -271,7 +271,7 @@ class FunctionsCustomClientTest extends Scope
'data' => 'foobar',
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
sleep(10);
@@ -361,7 +361,7 @@ class FunctionsCustomClientTest extends Scope
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Wait for deployment to be built.
sleep(10);
@@ -292,7 +292,7 @@ class FunctionsCustomServerTest extends Scope
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$this->assertIsInt($deployment['body']['$createdAt']);
$this->assertEquals('index.php', $deployment['body']['entrypoint']);
@@ -341,7 +341,7 @@ class FunctionsCustomServerTest extends Scope
}
@fclose($handle);
$this->assertEquals(201, $largeTag['headers']['status-code']);
$this->assertEquals(202, $largeTag['headers']['status-code']);
$this->assertNotEmpty($largeTag['body']['$id']);
$this->assertIsInt($largeTag['body']['$createdAt']);
$this->assertEquals('index.php', $largeTag['body']['entrypoint']);
@@ -484,7 +484,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$this->assertNotEmpty($execution['body']['$id']);
$this->assertNotEmpty($execution['body']['functionId']);
$this->assertIsInt($execution['body']['$createdAt']);
@@ -733,7 +733,7 @@ class FunctionsCustomServerTest extends Scope
'activate' => true,
]);
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(20);
@@ -747,7 +747,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
sleep(10);
@@ -818,7 +818,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(10);
@@ -839,7 +839,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@@ -930,7 +930,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(10);
@@ -944,7 +944,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@@ -1035,7 +1035,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(30);
@@ -1049,7 +1049,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@@ -1140,7 +1140,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(40);
@@ -1154,7 +1154,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@@ -1245,7 +1245,7 @@ class FunctionsCustomServerTest extends Scope
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(201, $deployment['headers']['status-code']);
$this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(30);
@@ -1259,7 +1259,7 @@ class FunctionsCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
@@ -1350,7 +1350,7 @@ class FunctionsCustomServerTest extends Scope
// ]);
// $deploymentId = $deployment['body']['$id'] ?? '';
// $this->assertEquals(201, $deployment['headers']['status-code']);
// $this->assertEquals(202, $deployment['headers']['status-code']);
// // Allow (slow) build step to run
// sleep(300);
@@ -1364,7 +1364,7 @@ class FunctionsCustomServerTest extends Scope
// $executionId = $execution['body']['$id'] ?? '';
// $this->assertEquals(201, $execution['headers']['status-code']);
// $this->assertEquals(202, $execution['headers']['status-code']);
// $executionId = $execution['body']['$id'] ?? '';
@@ -177,7 +177,7 @@ class RealtimeConsoleClientTest extends Scope
$attributeKey = $name['body']['key'];
$this->assertEquals($name['headers']['status-code'], 201);
$this->assertEquals($name['headers']['status-code'], 202);
$this->assertEquals($name['body']['key'], 'name');
$this->assertEquals($name['body']['type'], 'string');
$this->assertEquals($name['body']['size'], 256);
@@ -266,7 +266,7 @@ class RealtimeConsoleClientTest extends Scope
],
]);
$this->assertEquals($index['headers']['status-code'], 201);
$this->assertEquals($index['headers']['status-code'], 202);
$indexKey = $index['body']['key'];
$response = json_decode($client->receive(), true);
@@ -661,7 +661,7 @@ class RealtimeCustomClientTest extends Scope
'required' => true,
]);
$this->assertEquals($name['headers']['status-code'], 201);
$this->assertEquals($name['headers']['status-code'], 202);
$this->assertEquals($name['body']['key'], 'name');
$this->assertEquals($name['body']['type'], 'string');
$this->assertEquals($name['body']['size'], 256);
@@ -871,7 +871,7 @@ class RealtimeCustomClientTest extends Scope
'required' => true,
]);
$this->assertEquals($name['headers']['status-code'], 201);
$this->assertEquals($name['headers']['status-code'], 202);
$this->assertEquals($name['body']['key'], 'name');
$this->assertEquals($name['body']['type'], 'string');
$this->assertEquals($name['body']['size'], 256);
@@ -1222,7 +1222,7 @@ class RealtimeCustomClientTest extends Scope
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals($deployment['headers']['status-code'], 201);
$this->assertEquals($deployment['headers']['status-code'], 202);
$this->assertNotEmpty($deployment['body']['$id']);
// Wait for deployment to be built.
@@ -1242,7 +1242,7 @@ class RealtimeCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), []);
$this->assertEquals($execution['headers']['status-code'], 201);
$this->assertEquals($execution['headers']['status-code'], 202);
$this->assertNotEmpty($execution['body']['$id']);
$response = json_decode($client->receive(), true);
@@ -107,6 +107,8 @@ trait TeamsBaseClient
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertNotEmpty($response['body']['userId']);
$this->assertEquals($name, $response['body']['userName']);
$this->assertEquals($email, $response['body']['userEmail']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertNotEmpty($response['body']['teamName']);
$this->assertCount(2, $response['body']['roles']);
@@ -57,6 +57,8 @@ trait TeamsBaseServer
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertNotEmpty($response['body']['userId']);
$this->assertEquals('Friend User', $response['body']['userName']);
$this->assertEquals($email, $response['body']['userEmail']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertCount(2, $response['body']['roles']);
$this->assertIsInt($response['body']['joined']);
+3 -3
View File
@@ -115,11 +115,11 @@ trait WebhooksBase
$attributeId = $extra['body']['key'];
$this->assertEquals($firstName['headers']['status-code'], 201);
$this->assertEquals($firstName['headers']['status-code'], 202);
$this->assertEquals($firstName['body']['key'], 'firstName');
$this->assertEquals($lastName['headers']['status-code'], 201);
$this->assertEquals($lastName['headers']['status-code'], 202);
$this->assertEquals($lastName['body']['key'], 'lastName');
$this->assertEquals($extra['headers']['status-code'], 201);
$this->assertEquals($extra['headers']['status-code'], 202);
$this->assertEquals($extra['body']['key'], 'extra');
// wait for database worker to kick in
@@ -82,7 +82,7 @@ class WebhooksCustomServerTest extends Scope
]);
$indexKey = $index['body']['key'];
$this->assertEquals($index['headers']['status-code'], 201);
$this->assertEquals($index['headers']['status-code'], 202);
$this->assertEquals($index['body']['key'], 'fullname');
// wait for database worker to create index
@@ -505,7 +505,7 @@ class WebhooksCustomServerTest extends Scope
$id = $data['functionId'] ?? '';
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals($deployment['headers']['status-code'], 201);
$this->assertEquals($deployment['headers']['status-code'], 202);
$this->assertNotEmpty($deployment['body']['$id']);
$webhook = $this->getLastRequest();
@@ -595,7 +595,7 @@ class WebhooksCustomServerTest extends Scope
$executionId = $execution['body']['$id'] ?? '';
$this->assertEquals($execution['headers']['status-code'], 201);
$this->assertEquals($execution['headers']['status-code'], 202);
$this->assertNotEmpty($execution['body']['$id']);
$webhook = $this->getLastRequest();