Merge branch '1.8.x' of https://github.com/appwrite/appwrite into get-sequence-null

# Conflicts:
#	composer.json
#	composer.lock
This commit is contained in:
fogelito
2025-08-11 13:31:02 +03:00
78 changed files with 682 additions and 368 deletions
+2
View File
@@ -21,6 +21,7 @@ _APP_OPTIONS_ROUTER_PROTECTION=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DNS=8.8.8.8
_APP_DOMAIN=traefik
_APP_CONSOLE_DOMAIN=localhost
_APP_DOMAIN_FUNCTIONS=functions.localhost
@@ -28,6 +29,7 @@ _APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET_CNAME=test.localhost
_APP_DOMAIN_TARGET_A=127.0.0.1
_APP_DOMAIN_TARGET_AAAA=::1
_APP_DOMAIN_TARGET_CAA=digicert.com
_APP_RULES_FORMAT=md5
_APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
+8 -8
View File
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:0.10.1 AS final
FROM appwrite/base:0.10.3 AS final
LABEL maintainer="team@appwrite.io"
@@ -50,13 +50,13 @@ RUN mkdir -p /storage/uploads && \
mkdir -p /storage/certificates && \
mkdir -p /storage/functions && \
mkdir -p /storage/debug && \
chown -Rf www-data.www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \
chown -Rf www-data.www-data /storage/imports && chmod -Rf 0755 /storage/imports && \
chown -Rf www-data.www-data /storage/cache && chmod -Rf 0755 /storage/cache && \
chown -Rf www-data.www-data /storage/config && chmod -Rf 0755 /storage/config && \
chown -Rf www-data.www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \
chown -Rf www-data.www-data /storage/functions && chmod -Rf 0755 /storage/functions && \
chown -Rf www-data.www-data /storage/debug && chmod -Rf 0755 /storage/debug
chown -Rf www-data:www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \
chown -Rf www-data:www-data /storage/imports && chmod -Rf 0755 /storage/imports && \
chown -Rf www-data:www-data /storage/cache && chmod -Rf 0755 /storage/cache && \
chown -Rf www-data:www-data /storage/config && chmod -Rf 0755 /storage/config && \
chown -Rf www-data:www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \
chown -Rf www-data:www-data /storage/functions && chmod -Rf 0755 /storage/functions && \
chown -Rf www-data:www-data /storage/debug && chmod -Rf 0755 /storage/debug
# Executables
RUN chmod +x /usr/local/bin/doctor && \
+2 -2
View File
@@ -116,7 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '8.2.0',
'version' => '8.2.2',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -413,7 +413,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '9.1.0',
'version' => '9.1.2',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
+6 -3
View File
@@ -8479,17 +8479,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
+13 -3
View File
@@ -37978,17 +37978,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
@@ -44635,6 +44638,11 @@
"description": "AAAA target for your Appwrite custom domains.",
"x-example": "::1"
},
"_APP_DOMAIN_TARGET_CAA": {
"type": "string",
"description": "CAA target for your Appwrite custom domains.",
"x-example": "digicert.com"
},
"_APP_STORAGE_LIMIT": {
"type": "integer",
"description": "Maximum file size allowed for file upload in bytes.",
@@ -44692,6 +44700,7 @@
"_APP_DOMAIN_TARGET_CNAME",
"_APP_DOMAIN_TARGET_A",
"_APP_DOMAIN_TARGET_AAAA",
"_APP_DOMAIN_TARGET_CAA",
"_APP_STORAGE_LIMIT",
"_APP_COMPUTE_SIZE_LIMIT",
"_APP_USAGE_STATS",
@@ -44707,6 +44716,7 @@
"_APP_DOMAIN_TARGET_CNAME": "appwrite.io",
"_APP_DOMAIN_TARGET_A": "127.0.0.1",
"_APP_DOMAIN_TARGET_AAAA": "::1",
"_APP_DOMAIN_TARGET_CAA": "digicert.com",
"_APP_STORAGE_LIMIT": "30000000",
"_APP_COMPUTE_SIZE_LIMIT": "30000000",
"_APP_USAGE_STATS": "enabled",
+6 -3
View File
@@ -27864,17 +27864,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
@@ -8969,17 +8969,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
@@ -33744,17 +33744,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
+6 -3
View File
@@ -8516,17 +8516,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
+13 -3
View File
@@ -38139,17 +38139,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
@@ -44900,6 +44903,11 @@
"description": "AAAA target for your Appwrite custom domains.",
"x-example": "::1"
},
"_APP_DOMAIN_TARGET_CAA": {
"type": "string",
"description": "CAA target for your Appwrite custom domains.",
"x-example": "digicert.com"
},
"_APP_STORAGE_LIMIT": {
"type": "integer",
"description": "Maximum file size allowed for file upload in bytes.",
@@ -44957,6 +44965,7 @@
"_APP_DOMAIN_TARGET_CNAME",
"_APP_DOMAIN_TARGET_A",
"_APP_DOMAIN_TARGET_AAAA",
"_APP_DOMAIN_TARGET_CAA",
"_APP_STORAGE_LIMIT",
"_APP_COMPUTE_SIZE_LIMIT",
"_APP_USAGE_STATS",
@@ -44972,6 +44981,7 @@
"_APP_DOMAIN_TARGET_CNAME": "appwrite.io",
"_APP_DOMAIN_TARGET_A": "127.0.0.1",
"_APP_DOMAIN_TARGET_AAAA": "::1",
"_APP_DOMAIN_TARGET_CAA": "digicert.com",
"_APP_STORAGE_LIMIT": "30000000",
"_APP_COMPUTE_SIZE_LIMIT": "30000000",
"_APP_USAGE_STATS": "enabled",
+6 -3
View File
@@ -28092,17 +28092,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
+6 -3
View File
@@ -8999,17 +8999,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
+12 -3
View File
@@ -44308,17 +44308,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
@@ -50232,6 +50235,11 @@
"description": "AAAA target for your Appwrite custom domains.",
"x-example": "::1"
},
"_APP_DOMAIN_TARGET_CAA": {
"type": "string",
"description": "CAA target for your Appwrite custom domains.",
"x-example": "digicert.com"
},
"_APP_STORAGE_LIMIT": {
"type": "integer",
"description": "Maximum file size allowed for file upload in bytes.",
@@ -50289,6 +50297,7 @@
"_APP_DOMAIN_TARGET_CNAME",
"_APP_DOMAIN_TARGET_A",
"_APP_DOMAIN_TARGET_AAAA",
"_APP_DOMAIN_TARGET_CAA",
"_APP_STORAGE_LIMIT",
"_APP_COMPUTE_SIZE_LIMIT",
"_APP_USAGE_STATS",
+6 -3
View File
@@ -33889,17 +33889,20 @@
"type": "integer",
"description": "Document automatically incrementing ID.",
"x-example": 1,
"format": "int32"
"format": "int32",
"readOnly": true
},
"$collectionId": {
"type": "string",
"description": "Collection ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$databaseId": {
"type": "string",
"description": "Database ID.",
"x-example": "5e5ea5c15117e"
"x-example": "5e5ea5c15117e",
"readOnly": true
},
"$createdAt": {
"type": "string",
+18
View File
@@ -151,6 +151,24 @@ return [
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DOMAIN_TARGET_CAA',
'description' => 'A CAA record domain that can be used to validate custom domains. Value should be domain\'s hostname.',
'introduction' => '',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DNS',
'description' => 'DNS server to use for domain validation. Default: 8.8.8.8',
'introduction' => '',
'default' => '8.8.8.8',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_CONSOLE_WHITELIST_ROOT',
'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by inviting them to your project. By default this option is enabled.',
+2
View File
@@ -71,6 +71,8 @@ App::get('/v1/console/variables')
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
// Combine CAA domain with most common flags and tag (no parameters)
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
+13
View File
@@ -286,6 +286,19 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
}
// Ensure CAA won't block certificate issuance
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
$validationStart = \microtime(true);
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
if (!$validator->isValid($domain->get())) {
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
$log->addTag('dnsDomain', $domain->get());
$error = $validator->getDescription();
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
throw new Exception(Exception::RULE_VERIFICATION_FAILED, 'Domain verification failed because CAA records do not allow Appwrite\'s certificate issuer.');
}
}
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
// Issue a TLS certificate when domain is verified
+10 -2
View File
@@ -1038,11 +1038,19 @@ App::init()
/**
* Deprecation Warning
*/
/** @var \Appwrite\SDK\Method $sdk */
$sdk = $route->getLabel('sdk', false);
$deprecationWarning = 'This route is deprecated. See the updated documentation for improved compatibility and migration details.';
$sdkItems = is_array($sdk) ? $sdk : (!empty($sdk) ? [$sdk] : []);
foreach ($sdkItems as $sdkItem) {
if ($sdkItem->isDeprecated()) {
if (!empty($sdkItems) && count($sdkItems) > 0) {
$allDeprecated = true;
foreach ($sdkItems as $sdkItem) {
if (!$sdkItem->isDeprecated()) {
$allDeprecated = false;
break;
}
}
if ($allDeprecated) {
$warnings[] = $deprecationWarning;
}
}
+8
View File
@@ -95,6 +95,8 @@ $image = $this->getParam('image', '');
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -472,6 +474,8 @@ $image = $this->getParam('image', '');
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
@@ -629,6 +633,8 @@ $image = $this->getParam('image', '');
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_EMAIL_SECURITY
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -660,6 +666,8 @@ $image = $this->getParam('image', '');
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
+1
View File
@@ -55,6 +55,7 @@
"utopia-php/database": "dev-get-sequence-default as 0.77.3",
"utopia-php/detector": "0.1.*",
"utopia-php/domains": "0.8.*",
"utopia-php/dns": "0.3.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.4.*",
Generated
+92 -81
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": "5bdc8315d4b8a0d8004905a08c72336c",
"content-hash": "72786a5d87326f396250f18d725dea23",
"packages": [
{
"name": "adhocore/jwt",
@@ -3545,16 +3545,16 @@
},
{
"name": "utopia-php/database",
"version": "dev-get-sequence-default",
"version": "0.77.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "a50279437a3ce76e610c58a9988a0074c9f80c2e"
"reference": "a8ab3a7b25a5a1edf7e7afc83eda9f7aec5ba57d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/a50279437a3ce76e610c58a9988a0074c9f80c2e",
"reference": "a50279437a3ce76e610c58a9988a0074c9f80c2e",
"url": "https://api.github.com/repos/utopia-php/database/zipball/a8ab3a7b25a5a1edf7e7afc83eda9f7aec5ba57d",
"reference": "a8ab3a7b25a5a1edf7e7afc83eda9f7aec5ba57d",
"shasum": ""
},
"require": {
@@ -3595,9 +3595,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/get-sequence-default"
"source": "https://github.com/utopia-php/database/tree/0.77.0"
},
"time": "2025-08-11T10:20:16+00:00"
"time": "2025-08-07T09:30:32+00:00"
},
{
"name": "utopia-php/detector",
@@ -3644,6 +3644,62 @@
},
"time": "2025-05-19T11:01:28+00:00"
},
{
"name": "utopia-php/dns",
"version": "0.3.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/dns.git",
"reference": "8fd4161bc3a8021a670c1101b40f6b09a97f1a54"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/dns/zipball/8fd4161bc3a8021a670c1101b40f6b09a97f1a54",
"reference": "8fd4161bc3a8021a670c1101b40f6b09a97f1a54",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/cli": "0.15.*",
"utopia-php/telemetry": "^0.1.1"
},
"require-dev": {
"laravel/pint": "1.2.*",
"phpstan/phpstan": "1.8.*",
"phpunit/phpunit": "^9.3",
"rregeer/phpunit-coverage-check": "^0.3.1",
"swoole/ide-helper": "4.6.6"
},
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\DNS\\": "src/DNS"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Eldad Fux",
"email": "eldad@appwrite.io"
}
],
"description": "Lite & fast micro PHP DNS server abstraction that is **easy to use**.",
"keywords": [
"dns",
"framework",
"php",
"upf",
"utopia"
],
"support": {
"issues": "https://github.com/utopia-php/dns/issues",
"source": "https://github.com/utopia-php/dns/tree/0.3.0"
},
"time": "2025-08-04T11:05:53+00:00"
},
{
"name": "utopia-php/domains",
"version": "0.8.0",
@@ -6091,16 +6147,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.6.24",
"version": "9.6.23",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "ea49afa29aeea25ea7bf9de9fdd7cab163cc0701"
"reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea49afa29aeea25ea7bf9de9fdd7cab163cc0701",
"reference": "ea49afa29aeea25ea7bf9de9fdd7cab163cc0701",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95",
"reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95",
"shasum": ""
},
"require": {
@@ -6111,7 +6167,7 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.13.4",
"myclabs/deep-copy": "^1.13.1",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=7.3",
@@ -6122,11 +6178,11 @@
"phpunit/php-timer": "^5.0.3",
"sebastian/cli-parser": "^1.0.2",
"sebastian/code-unit": "^1.0.8",
"sebastian/comparator": "^4.0.9",
"sebastian/comparator": "^4.0.8",
"sebastian/diff": "^4.0.6",
"sebastian/environment": "^5.1.5",
"sebastian/exporter": "^4.0.6",
"sebastian/global-state": "^5.0.8",
"sebastian/global-state": "^5.0.7",
"sebastian/object-enumerator": "^4.0.4",
"sebastian/resource-operations": "^3.0.4",
"sebastian/type": "^3.2.1",
@@ -6174,7 +6230,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.24"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23"
},
"funding": [
{
@@ -6198,7 +6254,7 @@
"type": "tidelift"
}
],
"time": "2025-08-10T08:32:42+00:00"
"time": "2025-05-02T06:40:34+00:00"
},
{
"name": "psr/cache",
@@ -6418,16 +6474,16 @@
},
{
"name": "sebastian/comparator",
"version": "4.0.9",
"version": "4.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5"
"reference": "fa0f136dd2334583309d32b62544682ee972b51a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
"reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a",
"reference": "fa0f136dd2334583309d32b62544682ee972b51a",
"shasum": ""
},
"require": {
@@ -6480,27 +6536,15 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9"
"source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
"type": "tidelift"
}
],
"time": "2025-08-10T06:51:50+00:00"
"time": "2022-09-14T12:41:17+00:00"
},
{
"name": "sebastian/complexity",
@@ -6767,16 +6811,16 @@
},
{
"name": "sebastian/global-state",
"version": "5.0.8",
"version": "5.0.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6"
"reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
"reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
"reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
"shasum": ""
},
"require": {
@@ -6819,27 +6863,15 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8"
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
"type": "tidelift"
}
],
"time": "2025-08-10T07:10:35+00:00"
"time": "2024-03-02T06:35:11+00:00"
},
{
"name": "sebastian/lines-of-code",
@@ -7012,16 +7044,16 @@
},
{
"name": "sebastian/recursion-context",
"version": "4.0.6",
"version": "4.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "539c6691e0623af6dc6f9c20384c120f963465a0"
"reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0",
"reference": "539c6691e0623af6dc6f9c20384c120f963465a0",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
"reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
"shasum": ""
},
"require": {
@@ -7063,27 +7095,15 @@
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6"
"source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
"type": "tidelift"
}
],
"time": "2025-08-10T06:57:39+00:00"
"time": "2023-02-03T06:07:39+00:00"
},
{
"name": "sebastian/resource-operations",
@@ -8345,18 +8365,9 @@
"time": "2024-03-07T20:33:40+00:00"
}
],
"aliases": [
{
"package": "utopia-php/database",
"version": "dev-get-sequence-default",
"alias": "0.77.3",
"alias_normalized": "0.77.3.0"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"utopia-php/database": 20
},
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
+8
View File
@@ -120,6 +120,8 @@ services:
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -535,6 +537,8 @@ services:
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
@@ -704,6 +708,8 @@ services:
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_EMAIL_SECURITY
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -738,6 +744,8 @@ services:
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
@@ -15,7 +15,7 @@ functions.createExecution(
"<PATH>", // path (optional)
ExecutionMethod.GET, // method (optional)
mapOf( "a" to "b" ), // headers (optional)
"", // scheduledAt (optional)
"<SCHEDULED_AT>", // scheduledAt (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -15,5 +15,5 @@ val result = functions.createExecution(
path = "<PATH>", // (optional)
method = ExecutionMethod.GET, // (optional)
headers = mapOf( "a" to "b" ), // (optional)
scheduledAt = "", // (optional)
scheduledAt = "<SCHEDULED_AT>", // (optional)
)
@@ -16,7 +16,7 @@ functions.createExecution(
"<PATH>", // path (optional)
ExecutionMethod.GET, // method (optional)
mapOf( "a" to "b" ), // headers (optional)
"", // scheduledAt (optional)
"<SCHEDULED_AT>", // scheduledAt (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -16,5 +16,5 @@ val response = functions.createExecution(
path = "<PATH>", // optional
method = "GET", // optional
headers = mapOf( "a" to "b" ), // optional
scheduledAt = "" // optional
scheduledAt = "<SCHEDULED_AT>" // optional
)
+62 -25
View File
@@ -2,24 +2,38 @@
namespace Appwrite\Network\Validator;
use Utopia\DNS\Client;
use Utopia\Domains\Domain;
use Utopia\System\System;
use Utopia\Validator;
class DNS extends Validator
{
public const RECORD_A = 'a';
public const RECORD_AAAA = 'aaaa';
public const RECORD_CNAME = 'cname';
public const RECORD_A = 'A';
public const RECORD_AAAA = 'AAAA';
public const RECORD_CNAME = 'CNAME';
public const RECORD_CAA = 'CAA'; // You can provide domain only (as $target) for CAA validation
/**
* @var mixed
*/
protected mixed $logs;
/**
* @var string
*/
protected string $dnsServer;
/**
* @param string $target
*/
public function __construct(protected string $target, protected string $type = self::RECORD_CNAME)
public function __construct(protected string $target, protected string $type = self::RECORD_CNAME, string $dnsServer = '')
{
if (empty($dnsServer)) {
$dnsServer = System::getEnv('_APP_DNS', '8.8.8.8');
}
$this->dnsServer = $dnsServer;
}
/**
@@ -42,42 +56,65 @@ class DNS extends Validator
* Check if DNS record value matches specific value
*
* @param mixed $domain
*
* @return bool
*/
public function isValid($value): bool
{
$typeNative = match ($this->type) {
self::RECORD_A => DNS_A,
self::RECORD_AAAA => DNS_AAAA,
self::RECORD_CNAME => DNS_CNAME,
default => throw new \Exception('Record type not supported.')
};
$dnsKey = match ($this->type) {
self::RECORD_A => 'ip',
self::RECORD_AAAA => 'ipv6',
self::RECORD_CNAME => 'target',
default => throw new \Exception('Record type not supported.')
};
if (!is_string($value)) {
return false;
}
$dns = new Client($this->dnsServer);
try {
$records = \dns_get_record($value, $typeNative);
$this->logs = $records;
} catch (\Throwable $th) {
$rawQuery = $dns->query($value, $this->type);
// Some DNS servers return all records, not only type that's asked for
// Likely occurs when no records of specific type are found
$query = array_filter($rawQuery, function ($record) {
return $record->getTypeName() === $this->type;
});
$this->logs = $query;
} catch (\Exception $e) {
$this->logs = ['error' => $e->getMessage()];
return false;
}
if (!$records) {
if (empty($query)) {
// CAA records inherit from parent (custom CAA behaviour)
if ($this->type === self::RECORD_CAA) {
$domain = new Domain($value);
if ($domain->get() === $domain->getApex()) {
return true; // No CAA on apex domain means anyone can issue certificate
}
// Recursive validation by parent domain
$parts = \explode('.', $value);
\array_shift($parts);
$parentDomain = \implode('.', $parts);
$validator = new DNS($this->target, DNS::RECORD_CAA, $this->dnsServer);
return $validator->isValid($parentDomain);
}
return false;
}
foreach ($records as $record) {
if (isset($record[$dnsKey]) && $record[$dnsKey] === $this->target) {
foreach ($query as $record) {
// CAA validation only needs to ensure domain
if ($this->type === self::RECORD_CAA) {
// Extract domain; comments showcase extraction steps in most complex scenario
$rdata = $record->getRdata(); // 255 issuewild "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
$rdata = \explode(' ', $rdata, 3)[2] ?? ''; // "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
$rdata = \trim($rdata, '"'); // certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600
$rdata = \explode(';', $rdata, 2)[0] ?? ''; // certainly.com
if ($rdata === $this->target) {
return true;
}
}
if ($record->getRdata() === $this->target) {
return true;
}
}
@@ -106,8 +106,13 @@ class XList extends Action
]);
$audit = new Audit($dbForProject);
// getContext() => `document` or `row`.
$resource = 'database/' . $databaseId . '/collection/' . $collectionId . '/' .$this->getContext(). '/' . $document->getId();
$type = $this->getCollectionsEventsContext();
$context = $this->getContext();
$resource = match ($context) {
ROWS => "database/$databaseId/grid/$type/$collectionId/$context/{$document->getId()}",
default => "database/$databaseId/$type/$collectionId/$context/{$document->getId()}",
};
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -103,7 +103,12 @@ class XList extends Action
]);
$audit = new Audit($dbForProject);
$resource = 'database/' . $databaseId . '/' . $this->getContext() . '/' . $collectionId;
$context = $this->getContext();
$resource = match ($context) {
TABLES => "database/$databaseId/grid/$context/$collectionId",
default => "database/$databaseId/$context/$collectionId",
};
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -35,7 +35,7 @@ class Create extends BooleanCreate
->label('scope', 'tables.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -37,7 +37,7 @@ class Update extends BooleanUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -37,7 +37,7 @@ class Create extends DatetimeCreate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -39,7 +39,7 @@ class Update extends DatetimeUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -36,7 +36,7 @@ class Delete extends AttributesDelete
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.delete')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -36,7 +36,7 @@ class Create extends EmailCreate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -38,7 +38,7 @@ class Update extends EmailUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -38,7 +38,7 @@ class Create extends EnumCreate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -40,7 +40,7 @@ class Update extends EnumUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -36,7 +36,7 @@ class Create extends FloatCreate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -38,7 +38,7 @@ class Update extends FloatUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -36,7 +36,7 @@ class Create extends IPCreate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -38,7 +38,7 @@ class Update extends IPUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -36,7 +36,7 @@ class Create extends IntegerCreate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -38,7 +38,7 @@ class Update extends IntegerUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -37,7 +37,7 @@ class Create extends RelationshipCreate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -37,7 +37,7 @@ class Update extends RelationshipUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -38,7 +38,7 @@ class Create extends StringCreate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -40,7 +40,7 @@ class Update extends StringUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -36,7 +36,7 @@ class Create extends URLCreate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -38,7 +38,7 @@ class Update extends URLUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -38,7 +38,7 @@ class Create extends CollectionCreate
->label('scope', 'tables.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'table.create')
->label('audits.resource', 'database/{request.databaseId}/table/{response.$id}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{response.$id}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: null,
@@ -34,7 +34,7 @@ class Delete extends CollectionDelete
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].delete')
->label('audits.event', 'table.delete')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: null,
@@ -40,7 +40,7 @@ class Create extends IndexCreate
->label('scope', 'tables.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'index.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -39,7 +39,7 @@ class Delete extends IndexDelete
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].update')
->label('audits.event', 'index.delete')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
@@ -35,7 +35,7 @@ class Delete extends DocumentsDelete
->label('scope', 'rows.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'rows.delete')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -36,7 +36,7 @@ class Update extends DocumentsUpdate
->label('scope', 'rows.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'rows.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -35,7 +35,7 @@ class Upsert extends DocumentsUpsert
->label('scope', 'rows.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'row.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -36,7 +36,7 @@ class Decrement extends DecrementDocumentAttribute
->label('scope', 'rows.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'rows.decrement')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -36,7 +36,7 @@ class Increment extends IncrementDocumentAttribute
->label('scope', 'rows.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'rows.increment')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -44,7 +44,7 @@ class Create extends DocumentCreate
->label('scope', 'rows.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'row.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -40,7 +40,7 @@ class Delete extends DocumentDelete
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].delete')
->label('audits.event', 'row.delete')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}/row/{request.rowId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}/row/{request.rowId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -37,7 +37,7 @@ class Update extends DocumentUpdate
->label('scope', 'rows.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'row.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}/row/{response.$id}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}/row/{response.$id}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -37,7 +37,7 @@ class Upsert extends DocumentUpsert
->label('scope', 'rows.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'row.upsert')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}/row/{response.$id}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}/row/{response.$id}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
@@ -37,7 +37,7 @@ class Update extends CollectionUpdate
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].update')
->label('audits.event', 'table.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: null,
@@ -337,6 +337,19 @@ class Certificates extends Action
throw new Exception('Failed to verify domain DNS records.');
}
// Ensure CAA won't block certificate issuance
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
$validationStart = \microtime(true);
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
if (!$validator->isValid($domain->get())) {
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
$log->addTag('dnsDomain', $domain->get());
$error = $validator->getDescription();
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
throw new Exception('Failed to verify domain DNS records. CAA records do not allow Appwrite\'s certificate issuer.');
}
}
} else {
// Main domain validation
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
@@ -715,6 +715,7 @@ class OpenAPI3 extends Format
break;
}
$readOnly = $rule['readOnly'] ?? false;
if ($rule['array']) {
$output['components']['schemas'][$model->getType()]['properties'][$name] = [
'type' => 'array',
@@ -728,6 +729,9 @@ class OpenAPI3 extends Format
if ($format) {
$output['components']['schemas'][$model->getType()]['properties'][$name]['items']['format'] = $format;
}
if ($readOnly) {
$output['components']['schemas'][$model->getType()]['properties'][$name]['readOnly'] = true;
}
} else {
$output['components']['schemas'][$model->getType()]['properties'][$name] = [
'type' => $type,
@@ -738,6 +742,9 @@ class OpenAPI3 extends Format
if ($format) {
$output['components']['schemas'][$model->getType()]['properties'][$name]['format'] = $format;
}
if ($readOnly) {
$output['components']['schemas'][$model->getType()]['properties'][$name]['readOnly'] = true;
}
}
if ($items) {
$output['components']['schemas'][$model->getType()]['properties'][$name]['items'] = $items;
@@ -724,6 +724,7 @@ class Swagger2 extends Format
break;
}
$readOnly = $rule['readOnly'] ?? false;
if ($rule['type'] == 'json') {
$output['definitions'][$model->getType()]['properties'][$name] = [
'type' => $type,
@@ -731,6 +732,10 @@ class Swagger2 extends Format
'description' => $rule['description'] ?? '',
'x-example' => $rule['example'] ?? null,
];
if ($readOnly) {
$output['definitions'][$model->getType()]['properties'][$name]['readOnly'] = true;
}
continue;
}
@@ -747,6 +752,9 @@ class Swagger2 extends Format
if ($format) {
$output['definitions'][$model->getType()]['properties'][$name]['items']['format'] = $format;
}
if ($readOnly) {
$output['definitions'][$model->getType()]['properties'][$name]['readOnly'] = true;
}
} else {
$output['definitions'][$model->getType()]['properties'][$name] = [
'type' => $type,
@@ -757,6 +765,9 @@ class Swagger2 extends Format
if ($format) {
$output['definitions'][$model->getType()]['properties'][$name]['format'] = $format;
}
if ($readOnly) {
$output['definitions'][$model->getType()]['properties'][$name]['readOnly'] = true;
}
}
if ($items) {
$output['definitions'][$model->getType()]['properties'][$name]['items'] = $items;
+23 -1
View File
@@ -91,7 +91,8 @@ abstract class Model
'array' => false,
'description' => '',
'example' => '',
'sensitive' => false
'sensitive' => false,
'readOnly' => false
], $options);
return $this;
@@ -129,6 +130,27 @@ abstract class Model
return $list;
}
/**
* Get Readonly Fields
*
* Returns list of field names that are marked as readOnly
* and should not be allowed in create/update payloads
*
* @return array
*/
public function getReadonlyFields(): array
{
$list = [];
foreach ($this->rules as $key => $rule) {
if ($rule['readOnly'] ?? false) {
$list[] = $key;
}
}
return $list;
}
/**
* Is None
*
@@ -10,24 +10,30 @@ class ConsoleVariables extends Model
public function __construct()
{
$this
->addRule('_APP_DOMAIN_TARGET_CNAME', [
'type' => self::TYPE_STRING,
'description' => 'CNAME target for your Appwrite custom domains.',
'default' => '',
'example' => 'appwrite.io',
])
->addRule('_APP_DOMAIN_TARGET_A', [
'type' => self::TYPE_STRING,
'description' => 'A target for your Appwrite custom domains.',
'default' => '',
'example' => '127.0.0.1',
])
->addRule('_APP_DOMAIN_TARGET_AAAA', [
'type' => self::TYPE_STRING,
'description' => 'AAAA target for your Appwrite custom domains.',
'default' => '',
'example' => '::1',
])
->addRule('_APP_DOMAIN_TARGET_CNAME', [
'type' => self::TYPE_STRING,
'description' => 'CNAME target for your Appwrite custom domains.',
'default' => '',
'example' => 'appwrite.io',
])
->addRule('_APP_DOMAIN_TARGET_A', [
'type' => self::TYPE_STRING,
'description' => 'A target for your Appwrite custom domains.',
'default' => '',
'example' => '127.0.0.1',
])
->addRule('_APP_DOMAIN_TARGET_AAAA', [
'type' => self::TYPE_STRING,
'description' => 'AAAA target for your Appwrite custom domains.',
'default' => '',
'example' => '::1',
])
->addRule('_APP_DOMAIN_TARGET_CAA', [
'type' => self::TYPE_STRING,
'description' => 'CAA target for your Appwrite custom domains.',
'default' => '',
'example' => 'digicert.com',
])
->addRule('_APP_STORAGE_LIMIT', [
'type' => self::TYPE_INTEGER,
'description' => 'Maximum file size allowed for file upload in bytes.',
@@ -41,18 +41,21 @@ class Document extends Any
'description' => 'Document automatically incrementing ID.',
'default' => 0,
'example' => 1,
'readOnly' => true,
])
->addRule('$collectionId', [
'type' => self::TYPE_STRING,
'description' => 'Collection ID.',
'default' => '',
'example' => '5e5ea5c15117e',
'readOnly' => true,
])
->addRule('$databaseId', [
'type' => self::TYPE_STRING,
'description' => 'Database ID.',
'default' => '',
'example' => '5e5ea5c15117e',
'readOnly' => true,
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
+15
View File
@@ -97,6 +97,21 @@ abstract class Scope extends TestCase
return $request;
}
protected function assertSamePixels(string $expectedImagePath, string $actualImageBlob): void
{
$expected = new \Imagick($expectedImagePath);
$actual = new \Imagick();
$actual->readImageBlob($actualImageBlob);
foreach ([$expected, $actual] as $image) {
$image->setImageFormat('PNG');
$image->stripImage();
$image->setOption('png:exclude-chunks', 'date,time,iCCP,sRGB,gAMA,cHRM');
}
$this->assertSame($expected->getImageSignature(), $actual->getImageSignature());
}
/**
* @deprecated Use assertLastRequest instead. Used only historically in webhook tests
*/
+4 -4
View File
@@ -347,7 +347,7 @@ trait AvatarsBase
$this->assertEquals(400, $image->getImageWidth());
$this->assertEquals(400, $image->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
$this->assertEquals(strlen(\file_get_contents(__DIR__ . '/../../../resources/qr/qr-default.png')), strlen($response['body']));
$this->assertSamePixels(__DIR__ . '/../../../resources/qr/qr-default.png', $response['body']);
$response = $this->client->call(Client::METHOD_GET, '/avatars/qr', [
'x-appwrite-project' => $this->getProject()['$id'],
@@ -365,7 +365,7 @@ trait AvatarsBase
$this->assertEquals(200, $image->getImageWidth());
$this->assertEquals(200, $image->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
$this->assertEquals(strlen(\file_get_contents(__DIR__ . '/../../../resources/qr/qr-size-200.png')), strlen($response['body']));
$this->assertSamePixels(__DIR__ . '/../../../resources/qr/qr-size-200.png', $response['body']);
$response = $this->client->call(Client::METHOD_GET, '/avatars/qr', [
'x-appwrite-project' => $this->getProject()['$id'],
@@ -384,7 +384,7 @@ trait AvatarsBase
$this->assertEquals(200, $image->getImageWidth());
$this->assertEquals(200, $image->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
$this->assertEquals(strlen(\file_get_contents(__DIR__ . '/../../../resources/qr/qr-size-200-margin-10.png')), strlen($response['body']));
$this->assertSamePixels(__DIR__ . '/../../../resources/qr/qr-size-200-margin-10.png', $response['body']);
$response = $this->client->call(Client::METHOD_GET, '/avatars/qr', [
'x-appwrite-project' => $this->getProject()['$id'],
@@ -400,7 +400,7 @@ trait AvatarsBase
$this->assertEquals(200, $image->getImageWidth());
$this->assertEquals(200, $image->getImageHeight());
$this->assertEquals('PNG', $image->getImageFormat());
$this->assertEquals(strlen(\file_get_contents(__DIR__ . '/../../../resources/qr/qr-size-200-margin-10.png')), strlen($response['body']));
$this->assertSamePixels(__DIR__ . '/../../../resources/qr/qr-size-200-margin-10.png', $response['body']);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('attachment; filename="qr.png"', $response['headers']['content-disposition']);
@@ -24,10 +24,11 @@ class ConsoleConsoleClientTest extends Scope
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(13, $response['body']);
$this->assertCount(14, $response['body']);
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET_CNAME']);
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET_A']);
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET_AAAA']);
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET_CAA']);
$this->assertIsInt($response['body']['_APP_STORAGE_LIMIT']);
$this->assertIsInt($response['body']['_APP_COMPUTE_SIZE_LIMIT']);
$this->assertIsBool($response['body']['_APP_DOMAIN_ENABLED']);
+21 -7
View File
@@ -29,7 +29,9 @@ class AvatarsTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(18546, \strlen($creditCardIcon['body']));
$this->assertEquals(200, $creditCardIcon['headers']['status-code']);
$this->assertNotEmpty($creditCardIcon['body']);
$this->assertStringContainsString('image/', $creditCardIcon['headers']['content-type']);
return $creditCardIcon['body'];
}
@@ -50,7 +52,9 @@ class AvatarsTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(13312, \strlen($browserIcon['body']));
$this->assertEquals(200, $browserIcon['headers']['status-code']);
$this->assertNotEmpty($browserIcon['body']);
$this->assertStringContainsString('image/', $browserIcon['headers']['content-type']);
return $browserIcon['body'];
}
@@ -71,7 +75,9 @@ class AvatarsTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(8814, \strlen($countryFlag['body']));
$this->assertEquals(200, $countryFlag['headers']['status-code']);
$this->assertNotEmpty($countryFlag['body']);
$this->assertStringContainsString('image/', $countryFlag['headers']['content-type']);
return $countryFlag['body'];
}
@@ -92,7 +98,9 @@ class AvatarsTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(52585, \strlen($image['body']));
$this->assertEquals(200, $image['headers']['status-code']);
$this->assertNotEmpty($image['body']);
$this->assertStringContainsString('image/', $image['headers']['content-type']);
return $image['body'];
}
@@ -113,7 +121,9 @@ class AvatarsTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(5430, \strlen($favicon['body']));
$this->assertEquals(200, $favicon['headers']['status-code']);
$this->assertNotEmpty($favicon['body']);
$this->assertStringContainsString('image/', $favicon['headers']['content-type']);
return $favicon['body'];
}
@@ -134,7 +144,9 @@ class AvatarsTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(9169, \strlen($qrCode['body']));
$this->assertEquals(200, $qrCode['headers']['status-code']);
$this->assertNotEmpty($qrCode['body']);
$this->assertStringContainsString('image/', $qrCode['headers']['content-type']);
return $qrCode['body'];
}
@@ -155,7 +167,9 @@ class AvatarsTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(5025, \strlen($initials['body']));
$this->assertEquals(200, $initials['headers']['status-code']);
$this->assertNotEmpty($initials['body']);
$this->assertStringContainsString('image/', $initials['headers']['content-type']);
return $initials['body'];
}
@@ -1054,10 +1054,10 @@ trait MigrationsBase
$errorData = json_decode($errorJson, true);
$this->assertThat(
$errorData['message'],
$this->stringContains("CSV header mismatch. Missing column: 'age'")
implode("\n", $migration['body']['errors']),
$this->stringContains("CSV header validation failed: Missing required column: 'age'")
);
}, 60000, 500);
}, 60_000, 500);
// missing row data, fail in worker.
$missingColumn = $this->performCsvMigration(
@@ -1087,12 +1087,12 @@ trait MigrationsBase
$errorData = json_decode($errorJson, true);
$this->assertThat(
$errorData['message'],
implode("\n", $migration['body']['errors']),
$this->stringContains('CSV row does not match the number of header columns')
);
}, 60000, 500);
}, 60_000, 500);
// irrelevant column - email, fail in worker.
// irrelevant column - email, success.
$irrelevantColumn = $this->performCsvMigration(
[
'fileId' => $fileIds['irrelevant-column'],
@@ -1110,20 +1110,13 @@ trait MigrationsBase
$this->assertEquals(200, $migration['headers']['status-code']);
$this->assertEquals('finished', $migration['body']['stage']);
$this->assertEquals('failed', $migration['body']['status']);
$this->assertEquals('completed', $migration['body']['status']);
$this->assertEquals('CSV', $migration['body']['source']);
$this->assertEquals('Appwrite', $migration['body']['destination']);
$this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']);
$this->assertEmpty($migration['body']['statusCounters']);
$errorJson = $migration['body']['errors'][0];
$errorData = json_decode($errorJson, true);
$this->assertThat(
$errorData['message'],
$this->stringContains("CSV header mismatch. Unexpected column: 'email'")
);
}, 60000, 500);
$this->assertArrayHasKey(Resource::TYPE_ROW, $migration['body']['statusCounters']);
$this->assertEquals(100, $migration['body']['statusCounters'][Resource::TYPE_ROW]['success']);
}, 10_000, 500);
// all data exists, pass.
$migration = $this->performCsvMigration(
@@ -1165,7 +1158,7 @@ trait MigrationsBase
$this->assertEquals(200, $rows['headers']['status-code']);
$this->assertIsArray($rows['body']['rows']);
$this->assertIsNumeric($rows['body']['total']);
$this->assertEquals(100, $rows['body']['total']);
$this->assertEquals(200, $rows['body']['total']);
// all data exists and includes internals, pass.
$migration = $this->performCsvMigration(
+25 -25
View File
@@ -1,26 +1,26 @@
$id,$createdAt,$updatedAt,$permissions,name,age
z1y2x3w4v5u6t7s8,2022-10-23T10:33:01+00:00,2023-03-15T12:00:41+00:00,"read(\"any\"),update(\"user:123\")",Diamond Mendez,56
r9q0p1o2n3m4l5k6,2021-08-11T21:05:13+00:00,2024-01-02T08:45:22+00:00,"read(\"any\"),update(\"user:456\")",Michael Huff,20
j7i8h9g0f1e2d3c4,2020-05-29T14:22:56+00:00,2022-11-30T18:19:33+00:00,"read(\"any\")",Alyssa Rodriguez,37
b5a6z7y8x9w0v1u2,2023-01-18T03:44:09+00:00,2023-09-07T23:50:17+00:00,"read(\"any\")",Barbara Smith,26
t3s4r5q6p7o8n9m0,2020-11-02T09:12:45+00:00,2021-07-21T15:30:55+00:00,"read(\"any\")",Evelyn Edwards,54
l1k2j3i4h5g6f7e8,2022-03-19T19:55:27+00:00,2024-05-14T06:28:11+00:00,"read(\"any\")",Tina Richardson,41
d9c0b1a2z3y4x5w6,2021-04-07T01:18:34+00:00,2023-06-25T11:47:04+00:00,"read(\"any\")",Joel Hernandez,49
v7u8t9s0r1q2p3o4,2023-08-22T16:40:18+00:00,2024-02-19T04:09:58+00:00,"read(\"any\")",Zachary Cooper,59
n5m6l7k8j9i0h1g2,2020-02-12T07:59:01+00:00,2022-09-08T13:21:49+00:00,"read(\"any\")",Brittany Spears,20
f3e4d5c6b7a8z9y0,2021-12-05T22:33:12+00:00,2023-11-11T02:55:37+00:00,"read(\"any\")",Holly White,47
x1w2v3u4t5s6r7q8,2022-07-14T05:01:50+00:00,2024-04-01T20:10:26+00:00,"read(\"any\")",Kimberly Barnes,27
p9o0n1m2l3k4j5i6,2020-09-28T11:27:36+00:00,2021-10-17T09:38:08+00:00,"read(\"any\")",Stephen Miller,53
h7g8f9e0d1c2b3a4,2023-04-04T08:15:59+00:00,2024-06-29T17:03:14+00:00,"read(\"any\")",Yvonne Newman,41
y5x6w7v8u9t0s1r2,2021-01-25T18:09:21+00:00,2022-08-16T22:44:51+00:00,"read(\"any\")",Carol Kane,38
q3p4o5n6m7l8k9j0,2022-06-09T12:53:47+00:00,2023-12-24T01:16:05+00:00,"read(\"any\")",Doris Foster,44
i1h2g3f4e5d6c7b8,2020-07-03T23:37:02+00:00,2021-05-09T05:52:43+00:00,"read(\"any\")",Joseph Stokes,28
a9z0y1x2w3v4u5t6,2023-10-10T02:20:15+00:00,2024-03-28T14:33:29+00:00,"read(\"any\")",Steve Williams,31
s7r8q9p0o1n2m3l4,2021-06-16T13:48:53+00:00,2022-04-22T07:07:19+00:00,"read(\"any\")",James Carey,29
k5j6i7h8g9f0e1d2,2022-12-27T20:06:38+00:00,2023-08-03T10:25:57+00:00,"read(\"any\")",Kathryn Henry,38
c3b4a5z6y7x8w9v0,2020-04-20T04:41:24+00:00,2021-02-13T19:14:06+00:00,"read(\"any\")",Christopher Landry,23
u1t2s3r4q5p6o7n8,2023-05-08T00:58:10+00:00,2024-07-05T03:36:48+00:00,"read(\"any\")",Jennifer Mcgee,62
m9l0k1j2i3h4g5f6,2021-09-01T06:11:42+00:00,2022-01-26T16:59:23+00:00,"read(\"any\")",Cathy Church,35
e7d8c9b0a1z2y3x4,2022-02-18T15:24:07+00:00,2023-04-12T00:40:31+00:00,"read(\"any\")",Jose Lopez,41
w5v6u7t8s9r0q1p2,2020-12-13T09:03:55+00:00,2021-11-06T11:23:16+00:00,"read(\"any\")",William Rose,30
o3n4m5l6k7j8i9h0,2021-12-13T09:03:55+00:00,2022-11-06T11:23:16+00:00,"read(\"any\")",Charles Hammer,61
z1y2x3w4v5u6t7s8,2022-10-23T10:33:01+00:00,2023-03-15T12:00:41+00:00,"read(""any""),update(""user:123"")",Diamond Mendez,56
r9q0p1o2n3m4l5k6,2021-08-11T21:05:13+00:00,2024-01-02T08:45:22+00:00,"read(""any""),update(""user:456"")",Michael Huff,20
j7i8h9g0f1e2d3c4,2020-05-29T14:22:56+00:00,2022-11-30T18:19:33+00:00,"read(""any"")",Alyssa Rodriguez,37
b5a6z7y8x9w0v1u2,2023-01-18T03:44:09+00:00,2023-09-07T23:50:17+00:00,"read(""any"")",Barbara Smith,26
t3s4r5q6p7o8n9m0,2020-11-02T09:12:45+00:00,2021-07-21T15:30:55+00:00,"read(""any"")",Evelyn Edwards,54
l1k2j3i4h5g6f7e8,2022-03-19T19:55:27+00:00,2024-05-14T06:28:11+00:00,"read(""any"")",Tina Richardson,41
d9c0b1a2z3y4x5w6,2021-04-07T01:18:34+00:00,2023-06-25T11:47:04+00:00,"read(""any"")",Joel Hernandez,49
v7u8t9s0r1q2p3o4,2023-08-22T16:40:18+00:00,2024-02-19T04:09:58+00:00,"read(""any"")",Zachary Cooper,59
n5m6l7k8j9i0h1g2,2020-02-12T07:59:01+00:00,2022-09-08T13:21:49+00:00,"read(""any"")",Brittany Spears,20
f3e4d5c6b7a8z9y0,2021-12-05T22:33:12+00:00,2023-11-11T02:55:37+00:00,"read(""any"")",Holly White,47
x1w2v3u4t5s6r7q8,2022-07-14T05:01:50+00:00,2024-04-01T20:10:26+00:00,"read(""any"")",Kimberly Barnes,27
p9o0n1m2l3k4j5i6,2020-09-28T11:27:36+00:00,2021-10-17T09:38:08+00:00,"read(""any"")",Stephen Miller,53
h7g8f9e0d1c2b3a4,2023-04-04T08:15:59+00:00,2024-06-29T17:03:14+00:00,"read(""any"")",Yvonne Newman,41
y5x6w7v8u9t0s1r2,2021-01-25T18:09:21+00:00,2022-08-16T22:44:51+00:00,"read(""any"")",Carol Kane,38
q3p4o5n6m7l8k9j0,2022-06-09T12:53:47+00:00,2023-12-24T01:16:05+00:00,"read(""any"")",Doris Foster,44
i1h2g3f4e5d6c7b8,2020-07-03T23:37:02+00:00,2021-05-09T05:52:43+00:00,"read(""any"")",Joseph Stokes,28
a9z0y1x2w3v4u5t6,2023-10-10T02:20:15+00:00,2024-03-28T14:33:29+00:00,"read(""any"")",Steve Williams,31
s7r8q9p0o1n2m3l4,2021-06-16T13:48:53+00:00,2022-04-22T07:07:19+00:00,"read(""any"")",James Carey,29
k5j6i7h8g9f0e1d2,2022-12-27T20:06:38+00:00,2023-08-03T10:25:57+00:00,"read(""any"")",Kathryn Henry,38
c3b4a5z6y7x8w9v0,2020-04-20T04:41:24+00:00,2021-02-13T19:14:06+00:00,"read(""any"")",Christopher Landry,23
u1t2s3r4q5p6o7n8,2023-05-08T00:58:10+00:00,2024-07-05T03:36:48+00:00,"read(""any"")",Jennifer Mcgee,62
m9l0k1j2i3h4g5f6,2021-09-01T06:11:42+00:00,2022-01-26T16:59:23+00:00,"read(""any"")",Cathy Church,35
e7d8c9b0a1z2y3x4,2022-02-18T15:24:07+00:00,2023-04-12T00:40:31+00:00,"read(""any"")",Jose Lopez,41
w5v6u7t8s9r0q1p2,2020-12-13T09:03:55+00:00,2021-11-06T11:23:16+00:00,"read(""any"")",William Rose,30
o3n4m5l6k7j8i9h0,2021-12-13T09:03:55+00:00,2022-11-06T11:23:16+00:00,"read(""any"")",Charles Hammer,61
Can't render this file because it contains an unexpected character in line 2 and column 77.
+101 -101
View File
@@ -1,101 +1,101 @@
$id,name,age,email
hxfcwpcas5xokpwe,Diamond Mendez,56,diamond.mendez@example.com
gw8nxwf6esn3tfwf,Michael Huff,20,michael.huff@example.com
xb6bxg56lral1qy9,Alyssa Rodriguez,37,alyssa.rodriguez@example.com
imerjq5j36y3agh2,Barbara Smith,26,barbara.smith@example.com
07yq9qdlhmbzmr35,Evelyn Edwards,54,evelyn.edwards@example.com
ksqo631sbhwj5ltg,Tina Richardson,41,tina.richardson@example.com
j7zlndgu0gbshp15,Joel Hernandez,49,joel.hernandez@example.com
mfntvnljrcmf7h6v,Zachary Cooper,59,zachary.cooper@example.com
5f9b01nziqu2h8ed,Brittany Spears,20,brittany.spears@example.com
4vxzbnzraqznk5u8,Holly White,47,holly.white@example.com
d4ywy3mtphaatbpf,Kimberly Barnes,27,kimberly.barnes@example.com
88odnk6nthyyvbal,Stephen Miller,53,stephen.miller@example.com
08oekee3fn7mzaa5,Yvonne Newman,41,yvonne.newman@example.com
quw55kn9895i5e4v,Carol Kane,38,carol.kane@example.com
nge6bm8ykripei6f,Doris Foster,44,doris.foster@example.com
4k16i33s0xl2ypx9,Joseph Stokes,28,joseph.stokes@example.com
q0j5rxbgid66snyf,Steve Williams,31,steve.williams@example.com
n1oxun7mqq3p103y,James Carey,29,james.carey@example.com
0dbvs840jkf8i0ye,Kathryn Henry,38,kathryn.henry@example.com
5sfaidgs1h87v15v,Christopher Landry,23,christopher.landry@example.com
vg3punvfu5khmf41,Jennifer Mcgee,62,jennifer.mcgee@example.com
f933qydr9u5b2r11,Cathy Church,35,cathy.church@example.com
wjv87y1inf8yk32s,Jose Lopez,41,jose.lopez@example.com
uljysdvdlcyrbrwk,William Rose,30,william.rose@example.com
ot8xtzh77j55wq0s,Sarah Ford,26,sarah.ford@example.com
9t76vnsv2u36s43t,Alisha Jones,61,alisha.jones@example.com
66y4tnty62hw8c02,Kristin Kelly,61,kristin.kelly@example.com
2punfblazi5v16ar,Brendan Stout,40,brendan.stout@example.com
sxhr4nf5w2gx4wbg,Kelly Cruz,18,kelly.cruz@example.com
68dvrqfwqnkq5el9,Samantha Martin,50,samantha.martin@example.com
20192l6dbeinhkh0,David Santos,46,david.santos@example.com
si0l4dgay09ebfmf,Elizabeth Carroll,22,elizabeth.carroll@example.com
lhse40vbldqb6ap1,Corey Owens,46,corey.owens@example.com
h5t3pslykyx3kxfm,Shelby Mueller,65,shelby.mueller@example.com
ldc0luydrw6jub0f,Dr. Sylvia Myers,29,sylvia.myers@example.com
voc9628xg4dsgw2y,Scott Freeman,48,scott.freeman@example.com
o4y0gk3gqv1ax2fz,Christopher Atkinson,21,christopher.atkinson@example.com
u1n3x4e4u7e0vzj6,Sean Diaz,31,sean.diaz@example.com
s36eskwtm0w7lwr7,Bobby Dyer,57,bobby.dyer@example.com
4hjnag1p5iwvtixd,Daniel Hall,62,daniel.hall@example.com
m91d80oxsa216zbh,Jennifer Ramirez,65,jennifer.ramirez@example.com
5hj6858zo2g85n6v,Angela Jackson,57,angela.jackson@example.com
8m8oihv9a1e7nn92,Kelly Lewis,36,kelly.lewis@example.com
7azy39la0no0mxi7,Jessica Munoz,55,jessica.munoz@example.com
47pmjkhnnqhyit8c,Kelly George,65,kelly.george@example.com
6j6cpy4kgneg1mmh,Anthony Johnson,65,anthony.johnson@example.com
tnlmtvap1zz89km9,Regina Fields,61,regina.fields@example.com
6cyuvnwwqdmrpfzh,Sharon Schaefer,30,sharon.schaefer@example.com
p1v4pyu2pqodc0ey,Jacob French,62,jacob.french@example.com
6npynnhjt2jd05xo,Jessica Costa,23,jessica.costa@example.com
wcxedf13n2e9qi4l,George Hardy,53,george.hardy@example.com
yf2xlcmszk2tqeig,Andrea Allison,20,andrea.allison@example.com
3bf2zzv7poststwa,Kevin Ferguson,32,kevin.ferguson@example.com
c2iataz0hhv39q63,Joseph Johnson,58,joseph.johnson@example.com
3e8npxhov4a39pvq,Ashley Martinez,18,ashley.martinez@example.com
t7dp41tysipytywq,Charles Nixon,23,charles.nixon@example.com
z8cztq7c47phyfhk,Carol Dudley,40,carol.dudley@example.com
2636f9d8r4ipm3h6,David Weber,51,david.weber@example.com
eh3f6wxtvkjq6ykq,Scott Robinson,32,scott.robinson@example.com
raskbwpsje69a59h,Anthony Hardy,38,anthony.hardy@example.com
90hn1p0b4cs9e2og,Mackenzie Owens,52,mackenzie.owens@example.com
am3swwfbo076x0v1,Brian Foster,27,brian.foster@example.com
5uw7utb9lq5cfncw,Hannah Forbes,56,hannah.forbes@example.com
cs6mbfzkzifefx6r,Lauren Reed,26,lauren.reed@example.com
ftw3uvztziiz9x00,Morgan Smith,28,morgan.smith@example.com
uhrqseeo43mozpaq,Samantha Alexander,65,samantha.alexander@example.com
pvvmzyfc1lxor11e,Tiffany Roberts,20,tiffany.roberts@example.com
jia7bdag4abz123s,Emily Hayes,34,emily.hayes@example.com
h6oozcngbz8o5x4y,Rebecca Villegas,52,rebecca.villegas@example.com
9v6z1pn2f9twcy12,Donald Shah,61,donald.shah@example.com
wzz3jduioso77o7f,Denise Cain,59,denise.cain@example.com
u51plhgvjodkswnr,Kristine Ramirez,53,kristine.ramirez@example.com
t1uhkmiytfyc13vc,Stacey Adkins,61,stacey.adkins@example.com
iqaqnf0ybg2ct507,Daniel Hunt,20,daniel.hunt@example.com
idwrwv2uu4hcpv2i,Roberta Johnson,48,roberta.johnson@example.com
2yd2hd6auetjacyo,Jason Williamson,39,jason.williamson@example.com
egrmdbibnjhi914x,Sandra Robinson,50,sandra.robinson@example.com
15m1pz2bb0ercgyk,Steve Rice,25,steve.rice@example.com
0i21bhkxdagjurb7,Kimberly Fritz,53,kimberly.fritz@example.com
726ofi7h5snreq67,Brianna Reynolds,33,brianna.reynolds@example.com
csqxse3wym56eim6,Alexander Williams,50,alexander.williams@example.com
qeaoylnrsf8p3byg,Andrew Thomas,25,andrew.thomas@example.com
edsswobumzyzbvhf,Austin Williams,57,austin.williams@example.com
hdzhzpt0ahy5hkib,Nicholas Williams,24,nicholas.williams@example.com
w1qmvmg4roa8xnwu,Mrs. Michelle Cisneros,48,michelle.cisneros@example.com
3z3o73x7adyuo6w0,Stacey Smith,39,stacey.smith@example.com
sse2u5zlgoqrgmcf,Laura Beck,20,laura.beck@example.com
rvovijmvch58r4yx,Molly Clark,51,molly.clark@example.com
doe06nrx8sg5mcuv,Carmen Morris,41,carmen.morris@example.com
jbjdwuvj5s4kw04y,Amanda Munoz,20,amanda.munoz@example.com
6k2ewkla7js0yw23,Rachel Collins,44,rachel.collins@example.com
fcxuyr4kkhrnigu1,John Alexander,18,john.alexander@example.com
d25fuwlos5mk07o0,Stacy Hunter,22,stacy.hunter@example.com
1vdai2rxmwd57oet,Eric Massey,40,eric.massey@example.com
pq4jnt9izu1wlrzd,Scott Garcia,20,scott.garcia@example.com
lz9kfc0lty5xcz14,Cassandra Nelson,35,cassandra.nelson@example.com
pu7w6tyab5jd4we9,Aaron Johnson,50,aaron.johnson@example.com
8dupswd2kqwdyn8v,Shannon Sherman,45,shannon.sherman@example.com
ye466l71jthiz2p6,April Garcia,60,april.garcia@example.com
xogsmfwb73l16qdt,Evan Lynn,20,evan.lynn@example.com
$id,name,age,email
r5ctmrqwqn1m3rc0,Diamond Mendez,56,diamond.mendez@example.com
wxwp7e7q7nx3ltfx,Michael Huff,20,michael.huff@example.com
4ct0b38fwaojawlv,Alyssa Rodriguez,37,alyssa.rodriguez@example.com
o0jjcuygbta2zvga,Barbara Smith,26,barbara.smith@example.com
bdy6l2ofl8klb4pb,Evelyn Edwards,54,evelyn.edwards@example.com
rkccl72v7zwtbila,Tina Richardson,41,tina.richardson@example.com
cilw7um0cd927esj,Joel Hernandez,49,joel.hernandez@example.com
60povvz0votkve1j,Zachary Cooper,59,zachary.cooper@example.com
ayow5dzwktvbbtp2,Brittany Spears,20,brittany.spears@example.com
cfru98od0lab0b2n,Holly White,47,holly.white@example.com
vjxjldvu3r6uylq6,Kimberly Barnes,27,kimberly.barnes@example.com
d1p47hl97pw6xowb,Stephen Miller,53,stephen.miller@example.com
yxk6qaa5ryb3gqrb,Yvonne Newman,41,yvonne.newman@example.com
ifkeo7j8t7hfd7z8,Carol Kane,38,carol.kane@example.com
e1q4lq8vvpxp9ysb,Doris Foster,44,doris.foster@example.com
obec6d52dc6swzsf,Joseph Stokes,28,joseph.stokes@example.com
26v06la6ug8wkvim,Steve Williams,31,steve.williams@example.com
m4glre4ch12vkxp6,James Carey,29,james.carey@example.com
jee8fyfffnjugsd5,Kathryn Henry,38,kathryn.henry@example.com
miquc6ljb9l3a31r,Christopher Landry,23,christopher.landry@example.com
ghf7da7seeuj1zdl,Jennifer Mcgee,62,jennifer.mcgee@example.com
x7h11phjrz77w0q8,Cathy Church,35,cathy.church@example.com
dn8u5lsux4708z6j,Jose Lopez,41,jose.lopez@example.com
zb7fdlyohuyy5i9k,William Rose,30,william.rose@example.com
qyrj8m9krp4dt4wt,Sarah Ford,26,sarah.ford@example.com
t6t673zpfyhhz8pg,Alisha Jones,61,alisha.jones@example.com
0hfbo0iy1q9bwc2n,Kristin Kelly,61,kristin.kelly@example.com
8alv4e4xrpcj443z,Brendan Stout,40,brendan.stout@example.com
qxm7a0z32xdkzxdj,Kelly Cruz,18,kelly.cruz@example.com
885mti7j7oiz5p5g,Samantha Martin,50,samantha.martin@example.com
v8i7dvhby6711m66,David Santos,46,david.santos@example.com
rggc0ow8ccd2jgvp,Elizabeth Carroll,22,elizabeth.carroll@example.com
012472s64rvzq1c4,Corey Owens,46,corey.owens@example.com
0k2xrwj4g33ut14y,Shelby Mueller,65,shelby.mueller@example.com
s3y9rl4uzf3difiq,Dr. Sylvia Myers,29,sylvia.myers@example.com
ntpc2td892t7f6an,Scott Freeman,48,scott.freeman@example.com
7f703gibyr5ijdmt,Christopher Atkinson,21,christopher.atkinson@example.com
r2jdf2pivkxmqd0l,Sean Diaz,31,sean.diaz@example.com
fj98fji1lrxeigs9,Bobby Dyer,57,bobby.dyer@example.com
mehqmzp9u7xv1z3j,Daniel Hall,62,daniel.hall@example.com
4cd5ln65qjfv3h4j,Jennifer Ramirez,65,jennifer.ramirez@example.com
wdi6ap0oa7m1ab1d,Angela Jackson,57,angela.jackson@example.com
l2foqjhxvjhjzijb,Kelly Lewis,36,kelly.lewis@example.com
d963t5yu35uagwm4,Jessica Munoz,55,jessica.munoz@example.com
99ez9uxsim8zp64m,Kelly George,65,kelly.george@example.com
v7wl221gycftl63d,Anthony Johnson,65,anthony.johnson@example.com
p2zzj0lnmjvqzfc3,Regina Fields,61,regina.fields@example.com
fk655e243z2ivvx6,Sharon Schaefer,30,sharon.schaefer@example.com
4ywsv6fw8g2d8ncw,Jacob French,62,jacob.french@example.com
y61q9k6g4h0fxxz4,Jessica Costa,23,jessica.costa@example.com
knj4hfzsthk7vx5n,George Hardy,53,george.hardy@example.com
a88u9w2pct2nn8l6,Andrea Allison,20,andrea.allison@example.com
hw960v1ybycrwr5o,Kevin Ferguson,32,kevin.ferguson@example.com
j9garslpgx6jgzgb,Joseph Johnson,58,joseph.johnson@example.com
gv101bz36elm84cd,Ashley Martinez,18,ashley.martinez@example.com
xrvzgt3gc0c7g4cl,Charles Nixon,23,charles.nixon@example.com
awjlu7uk0eutcfpb,Carol Dudley,40,carol.dudley@example.com
95oi26p2zdudpime,David Weber,51,david.weber@example.com
h8x7pkhdvu5bcp89,Scott Robinson,32,scott.robinson@example.com
oj6cu4jm1z2afe7s,Anthony Hardy,38,anthony.hardy@example.com
hgsdi1g30poqqmf0,Mackenzie Owens,52,mackenzie.owens@example.com
8fzdz914bqlqk2tc,Brian Foster,27,brian.foster@example.com
fwlqoeiunjhczpl0,Hannah Forbes,56,hannah.forbes@example.com
rsv8156goe8z4j6j,Lauren Reed,26,lauren.reed@example.com
1fjqv3w7uwbswe2p,Morgan Smith,28,morgan.smith@example.com
soqrzmhhg05hhzn4,Samantha Alexander,65,samantha.alexander@example.com
8quy52cto9kjjokp,Tiffany Roberts,20,tiffany.roberts@example.com
e3i1g1lw04v7jd89,Emily Hayes,34,emily.hayes@example.com
s7n8lzb0sw7h93z1,Rebecca Villegas,52,rebecca.villegas@example.com
e2lc7i81tpkqs1rp,Donald Shah,61,donald.shah@example.com
3oe2mysup1xluiw0,Denise Cain,59,denise.cain@example.com
1vqypc37f85nuqz4,Kristine Ramirez,53,kristine.ramirez@example.com
m0uh7r3dc6z8ucb4,Stacey Adkins,61,stacey.adkins@example.com
jdofz6x1ahganmqf,Daniel Hunt,20,daniel.hunt@example.com
vbe903c2q4m4q97g,Roberta Johnson,48,roberta.johnson@example.com
sndngrxuwpd93pdb,Jason Williamson,39,jason.williamson@example.com
66hvaw2p5xwf07p8,Sandra Robinson,50,sandra.robinson@example.com
9pvingfsl8cmag5c,Steve Rice,25,steve.rice@example.com
qe154m5hh00u4iiz,Kimberly Fritz,53,kimberly.fritz@example.com
avqnbrco2f0tfupk,Brianna Reynolds,33,brianna.reynolds@example.com
cqs10gi2qu1r3ugb,Alexander Williams,50,alexander.williams@example.com
jrpmfi6hmm7pmegp,Andrew Thomas,25,andrew.thomas@example.com
heeab2qqf0zm446f,Austin Williams,57,austin.williams@example.com
bkhugvnil7kjchm6,Nicholas Williams,24,nicholas.williams@example.com
b045j302pvv8l1p4,Mrs. Michelle Cisneros,48,michelle.cisneros@example.com
aikhii5q210lrfpr,Stacey Smith,39,stacey.smith@example.com
x0zajitea1z2dfo0,Laura Beck,20,laura.beck@example.com
abeecki7mdff1tv0,Molly Clark,51,molly.clark@example.com
yizama8r3i1to548,Carmen Morris,41,carmen.morris@example.com
8690yh971g4rgspj,Amanda Munoz,20,amanda.munoz@example.com
cd9vk5v97t359ul2,Rachel Collins,44,rachel.collins@example.com
wrkgmx1v0w9ja4l8,John Alexander,18,john.alexander@example.com
kxp3ucqo6ped4ss7,Stacy Hunter,22,stacy.hunter@example.com
dbvv8okae2qgo0gm,Eric Massey,40,eric.massey@example.com
9tn3nm6ppnayisje,Scott Garcia,20,scott.garcia@example.com
1xuc5t60xpcvd4qi,Cassandra Nelson,35,cassandra.nelson@example.com
qao1nulwn0kqyfkc,Aaron Johnson,50,aaron.johnson@example.com
kd2q6owvuwsy5knx,Shannon Sherman,45,shannon.sherman@example.com
wsl37kjo0bib4wrc,April Garcia,60,april.garcia@example.com
ujlz7k84xzfx4khs,Evan Lynn,20,evan.lynn@example.com
1 $id name age email
2 hxfcwpcas5xokpwe r5ctmrqwqn1m3rc0 Diamond Mendez 56 diamond.mendez@example.com
3 gw8nxwf6esn3tfwf wxwp7e7q7nx3ltfx Michael Huff 20 michael.huff@example.com
4 xb6bxg56lral1qy9 4ct0b38fwaojawlv Alyssa Rodriguez 37 alyssa.rodriguez@example.com
5 imerjq5j36y3agh2 o0jjcuygbta2zvga Barbara Smith 26 barbara.smith@example.com
6 07yq9qdlhmbzmr35 bdy6l2ofl8klb4pb Evelyn Edwards 54 evelyn.edwards@example.com
7 ksqo631sbhwj5ltg rkccl72v7zwtbila Tina Richardson 41 tina.richardson@example.com
8 j7zlndgu0gbshp15 cilw7um0cd927esj Joel Hernandez 49 joel.hernandez@example.com
9 mfntvnljrcmf7h6v 60povvz0votkve1j Zachary Cooper 59 zachary.cooper@example.com
10 5f9b01nziqu2h8ed ayow5dzwktvbbtp2 Brittany Spears 20 brittany.spears@example.com
11 4vxzbnzraqznk5u8 cfru98od0lab0b2n Holly White 47 holly.white@example.com
12 d4ywy3mtphaatbpf vjxjldvu3r6uylq6 Kimberly Barnes 27 kimberly.barnes@example.com
13 88odnk6nthyyvbal d1p47hl97pw6xowb Stephen Miller 53 stephen.miller@example.com
14 08oekee3fn7mzaa5 yxk6qaa5ryb3gqrb Yvonne Newman 41 yvonne.newman@example.com
15 quw55kn9895i5e4v ifkeo7j8t7hfd7z8 Carol Kane 38 carol.kane@example.com
16 nge6bm8ykripei6f e1q4lq8vvpxp9ysb Doris Foster 44 doris.foster@example.com
17 4k16i33s0xl2ypx9 obec6d52dc6swzsf Joseph Stokes 28 joseph.stokes@example.com
18 q0j5rxbgid66snyf 26v06la6ug8wkvim Steve Williams 31 steve.williams@example.com
19 n1oxun7mqq3p103y m4glre4ch12vkxp6 James Carey 29 james.carey@example.com
20 0dbvs840jkf8i0ye jee8fyfffnjugsd5 Kathryn Henry 38 kathryn.henry@example.com
21 5sfaidgs1h87v15v miquc6ljb9l3a31r Christopher Landry 23 christopher.landry@example.com
22 vg3punvfu5khmf41 ghf7da7seeuj1zdl Jennifer Mcgee 62 jennifer.mcgee@example.com
23 f933qydr9u5b2r11 x7h11phjrz77w0q8 Cathy Church 35 cathy.church@example.com
24 wjv87y1inf8yk32s dn8u5lsux4708z6j Jose Lopez 41 jose.lopez@example.com
25 uljysdvdlcyrbrwk zb7fdlyohuyy5i9k William Rose 30 william.rose@example.com
26 ot8xtzh77j55wq0s qyrj8m9krp4dt4wt Sarah Ford 26 sarah.ford@example.com
27 9t76vnsv2u36s43t t6t673zpfyhhz8pg Alisha Jones 61 alisha.jones@example.com
28 66y4tnty62hw8c02 0hfbo0iy1q9bwc2n Kristin Kelly 61 kristin.kelly@example.com
29 2punfblazi5v16ar 8alv4e4xrpcj443z Brendan Stout 40 brendan.stout@example.com
30 sxhr4nf5w2gx4wbg qxm7a0z32xdkzxdj Kelly Cruz 18 kelly.cruz@example.com
31 68dvrqfwqnkq5el9 885mti7j7oiz5p5g Samantha Martin 50 samantha.martin@example.com
32 20192l6dbeinhkh0 v8i7dvhby6711m66 David Santos 46 david.santos@example.com
33 si0l4dgay09ebfmf rggc0ow8ccd2jgvp Elizabeth Carroll 22 elizabeth.carroll@example.com
34 lhse40vbldqb6ap1 012472s64rvzq1c4 Corey Owens 46 corey.owens@example.com
35 h5t3pslykyx3kxfm 0k2xrwj4g33ut14y Shelby Mueller 65 shelby.mueller@example.com
36 ldc0luydrw6jub0f s3y9rl4uzf3difiq Dr. Sylvia Myers 29 sylvia.myers@example.com
37 voc9628xg4dsgw2y ntpc2td892t7f6an Scott Freeman 48 scott.freeman@example.com
38 o4y0gk3gqv1ax2fz 7f703gibyr5ijdmt Christopher Atkinson 21 christopher.atkinson@example.com
39 u1n3x4e4u7e0vzj6 r2jdf2pivkxmqd0l Sean Diaz 31 sean.diaz@example.com
40 s36eskwtm0w7lwr7 fj98fji1lrxeigs9 Bobby Dyer 57 bobby.dyer@example.com
41 4hjnag1p5iwvtixd mehqmzp9u7xv1z3j Daniel Hall 62 daniel.hall@example.com
42 m91d80oxsa216zbh 4cd5ln65qjfv3h4j Jennifer Ramirez 65 jennifer.ramirez@example.com
43 5hj6858zo2g85n6v wdi6ap0oa7m1ab1d Angela Jackson 57 angela.jackson@example.com
44 8m8oihv9a1e7nn92 l2foqjhxvjhjzijb Kelly Lewis 36 kelly.lewis@example.com
45 7azy39la0no0mxi7 d963t5yu35uagwm4 Jessica Munoz 55 jessica.munoz@example.com
46 47pmjkhnnqhyit8c 99ez9uxsim8zp64m Kelly George 65 kelly.george@example.com
47 6j6cpy4kgneg1mmh v7wl221gycftl63d Anthony Johnson 65 anthony.johnson@example.com
48 tnlmtvap1zz89km9 p2zzj0lnmjvqzfc3 Regina Fields 61 regina.fields@example.com
49 6cyuvnwwqdmrpfzh fk655e243z2ivvx6 Sharon Schaefer 30 sharon.schaefer@example.com
50 p1v4pyu2pqodc0ey 4ywsv6fw8g2d8ncw Jacob French 62 jacob.french@example.com
51 6npynnhjt2jd05xo y61q9k6g4h0fxxz4 Jessica Costa 23 jessica.costa@example.com
52 wcxedf13n2e9qi4l knj4hfzsthk7vx5n George Hardy 53 george.hardy@example.com
53 yf2xlcmszk2tqeig a88u9w2pct2nn8l6 Andrea Allison 20 andrea.allison@example.com
54 3bf2zzv7poststwa hw960v1ybycrwr5o Kevin Ferguson 32 kevin.ferguson@example.com
55 c2iataz0hhv39q63 j9garslpgx6jgzgb Joseph Johnson 58 joseph.johnson@example.com
56 3e8npxhov4a39pvq gv101bz36elm84cd Ashley Martinez 18 ashley.martinez@example.com
57 t7dp41tysipytywq xrvzgt3gc0c7g4cl Charles Nixon 23 charles.nixon@example.com
58 z8cztq7c47phyfhk awjlu7uk0eutcfpb Carol Dudley 40 carol.dudley@example.com
59 2636f9d8r4ipm3h6 95oi26p2zdudpime David Weber 51 david.weber@example.com
60 eh3f6wxtvkjq6ykq h8x7pkhdvu5bcp89 Scott Robinson 32 scott.robinson@example.com
61 raskbwpsje69a59h oj6cu4jm1z2afe7s Anthony Hardy 38 anthony.hardy@example.com
62 90hn1p0b4cs9e2og hgsdi1g30poqqmf0 Mackenzie Owens 52 mackenzie.owens@example.com
63 am3swwfbo076x0v1 8fzdz914bqlqk2tc Brian Foster 27 brian.foster@example.com
64 5uw7utb9lq5cfncw fwlqoeiunjhczpl0 Hannah Forbes 56 hannah.forbes@example.com
65 cs6mbfzkzifefx6r rsv8156goe8z4j6j Lauren Reed 26 lauren.reed@example.com
66 ftw3uvztziiz9x00 1fjqv3w7uwbswe2p Morgan Smith 28 morgan.smith@example.com
67 uhrqseeo43mozpaq soqrzmhhg05hhzn4 Samantha Alexander 65 samantha.alexander@example.com
68 pvvmzyfc1lxor11e 8quy52cto9kjjokp Tiffany Roberts 20 tiffany.roberts@example.com
69 jia7bdag4abz123s e3i1g1lw04v7jd89 Emily Hayes 34 emily.hayes@example.com
70 h6oozcngbz8o5x4y s7n8lzb0sw7h93z1 Rebecca Villegas 52 rebecca.villegas@example.com
71 9v6z1pn2f9twcy12 e2lc7i81tpkqs1rp Donald Shah 61 donald.shah@example.com
72 wzz3jduioso77o7f 3oe2mysup1xluiw0 Denise Cain 59 denise.cain@example.com
73 u51plhgvjodkswnr 1vqypc37f85nuqz4 Kristine Ramirez 53 kristine.ramirez@example.com
74 t1uhkmiytfyc13vc m0uh7r3dc6z8ucb4 Stacey Adkins 61 stacey.adkins@example.com
75 iqaqnf0ybg2ct507 jdofz6x1ahganmqf Daniel Hunt 20 daniel.hunt@example.com
76 idwrwv2uu4hcpv2i vbe903c2q4m4q97g Roberta Johnson 48 roberta.johnson@example.com
77 2yd2hd6auetjacyo sndngrxuwpd93pdb Jason Williamson 39 jason.williamson@example.com
78 egrmdbibnjhi914x 66hvaw2p5xwf07p8 Sandra Robinson 50 sandra.robinson@example.com
79 15m1pz2bb0ercgyk 9pvingfsl8cmag5c Steve Rice 25 steve.rice@example.com
80 0i21bhkxdagjurb7 qe154m5hh00u4iiz Kimberly Fritz 53 kimberly.fritz@example.com
81 726ofi7h5snreq67 avqnbrco2f0tfupk Brianna Reynolds 33 brianna.reynolds@example.com
82 csqxse3wym56eim6 cqs10gi2qu1r3ugb Alexander Williams 50 alexander.williams@example.com
83 qeaoylnrsf8p3byg jrpmfi6hmm7pmegp Andrew Thomas 25 andrew.thomas@example.com
84 edsswobumzyzbvhf heeab2qqf0zm446f Austin Williams 57 austin.williams@example.com
85 hdzhzpt0ahy5hkib bkhugvnil7kjchm6 Nicholas Williams 24 nicholas.williams@example.com
86 w1qmvmg4roa8xnwu b045j302pvv8l1p4 Mrs. Michelle Cisneros 48 michelle.cisneros@example.com
87 3z3o73x7adyuo6w0 aikhii5q210lrfpr Stacey Smith 39 stacey.smith@example.com
88 sse2u5zlgoqrgmcf x0zajitea1z2dfo0 Laura Beck 20 laura.beck@example.com
89 rvovijmvch58r4yx abeecki7mdff1tv0 Molly Clark 51 molly.clark@example.com
90 doe06nrx8sg5mcuv yizama8r3i1to548 Carmen Morris 41 carmen.morris@example.com
91 jbjdwuvj5s4kw04y 8690yh971g4rgspj Amanda Munoz 20 amanda.munoz@example.com
92 6k2ewkla7js0yw23 cd9vk5v97t359ul2 Rachel Collins 44 rachel.collins@example.com
93 fcxuyr4kkhrnigu1 wrkgmx1v0w9ja4l8 John Alexander 18 john.alexander@example.com
94 d25fuwlos5mk07o0 kxp3ucqo6ped4ss7 Stacy Hunter 22 stacy.hunter@example.com
95 1vdai2rxmwd57oet dbvv8okae2qgo0gm Eric Massey 40 eric.massey@example.com
96 pq4jnt9izu1wlrzd 9tn3nm6ppnayisje Scott Garcia 20 scott.garcia@example.com
97 lz9kfc0lty5xcz14 1xuc5t60xpcvd4qi Cassandra Nelson 35 cassandra.nelson@example.com
98 pu7w6tyab5jd4we9 qao1nulwn0kqyfkc Aaron Johnson 50 aaron.johnson@example.com
99 8dupswd2kqwdyn8v kd2q6owvuwsy5knx Shannon Sherman 45 shannon.sherman@example.com
100 ye466l71jthiz2p6 wsl37kjo0bib4wrc April Garcia 60 april.garcia@example.com
101 xogsmfwb73l16qdt ujlz7k84xzfx4khs Evan Lynn 20 evan.lynn@example.com
+58
View File
@@ -3,8 +3,18 @@
namespace Tests\Unit\Network\Validators;
use Appwrite\Network\Validator\DNS;
use Appwrite\Tests\Retry;
use PHPUnit\Framework\TestCase;
/*
DNS Setup (on Appwrite Labs digital ocean team, network tab):
certainly.caa.appwrite.org: CAA 0 issue "certainly.com"
certainly-full.caa.appwrite.org: CAA 128 issuewild "certainly.com;account=123456;validationmethods=dns-01"
letsencrypt.certainly.caa.appwrite.org: CAA 0 issue "letsencrypt.org"
*/
class DNSTest extends TestCase
{
public function setUp(): void
@@ -47,4 +57,52 @@ class DNSTest extends TestCase
$this->assertEquals($validator->isValid('aaaa-unit-test.appwrite.org'), true);
$this->assertEquals($validator->isValid('test1.appwrite.org'), false);
}
#[Retry(count: 5)]
public function testCAA(): void
{
$certainly = new DNS('certainly.com', DNS::RECORD_CAA, 'ns1.digitalocean.com');
$letsencrypt = new DNS('letsencrypt.org', DNS::RECORD_CAA, 'ns1.digitalocean.com');
// No CAA record succeeds on main domain & subdomains for any issuer
$this->assertEquals($certainly->isValid('caa.appwrite.org'), true);
$this->assertEquals($certainly->isValid('sub.caa.appwrite.org'), true);
$this->assertEquals($certainly->isValid('sub.sub.caa.appwrite.org'), true);
$this->assertEquals($letsencrypt->isValid('caa.appwrite.org'), true);
$this->assertEquals($letsencrypt->isValid('sub.caa.appwrite.org'), true);
$this->assertEquals($letsencrypt->isValid('sub.sub.caa.appwrite.org'), true);
// Custom flags and tag is allowed, but only for Certainly
$this->assertEquals($certainly->isValid('certainly-full.caa.appwrite.org'), true);
$this->assertEquals($letsencrypt->isValid('certainly-full.caa.appwrite.org'), false);
// Custom flags&tag are not allowed if validator includes specific flags&tag
$certainlyFull = new DNS('0 issue "certainly.com"', DNS::RECORD_CAA);
$this->assertEquals($certainlyFull->isValid('certainly-full.caa.appwrite.org'), false);
// Custom flags&tag still allows if they match exactly
$certainlyFull = new DNS('128 issuewild "certainly.com;account=123456;validationmethods=dns-01"', DNS::RECORD_CAA);
$this->assertEquals($certainlyFull->isValid('certainly-full.caa.appwrite.org'), true);
// Certainly CAA allows Certainly, but not LetsEncrypt; Same for subdomains
$this->assertEquals($certainly->isValid('certainly.caa.appwrite.org'), true);
$this->assertEquals($letsencrypt->isValid('certainly.caa.appwrite.org'), false);
$this->assertEquals($certainly->isValid('sub.certainly.caa.appwrite.org'), true);
$this->assertEquals($letsencrypt->isValid('sub.certainly.caa.appwrite.org'), false);
$this->assertEquals($certainly->isValid('sub.sub.certainly.caa.appwrite.org'), true);
$this->assertEquals($letsencrypt->isValid('sub.sub.certainly.caa.appwrite.org'), false);
// LetsEncrypt CAA on subdomain with parent allowing Certainly. Only LetsEncrypt is allowed; Same for subdomains
$this->assertEquals($certainly->isValid('letsencrypt.certainly.caa.appwrite.org'), false);
$this->assertEquals($letsencrypt->isValid('letsencrypt.certainly.caa.appwrite.org'), true);
$this->assertEquals($certainly->isValid('sub.letsencrypt.certainly.caa.appwrite.org'), false);
$this->assertEquals($letsencrypt->isValid('sub.letsencrypt.certainly.caa.appwrite.org'), true);
$this->assertEquals($certainly->isValid('sub.sub.letsencrypt.certainly.caa.appwrite.org'), false);
$this->assertEquals($letsencrypt->isValid('sub.sub.letsencrypt.certainly.caa.appwrite.org'), true);
}
}