Merge branch 'master' into 0.13.x
This commit is contained in:
commit
53de779e89
|
@ -1,3 +1,12 @@
|
|||
# Version 0.12.3
|
||||
|
||||
## Bugs
|
||||
- Fix update membership roles (#2799)
|
||||
- Fix migration to 0.12.x to populate search fields (#2799)
|
||||
|
||||
## Security
|
||||
- Fix URL schema Validation to only allow http/https (#2801)
|
||||
|
||||
# Version 0.12.2
|
||||
|
||||
## Bugs
|
||||
|
|
|
@ -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.2
|
||||
appwrite/appwrite:0.12.3
|
||||
```
|
||||
|
||||
### 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.2
|
||||
appwrite/appwrite:0.12.3
|
||||
```
|
||||
|
||||
#### 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.2
|
||||
appwrite/appwrite:0.12.3
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 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.2
|
||||
appwrite/appwrite:0.12.3
|
||||
```
|
||||
|
||||
### 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.2
|
||||
appwrite/appwrite:0.12.3
|
||||
```
|
||||
|
||||
#### 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.2
|
||||
appwrite/appwrite:0.12.3
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
|
@ -1541,6 +1541,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' => [
|
||||
[
|
||||
|
@ -1564,6 +1575,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
|
@ -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 */
|
||||
|
|
|
@ -585,7 +585,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)
|
||||
|
@ -707,7 +707,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, Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// 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')
|
||||
|
|
|
@ -70,7 +70,7 @@ const APP_LIMIT_ENCRYPTION = 20000000; //20MB
|
|||
const APP_LIMIT_COMPRESSION = 20000000; //20MB
|
||||
const APP_LIMIT_PREVIEW = 10000000; //10MB file size limit for preview endpoint
|
||||
const APP_CACHE_BUSTER = 201;
|
||||
const APP_VERSION_STABLE = '0.13.0';
|
||||
const APP_VERSION_STABLE = '0.12.3';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
|
|
@ -494,7 +494,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
|
|||
<input type="hidden" data-forms-key-value data-ls-attrs="name={{$index}}" data-ls-bind="{{var}}" />
|
||||
</div>
|
||||
<div class="col span-2">
|
||||
<button type="button" data-remove class="reverse danger round pull-end"><i class="icon-cancel"></i></button>
|
||||
<button type="button" data-remove class="close pull-end is-margin-top-10"><i class="icon-cancel"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -507,7 +507,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
|
|||
<input type="hidden" data-ls-attrs="data-forms-key-value"/>
|
||||
</div>
|
||||
<div class="col span-2">
|
||||
<button type="button" data-remove class="reverse danger round pull-end"><i class="icon-cancel"></i></button>
|
||||
<button type="button" data-remove class="close pull-end is-margin-top-10"><i class="icon-cancel"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -241,11 +241,6 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="text-align-center text-size-small text-bold text-success" data-ls-if="!!({{console-project.serviceStatusFor<?php echo ucFirst($this->escape($key)); ?>}})">Enabled</div>
|
||||
<div class="text-align-center text-size-small text-bold text-danger" data-ls-if="(!{{console-project.serviceStatusFor<?php echo ucFirst($this->escape($key)); ?>}})">Disabled</div>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
|
@ -548,7 +543,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
<div class="margin-bottom-tiny">
|
||||
<span data-ls-bind="{{member.name}}"></span> <span class="tag" data-ls-bind="{{member.roles.0}}"></span> <span data-ls-if="false === {{member.confirm}}" class="tag red">Pending Approval</span>
|
||||
</div>
|
||||
<span class="text-size-small text-fade" data-ls-bind="{{member.email}}"></small>
|
||||
<span class="text-size-small text-fade" data-ls-bind="{{member.email}}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -79,13 +79,13 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
</td>
|
||||
<td data-title="Name: ">
|
||||
<a data-ls-attrs="href=/console/users/user?id={{user.$id}}&project={{router.params.project}}">
|
||||
<span data-ls-bind="{{user.name}}"></span>
|
||||
<span data-ls-bind="{{user.name}}" data-ls-attrs="title={{user.name}}"></span>
|
||||
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} !== ''">Unknown</span>
|
||||
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} === ''">Anonymous User</span>
|
||||
</a>
|
||||
</td>
|
||||
<td data-title="Email: ">
|
||||
<small data-ls-bind="{{user.email}}"></span>
|
||||
<small data-ls-bind="{{user.email}}" data-ls-attrs="title={{user.email}}"></span>
|
||||
</td>
|
||||
<td data-title="Status: ">
|
||||
<span data-ls-if="{{user.emailVerification}} === true && {{user.status}} === true">
|
||||
|
@ -245,7 +245,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
<img src="" data-ls-attrs="src={{team.name|avatar}}" data-size="45" alt="Collection Avatar" class="avatar margin-end pull-start" loading="lazy" width="30" height="30" />
|
||||
</td>
|
||||
<td data-title="Name: ">
|
||||
<a data-ls-attrs="href=/console/users/teams/team?id={{team.$id}}&project={{router.params.project}}" data-ls-bind="{{team.name}}"></a>
|
||||
<a data-ls-attrs="href=/console/users/teams/team?id={{team.$id}}&project={{router.params.project}}" data-ls-bind="{{team.name}}" data-ls-attrs="title={{team.name}}"></a>
|
||||
</td>
|
||||
<td data-title="Members: "><span data-ls-bind="{{team.sum}} members"></span></td>
|
||||
<td data-title="Date Created: "><small data-ls-bind="{{team.dateCreated|dateText}}"></small></td>
|
||||
|
@ -406,7 +406,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
|||
|
||||
<img src="<?php echo $this->escape($icon); ?>?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="Email/Password Logo" class="pull-start provider margin-end" />
|
||||
|
||||
<span class="text-size-small"><?php echo $this->escape($name); ?><?php if(!$enabled): ?> <spann class="text-fade text-size-xs">soon</span><?php endif; ?>
|
||||
<span class="text-size-small"><?php echo $this->escape($name); ?><?php if(!$enabled): ?> <span class="text-fade text-size-xs">soon</span><?php endif; ?>
|
||||
|
||||
<?php if( in_array($key, ['usersAuthMagicURL', 'usersAuthInvites']) && !$smtpEnabled): ?>
|
||||
<p class="margin-bottom-no text-one-liner text-size-small text-danger">
|
||||
|
|
|
@ -295,8 +295,8 @@
|
|||
data-param-user-id="{{router.params.id}}"
|
||||
data-event="load,users.update">
|
||||
|
||||
<div data-ls-if="{{sessions.sessions.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
|
||||
No sessions available.
|
||||
<div class="box margin-top margin-bottom" data-ls-if="{{sessions.sessions.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
|
||||
<h3 class="text-bold margin-bottom-no">No sessions available.</h3>
|
||||
</div>
|
||||
|
||||
<div data-ls-if="{{sessions.sessions.length}} !== 0" style="display: none">
|
||||
|
@ -372,8 +372,8 @@
|
|||
data-param-user-id="{{router.params.id}}"
|
||||
data-event="load,logs-load">
|
||||
|
||||
<div data-ls-if="{{logs.logs.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
|
||||
No logs available.
|
||||
<div class="box margin-top margin-bottom" data-ls-if="{{logs.logs.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
|
||||
<h3 class="text-bold margin-bottom-no">No logs available.</h3>
|
||||
</div>
|
||||
|
||||
<div class="box" data-ls-if="{{logs.logs.length}} !== 0" style="display: none">
|
||||
|
|
2
public/dist/styles/default-ltr.css
vendored
2
public/dist/styles/default-ltr.css
vendored
File diff suppressed because one or more lines are too long
2
public/dist/styles/default-rtl.css
vendored
2
public/dist/styles/default-rtl.css
vendored
File diff suppressed because one or more lines are too long
|
@ -164,17 +164,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
button.close {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: var(--config-color-normal);
|
||||
color: var(--config-color-background-fade);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.paging {
|
||||
form {
|
||||
padding: 0;
|
||||
|
|
|
@ -239,6 +239,20 @@ button,
|
|||
}
|
||||
}
|
||||
|
||||
button.close {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: var(--config-color-normal);
|
||||
color: var(--config-color-background-fade);
|
||||
border-radius: 50%;
|
||||
.icon-cancel::before{line-height:1;}
|
||||
&.is-margin-top-10{margin-top:10px;}
|
||||
}
|
||||
|
||||
|
||||
label {
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
|
@ -622,6 +636,10 @@ input[type=checkbox], input[type=radio] {
|
|||
}
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
&:after {border-radius:4px;}
|
||||
}
|
||||
|
||||
.input-copy {
|
||||
position: relative;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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