Merge remote-tracking branch 'origin/master' into feat-permissions-improvements
This commit is contained in:
commit
9aa7085ae9
27
CHANGES.md
27
CHANGES.md
|
@ -1,3 +1,16 @@
|
|||
# Version 0.12.2
|
||||
|
||||
## Bugs
|
||||
- Fix security vulnerability in the Console (#2778)
|
||||
- Fix security vulnerability in the ACME-Challenge (#2780)
|
||||
|
||||
## Upgrades
|
||||
|
||||
- Upgraded `redis` extenstion to version 5.3.6
|
||||
- Upgraded `swoole` extenstion to version 4.8.6
|
||||
- Upgraded `imagick` extenstion to version 3.7.0
|
||||
- Upgraded GEO IP database to version February 2022
|
||||
|
||||
# Version 0.12.1
|
||||
|
||||
## Bugs
|
||||
|
@ -84,6 +97,20 @@
|
|||
- Upgraded InfluxDB to 1.4.0
|
||||
- Upgraded Telegraf to 1.3.0
|
||||
|
||||
# Version 0.11.1
|
||||
|
||||
## Bugs
|
||||
- Fix security vulnerability in the Console (#2777)
|
||||
- Fix security vulnerability in the ACME-Challenge (#2779)
|
||||
|
||||
## Upgrades
|
||||
- Upgraded redis extenstion to version 5.3.6
|
||||
- Upgraded swoole extenstion to version 4.8.6
|
||||
- Upgraded imagick extenstion to version 3.7.0
|
||||
- Upgraded yaml extenstion to version 2.2.2
|
||||
- Upgraded maxminddb extenstion to version 1.11.0
|
||||
- Upgraded GEO IP database to version February 2022
|
||||
|
||||
# Version 0.11.0
|
||||
|
||||
## Features
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
[English](README.md) | 简体中文
|
||||
|
||||
Appwrite是一个基于dcoker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
|
||||
|
||||
Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存, 图像处理,云函数计算,[等多种服务](https:/ /appwrite.io/docs)。
|
||||
|
||||
|
@ -59,7 +59,7 @@ docker run -it --rm \
|
|||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:0.12.1
|
||||
appwrite/appwrite:0.12.2
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
@ -71,7 +71,7 @@ docker run -it --rm ^
|
|||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:0.12.1
|
||||
appwrite/appwrite:0.12.2
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
@ -81,7 +81,7 @@ docker run -it --rm ,
|
|||
--volume /var/run/docker.sock:/var/run/docker.sock ,
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
|
||||
--entrypoint="install" ,
|
||||
appwrite/appwrite:0.12.1
|
||||
appwrite/appwrite:0.12.2
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
|
|
@ -62,7 +62,7 @@ docker run -it --rm \
|
|||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:0.12.1
|
||||
appwrite/appwrite:0.12.2
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
@ -74,7 +74,7 @@ docker run -it --rm ^
|
|||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:0.12.1
|
||||
appwrite/appwrite:0.12.2
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
@ -84,7 +84,7 @@ docker run -it --rm ,
|
|||
--volume /var/run/docker.sock:/var/run/docker.sock ,
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
|
||||
--entrypoint="install" ,
|
||||
appwrite/appwrite:0.12.1
|
||||
appwrite/appwrite:0.12.2
|
||||
```
|
||||
|
||||
Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after installation completes.
|
||||
|
|
|
@ -4,11 +4,10 @@
|
|||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| < 0.5 | :x: |
|
||||
| 0.6.x | :white_check_mark: |
|
||||
| 0.7.x | :white_check_mark: |
|
||||
| 0.8.0 | :white_check_mark: |
|
||||
| <= 0.10 | :x: |
|
||||
| 0.11.x | :white_check_mark: |
|
||||
| 0.12.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
For security issues, kindly email us at security@appwrite.io instead of posting a public issue in GitHub.
|
||||
For security issues, kindly email us at security@appwrite.io instead of posting a public issue in GitHub.
|
||||
|
|
|
@ -1532,6 +1532,17 @@ $collections = [
|
|||
'array' => false,
|
||||
'filters' => ['encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => 'search',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
@ -1555,6 +1566,13 @@ $collections = [
|
|||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_search',
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -152,7 +152,7 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_LOGGING_PROVIDER',
|
||||
'description' => 'This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, to enable the logger set the value to one of \'sentry\', \'raygun\', \'appsignal\'',
|
||||
'description' => 'This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, to enable the logger set the value to one of \'sentry\', \'raygun\', \'appsignal\', \'logowl\'',
|
||||
'introduction' => '0.12.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
|
@ -161,7 +161,7 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_LOGGING_CONFIG',
|
||||
'description' => 'This variable configures authentication to 3rd party error logging providers. If using Sentry, this should be \'SENTRY_API_KEY;SENTRY_APP_ID\'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key.',
|
||||
'description' => 'This variable configures authentication to 3rd party error logging providers. If using Sentry, this should be \'SENTRY_API_KEY;SENTRY_APP_ID\'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key. If using LogOwl, this should be LogOwl Service Ticket.',
|
||||
'introduction' => '0.12.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
|
|
|
@ -144,7 +144,7 @@ App::get('/v1/avatars/image')
|
|||
->label('sdk.description', '/docs/references/avatars/get-image.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
|
||||
->param('url', '', new URL(), 'Image URL which you want to crop.')
|
||||
->param('url', '', new URL(['http', 'https']), 'Image URL which you want to crop.')
|
||||
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000.', true)
|
||||
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000.', true)
|
||||
->inject('response')
|
||||
|
@ -213,7 +213,7 @@ App::get('/v1/avatars/favicon')
|
|||
->label('sdk.description', '/docs/references/avatars/get-favicon.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
|
||||
->param('url', '', new URL(), 'Website URL which you want to fetch the favicon from.')
|
||||
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.')
|
||||
->inject('response')
|
||||
->action(function ($url, $response) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
|
|
|
@ -582,7 +582,7 @@ App::post('/v1/projects/:projectId/webhooks')
|
|||
->param('projectId', null, new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
|
||||
->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.')
|
||||
->param('url', null, new URL(), 'Webhook URL.')
|
||||
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
|
||||
->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
|
||||
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
|
||||
|
@ -704,7 +704,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
->param('webhookId', null, new UID(), 'Webhook unique ID.')
|
||||
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
|
||||
->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.')
|
||||
->param('url', null, new URL(), 'Webhook URL.')
|
||||
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
|
||||
->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
|
||||
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
|
||||
|
|
|
@ -63,7 +63,9 @@ App::post('/v1/teams')
|
|||
])));
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser) { // Don't add user on server mode
|
||||
$membershipId = $dbForProject->getId();
|
||||
$membership = new Document([
|
||||
'$id' => $membershipId,
|
||||
'$read' => ['user:'.$user->getId(), 'team:'.$team->getId()],
|
||||
'$write' => ['user:'.$user->getId(), 'team:'.$team->getId().'/owner'],
|
||||
'userId' => $user->getId(),
|
||||
|
@ -73,6 +75,7 @@ App::post('/v1/teams')
|
|||
'joined' => \time(),
|
||||
'confirm' => true,
|
||||
'secret' => '',
|
||||
'search' => implode(' ', [$membershipId, $user->getId()])
|
||||
]);
|
||||
|
||||
$membership = $dbForProject->createDocument('memberships', $membership);
|
||||
|
@ -353,8 +356,9 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
|
||||
$secret = Auth::tokenGenerator();
|
||||
|
||||
$membershipId = $dbForProject->getId();
|
||||
$membership = new Document([
|
||||
'$id' => $dbForProject->getId(),
|
||||
'$id' => $membershipId,
|
||||
'$read' => ['role:all'],
|
||||
'$write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'],
|
||||
'userId' => $invitee->getId(),
|
||||
|
@ -364,6 +368,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
'joined' => ($isPrivilegedUser || $isAppUser) ? \time() : 0,
|
||||
'confirm' => ($isPrivilegedUser || $isAppUser),
|
||||
'secret' => Auth::hash($secret),
|
||||
'search' => implode(' ', [$membershipId, $invitee->getId()])
|
||||
]);
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership
|
||||
|
@ -458,8 +463,27 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
}
|
||||
}
|
||||
|
||||
$memberships = $dbForProject->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset, [], [$orderType], $cursorMembership ?? null, $cursorDirection);
|
||||
$sum = $dbForProject->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], APP_LIMIT_COUNT);
|
||||
$queries = [new Query('teamId', Query::TYPE_EQUAL, [$teamId])];
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
}
|
||||
|
||||
$memberships = $dbForProject->find(
|
||||
collection: 'memberships',
|
||||
queries: $queries,
|
||||
limit: $limit,
|
||||
offset: $offset,
|
||||
orderTypes: [$orderType],
|
||||
cursor: $cursorMembership ?? null,
|
||||
cursorDirection: $cursorDirection
|
||||
);
|
||||
|
||||
$sum = $dbForProject->count(
|
||||
collection:'memberships',
|
||||
queries: $queries,
|
||||
max: APP_LIMIT_COUNT
|
||||
);
|
||||
|
||||
$memberships = array_filter($memberships, fn(Document $membership) => !empty($membership->getAttribute('userId')));
|
||||
|
||||
|
@ -565,25 +589,40 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
|||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isOwner = Authorization::isRole('team:'.$team->getId().'/owner');;
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');;
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception('User is not allowed to modify roles', 401);
|
||||
}
|
||||
|
||||
// Update the roles
|
||||
/**
|
||||
* Update the roles
|
||||
*/
|
||||
$membership->setAttribute('roles', $roles);
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
|
||||
// TODO sync updated membership in the user $profile object using TYPE_REPLACE
|
||||
/**
|
||||
* Replace membership on profile
|
||||
*/
|
||||
$memberships = array_filter($profile->getAttribute('memberships'), fn (Document $m) => $m->getId() !== $membership->getId());
|
||||
|
||||
$profile
|
||||
->setAttribute('memberships', $memberships)
|
||||
->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('users', $profile->getId(), $profile));
|
||||
|
||||
$audits
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('event', 'teams.memberships.update')
|
||||
->setParam('resource', 'team/'.$teamId)
|
||||
;
|
||||
->setParam('resource', 'team/' . $teamId);
|
||||
|
||||
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
|
||||
$response->dynamic(
|
||||
$membership
|
||||
->setAttribute('email', $profile->getAttribute('email'))
|
||||
->setAttribute('name', $profile->getAttribute('name')),
|
||||
Response::MODEL_MEMBERSHIP
|
||||
);
|
||||
});
|
||||
|
||||
App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
|
|
@ -19,6 +19,7 @@ use Utopia\Database\Document;
|
|||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Appwrite\Utopia\Request\Filters\V12;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
|
@ -513,8 +514,25 @@ App::get('/.well-known/acme-challenge')
|
|||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function ($request, $response) {
|
||||
$uriChunks = \explode('/', $request->getURI());
|
||||
$token = $uriChunks[\count($uriChunks) - 1];
|
||||
|
||||
$validator = new Text(100, [
|
||||
...Text::NUMBERS,
|
||||
...Text::ALPHABET_LOWER,
|
||||
...Text::ALPHABET_UPPER,
|
||||
'-',
|
||||
'_'
|
||||
]);
|
||||
|
||||
if (!$validator->isValid($token) || \count($uriChunks) !== 4) {
|
||||
throw new Exception('Invalid challenge token.', 400);
|
||||
}
|
||||
|
||||
$filePath = '/.well-known/acme-challenge' . $token;
|
||||
|
||||
$base = \realpath(APP_STORAGE_CERTIFICATES);
|
||||
$path = \str_replace('/.well-known/acme-challenge/', '', $request->getURI());
|
||||
$path = \str_replace('/.well-known/acme-challenge/', '', $filePath);
|
||||
$absolute = \realpath($base.'/.well-known/acme-challenge/'.$path);
|
||||
|
||||
if (!$base) {
|
||||
|
|
|
@ -365,6 +365,10 @@ services:
|
|||
- _APP_INFLUXDB_HOST
|
||||
- _APP_INFLUXDB_PORT
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
|
||||
appwrite-schedule:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
"slickdeals/statsd": "3.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"appwrite/sdk-generator": "0.17.1",
|
||||
"appwrite/sdk-generator": "0.17.2",
|
||||
"phpunit/phpunit": "9.5.10",
|
||||
"swoole/ide-helper": "4.8.3",
|
||||
"textalk/websocket": "1.5.5",
|
||||
|
|
88
composer.lock
generated
88
composer.lock
generated
|
@ -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": "eba5438fb1baf147ea65b20bba4dd5e8",
|
||||
"content-hash": "85bddee9d6951fa078bee158e8b9bd5a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
@ -531,12 +531,12 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
@ -1033,12 +1033,12 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MongoDB\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"MongoDB\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
@ -2145,12 +2145,12 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "195fd16b69c0cd388c16051826ec59fa4647a610"
|
||||
"reference": "49c74b0d8455c616895b794b24496f32f0e91b5a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/195fd16b69c0cd388c16051826ec59fa4647a610",
|
||||
"reference": "195fd16b69c0cd388c16051826ec59fa4647a610",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/49c74b0d8455c616895b794b24496f32f0e91b5a",
|
||||
"reference": "49c74b0d8455c616895b794b24496f32f0e91b5a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2200,7 +2200,7 @@
|
|||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/fix-perms"
|
||||
},
|
||||
"time": "2022-02-07T12:09:11+00:00"
|
||||
"time": "2022-02-17T14:32:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
|
@ -2258,16 +2258,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/framework",
|
||||
"version": "0.19.5",
|
||||
"version": "0.19.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/framework.git",
|
||||
"reference": "1c28ba9a5b491cf7c90c535fefee5832c7133623"
|
||||
"reference": "7d9b28365fb794001cb34dd028659452d4e71b7d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/framework/zipball/1c28ba9a5b491cf7c90c535fefee5832c7133623",
|
||||
"reference": "1c28ba9a5b491cf7c90c535fefee5832c7133623",
|
||||
"url": "https://api.github.com/repos/utopia-php/framework/zipball/7d9b28365fb794001cb34dd028659452d4e71b7d",
|
||||
"reference": "7d9b28365fb794001cb34dd028659452d4e71b7d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2301,9 +2301,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/framework/issues",
|
||||
"source": "https://github.com/utopia-php/framework/tree/0.19.5"
|
||||
"source": "https://github.com/utopia-php/framework/tree/0.19.6"
|
||||
},
|
||||
"time": "2022-01-04T14:40:23+00:00"
|
||||
"time": "2022-02-10T17:05:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
|
@ -3083,16 +3083,16 @@
|
|||
},
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "0.17.1",
|
||||
"version": "0.17.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "3542c6ed0f808b6a9f6735a8aad7ccda961bea29"
|
||||
"reference": "37bc6fc1b4b4940c7659748d7d2d5110da5457a4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/3542c6ed0f808b6a9f6735a8aad7ccda961bea29",
|
||||
"reference": "3542c6ed0f808b6a9f6735a8aad7ccda961bea29",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/37bc6fc1b4b4940c7659748d7d2d5110da5457a4",
|
||||
"reference": "37bc6fc1b4b4940c7659748d7d2d5110da5457a4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3126,9 +3126,9 @@
|
|||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.17.1"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.17.2"
|
||||
},
|
||||
"time": "2022-01-07T12:55:37+00:00"
|
||||
"time": "2022-01-28T08:25:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
|
@ -3966,16 +3966,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phar-io/version",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phar-io/version.git",
|
||||
"reference": "bae7c545bef187884426f042434e561ab1ddb182"
|
||||
"reference": "15a90844ad40f127afd244c0cad228de2a80052a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182",
|
||||
"reference": "bae7c545bef187884426f042434e561ab1ddb182",
|
||||
"url": "https://api.github.com/repos/phar-io/version/zipball/15a90844ad40f127afd244c0cad228de2a80052a",
|
||||
"reference": "15a90844ad40f127afd244c0cad228de2a80052a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -4011,9 +4011,9 @@
|
|||
"description": "Library for handling version information and constraints",
|
||||
"support": {
|
||||
"issues": "https://github.com/phar-io/version/issues",
|
||||
"source": "https://github.com/phar-io/version/tree/3.1.0"
|
||||
"source": "https://github.com/phar-io/version/tree/3.1.1"
|
||||
},
|
||||
"time": "2021-02-23T14:00:09+00:00"
|
||||
"time": "2022-02-07T21:56:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
|
@ -4622,11 +4622,11 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
],
|
||||
"files": [
|
||||
"src/Framework/Assert/Functions.php"
|
||||
],
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
|
@ -5222,16 +5222,16 @@
|
|||
},
|
||||
{
|
||||
"name": "sebastian/global-state",
|
||||
"version": "5.0.3",
|
||||
"version": "5.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/global-state.git",
|
||||
"reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49"
|
||||
"reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49",
|
||||
"reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
|
||||
"reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -5274,7 +5274,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/global-state/issues",
|
||||
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3"
|
||||
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -5282,7 +5282,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-06-11T13:31:12+00:00"
|
||||
"time": "2022-02-14T08:28:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/lines-of-code",
|
||||
|
@ -6016,12 +6016,12 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
|
|
@ -437,6 +437,10 @@ services:
|
|||
- _APP_INFLUXDB_HOST
|
||||
- _APP_INFLUXDB_PORT
|
||||
- _APP_USAGE_SYNC_INTERVAL
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
|
||||
appwrite-schedule:
|
||||
entrypoint: schedule
|
||||
|
|
|
@ -65,7 +65,7 @@ class Bitly extends OAuth2
|
|||
protected function getTokens(string $code): array
|
||||
{
|
||||
if(empty($this->tokens)) {
|
||||
$this->tokens = \json_decode($this->request(
|
||||
$response = $this->request(
|
||||
'POST',
|
||||
$this->resourceEndpoint . 'oauth/access_token',
|
||||
["Content-Type: application/x-www-form-urlencoded"],
|
||||
|
@ -76,7 +76,11 @@ class Bitly extends OAuth2
|
|||
"redirect_uri" => $this->callback,
|
||||
"state" => \json_encode($this->state)
|
||||
])
|
||||
), true);
|
||||
);
|
||||
|
||||
$output = [];
|
||||
\parse_str($response, $output);
|
||||
$this->tokens = $output;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
|
@ -89,7 +93,7 @@ class Bitly extends OAuth2
|
|||
*/
|
||||
public function refreshTokens(string $refreshToken):array
|
||||
{
|
||||
$this->tokens = \json_decode($this->request(
|
||||
$response = $this->request(
|
||||
'POST',
|
||||
$this->resourceEndpoint . 'oauth/access_token',
|
||||
["Content-Type: application/x-www-form-urlencoded"],
|
||||
|
@ -99,7 +103,11 @@ class Bitly extends OAuth2
|
|||
"refresh_token" => $refreshToken,
|
||||
'grant_type' => 'refresh_token'
|
||||
])
|
||||
), true);
|
||||
);
|
||||
|
||||
$output = [];
|
||||
\parse_str($response, $output);
|
||||
$this->tokens = $output;
|
||||
|
||||
if(empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
|
|
|
@ -53,7 +53,7 @@ class Github extends OAuth2
|
|||
protected function getTokens(string $code): array
|
||||
{
|
||||
if(empty($this->tokens)) {
|
||||
$this->tokens = \json_decode($this->request(
|
||||
$response = $this->request(
|
||||
'POST',
|
||||
'https://github.com/login/oauth/access_token',
|
||||
[],
|
||||
|
@ -63,7 +63,11 @@ class Github extends OAuth2
|
|||
'client_secret' => $this->appSecret,
|
||||
'code' => $code
|
||||
])
|
||||
), true);
|
||||
);
|
||||
|
||||
$output = [];
|
||||
\parse_str($response, $output);
|
||||
$this->tokens = $output;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
|
@ -76,7 +80,7 @@ class Github extends OAuth2
|
|||
*/
|
||||
public function refreshTokens(string $refreshToken):array
|
||||
{
|
||||
$this->tokens = \json_decode($this->request(
|
||||
$response = $this->request(
|
||||
'POST',
|
||||
'https://github.com/login/oauth/access_token',
|
||||
[],
|
||||
|
@ -86,7 +90,11 @@ class Github extends OAuth2
|
|||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken
|
||||
])
|
||||
), true);
|
||||
);
|
||||
|
||||
$output = [];
|
||||
\parse_str($response, $output);
|
||||
$this->tokens = $output;
|
||||
|
||||
if(empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
|
|
|
@ -290,7 +290,7 @@ class Realtime extends Adapter
|
|||
|
||||
$channels[] = 'documents';
|
||||
$channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents';
|
||||
$channels[] = 'documents.' . $payload->getId();
|
||||
$channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents.' . $payload->getId();
|
||||
|
||||
$roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead();
|
||||
|
||||
|
|
|
@ -9,10 +9,20 @@ use Utopia\Validator;
|
|||
*
|
||||
* Validate that an variable is a valid URL
|
||||
*
|
||||
* @package Utopia\Validator
|
||||
* @package Appwrite\Network\Validator
|
||||
*/
|
||||
class URL extends Validator
|
||||
{
|
||||
protected array $allowedSchemes;
|
||||
|
||||
/**
|
||||
* @param array $allowedSchemes
|
||||
*/
|
||||
public function __construct(array $allowedSchemes = [])
|
||||
{
|
||||
$this->allowedSchemes = $allowedSchemes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description
|
||||
*
|
||||
|
@ -22,6 +32,10 @@ class URL extends Validator
|
|||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
if (!empty($this->allowedSchemes)) {
|
||||
return 'Value must be a valid URL with following schemes (' . \implode(', ', $this->allowedSchemes) . ')';
|
||||
}
|
||||
|
||||
return 'Value must be a valid URL';
|
||||
}
|
||||
|
||||
|
@ -39,6 +53,10 @@ class URL extends Validator
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!empty($this->allowedSchemes) && !\in_array(\parse_url($value, PHP_URL_SCHEME), $this->allowedSchemes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ class Func extends Model
|
|||
->addRule('execute', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Execution permissions.',
|
||||
'default' => '',
|
||||
'default' => [],
|
||||
'example' => 'role:member',
|
||||
'array' => false,
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
@ -119,16 +119,24 @@ class Client
|
|||
}
|
||||
|
||||
/**
|
||||
* @param mixed $endpoint
|
||||
* @param string $endpoint
|
||||
* @return self $this
|
||||
*/
|
||||
public function setEndpoint($endpoint): self
|
||||
public function setEndpoint(string $endpoint): self
|
||||
{
|
||||
$this->endpoint = $endpoint;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEndpoint(): string
|
||||
{
|
||||
return $this->endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
|
@ -183,12 +191,13 @@ class Client
|
|||
unset($headers[$i]);
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_PATH_AS_IS, 1);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36');
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
|
||||
$len = strlen($header);
|
||||
|
|
|
@ -94,6 +94,35 @@ class HTTPTest extends Scope
|
|||
$this->assertStringContainsString('# robotstxt.org/', $response['body']);
|
||||
}
|
||||
|
||||
public function testAcmeChallenge()
|
||||
{
|
||||
// Preparation
|
||||
$previousEndpoint = $this->client->getEndpoint();
|
||||
$this->client->setEndpoint("http://localhost");
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/8DdIKX257k6Dih5s_saeVMpTnjPJdKO5Ase0OCiJrIg', \array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
]), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
// 'Unknown path', but validation passed
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/../../../../../../../etc/passwd', \array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
]), []);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->setEndpoint($previousEndpoint);
|
||||
}
|
||||
|
||||
// public function testSpecSwagger2()
|
||||
// {
|
||||
// $response = $this->client->call(Client::METHOD_GET, '/specs/swagger2?platform=client', [
|
||||
|
|
|
@ -259,6 +259,14 @@ trait AvatarsBase
|
|||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/avatars/image', [
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'url' => 'invalid://appwrite.io/images/apple.png'
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// TODO Add test for non-image file (PDF, WORD)
|
||||
|
||||
return [];
|
||||
|
|
|
@ -25,6 +25,7 @@ class FunctionsCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'functionId' => 'unique()',
|
||||
'execute' => ['role:all'],
|
||||
'name' => 'Test',
|
||||
'runtime' => 'php-8.0',
|
||||
'vars' => [
|
||||
|
@ -58,6 +59,9 @@ class FunctionsCustomServerTest extends Scope
|
|||
'account.create',
|
||||
'account.delete',
|
||||
], $response1['body']['events']);
|
||||
$this->assertEquals([
|
||||
'role:all'
|
||||
], $response1['body']['execute']);
|
||||
$this->assertEquals('0 0 1 1 *', $response1['body']['schedule']);
|
||||
$this->assertEquals(10, $response1['body']['timeout']);
|
||||
|
||||
|
|
|
@ -834,6 +834,17 @@ class ProjectsConsoleClientTest extends Scope
|
|||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/webhooks', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Webhook Test',
|
||||
'events' => ['account.create', 'account.update.email'],
|
||||
'url' => 'invalid://appwrite.io',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -979,6 +990,17 @@ class ProjectsConsoleClientTest extends Scope
|
|||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Webhook Test Update',
|
||||
'events' => ['account.delete', 'account.sessions.delete', 'storage.files.create'],
|
||||
'url' => 'invalid://appwrite.io/new',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
|
|
@ -80,8 +80,8 @@ class RealtimeCustomClientTest extends Scope
|
|||
'collections.1.documents',
|
||||
'collections.2.documents',
|
||||
'documents',
|
||||
'documents.1',
|
||||
'documents.2',
|
||||
'collections.1.documents.1',
|
||||
'collections.2.documents.2',
|
||||
], $headers);
|
||||
|
||||
$response = json_decode($client->receive(), true);
|
||||
|
@ -100,8 +100,8 @@ class RealtimeCustomClientTest extends Scope
|
|||
$this->assertContains('collections.1.documents', $response['data']['channels']);
|
||||
$this->assertContains('collections.2.documents', $response['data']['channels']);
|
||||
$this->assertContains('documents', $response['data']['channels']);
|
||||
$this->assertContains('documents.1', $response['data']['channels']);
|
||||
$this->assertContains('documents.2', $response['data']['channels']);
|
||||
$this->assertContains('collections.1.documents.1', $response['data']['channels']);
|
||||
$this->assertContains('collections.2.documents.2', $response['data']['channels']);
|
||||
$this->assertEquals($userId, $response['data']['user']['$id']);
|
||||
|
||||
$client->close();
|
||||
|
@ -606,7 +606,7 @@ class RealtimeCustomClientTest extends Scope
|
|||
$this->assertArrayHasKey('timestamp', $response['data']);
|
||||
$this->assertCount(3, $response['data']['channels']);
|
||||
$this->assertContains('documents', $response['data']['channels']);
|
||||
$this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']);
|
||||
$this->assertEquals('database.documents.create', $response['data']['event']);
|
||||
$this->assertNotEmpty($response['data']['payload']);
|
||||
|
@ -638,7 +638,7 @@ class RealtimeCustomClientTest extends Scope
|
|||
$this->assertArrayHasKey('timestamp', $response['data']);
|
||||
$this->assertCount(3, $response['data']['channels']);
|
||||
$this->assertContains('documents', $response['data']['channels']);
|
||||
$this->assertContains('documents.' . $data['documentId'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $data['documentId'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']);
|
||||
$this->assertEquals('database.documents.update', $response['data']['event']);
|
||||
$this->assertNotEmpty($response['data']['payload']);
|
||||
|
@ -676,7 +676,7 @@ class RealtimeCustomClientTest extends Scope
|
|||
$this->assertArrayHasKey('timestamp', $response['data']);
|
||||
$this->assertCount(3, $response['data']['channels']);
|
||||
$this->assertContains('documents', $response['data']['channels']);
|
||||
$this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']);
|
||||
$this->assertEquals('database.documents.delete', $response['data']['event']);
|
||||
$this->assertNotEmpty($response['data']['payload']);
|
||||
|
@ -767,7 +767,7 @@ class RealtimeCustomClientTest extends Scope
|
|||
$this->assertArrayHasKey('timestamp', $response['data']);
|
||||
$this->assertCount(3, $response['data']['channels']);
|
||||
$this->assertContains('documents', $response['data']['channels']);
|
||||
$this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']);
|
||||
$this->assertEquals('database.documents.create', $response['data']['event']);
|
||||
$this->assertNotEmpty($response['data']['payload']);
|
||||
|
@ -798,7 +798,7 @@ class RealtimeCustomClientTest extends Scope
|
|||
$this->assertArrayHasKey('timestamp', $response['data']);
|
||||
$this->assertCount(3, $response['data']['channels']);
|
||||
$this->assertContains('documents', $response['data']['channels']);
|
||||
$this->assertContains('documents.' . $data['documentId'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $data['documentId'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']);
|
||||
$this->assertEquals('database.documents.update', $response['data']['event']);
|
||||
$this->assertNotEmpty($response['data']['payload']);
|
||||
|
@ -836,7 +836,7 @@ class RealtimeCustomClientTest extends Scope
|
|||
$this->assertArrayHasKey('timestamp', $response['data']);
|
||||
$this->assertCount(3, $response['data']['channels']);
|
||||
$this->assertContains('documents', $response['data']['channels']);
|
||||
$this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']);
|
||||
$this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']);
|
||||
$this->assertEquals('database.documents.delete', $response['data']['event']);
|
||||
$this->assertNotEmpty($response['data']['payload']);
|
||||
|
|
|
@ -28,6 +28,48 @@ trait TeamsBaseClient
|
|||
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['email']);
|
||||
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
|
||||
|
||||
$membershipId = $response['body']['memberships'][0]['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => $this->getUser()['$id']
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['sum']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]);
|
||||
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['name']);
|
||||
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['email']);
|
||||
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => $membershipId
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['sum']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]);
|
||||
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['name']);
|
||||
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['email']);
|
||||
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => 'unknown'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['sum']);
|
||||
$this->assertEmpty($response['body']['memberships']);
|
||||
$this->assertEquals(0, $response['body']['sum']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
|
|
@ -17,10 +17,7 @@ use PHPUnit\Framework\TestCase;
|
|||
|
||||
class URLTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Domain
|
||||
*/
|
||||
protected $url = null;
|
||||
protected ?URL $url;
|
||||
|
||||
public function setUp():void
|
||||
{
|
||||
|
@ -32,9 +29,9 @@ class URLTest extends TestCase
|
|||
$this->url = null;
|
||||
}
|
||||
|
||||
public function testIsValid()
|
||||
public function testIsValid(): void
|
||||
{
|
||||
// Assertions
|
||||
$this->assertEquals('Value must be a valid URL', $this->url->getDescription());
|
||||
$this->assertEquals(true, $this->url->isValid('http://example.com'));
|
||||
$this->assertEquals(true, $this->url->isValid('https://example.com'));
|
||||
$this->assertEquals(true, $this->url->isValid('htts://example.com')); // does not validate protocol
|
||||
|
@ -45,4 +42,13 @@ class URLTest extends TestCase
|
|||
$this->assertEquals(true, $this->url->isValid('http://www.example.com/foo%2\u00c2\u00a9zbar'));
|
||||
$this->assertEquals(true, $this->url->isValid('http://www.example.com/?q=%3Casdf%3E'));
|
||||
}
|
||||
|
||||
public function testIsValidAllowedSchemes(): void
|
||||
{
|
||||
$this->url = new URL(['http', 'https']);
|
||||
$this->assertEquals('Value must be a valid URL with following schemes (http, https)', $this->url->getDescription());
|
||||
$this->assertEquals(true, $this->url->isValid('http://example.com'));
|
||||
$this->assertEquals(true, $this->url->isValid('https://example.com'));
|
||||
$this->assertEquals(false, $this->url->isValid('gopher://www.example.com'));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue