Merge remote-tracking branch 'origin/0.16.x' into refactor-permissions-inc-console-fix
# Conflicts: # app/controllers/general.php # composer.lock
This commit is contained in:
commit
ed712fb196
|
@ -22,6 +22,7 @@ ports:
|
|||
vscode:
|
||||
extensions:
|
||||
- ms-azuretools.vscode-docker
|
||||
- zobo.php-intellisense
|
||||
|
||||
github:
|
||||
# https://www.gitpod.io/docs/prebuilds#github-specific-configuration
|
||||
|
|
|
@ -31,6 +31,16 @@ return [ // Ordered by ABC.
|
|||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'authentik' => [
|
||||
'name' => 'Authentik',
|
||||
'developers' => 'https://goauthentik.io/docs/',
|
||||
'icon' => 'icon-authentik',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => 'authentik.phtml',
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'autodesk' => [
|
||||
'name' => 'Autodesk',
|
||||
'developers' => 'https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/',
|
||||
|
@ -91,6 +101,16 @@ return [ // Ordered by ABC.
|
|||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'disqus' => [
|
||||
'name' => 'Disqus',
|
||||
'developers' => 'https://disqus.com/api/docs/auth/',
|
||||
'icon' => 'icon-disqus',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'dropbox' => [
|
||||
'name' => 'Dropbox',
|
||||
'developers' => 'https://www.dropbox.com/developers/documentation',
|
||||
|
@ -201,6 +221,16 @@ return [ // Ordered by ABC.
|
|||
'beta' => false,
|
||||
'mock' => false
|
||||
],
|
||||
'podio' => [
|
||||
'name' => 'Podio',
|
||||
'developers' => 'https://developers.podio.com/doc/oauth-authorization',
|
||||
'icon' => 'icon-podio',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'salesforce' => [
|
||||
'name' => 'Salesforce',
|
||||
'developers' => 'https://developer.salesforce.com/docs/',
|
||||
|
|
|
@ -1477,7 +1477,7 @@ App::patch('/v1/account/name')
|
|||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email')])));
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email', ''), $user->getAttribute('phone', '')])));
|
||||
|
||||
$audits
|
||||
->setResource('user/' . $user->getId())
|
||||
|
@ -1573,7 +1573,7 @@ App::patch('/v1/account/email')
|
|||
->setAttribute('password', $isAnonymousUser ? Auth::passwordHash($password) : $user->getAttribute('password', ''))
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $email, $user->getAttribute('phone', '')]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
@ -1626,7 +1626,7 @@ App::patch('/v1/account/phone')
|
|||
$user
|
||||
->setAttribute('phone', $phone)
|
||||
->setAttribute('phoneVerification', false) // After this user needs to confirm phone number again
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name'), $user->getAttribute('email')]));
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $phone]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
|
|
@ -31,12 +31,14 @@ use Utopia\Validator\Range;
|
|||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::init(function (Document $project) {
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN);
|
||||
}
|
||||
}, ['project'], 'projects');
|
||||
App::init()
|
||||
->groups(['projects'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
if ($project->getId() !== 'console') {
|
||||
throw new Exception('Access to this API is forbidden.', 401, Exception::GENERAL_ACCESS_FORBIDDEN);
|
||||
}
|
||||
});
|
||||
|
||||
App::post('/v1/projects')
|
||||
->desc('Create Project')
|
||||
|
|
|
@ -5,7 +5,6 @@ use Appwrite\ClamAV\Network;
|
|||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Permissions\Permissions;
|
||||
use Appwrite\Permissions\PermissionsProcessor;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
|
|
|
@ -445,8 +445,8 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$response->dynamic(
|
||||
$membership
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email')),
|
||||
->setAttribute('userName', $invitee->getAttribute('name'))
|
||||
->setAttribute('userEmail', $invitee->getAttribute('email')),
|
||||
Response::MODEL_MEMBERSHIP
|
||||
);
|
||||
});
|
||||
|
|
|
@ -509,7 +509,7 @@ App::patch('/v1/users/:userId/name')
|
|||
|
||||
$user
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email'), $name]));
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $user->getAttribute('email', ''), $name, $user->getAttribute('phone', '')]));
|
||||
;
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
@ -599,7 +599,7 @@ App::patch('/v1/users/:userId/email')
|
|||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false)
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name')]))
|
||||
->setAttribute('search', \implode(' ', [$user->getId(), $email, $user->getAttribute('name', ''), $user->getAttribute('phone', '')]))
|
||||
;
|
||||
|
||||
try {
|
||||
|
@ -649,6 +649,7 @@ App::patch('/v1/users/:userId/phone')
|
|||
$user
|
||||
->setAttribute('phone', $number)
|
||||
->setAttribute('phoneVerification', false)
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $number]));
|
||||
;
|
||||
|
||||
try {
|
||||
|
|
|
@ -35,485 +35,506 @@ Config::setParam('domainVerification', false);
|
|||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
App::init(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients) {
|
||||
App::init()
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('console')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('user')
|
||||
->inject('locale')
|
||||
->inject('clients')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients) {
|
||||
/*
|
||||
* Request format
|
||||
*/
|
||||
$route = $utopia->match($request);
|
||||
Request::setRoute($route);
|
||||
|
||||
/*
|
||||
* Request format
|
||||
*/
|
||||
$route = $utopia->match($request);
|
||||
Request::setRoute($route);
|
||||
|
||||
$requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($requestFormat) {
|
||||
switch ($requestFormat) {
|
||||
case version_compare($requestFormat, '0.12.0', '<'):
|
||||
Request::setFilter(new RequestV12());
|
||||
break;
|
||||
case version_compare($requestFormat, '0.13.0', '<'):
|
||||
Request::setFilter(new RequestV13());
|
||||
break;
|
||||
case version_compare($requestFormat, '0.14.0', '<'):
|
||||
Request::setFilter(new RequestV14());
|
||||
break;
|
||||
default:
|
||||
Request::setFilter(null);
|
||||
}
|
||||
} else {
|
||||
Request::setFilter(null);
|
||||
}
|
||||
|
||||
$domain = $request->getHostname();
|
||||
$domains = Config::getParam('domains', []);
|
||||
if (!array_key_exists($domain, $domains)) {
|
||||
$domain = new Domain(!empty($domain) ? $domain : '');
|
||||
|
||||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$domains[$domain->get()] = false;
|
||||
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
|
||||
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
} else {
|
||||
Authorization::disable();
|
||||
|
||||
$envDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
$mainDomain = null;
|
||||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
$mainDomain = $envDomain;
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
|
||||
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
|
||||
$requestFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($requestFormat) {
|
||||
switch ($requestFormat) {
|
||||
case version_compare($requestFormat, '0.12.0', '<'):
|
||||
Request::setFilter(new RequestV12());
|
||||
break;
|
||||
case version_compare($requestFormat, '0.13.0', '<'):
|
||||
Request::setFilter(new RequestV13());
|
||||
break;
|
||||
case version_compare($requestFormat, '0.14.0', '<'):
|
||||
Request::setFilter(new RequestV14());
|
||||
break;
|
||||
default:
|
||||
Request::setFilter(null);
|
||||
}
|
||||
} else {
|
||||
Request::setFilter(null);
|
||||
}
|
||||
|
||||
if ($mainDomain !== $domain->get()) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
$domain = $request->getHostname();
|
||||
$domains = Config::getParam('domains', []);
|
||||
if (!array_key_exists($domain, $domains)) {
|
||||
$domain = new Domain(!empty($domain) ? $domain : '');
|
||||
|
||||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$domains[$domain->get()] = false;
|
||||
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
|
||||
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('domains', [
|
||||
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
|
||||
]);
|
||||
Authorization::disable();
|
||||
|
||||
if (!$domainDocument) {
|
||||
$domainDocument = new Document([
|
||||
'domain' => $domain->get(),
|
||||
'tld' => $domain->getSuffix(),
|
||||
'registerable' => $domain->getRegisterable(),
|
||||
'verification' => false,
|
||||
'certificateId' => null,
|
||||
$envDomain = App::getEnv('_APP_DOMAIN', '');
|
||||
$mainDomain = null;
|
||||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
$mainDomain = $envDomain;
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']);
|
||||
$mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get();
|
||||
}
|
||||
|
||||
if ($mainDomain !== $domain->get()) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->findOne('domains', [
|
||||
new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()])
|
||||
]);
|
||||
|
||||
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
|
||||
if (!$domainDocument) {
|
||||
$domainDocument = new Document([
|
||||
'domain' => $domain->get(),
|
||||
'tld' => $domain->getSuffix(),
|
||||
'registerable' => $domain->getRegisterable(),
|
||||
'verification' => false,
|
||||
'certificateId' => null,
|
||||
]);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
$domainDocument = $dbForConsole->createDocument('domains', $domainDocument);
|
||||
|
||||
(new Certificate())
|
||||
->setDomain($domainDocument)
|
||||
->trigger();
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
|
||||
(new Certificate())
|
||||
->setDomain($domainDocument)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
$domains[$domain->get()] = true;
|
||||
|
||||
Authorization::reset(); // ensure authorization is re-enabled
|
||||
}
|
||||
$domains[$domain->get()] = true;
|
||||
|
||||
Authorization::reset(); // ensure authorization is re-enabled
|
||||
}
|
||||
Config::setParam('domains', $domains);
|
||||
}
|
||||
|
||||
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
if (\in_array($localeParam, Config::getParam('locale-codes'))) {
|
||||
$locale->setDefault($localeParam);
|
||||
}
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!empty($route->getLabel('sdk.auth', [])) && $project->isEmpty() && ($route->getLabel('scope', '') !== 'public')) {
|
||||
throw new AppwriteException('Missing or unknown project ID', 400, AppwriteException::PROJECT_UNKNOWN);
|
||||
}
|
||||
|
||||
$referrer = $request->getReferer();
|
||||
$origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST);
|
||||
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
|
||||
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
|
||||
|
||||
$refDomainOrigin = 'localhost';
|
||||
$validator = new Hostname($clients);
|
||||
if ($validator->isValid($origin)) {
|
||||
$refDomainOrigin = $origin;
|
||||
}
|
||||
|
||||
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
|
||||
|
||||
$refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
|
||||
? $refDomain
|
||||
: (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
|
||||
|
||||
$selfDomain = new Domain($request->getHostname());
|
||||
$endDomain = new Domain((string)$origin);
|
||||
|
||||
Config::setParam(
|
||||
'domainVerification',
|
||||
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
|
||||
$endDomain->getRegisterable() !== ''
|
||||
);
|
||||
|
||||
Config::setParam('cookieDomain', (
|
||||
$request->getHostname() === 'localhost' ||
|
||||
$request->getHostname() === 'localhost:' . $request->getPort() ||
|
||||
(\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false)
|
||||
)
|
||||
? null
|
||||
: '.' . $request->getHostname());
|
||||
|
||||
/*
|
||||
* Response format
|
||||
*/
|
||||
$responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
switch ($responseFormat) {
|
||||
case version_compare($responseFormat, '0.11.2', '<='):
|
||||
Response::setFilter(new ResponseV11());
|
||||
break;
|
||||
case version_compare($responseFormat, '0.12.4', '<='):
|
||||
Response::setFilter(new ResponseV12());
|
||||
break;
|
||||
case version_compare($responseFormat, '0.13.4', '<='):
|
||||
Response::setFilter(new ResponseV13());
|
||||
break;
|
||||
case version_compare($responseFormat, '0.14.0', '<='):
|
||||
Response::setFilter(new ResponseV14());
|
||||
break;
|
||||
default:
|
||||
Response::setFilter(null);
|
||||
}
|
||||
} else {
|
||||
Response::setFilter(null);
|
||||
}
|
||||
|
||||
/*
|
||||
* Security Headers
|
||||
*
|
||||
* As recommended at:
|
||||
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
*/
|
||||
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https') {
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException('Method unsupported over HTTP.', 500, AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED);
|
||||
}
|
||||
|
||||
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
Config::setParam('domains', $domains);
|
||||
}
|
||||
|
||||
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $refDomain)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
;
|
||||
|
||||
/*
|
||||
* Validate Client Domain - Check to avoid CSRF attack
|
||||
* Adding Appwrite API domains to allow XDOMAIN communication
|
||||
* Skip this check for non-web platforms which are not required to send an origin header
|
||||
*/
|
||||
$origin = $request->getOrigin($request->getReferer(''));
|
||||
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
|
||||
|
||||
if (
|
||||
!$originValidator->isValid($origin)
|
||||
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
|
||||
&& $route->getLabel('origin', false) !== '*'
|
||||
&& empty($request->getHeader('x-appwrite-key', ''))
|
||||
) {
|
||||
throw new AppwriteException($originValidator->getDescription(), 403, AppwriteException::GENERAL_UNKNOWN_ORIGIN);
|
||||
}
|
||||
|
||||
/*
|
||||
* ACL Check
|
||||
*/
|
||||
$role = ($user->isEmpty()) ? Auth::USER_ROLE_GUESTS : Auth::USER_ROLE_USERS;
|
||||
|
||||
// Add user roles
|
||||
$memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships');
|
||||
|
||||
if ($memberships) {
|
||||
foreach ($memberships->getAttribute('roles', []) as $memberRole) {
|
||||
switch ($memberRole) {
|
||||
case 'owner':
|
||||
$role = Auth::USER_ROLE_OWNER;
|
||||
break;
|
||||
case 'admin':
|
||||
$role = Auth::USER_ROLE_ADMIN;
|
||||
break;
|
||||
case 'developer':
|
||||
$role = Auth::USER_ROLE_DEVELOPER;
|
||||
break;
|
||||
}
|
||||
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
if (\in_array($localeParam, Config::getParam('locale-codes'))) {
|
||||
$locale->setDefault($localeParam);
|
||||
}
|
||||
}
|
||||
|
||||
$roles = Config::getParam('roles', []);
|
||||
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
|
||||
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
|
||||
|
||||
$authKey = $request->getHeader('x-appwrite-key', '');
|
||||
|
||||
if (!empty($authKey)) { // API Key authentication
|
||||
// Check if given key match project API keys
|
||||
$key = $project->find('secret', $authKey, 'keys');
|
||||
|
||||
/*
|
||||
* Try app auth when we have project key and no user
|
||||
* Mock user to app and grant API key scopes in addition to default app scopes
|
||||
*/
|
||||
if ($key && $user->isEmpty()) {
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $project->getAttribute('name', 'Untitled'),
|
||||
]);
|
||||
|
||||
$role = Auth::USER_ROLE_APP;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
|
||||
|
||||
$expire = $key->getAttribute('expire', 0);
|
||||
|
||||
if (!empty($expire) && $expire < \time()) {
|
||||
throw new AppwriteException('Project key expired', 401, AppwriteException:: PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
Authorization::setRole(Auth::USER_ROLE_APP);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
}
|
||||
}
|
||||
|
||||
Authorization::setRole($role);
|
||||
|
||||
foreach (Auth::getRoles($user) as $authRole) {
|
||||
Authorization::setRole($authRole);
|
||||
}
|
||||
|
||||
$service = $route->getLabel('sdk.namespace', '');
|
||||
if (!empty($service)) {
|
||||
if (
|
||||
array_key_exists($service, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$service]
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
if (!\in_array($scope, $scopes)) {
|
||||
if ($project->isEmpty()) { // Check if permission is denied because project is missing
|
||||
if ($project->isEmpty()) {
|
||||
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
throw new AppwriteException($user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')', 401, AppwriteException::GENERAL_UNAUTHORIZED_SCOPE);
|
||||
}
|
||||
|
||||
if (false === $user->getAttribute('status')) { // Account is blocked
|
||||
throw new AppwriteException('Invalid credentials. User is blocked', 401, AppwriteException::USER_BLOCKED);
|
||||
}
|
||||
|
||||
if ($user->getAttribute('reset')) {
|
||||
throw new AppwriteException('Password reset is required', 412, AppwriteException::USER_PASSWORD_RESET_REQUIRED);
|
||||
}
|
||||
}, ['utopia', 'request', 'response', 'console', 'project', 'dbForConsole', 'user', 'locale', 'clients']);
|
||||
|
||||
App::options(function (Request $request, Response $response) {
|
||||
|
||||
$origin = $request->getOrigin();
|
||||
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
->noContent();
|
||||
}, ['request', 'response']);
|
||||
|
||||
App::error(function (Throwable $error, App $utopia, Request $request, Response $response, View $layout, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
|
||||
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->match($request);
|
||||
|
||||
/** Delegate PDO exceptions to the global handler so the database connection can be returned to the pool */
|
||||
if ($error instanceof PDOException) {
|
||||
throw $error;
|
||||
}
|
||||
|
||||
if ($logger) {
|
||||
if ($error->getCode() >= 500 || $error->getCode() === 0) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
} catch (\Throwable $th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
$log = new Utopia\Logger\Log();
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
}
|
||||
|
||||
$log->setNamespace("http");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('method', $route->getMethod());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('projectId', $project->getId());
|
||||
$log->addTag('hostname', $request->getHostname());
|
||||
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
$log->addExtra('roles', Authorization::$roles);
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
|
||||
$log->addBreadcrumb($loggerBreadcrumb);
|
||||
}
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
$file = $error->getFile();
|
||||
$line = $error->getLine();
|
||||
$trace = $error->getTrace();
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
|
||||
if ($route) {
|
||||
Console::error('[Error] Method: ' . $route->getMethod());
|
||||
Console::error('[Error] URL: ' . $route->getPath());
|
||||
if (!empty($route->getLabel('sdk.auth', [])) && $project->isEmpty() && ($route->getLabel('scope', '') !== 'public')) {
|
||||
throw new AppwriteException('Missing or unknown project ID', 400, AppwriteException::PROJECT_UNKNOWN);
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
Console::error('[Error] Message: ' . $message);
|
||||
Console::error('[Error] File: ' . $file);
|
||||
Console::error('[Error] Line: ' . $line);
|
||||
}
|
||||
$referrer = $request->getReferer();
|
||||
$origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST);
|
||||
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
|
||||
$port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
|
||||
|
||||
/** Handle Utopia Errors */
|
||||
if ($error instanceof Utopia\Exception) {
|
||||
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
|
||||
switch ($code) {
|
||||
case 400:
|
||||
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
|
||||
break;
|
||||
case 404:
|
||||
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
break;
|
||||
$refDomainOrigin = 'localhost';
|
||||
$validator = new Hostname($clients);
|
||||
if ($validator->isValid($origin)) {
|
||||
$refDomainOrigin = $origin;
|
||||
}
|
||||
}
|
||||
|
||||
/** Wrap all exceptions inside Appwrite\Extend\Exception */
|
||||
if (!($error instanceof AppwriteException)) {
|
||||
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
|
||||
}
|
||||
$refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : '');
|
||||
|
||||
switch ($code) { // Don't show 500 errors!
|
||||
case 400: // Error allowed publicly
|
||||
case 401: // Error allowed publicly
|
||||
case 402: // Error allowed publicly
|
||||
case 403: // Error allowed publicly
|
||||
case 404: // Error allowed publicly
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
case 416: // Error allowed publicly
|
||||
case 429: // Error allowed publicly
|
||||
case 501: // Error allowed publicly
|
||||
case 503: // Error allowed publicly
|
||||
break;
|
||||
default:
|
||||
$code = 500; // All other errors get the generic 500 server error status code
|
||||
$message = 'Server Error';
|
||||
}
|
||||
$refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible
|
||||
? $refDomain
|
||||
: (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
|
||||
|
||||
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
|
||||
$selfDomain = new Domain($request->getHostname());
|
||||
$endDomain = new Domain((string)$origin);
|
||||
|
||||
$type = $error->getType();
|
||||
Config::setParam(
|
||||
'domainVerification',
|
||||
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
|
||||
$endDomain->getRegisterable() !== ''
|
||||
);
|
||||
|
||||
$output = ((App::isDevelopment())) ? [
|
||||
'message' => $message,
|
||||
'code' => $code,
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
'trace' => $trace,
|
||||
'version' => $version,
|
||||
'type' => $type,
|
||||
] : [
|
||||
'message' => $message,
|
||||
'code' => $code,
|
||||
'version' => $version,
|
||||
'type' => $type,
|
||||
];
|
||||
Config::setParam('cookieDomain', (
|
||||
$request->getHostname() === 'localhost' ||
|
||||
$request->getHostname() === 'localhost:' . $request->getPort() ||
|
||||
(\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false)
|
||||
)
|
||||
? null
|
||||
: '.' . $request->getHostname());
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Expires', '0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->setStatusCode($code)
|
||||
;
|
||||
/*
|
||||
* Response format
|
||||
*/
|
||||
$responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
switch ($responseFormat) {
|
||||
case version_compare($responseFormat, '0.11.2', '<='):
|
||||
Response::setFilter(new ResponseV11());
|
||||
break;
|
||||
case version_compare($responseFormat, '0.12.4', '<='):
|
||||
Response::setFilter(new ResponseV12());
|
||||
break;
|
||||
case version_compare($responseFormat, '0.13.4', '<='):
|
||||
Response::setFilter(new ResponseV13());
|
||||
break;
|
||||
case version_compare($responseFormat, '0.14.0', '<='):
|
||||
Response::setFilter(new ResponseV14());
|
||||
break;
|
||||
default:
|
||||
Response::setFilter(null);
|
||||
}
|
||||
} else {
|
||||
Response::setFilter(null);
|
||||
}
|
||||
|
||||
$template = ($route) ? $route->getLabel('error', null) : null;
|
||||
/*
|
||||
* Security Headers
|
||||
*
|
||||
* As recommended at:
|
||||
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
*/
|
||||
if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https') {
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException('Method unsupported over HTTP.', 500, AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED);
|
||||
}
|
||||
|
||||
if ($template) {
|
||||
$comp = new View($template);
|
||||
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
}
|
||||
|
||||
$comp
|
||||
->setParam('development', App::isDevelopment())
|
||||
->setParam('projectName', $project->getAttribute('name'))
|
||||
->setParam('projectURL', $project->getAttribute('url'))
|
||||
->setParam('message', $error->getMessage())
|
||||
->setParam('code', $code)
|
||||
->setParam('trace', $trace)
|
||||
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $refDomain)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', $project->getAttribute('name') . ' - Error')
|
||||
->setParam('description', 'No Description')
|
||||
->setParam('body', $comp)
|
||||
->setParam('version', $version)
|
||||
->setParam('litespeed', false)
|
||||
/*
|
||||
* Validate Client Domain - Check to avoid CSRF attack
|
||||
* Adding Appwrite API domains to allow XDOMAIN communication
|
||||
* Skip this check for non-web platforms which are not required to send an origin header
|
||||
*/
|
||||
$origin = $request->getOrigin($request->getReferer(''));
|
||||
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
|
||||
|
||||
if (
|
||||
!$originValidator->isValid($origin)
|
||||
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
|
||||
&& $route->getLabel('origin', false) !== '*'
|
||||
&& empty($request->getHeader('x-appwrite-key', ''))
|
||||
) {
|
||||
throw new AppwriteException($originValidator->getDescription(), 403, AppwriteException::GENERAL_UNKNOWN_ORIGIN);
|
||||
}
|
||||
|
||||
/*
|
||||
* ACL Check
|
||||
*/
|
||||
$role = ($user->isEmpty()) ? Auth::USER_ROLE_GUESTS : Auth::USER_ROLE_USERS;
|
||||
|
||||
// Add user roles
|
||||
$memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships');
|
||||
|
||||
if ($memberships) {
|
||||
foreach ($memberships->getAttribute('roles', []) as $memberRole) {
|
||||
switch ($memberRole) {
|
||||
case 'owner':
|
||||
$role = Auth::USER_ROLE_OWNER;
|
||||
break;
|
||||
case 'admin':
|
||||
$role = Auth::USER_ROLE_ADMIN;
|
||||
break;
|
||||
case 'developer':
|
||||
$role = Auth::USER_ROLE_DEVELOPER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$roles = Config::getParam('roles', []);
|
||||
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
|
||||
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
|
||||
|
||||
$authKey = $request->getHeader('x-appwrite-key', '');
|
||||
|
||||
if (!empty($authKey)) { // API Key authentication
|
||||
// Check if given key match project API keys
|
||||
$key = $project->find('secret', $authKey, 'keys');
|
||||
|
||||
/*
|
||||
* Try app auth when we have project key and no user
|
||||
* Mock user to app and grant API key scopes in addition to default app scopes
|
||||
*/
|
||||
if ($key && $user->isEmpty()) {
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $project->getAttribute('name', 'Untitled'),
|
||||
]);
|
||||
|
||||
$role = Auth::USER_ROLE_APP;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
|
||||
|
||||
$expire = $key->getAttribute('expire', 0);
|
||||
|
||||
if (!empty($expire) && $expire < \time()) {
|
||||
throw new AppwriteException('Project key expired', 401, AppwriteException:: PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
Authorization::setRole(Auth::USER_ROLE_APP);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
}
|
||||
}
|
||||
|
||||
Authorization::setRole($role);
|
||||
|
||||
foreach (Auth::getRoles($user) as $authRole) {
|
||||
Authorization::setRole($authRole);
|
||||
}
|
||||
|
||||
$service = $route->getLabel('sdk.namespace', '');
|
||||
if (!empty($service)) {
|
||||
if (
|
||||
array_key_exists($service, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$service]
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
) {
|
||||
throw new AppwriteException('Service is disabled', 503, AppwriteException::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
if (!\in_array($scope, $scopes)) {
|
||||
if ($project->isEmpty()) { // Check if permission is denied because project is missing
|
||||
throw new AppwriteException('Project not found', 404, AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
throw new AppwriteException($user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')', 401, AppwriteException::GENERAL_UNAUTHORIZED_SCOPE);
|
||||
}
|
||||
|
||||
if (false === $user->getAttribute('status')) { // Account is blocked
|
||||
throw new AppwriteException('Invalid credentials. User is blocked', 401, AppwriteException::USER_BLOCKED);
|
||||
}
|
||||
|
||||
if ($user->getAttribute('reset')) {
|
||||
throw new AppwriteException('Password reset is required', 412, AppwriteException::USER_PASSWORD_RESET_REQUIRED);
|
||||
}
|
||||
});
|
||||
|
||||
App::options()
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
|
||||
$origin = $request->getOrigin();
|
||||
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
->noContent();
|
||||
});
|
||||
|
||||
App::error()
|
||||
->inject('error')
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->inject('project')
|
||||
->inject('logger')
|
||||
->inject('loggerBreadcrumbs')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, View $layout, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
|
||||
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->match($request);
|
||||
|
||||
/** Delegate PDO exceptions to the global handler so the database connection can be returned to the pool */
|
||||
if ($error instanceof PDOException) {
|
||||
throw $error;
|
||||
}
|
||||
|
||||
if ($logger) {
|
||||
if ($error->getCode() >= 500 || $error->getCode() === 0) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
} catch (\Throwable $th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
$log = new Utopia\Logger\Log();
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
}
|
||||
|
||||
$log->setNamespace("http");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('method', $route->getMethod());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('projectId', $project->getId());
|
||||
$log->addTag('hostname', $request->getHostname());
|
||||
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
$log->addExtra('roles', Authorization::$roles);
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
|
||||
$log->addBreadcrumb($loggerBreadcrumb);
|
||||
}
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
$file = $error->getFile();
|
||||
$line = $error->getLine();
|
||||
$trace = $error->getTrace();
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
|
||||
if ($route) {
|
||||
Console::error('[Error] Method: ' . $route->getMethod());
|
||||
Console::error('[Error] URL: ' . $route->getPath());
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
Console::error('[Error] Message: ' . $message);
|
||||
Console::error('[Error] File: ' . $file);
|
||||
Console::error('[Error] Line: ' . $line);
|
||||
}
|
||||
|
||||
/** Handle Utopia Errors */
|
||||
if ($error instanceof Utopia\Exception) {
|
||||
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
|
||||
switch ($code) {
|
||||
case 400:
|
||||
$error->setType(AppwriteException::GENERAL_ARGUMENT_INVALID);
|
||||
break;
|
||||
case 404:
|
||||
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Wrap all exceptions inside Appwrite\Extend\Exception */
|
||||
if (!($error instanceof AppwriteException)) {
|
||||
$error = new AppwriteException($message, $code, AppwriteException::GENERAL_UNKNOWN, $error);
|
||||
}
|
||||
|
||||
switch ($code) { // Don't show 500 errors!
|
||||
case 400: // Error allowed publicly
|
||||
case 401: // Error allowed publicly
|
||||
case 402: // Error allowed publicly
|
||||
case 403: // Error allowed publicly
|
||||
case 404: // Error allowed publicly
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
case 416: // Error allowed publicly
|
||||
case 429: // Error allowed publicly
|
||||
case 501: // Error allowed publicly
|
||||
case 503: // Error allowed publicly
|
||||
break;
|
||||
default:
|
||||
$code = 500; // All other errors get the generic 500 server error status code
|
||||
$message = 'Server Error';
|
||||
}
|
||||
|
||||
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
|
||||
|
||||
$type = $error->getType();
|
||||
|
||||
$output = ((App::isDevelopment())) ? [
|
||||
'message' => $message,
|
||||
'code' => $code,
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
'trace' => $trace,
|
||||
'version' => $version,
|
||||
'type' => $type,
|
||||
] : [
|
||||
'message' => $message,
|
||||
'code' => $code,
|
||||
'version' => $version,
|
||||
'type' => $type,
|
||||
];
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Expires', '0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->setStatusCode($code)
|
||||
;
|
||||
|
||||
$response->html($layout->render());
|
||||
}
|
||||
$template = ($route) ? $route->getLabel('error', null) : null;
|
||||
|
||||
$response->dynamic(
|
||||
new Document($output),
|
||||
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
|
||||
);
|
||||
}, ['error', 'utopia', 'request', 'response', 'layout', 'project', 'logger', 'loggerBreadcrumbs']);
|
||||
if ($template) {
|
||||
$comp = new View($template);
|
||||
|
||||
$comp
|
||||
->setParam('development', App::isDevelopment())
|
||||
->setParam('projectName', $project->getAttribute('name'))
|
||||
->setParam('projectURL', $project->getAttribute('url'))
|
||||
->setParam('message', $error->getMessage())
|
||||
->setParam('code', $code)
|
||||
->setParam('trace', $trace)
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', $project->getAttribute('name') . ' - Error')
|
||||
->setParam('description', 'No Description')
|
||||
->setParam('body', $comp)
|
||||
->setParam('version', $version)
|
||||
->setParam('litespeed', false)
|
||||
;
|
||||
|
||||
$response->html($layout->render());
|
||||
}
|
||||
|
||||
$response->dynamic(
|
||||
new Document($output),
|
||||
$utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
|
||||
);
|
||||
});
|
||||
|
||||
App::get('/manifest.json')
|
||||
->desc('Progressive app manifest file')
|
||||
|
|
|
@ -214,7 +214,7 @@ App::get('/v1/mock/tests/general/download')
|
|||
->addHeader('Content-Disposition', 'attachment; filename="test.txt"')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->send("Download test passed.")
|
||||
->send("GET:/v1/mock/tests/general/download:passed")
|
||||
;
|
||||
});
|
||||
|
||||
|
@ -558,24 +558,29 @@ App::get('/v1/mock/tests/general/oauth2/failure')
|
|||
]);
|
||||
});
|
||||
|
||||
App::shutdown(function (App $utopia, Response $response, Request $request) {
|
||||
App::shutdown()
|
||||
->groups(['mock'])
|
||||
->inject('utopia')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->action(function (App $utopia, Response $response, Request $request) {
|
||||
|
||||
$result = [];
|
||||
$route = $utopia->match($request);
|
||||
$path = APP_STORAGE_CACHE . '/tests.json';
|
||||
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
|
||||
$result = [];
|
||||
$route = $utopia->match($request);
|
||||
$path = APP_STORAGE_CACHE . '/tests.json';
|
||||
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
|
||||
|
||||
if (!\is_array($tests)) {
|
||||
throw new Exception('Failed to read results', 500, Exception::GENERAL_MOCK);
|
||||
}
|
||||
if (!\is_array($tests)) {
|
||||
throw new Exception('Failed to read results', 500, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
$result[$route->getMethod() . ':' . $route->getPath()] = true;
|
||||
$result[$route->getMethod() . ':' . $route->getPath()] = true;
|
||||
|
||||
$tests = \array_merge($tests, $result);
|
||||
$tests = \array_merge($tests, $result);
|
||||
|
||||
if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
|
||||
throw new Exception('Failed to save results', 500, Exception::GENERAL_MOCK);
|
||||
}
|
||||
if (!\file_put_contents($path, \json_encode($tests), LOCK_EX)) {
|
||||
throw new Exception('Failed to save results', 500, Exception::GENERAL_MOCK);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
|
||||
}, ['utopia', 'response', 'request'], 'mock');
|
||||
$response->dynamic(new Document(['result' => $route->getMethod() . ':' . $route->getPath() . ':passed']), Response::MODEL_MOCK);
|
||||
});
|
||||
|
|
|
@ -19,234 +19,267 @@ use Utopia\Database\Document;
|
|||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
App::init(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
|
||||
App::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('events')
|
||||
->inject('audits')
|
||||
->inject('mails')
|
||||
->inject('usage')
|
||||
->inject('deletes')
|
||||
->inject('database')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Mail $mails, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
$route = $utopia->match($request);
|
||||
|
||||
if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
|
||||
throw new Exception('Missing or unknown project ID', 400, Exception::PROJECT_UNKNOWN);
|
||||
}
|
||||
if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
|
||||
throw new Exception('Missing or unknown project ID', 400, Exception::PROJECT_UNKNOWN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
*/
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
$timeLimitArray = [];
|
||||
/*
|
||||
* Abuse Check
|
||||
*/
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
$timeLimitArray = [];
|
||||
|
||||
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
|
||||
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
|
||||
|
||||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||
$timeLimit
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath());
|
||||
$timeLimitArray[] = $timeLimit;
|
||||
}
|
||||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||
$timeLimit
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath());
|
||||
$timeLimitArray[] = $timeLimit;
|
||||
}
|
||||
|
||||
$closestLimit = null;
|
||||
$closestLimit = null;
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
foreach ($timeLimitArray as $timeLimit) {
|
||||
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
|
||||
if (!empty($value)) {
|
||||
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
|
||||
foreach ($timeLimitArray as $timeLimit) {
|
||||
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
|
||||
if (!empty($value)) {
|
||||
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
|
||||
}
|
||||
}
|
||||
|
||||
$abuse = new Abuse($timeLimit);
|
||||
|
||||
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $timeLimit->remaining();
|
||||
$response
|
||||
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
|
||||
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
|
||||
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
|
||||
;
|
||||
}
|
||||
|
||||
if (
|
||||
(App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled' // Route is rate-limited
|
||||
&& $abuse->check()) // Abuse is not disabled
|
||||
&& (!$isAppUser && !$isPrivilegedUser)
|
||||
) { // User is not an admin or API key
|
||||
throw new Exception('Too many requests', 429, Exception::GENERAL_RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
$abuse = new Abuse($timeLimit);
|
||||
|
||||
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $timeLimit->remaining();
|
||||
$response
|
||||
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
|
||||
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
|
||||
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
|
||||
;
|
||||
}
|
||||
|
||||
if (
|
||||
(App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled' // Route is rate-limited
|
||||
&& $abuse->check()) // Abuse is not disabled
|
||||
&& (!$isAppUser && !$isPrivilegedUser)
|
||||
) { // User is not an admin or API key
|
||||
throw new Exception('Too many requests', 429, Exception::GENERAL_RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
$events
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$mails
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$audits
|
||||
->setMode($mode)
|
||||
->setUserAgent($request->getUserAgent(''))
|
||||
->setIP($request->getIP())
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$usage
|
||||
->setParam('projectId', $project->getId())
|
||||
->setParam('httpRequest', 1)
|
||||
->setParam('httpUrl', $request->getHostname() . $request->getURI())
|
||||
->setParam('httpMethod', $request->getMethod())
|
||||
->setParam('httpPath', $route->getPath())
|
||||
->setParam('networkRequestSize', 0)
|
||||
->setParam('networkResponseSize', 0)
|
||||
->setParam('storage', 0)
|
||||
;
|
||||
|
||||
$deletes->setProject($project);
|
||||
$database->setProject($project);
|
||||
}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'mails', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api');
|
||||
|
||||
App::init(function (App $utopia, Request $request, Document $project) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
switch ($route->getLabel('auth.type', '')) {
|
||||
case 'emailPassword':
|
||||
if (($auths['emailPassword'] ?? true) === false) {
|
||||
throw new Exception('Email / Password authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'magic-url':
|
||||
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
|
||||
throw new Exception('Magic URL authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'anonymous':
|
||||
if (($auths['anonymous'] ?? true) === false) {
|
||||
throw new Exception('Anonymous authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'invites':
|
||||
if (($auths['invites'] ?? true) === false) {
|
||||
throw new Exception('Invites authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'jwt':
|
||||
if (($auths['JWT'] ?? true) === false) {
|
||||
throw new Exception('JWT authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
break;
|
||||
}
|
||||
}, ['utopia', 'request', 'project'], 'auth');
|
||||
|
||||
App::shutdown(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) {
|
||||
|
||||
if (!empty($events->getEvent())) {
|
||||
if (empty($events->getPayload())) {
|
||||
$events->setPayload($response->getPayload());
|
||||
}
|
||||
/**
|
||||
* Trigger functions.
|
||||
*/
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::FUNCTIONS_CLASS_NAME)
|
||||
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
|
||||
->trigger();
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
/**
|
||||
* Trigger webhooks.
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
->trigger();
|
||||
$mails
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
/**
|
||||
* Trigger realtime.
|
||||
*/
|
||||
if ($project->getId() !== 'console') {
|
||||
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
|
||||
$payload = new Document($events->getPayload());
|
||||
$audits
|
||||
->setMode($mode)
|
||||
->setUserAgent($request->getUserAgent(''))
|
||||
->setIP($request->getIP())
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
;
|
||||
|
||||
$db = $events->getContext('database');
|
||||
$collection = $events->getContext('collection');
|
||||
$bucket = $events->getContext('bucket');
|
||||
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $payload,
|
||||
project: $project,
|
||||
database: $db,
|
||||
collection: $collection,
|
||||
bucket: $bucket,
|
||||
);
|
||||
|
||||
Realtime::send(
|
||||
projectId: $target['projectId'] ?? $project->getId(),
|
||||
payload: $events->getPayload(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'permissionsChanged' => $target['permissionsChanged'],
|
||||
'userId' => $events->getParam('userId')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($audits->getResource())) {
|
||||
foreach ($events->getParams() as $key => $value) {
|
||||
$audits->setParam($key, $value);
|
||||
}
|
||||
$audits->trigger();
|
||||
}
|
||||
|
||||
if (!empty($deletes->getType())) {
|
||||
$deletes->trigger();
|
||||
}
|
||||
|
||||
if (!empty($database->getType())) {
|
||||
$database->trigger();
|
||||
}
|
||||
|
||||
$route = $utopia->match($request);
|
||||
if (
|
||||
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
|
||||
&& $project->getId()
|
||||
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
|
||||
&& !empty($route->getLabel('sdk.namespace', null))
|
||||
) { // Don't calculate console usage on admin mode
|
||||
$usage
|
||||
->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
|
||||
->setParam('networkResponseSize', $response->getSize())
|
||||
->submit();
|
||||
}
|
||||
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode', 'dbForProject'], 'api');
|
||||
->setParam('projectId', $project->getId())
|
||||
->setParam('httpRequest', 1)
|
||||
->setParam('httpUrl', $request->getHostname() . $request->getURI())
|
||||
->setParam('httpMethod', $request->getMethod())
|
||||
->setParam('httpPath', $route->getPath())
|
||||
->setParam('networkRequestSize', 0)
|
||||
->setParam('networkResponseSize', 0)
|
||||
->setParam('storage', 0)
|
||||
;
|
||||
|
||||
$deletes->setProject($project);
|
||||
$database->setProject($project);
|
||||
});
|
||||
|
||||
App::init()
|
||||
->groups(['auth'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->action(function (App $utopia, Request $request, Document $project) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
switch ($route->getLabel('auth.type', '')) {
|
||||
case 'emailPassword':
|
||||
if (($auths['emailPassword'] ?? true) === false) {
|
||||
throw new Exception('Email / Password authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'magic-url':
|
||||
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
|
||||
throw new Exception('Magic URL authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'anonymous':
|
||||
if (($auths['anonymous'] ?? true) === false) {
|
||||
throw new Exception('Anonymous authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'invites':
|
||||
if (($auths['invites'] ?? true) === false) {
|
||||
throw new Exception('Invites authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'jwt':
|
||||
if (($auths['JWT'] ?? true) === false) {
|
||||
throw new Exception('JWT authentication is disabled for this project', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
App::shutdown()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('events')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
->inject('deletes')
|
||||
->inject('database')
|
||||
->inject('mode')
|
||||
->inject('dbForProject')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) {
|
||||
|
||||
if (!empty($events->getEvent())) {
|
||||
if (empty($events->getPayload())) {
|
||||
$events->setPayload($response->getPayload());
|
||||
}
|
||||
/**
|
||||
* Trigger functions.
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::FUNCTIONS_CLASS_NAME)
|
||||
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
|
||||
->trigger();
|
||||
|
||||
/**
|
||||
* Trigger webhooks.
|
||||
*/
|
||||
$events
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
->trigger();
|
||||
|
||||
/**
|
||||
* Trigger realtime.
|
||||
*/
|
||||
if ($project->getId() !== 'console') {
|
||||
$allEvents = Event::generateEvents($events->getEvent(), $events->getParams());
|
||||
$payload = new Document($events->getPayload());
|
||||
|
||||
$db = $events->getContext('database');
|
||||
$collection = $events->getContext('collection');
|
||||
$bucket = $events->getContext('bucket');
|
||||
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $payload,
|
||||
project: $project,
|
||||
database: $db,
|
||||
collection: $collection,
|
||||
bucket: $bucket,
|
||||
);
|
||||
|
||||
Realtime::send(
|
||||
projectId: $target['projectId'] ?? $project->getId(),
|
||||
payload: $events->getPayload(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'permissionsChanged' => $target['permissionsChanged'],
|
||||
'userId' => $events->getParam('userId')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($audits->getResource())) {
|
||||
foreach ($events->getParams() as $key => $value) {
|
||||
$audits->setParam($key, $value);
|
||||
}
|
||||
$audits->trigger();
|
||||
}
|
||||
|
||||
if (!empty($deletes->getType())) {
|
||||
$deletes->trigger();
|
||||
}
|
||||
|
||||
if (!empty($database->getType())) {
|
||||
$database->trigger();
|
||||
}
|
||||
|
||||
$route = $utopia->match($request);
|
||||
if (
|
||||
App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
|
||||
&& $project->getId()
|
||||
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
|
||||
&& !empty($route->getLabel('sdk.namespace', null))
|
||||
) { // Don't calculate console usage on admin mode
|
||||
$usage
|
||||
->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
|
||||
->setParam('networkResponseSize', $response->getSize())
|
||||
->submit();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,54 +6,59 @@ use Appwrite\Utopia\Response;
|
|||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\View;
|
||||
|
||||
App::init(function (App $utopia, Request $request, Response $response, View $layout) {
|
||||
App::init()
|
||||
->groups(['web'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function (App $utopia, Request $request, Response $response, View $layout) {
|
||||
/* AJAX check */
|
||||
if (!empty($request->getQuery('version', ''))) {
|
||||
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml');
|
||||
}
|
||||
|
||||
/* AJAX check */
|
||||
if (!empty($request->getQuery('version', ''))) {
|
||||
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml');
|
||||
}
|
||||
$port = $request->getPort();
|
||||
$protocol = $request->getProtocol();
|
||||
$domain = $request->getHostname();
|
||||
|
||||
$port = $request->getPort();
|
||||
$protocol = $request->getProtocol();
|
||||
$domain = $request->getHostname();
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('protocol', $protocol)
|
||||
->setParam('domain', $domain)
|
||||
->setParam('endpoint', $protocol . '://' . $domain . ($port != 80 && $port != 443 ? ':' . $port : ''))
|
||||
->setParam('home', App::getEnv('_APP_HOME'))
|
||||
->setParam('setup', App::getEnv('_APP_SETUP'))
|
||||
->setParam('class', 'unknown')
|
||||
->setParam('icon', '/images/favicon.png')
|
||||
->setParam('roles', [
|
||||
['type' => 'owner', 'label' => 'Owner'],
|
||||
['type' => 'developer', 'label' => 'Developer'],
|
||||
['type' => 'admin', 'label' => 'Admin'],
|
||||
])
|
||||
->setParam('runtimes', Config::getParam('runtimes'))
|
||||
->setParam('mode', App::getMode())
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('protocol', $protocol)
|
||||
->setParam('domain', $domain)
|
||||
->setParam('endpoint', $protocol . '://' . $domain . ($port != 80 && $port != 443 ? ':' . $port : ''))
|
||||
->setParam('home', App::getEnv('_APP_HOME'))
|
||||
->setParam('setup', App::getEnv('_APP_SETUP'))
|
||||
->setParam('class', 'unknown')
|
||||
->setParam('icon', '/images/favicon.png')
|
||||
->setParam('roles', [
|
||||
['type' => 'owner', 'label' => 'Owner'],
|
||||
['type' => 'developer', 'label' => 'Developer'],
|
||||
['type' => 'admin', 'label' => 'Admin'],
|
||||
])
|
||||
->setParam('runtimes', Config::getParam('runtimes'))
|
||||
->setParam('mode', App::getMode())
|
||||
;
|
||||
$time = (60 * 60 * 24 * 45); // 45 days cache
|
||||
|
||||
$time = (60 * 60 * 24 * 45); // 45 days cache
|
||||
$response
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
|
||||
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
|
||||
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
|
||||
;
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
|
||||
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
|
||||
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
|
||||
;
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$route = $utopia->match($request);
|
||||
$route->label('error', __DIR__ . '/../../views/general/error.phtml');
|
||||
|
||||
$route->label('error', __DIR__ . '/../../views/general/error.phtml');
|
||||
$scope = $route->getLabel('scope', '');
|
||||
|
||||
$scope = $route->getLabel('scope', '');
|
||||
|
||||
$layout
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
->setParam('isDev', App::isDevelopment())
|
||||
->setParam('class', $scope)
|
||||
;
|
||||
}, ['utopia', 'request', 'response', 'layout'], 'web');
|
||||
$layout
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
->setParam('isDev', App::isDevelopment())
|
||||
->setParam('class', $scope)
|
||||
;
|
||||
});
|
||||
|
|
|
@ -10,31 +10,36 @@ use Utopia\Domains\Domain;
|
|||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Storage\Storage;
|
||||
|
||||
App::init(function (View $layout) {
|
||||
App::init()
|
||||
->groups(['console'])
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
$layout
|
||||
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
|
||||
->setParam('analytics', 'UA-26264668-5')
|
||||
;
|
||||
});
|
||||
|
||||
$layout
|
||||
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
|
||||
->setParam('analytics', 'UA-26264668-5')
|
||||
;
|
||||
}, ['layout'], 'console');
|
||||
App::shutdown()
|
||||
->groups(['console'])
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function (Response $response, View $layout) {
|
||||
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
|
||||
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
|
||||
|
||||
App::shutdown(function (Response $response, View $layout) {
|
||||
$footer
|
||||
->setParam('home', App::getEnv('_APP_HOME', ''))
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
;
|
||||
|
||||
$header = new View(__DIR__ . '/../../views/console/comps/header.phtml');
|
||||
$footer = new View(__DIR__ . '/../../views/console/comps/footer.phtml');
|
||||
$layout
|
||||
->setParam('header', [$header])
|
||||
->setParam('footer', [$footer])
|
||||
;
|
||||
|
||||
$footer
|
||||
->setParam('home', App::getEnv('_APP_HOME', ''))
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
;
|
||||
|
||||
$layout
|
||||
->setParam('header', [$header])
|
||||
->setParam('footer', [$footer])
|
||||
;
|
||||
|
||||
$response->html($layout->render());
|
||||
}, ['response', 'layout'], 'console');
|
||||
$response->html($layout->render());
|
||||
});
|
||||
|
||||
App::get('/error/:code')
|
||||
->groups(['web', 'console'])
|
||||
|
|
|
@ -7,29 +7,34 @@ use Utopia\Config\Config;
|
|||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
App::init(function (View $layout) {
|
||||
App::init()
|
||||
->groups(['home'])
|
||||
->inject('layout')
|
||||
->action(function (View $layout) {
|
||||
$header = new View(__DIR__ . '/../../views/home/comps/header.phtml');
|
||||
$footer = new View(__DIR__ . '/../../views/home/comps/footer.phtml');
|
||||
|
||||
$header = new View(__DIR__ . '/../../views/home/comps/header.phtml');
|
||||
$footer = new View(__DIR__ . '/../../views/home/comps/footer.phtml');
|
||||
$footer
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
;
|
||||
|
||||
$footer
|
||||
->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
|
||||
;
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('description', '')
|
||||
->setParam('class', 'home')
|
||||
->setParam('platforms', Config::getParam('platforms'))
|
||||
->setParam('header', [$header])
|
||||
->setParam('footer', [$footer])
|
||||
;
|
||||
});
|
||||
|
||||
$layout
|
||||
->setParam('title', APP_NAME)
|
||||
->setParam('description', '')
|
||||
->setParam('class', 'home')
|
||||
->setParam('platforms', Config::getParam('platforms'))
|
||||
->setParam('header', [$header])
|
||||
->setParam('footer', [$footer])
|
||||
;
|
||||
}, ['layout'], 'home');
|
||||
|
||||
App::shutdown(function (Response $response, View $layout) {
|
||||
|
||||
$response->html($layout->render());
|
||||
}, ['response', 'layout'], 'home');
|
||||
App::shutdown()
|
||||
->groups(['home'])
|
||||
->inject('response')
|
||||
->inject('layout')
|
||||
->action(function (Response $response, View $layout) {
|
||||
$response->html($layout->render());
|
||||
});
|
||||
|
||||
App::get('/')
|
||||
->groups(['web', 'home'])
|
||||
|
|
|
@ -581,57 +581,64 @@ App::setResource('orchestrationPool', fn() => $orchestrationPool);
|
|||
App::setResource('activeRuntimes', fn() => $activeRuntimes);
|
||||
|
||||
/** Set callbacks */
|
||||
App::error(function ($utopia, $error, $request, $response) {
|
||||
$route = $utopia->match($request);
|
||||
logError($error, "httpError", $route);
|
||||
App::error()
|
||||
->inject('utopia')
|
||||
->inject('error')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (App $utopia, throwable $error, Request $request, Response $response) {
|
||||
$route = $utopia->match($request);
|
||||
logError($error, "httpError", $route);
|
||||
|
||||
switch ($error->getCode()) {
|
||||
case 400: // Error allowed publicly
|
||||
case 401: // Error allowed publicly
|
||||
case 402: // Error allowed publicly
|
||||
case 403: // Error allowed publicly
|
||||
case 404: // Error allowed publicly
|
||||
case 406: // Error allowed publicly
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
case 425: // Error allowed publicly
|
||||
case 429: // Error allowed publicly
|
||||
case 501: // Error allowed publicly
|
||||
case 503: // Error allowed publicly
|
||||
$code = $error->getCode();
|
||||
break;
|
||||
default:
|
||||
$code = 500; // All other errors get the generic 500 server error status code
|
||||
}
|
||||
switch ($error->getCode()) {
|
||||
case 400: // Error allowed publicly
|
||||
case 401: // Error allowed publicly
|
||||
case 402: // Error allowed publicly
|
||||
case 403: // Error allowed publicly
|
||||
case 404: // Error allowed publicly
|
||||
case 406: // Error allowed publicly
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
case 425: // Error allowed publicly
|
||||
case 429: // Error allowed publicly
|
||||
case 501: // Error allowed publicly
|
||||
case 503: // Error allowed publicly
|
||||
$code = $error->getCode();
|
||||
break;
|
||||
default:
|
||||
$code = 500; // All other errors get the generic 500 server error status code
|
||||
}
|
||||
|
||||
$output = [
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
'file' => $error->getFile(),
|
||||
'line' => $error->getLine(),
|
||||
'trace' => $error->getTrace(),
|
||||
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
|
||||
];
|
||||
$output = [
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
'file' => $error->getFile(),
|
||||
'line' => $error->getLine(),
|
||||
'trace' => $error->getTrace(),
|
||||
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
|
||||
];
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Expires', '0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->setStatusCode($code);
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Expires', '0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->setStatusCode($code);
|
||||
|
||||
$response->json($output);
|
||||
}, ['utopia', 'error', 'request', 'response']);
|
||||
$response->json($output);
|
||||
});
|
||||
|
||||
App::init(function ($request, $response) {
|
||||
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
|
||||
if (empty($secretKey)) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
App::init()
|
||||
->inject('request')
|
||||
->action(function (Request $request) {
|
||||
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
|
||||
if (empty($secretKey)) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
|
||||
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
}, ['request', 'response']);
|
||||
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
|
||||
throw new Exception('Missing executor key', 401);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$http->on('start', function ($http) {
|
||||
|
|
12
app/views/console/users/oauth/authentik.phtml
Normal file
12
app/views/console/users/oauth/authentik.phtml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
$provider = $this->getParam('provider', '');
|
||||
?>
|
||||
|
||||
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid">Client ID<span class="tooltip" data-tooltip="Provided in the Provider you created in authentik"><i class="icon-info-circled"></i></span></label>
|
||||
<input name="appId" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Appid" type="text" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Appid}}" placeholder="Client ID" />
|
||||
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret">Client Secret <span class="tooltip" data-tooltip="Provided in the Provider you created in authentik"><i class="icon-info-circled"></i></span></label>
|
||||
<input name="clientSecret" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>ClientSecret" type="password" autocomplete="off" placeholder="Client Secret" />
|
||||
<label for="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain">authentik Base-Domain<span class="tooltip" data-tooltip="Your authentik Base-Domain (without 'https://')"><i class="icon-info-circled"></i></span></label>
|
||||
<input name="authentikDomain" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Domain" type="text" autocomplete="off" placeholder="auth.example.com" />
|
||||
<?php /*Hidden input for the final secret. Gets filled with a JSON via JS. */ ?>
|
||||
<input name="secret" data-forms-oauth-custom="<?php echo $this->escape(ucfirst($provider)); ?>" id="oauth2<?php echo $this->escape(ucfirst($provider)); ?>Secret" type="hidden" autocomplete="off" data-ls-bind="{{console-project.provider<?php echo $this->escape(ucfirst($provider)); ?>Secret}}" />
|
|
@ -595,7 +595,7 @@ services:
|
|||
- _APP_REDIS_PASS
|
||||
|
||||
mariadb:
|
||||
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
|
||||
image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p
|
||||
container_name: appwrite-mariadb
|
||||
<<: *x-logging
|
||||
restart: unless-stopped
|
||||
|
@ -611,7 +611,7 @@ services:
|
|||
command: 'mysqld --innodb-flush-method=fsync'
|
||||
|
||||
redis:
|
||||
image: redis:6.2-alpine
|
||||
image: redis:7.0.4-alpine
|
||||
container_name: appwrite-redis
|
||||
<<: *x-logging
|
||||
restart: unless-stopped
|
||||
|
|
|
@ -20,11 +20,6 @@ Console::success(APP_NAME . ' deletes worker v1 has started' . "\n");
|
|||
|
||||
class DeletesV1 extends Worker
|
||||
{
|
||||
/**
|
||||
* @var Database
|
||||
*/
|
||||
protected $consoleDB = null;
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return "deletes";
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\E2E\\": "tests/e2e",
|
||||
"Tests\\Unit\\": "tests/unit",
|
||||
"Appwrite\\Tests\\": "tests/extensions"
|
||||
}
|
||||
},
|
||||
|
@ -42,7 +43,7 @@
|
|||
"ext-sockets": "*",
|
||||
"appwrite/php-clamav": "1.1.*",
|
||||
"appwrite/php-runtimes": "0.10.*",
|
||||
"utopia-php/framework": "0.19.*",
|
||||
"utopia-php/framework": "0.20.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/abuse": "dev-refactor-permissions-premerge as 0.7.0",
|
||||
"utopia-php/analytics": "0.2.*",
|
||||
|
|
12
composer.lock
generated
12
composer.lock
generated
|
@ -2169,16 +2169,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/framework",
|
||||
"version": "0.19.21",
|
||||
"version": "0.20.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/framework.git",
|
||||
"reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d"
|
||||
"reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/framework/zipball/3b7bd8e4acf84fd7d560ced8e0142221d302575d",
|
||||
"reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d",
|
||||
"url": "https://api.github.com/repos/utopia-php/framework/zipball/beb5e861c7d0a6256a1272e6b9d70b060ca8629a",
|
||||
"reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2212,9 +2212,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/framework/issues",
|
||||
"source": "https://github.com/utopia-php/framework/tree/0.19.21"
|
||||
"source": "https://github.com/utopia-php/framework/tree/0.20.0"
|
||||
},
|
||||
"time": "2022-05-12T18:42:28+00:00"
|
||||
"time": "2022-07-30T09:55:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
|
|
|
@ -638,7 +638,7 @@ services:
|
|||
- _APP_REDIS_PASS
|
||||
|
||||
mariadb:
|
||||
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
|
||||
image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p
|
||||
container_name: appwrite-mariadb
|
||||
<<: *x-logging
|
||||
networks:
|
||||
|
@ -668,7 +668,7 @@ services:
|
|||
# - SMARTHOST_PORT=587
|
||||
|
||||
redis:
|
||||
image: redis:6.2-alpine
|
||||
image: redis:7.0.4-alpine
|
||||
<<: *x-logging
|
||||
container_name: appwrite-redis
|
||||
command: >
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.
|
||||
You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard.
|
||||
|
||||
When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
The Locale service allows you to customize your app based on your users' location. Using this service, you can get your users' location, IP address, list of countries and continents names, phone codes, currencies, and more.
|
||||
The Locale service allows you to customize your app based on your users' location. Using this service, you can get your users' location, IP address, list of countries and continents names, phone codes, currencies, and more. Country codes returned follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard.
|
||||
|
||||
The user service supports multiple locales. This feature allows you to fetch countries and continents information in your app language. To switch locales, all you need to do is pass the 'X-Appwrite-Locale' header or set the 'setLocale' method using any of our available SDKs. [View here the list of available locales](https://github.com/appwrite/appwrite/blob/master/app/config/locale/codes.php).
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</extensions>
|
||||
<testsuites>
|
||||
<testsuite name="unit">
|
||||
<directory>./tests/unit/</directory>
|
||||
<directory>./tests/unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="e2e">
|
||||
<file>./tests/e2e/Client.php</file>
|
||||
|
|
2
public/dist/scripts/app-all.js
vendored
2
public/dist/scripts/app-all.js
vendored
|
@ -4141,7 +4141,7 @@ list["filters-"+filter.key]=params[key][i];}}}}
|
|||
return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y<events.length;y++){if(events[y]==="init"){element.addEventListener("rendered",function(){apply(form.toJson(element));},{once:true});}else{}
|
||||
element.setAttribute("data-event","none");}
|
||||
break;default:break;}}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-headers",controller:function(element){let key=document.createElement("input");let value=document.createElement("input");let wrap=document.createElement("div");let cell1=document.createElement("div");let cell2=document.createElement("div");key.type="text";key.className="margin-bottom-no";key.placeholder="Key";value.type="text";value.className="margin-bottom-no";value.placeholder="Value";wrap.className="row thin margin-bottom-small";cell1.className="col span-6";cell2.className="col span-6";element.parentNode.insertBefore(wrap,element);cell1.appendChild(key);cell2.appendChild(value);wrap.appendChild(cell1);wrap.appendChild(cell2);key.addEventListener("input",function(){syncA();});value.addEventListener("input",function(){syncA();});element.addEventListener("change",function(){syncB();});let syncA=function(){element.value=key.value.toLowerCase()+":"+value.value.toLowerCase();};let syncB=function(){let split=element.value.toLowerCase().split(":");key.value=split[0]||"";value.value=split[1]||"";key.value=key.value.trim();value.value=value.value.trim();};syncB();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-key-value",controller:function(element){let key=document.createElement("input");let value=document.createElement("input");let wrap=document.createElement("div");let cell1=document.createElement("div");let cell2=document.createElement("div");key.type="text";key.className="margin-bottom-no";key.placeholder="Key";key.required=true;value.type="text";value.className="margin-bottom-no";value.placeholder="Value";value.required=true;wrap.className="row thin margin-bottom-small";cell1.className="col span-6";cell2.className="col span-6";element.parentNode.insertBefore(wrap,element);cell1.appendChild(key);cell2.appendChild(value);wrap.appendChild(cell1);wrap.appendChild(cell2);key.addEventListener("input",function(){syncA();});value.addEventListener("input",function(){syncA();});element.addEventListener("change",function(){syncB();});let syncA=function(){element.name=key.value;element.value=value.value;};let syncB=function(){key.value=element.name||"";value.value=element.value||"";};syncB();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-down",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-down]")).map(function(obj){obj.addEventListener("click",function(){if(element.nextElementSibling){console.log('down',element.offsetHeight);element.parentNode.insertBefore(element.nextElementSibling,element);element.scrollIntoView({block:'center'});}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-up",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-up]")).map(function(obj){obj.addEventListener("click",function(){if(element.previousElementSibling){console.log('up',element);element.parentNode.insertBefore(element,element.previousElementSibling);element.scrollIntoView({block:'center'});}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-nav",repeat:false,controller:function(element,view,container,document){let titles=document.querySelectorAll('[data-forms-nav-anchor]');let links=element.querySelectorAll('[data-forms-nav-link]');let minLink=null;let check=function(){let minDistance=null;let minElement=null;for(let i=0;i<titles.length;++i){let title=titles[i];let distance=title.getBoundingClientRect().top;console.log(i);if((minDistance===null||minDistance>=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');}
|
||||
console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"},"Gitlab":{"endpoint":"oauth2GitlabEndpoint","clientSecret":"oauth2GitlabClientSecret",},}
|
||||
console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"},"Authentik":{"clientSecret":"oauth2AuthentikClientSecret","authentikDomain":"oauth2AuthentikDomain"},"Gitlab":{"endpoint":"oauth2GitlabEndpoint","clientSecret":"oauth2GitlabClientSecret",},}
|
||||
let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unknown")}
|
||||
let config=providers[provider];element.addEventListener('change',sync);let elements={};for(const key in config){if(Object.hasOwnProperty.call(config,key)){elements[key]=document.getElementById(config[key]);elements[key].addEventListener('change',update);}}
|
||||
function update(){let json={};for(const key in elements){if(Object.hasOwnProperty.call(elements,key)){json[key]=elements[key].value}}
|
||||
|
|
2
public/dist/scripts/app.js
vendored
2
public/dist/scripts/app.js
vendored
|
@ -806,7 +806,7 @@ list["filters-"+filter.key]=params[key][i];}}}}
|
|||
return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y<events.length;y++){if(events[y]==="init"){element.addEventListener("rendered",function(){apply(form.toJson(element));},{once:true});}else{}
|
||||
element.setAttribute("data-event","none");}
|
||||
break;default:break;}}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-headers",controller:function(element){let key=document.createElement("input");let value=document.createElement("input");let wrap=document.createElement("div");let cell1=document.createElement("div");let cell2=document.createElement("div");key.type="text";key.className="margin-bottom-no";key.placeholder="Key";value.type="text";value.className="margin-bottom-no";value.placeholder="Value";wrap.className="row thin margin-bottom-small";cell1.className="col span-6";cell2.className="col span-6";element.parentNode.insertBefore(wrap,element);cell1.appendChild(key);cell2.appendChild(value);wrap.appendChild(cell1);wrap.appendChild(cell2);key.addEventListener("input",function(){syncA();});value.addEventListener("input",function(){syncA();});element.addEventListener("change",function(){syncB();});let syncA=function(){element.value=key.value.toLowerCase()+":"+value.value.toLowerCase();};let syncB=function(){let split=element.value.toLowerCase().split(":");key.value=split[0]||"";value.value=split[1]||"";key.value=key.value.trim();value.value=value.value.trim();};syncB();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-key-value",controller:function(element){let key=document.createElement("input");let value=document.createElement("input");let wrap=document.createElement("div");let cell1=document.createElement("div");let cell2=document.createElement("div");key.type="text";key.className="margin-bottom-no";key.placeholder="Key";key.required=true;value.type="text";value.className="margin-bottom-no";value.placeholder="Value";value.required=true;wrap.className="row thin margin-bottom-small";cell1.className="col span-6";cell2.className="col span-6";element.parentNode.insertBefore(wrap,element);cell1.appendChild(key);cell2.appendChild(value);wrap.appendChild(cell1);wrap.appendChild(cell2);key.addEventListener("input",function(){syncA();});value.addEventListener("input",function(){syncA();});element.addEventListener("change",function(){syncB();});let syncA=function(){element.name=key.value;element.value=value.value;};let syncB=function(){key.value=element.name||"";value.value=element.value||"";};syncB();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-down",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-down]")).map(function(obj){obj.addEventListener("click",function(){if(element.nextElementSibling){console.log('down',element.offsetHeight);element.parentNode.insertBefore(element.nextElementSibling,element);element.scrollIntoView({block:'center'});}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-up",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-up]")).map(function(obj){obj.addEventListener("click",function(){if(element.previousElementSibling){console.log('up',element);element.parentNode.insertBefore(element,element.previousElementSibling);element.scrollIntoView({block:'center'});}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-nav",repeat:false,controller:function(element,view,container,document){let titles=document.querySelectorAll('[data-forms-nav-anchor]');let links=element.querySelectorAll('[data-forms-nav-link]');let minLink=null;let check=function(){let minDistance=null;let minElement=null;for(let i=0;i<titles.length;++i){let title=titles[i];let distance=title.getBoundingClientRect().top;console.log(i);if((minDistance===null||minDistance>=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');}
|
||||
console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"},"Gitlab":{"endpoint":"oauth2GitlabEndpoint","clientSecret":"oauth2GitlabClientSecret",},}
|
||||
console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"},"Authentik":{"clientSecret":"oauth2AuthentikClientSecret","authentikDomain":"oauth2AuthentikDomain"},"Gitlab":{"endpoint":"oauth2GitlabEndpoint","clientSecret":"oauth2GitlabClientSecret",},}
|
||||
let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unknown")}
|
||||
let config=providers[provider];element.addEventListener('change',sync);let elements={};for(const key in config){if(Object.hasOwnProperty.call(config,key)){elements[key]=document.getElementById(config[key]);elements[key].addEventListener('change',update);}}
|
||||
function update(){let json={};for(const key in elements){if(Object.hasOwnProperty.call(elements,key)){json[key]=elements[key].value}}
|
||||
|
|
BIN
public/images/users/authentik.png
Normal file
BIN
public/images/users/authentik.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 827 B |
BIN
public/images/users/disqus.png
Normal file
BIN
public/images/users/disqus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
public/images/users/podio.png
Normal file
BIN
public/images/users/podio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -26,6 +26,10 @@
|
|||
"clientSecret": "oauth2Auth0ClientSecret",
|
||||
"auth0Domain": "oauth2Auth0Domain"
|
||||
},
|
||||
"Authentik": {
|
||||
"clientSecret": "oauth2AuthentikClientSecret",
|
||||
"authentikDomain": "oauth2AuthentikDomain"
|
||||
},
|
||||
"Gitlab": {
|
||||
"endpoint": "oauth2GitlabEndpoint",
|
||||
"clientSecret": "oauth2GitlabClientSecret",
|
||||
|
|
227
src/Appwrite/Auth/OAuth2/Authentik.php
Normal file
227
src/Appwrite/Auth/OAuth2/Authentik.php
Normal file
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
// Reference Material
|
||||
// https://goauthentik.io/docs/providers/oauth2/
|
||||
|
||||
class Authentik extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'offline_access'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'authentik';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
return 'https://' . $this->getAuthentikDomain() . '/application/o/authorize?' . \http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'state' => \json_encode($this->state),
|
||||
'scope' => \implode(' ', $this->getScopes()),
|
||||
'response_type' => 'code'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$headers = ['Content-Type: application/x-www-form-urlencoded'];
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
'https://' . $this->getAuthentikDomain() . '/application/o/token/',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'code' => $code,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->getClientSecret(),
|
||||
'redirect_uri' => $this->callback,
|
||||
'scope' => \implode(' ', $this->getScopes()),
|
||||
'grant_type' => 'authorization_code'
|
||||
])
|
||||
), true);
|
||||
}
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
$headers = ['Content-Type: application/x-www-form-urlencoded'];
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
'https://' . $this->getAuthentikDomain() . '/application/o/token/',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->getClientSecret(),
|
||||
'grant_type' => 'refresh_token'
|
||||
])
|
||||
), true);
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
if (isset($user['sub'])) {
|
||||
return $user['sub'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
if (isset($user['email'])) {
|
||||
return $user['email'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the User email is verified
|
||||
*
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
if ($user['email_verified'] ?? false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
if (isset($user['name'])) {
|
||||
return $user['name'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$headers = ['Authorization: Bearer ' . \urlencode($accessToken)];
|
||||
$user = $this->request('GET', 'https://' . $this->getAuthentikDomain() . '/application/o/userinfo/', $headers);
|
||||
$this->user = \json_decode($user, true);
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the Client Secret from the JSON stored in appSecret
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getClientSecret(): string
|
||||
{
|
||||
$secret = $this->getAppSecret();
|
||||
|
||||
return $secret['clientSecret'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the authentik Domain from the JSON stored in appSecret
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getAuthentikDomain(): string
|
||||
{
|
||||
$secret = $this->getAppSecret();
|
||||
return $secret['authentikDomain'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON stored in appSecret
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAppSecret(): array
|
||||
{
|
||||
try {
|
||||
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (\Throwable $th) {
|
||||
throw new \Exception('Invalid secret');
|
||||
}
|
||||
return $secret;
|
||||
}
|
||||
}
|
188
src/Appwrite/Auth/OAuth2/Disqus.php
Normal file
188
src/Appwrite/Auth/OAuth2/Disqus.php
Normal file
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
// Reference Material
|
||||
// https://disqus.com/api/docs/auth/
|
||||
|
||||
class Disqus extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $endpoint = 'https://disqus.com/api/';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [
|
||||
'read',
|
||||
'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'disqus';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
$url = $this->endpoint . 'oauth/2.0/authorize/?' .
|
||||
\http_build_query([
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->appID,
|
||||
'state' => \json_encode($this->state),
|
||||
'redirect_uri' => $this->callback,
|
||||
'scope' => \implode(',', $this->getScopes())
|
||||
]);
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->endpoint . 'oauth/2.0/access_token/',
|
||||
['Content-Type: application/x-www-form-urlencoded'],
|
||||
\http_build_query([
|
||||
'grant_type' => 'authorization_code',
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'redirect_uri' => $this->callback,
|
||||
'code' => $code,
|
||||
'scope' => \implode(' ', $this->getScopes()),
|
||||
])
|
||||
), true);
|
||||
}
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->endpoint . 'oauth/2.0/access_token/?',
|
||||
['Content-Type: application/x-www-form-urlencoded'],
|
||||
\http_build_query([
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
])
|
||||
), true);
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
$userId = $user['id'];
|
||||
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
$userEmail = $user['email'];
|
||||
|
||||
return $userEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
|
||||
// Look out for the change in their enpoint.
|
||||
// It's in Beta so they may provide a parameter in the future.
|
||||
// https://disqus.com/api/docs/users/details/
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
$username = $user['name'] ?? '';
|
||||
|
||||
return $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$user = $this->request(
|
||||
'GET',
|
||||
$this->endpoint . '3.0/users/details.json?' . \http_build_query([
|
||||
'access_token' => $accessToken,
|
||||
'api_key' => $this->appID,
|
||||
'api_secret' => $this->appSecret
|
||||
]),
|
||||
);
|
||||
$this->user = \json_decode($user, true)['response'];
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
}
|
|
@ -80,7 +80,6 @@ class Linkedin extends OAuth2
|
|||
])
|
||||
), true);
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
|
@ -107,7 +106,6 @@ class Linkedin extends OAuth2
|
|||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
|
|
199
src/Appwrite/Auth/OAuth2/Podio.php
Normal file
199
src/Appwrite/Auth/OAuth2/Podio.php
Normal file
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
// Reference Material
|
||||
// https://developers.podio.com/doc/oauth-authorization
|
||||
|
||||
class Podio extends OAuth2
|
||||
{
|
||||
/**
|
||||
* Endpoint used for initiating OAuth flow
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $endpoint = 'https://podio.com/oauth';
|
||||
|
||||
/**
|
||||
* Endpoint for communication with API server
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $apiEndpoint = 'https://api.podio.com';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = []; // No scopes required
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'podio';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
$url = $this->endpoint . '/authorize?' .
|
||||
\http_build_query([
|
||||
'client_id' => $this->appID,
|
||||
'state' => \json_encode($this->state),
|
||||
'redirect_uri' => $this->callback
|
||||
]);
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->apiEndpoint . '/oauth/token',
|
||||
['Content-Type: application/x-www-form-urlencoded'],
|
||||
\http_build_query([
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $code,
|
||||
'redirect_uri' => $this->callback,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret
|
||||
])
|
||||
), true);
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->apiEndpoint . '/oauth/token',
|
||||
['Content-Type: application/x-www-form-urlencoded'],
|
||||
\http_build_query([
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
])
|
||||
), true);
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return \strval($user['user_id']) ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['mail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the OAuth email is verified
|
||||
*
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
$mails = $user['mails'];
|
||||
$mainMailIndex = \array_search($user['mail'], \array_map(fn($m) => $m['mail'], $mails));
|
||||
$mainMain = $mails[$mainMailIndex];
|
||||
|
||||
if ($mainMain['verified'] ?? false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['name'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$user = \json_decode($this->request(
|
||||
'GET',
|
||||
$this->apiEndpoint . '/user',
|
||||
['Authorization: Bearer ' . \urlencode($accessToken)]
|
||||
), true);
|
||||
|
||||
$profile = \json_decode($this->request(
|
||||
'GET',
|
||||
$this->apiEndpoint . '/user/profile',
|
||||
['Authorization: Bearer ' . \urlencode($accessToken)]
|
||||
), true);
|
||||
|
||||
$this->user = $user;
|
||||
$this->user['name'] = $profile['name'];
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
}
|
|
@ -199,7 +199,7 @@ class Response extends SwooleResponse
|
|||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $payload = [];
|
||||
protected array $payload = [];
|
||||
|
||||
/**
|
||||
* Response constructor.
|
||||
|
@ -300,8 +300,7 @@ class Response extends SwooleResponse
|
|||
// Verification
|
||||
// Recovery
|
||||
// Tests (keep last)
|
||||
->setModel(new Mock())
|
||||
;
|
||||
->setModel(new Mock());
|
||||
|
||||
parent::__construct($response);
|
||||
}
|
||||
|
@ -391,12 +390,13 @@ class Response extends SwooleResponse
|
|||
|
||||
if ($model->isAny()) {
|
||||
$this->payload = $document->getArrayCopy();
|
||||
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
foreach ($model->getRules() as $key => $rule) {
|
||||
if (!$document->isSet($key) && $rule['require']) { // do not set attribute in response if not required
|
||||
if (!is_null($rule['default'])) {
|
||||
if (!$document->isSet($key) && $rule['required']) { // do not set attribute in response if not required
|
||||
if (\array_key_exists('default', $rule)) {
|
||||
$document->setAttribute($key, $rule['default']);
|
||||
} else {
|
||||
throw new Exception('Model ' . $model->getName() . ' is missing response key: ' . $key);
|
||||
|
@ -408,7 +408,7 @@ class Response extends SwooleResponse
|
|||
throw new Exception($key . ' must be an array of type ' . $rule['type']);
|
||||
}
|
||||
|
||||
foreach ($data[$key] as &$item) {
|
||||
foreach ($data[$key] as $index => $item) {
|
||||
if ($item instanceof Document) {
|
||||
if (\is_array($rule['type'])) {
|
||||
foreach ($rule['type'] as $type) {
|
||||
|
@ -432,9 +432,13 @@ class Response extends SwooleResponse
|
|||
throw new Exception('Missing model for rule: ' . $ruleType);
|
||||
}
|
||||
|
||||
$item = $this->output($item, $ruleType);
|
||||
$data[$key][$index] = $this->output($item, $ruleType);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($data[$key] instanceof Document) {
|
||||
$data[$key] = $this->output($data[$key], $rule['type']);
|
||||
}
|
||||
}
|
||||
|
||||
$output[$key] = $data[$key];
|
||||
|
@ -465,8 +469,7 @@ class Response extends SwooleResponse
|
|||
|
||||
$this
|
||||
->setContentType(Response::CONTENT_TYPE_YAML)
|
||||
->send(yaml_emit($data, YAML_UTF8_ENCODING))
|
||||
;
|
||||
->send(yaml_emit($data, YAML_UTF8_ENCODING));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,22 +15,28 @@ abstract class Model
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $none = false;
|
||||
protected bool $none = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $any = false;
|
||||
protected bool $any = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $public = true;
|
||||
protected bool $public = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $rules = [];
|
||||
protected array $rules = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $conditions = [];
|
||||
|
||||
|
||||
/**
|
||||
* Filter Document Structure
|
||||
|
@ -76,12 +82,10 @@ abstract class Model
|
|||
protected function addRule(string $key, array $options): self
|
||||
{
|
||||
$this->rules[$key] = array_merge([
|
||||
'require' => true,
|
||||
'type' => '',
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
'description' => '',
|
||||
'default' => null,
|
||||
'example' => '',
|
||||
'array' => false
|
||||
'example' => ''
|
||||
], $options);
|
||||
|
||||
return $this;
|
||||
|
@ -92,7 +96,7 @@ abstract class Model
|
|||
$list = [];
|
||||
|
||||
foreach ($this->rules as $key => $rule) {
|
||||
if ($rule['require'] ?? false) {
|
||||
if ($rule['required'] ?? false) {
|
||||
$list[] = $key;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ class Any extends Model
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $any = true;
|
||||
protected bool $any = true;
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
|
|
|
@ -39,7 +39,6 @@ class Attribute extends Model
|
|||
'description' => 'Is attribute an array?',
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
'require' => false
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -28,9 +28,7 @@ class AttributeBoolean extends Attribute
|
|||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
|
||||
'default' => null,
|
||||
'example' => false,
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
'example' => false
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -37,8 +37,6 @@ class AttributeEmail extends Attribute
|
|||
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
|
||||
'default' => null,
|
||||
'example' => 'default@example.com',
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ class AttributeEnum extends Attribute
|
|||
'default' => null,
|
||||
'example' => 'element',
|
||||
'array' => true,
|
||||
'require' => true,
|
||||
])
|
||||
->addRule('format', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
@ -45,8 +44,6 @@ class AttributeEnum extends Attribute
|
|||
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
|
||||
'default' => null,
|
||||
'example' => 'element',
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -29,24 +29,18 @@ class AttributeFloat extends Attribute
|
|||
'description' => 'Minimum value to enforce for new documents.',
|
||||
'default' => null,
|
||||
'example' => 1.5,
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
->addRule('max', [
|
||||
'type' => self::TYPE_FLOAT,
|
||||
'description' => 'Maximum value to enforce for new documents.',
|
||||
'default' => null,
|
||||
'example' => 10.5,
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
->addRule('default', [
|
||||
'type' => self::TYPE_FLOAT,
|
||||
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
|
||||
'default' => null,
|
||||
'example' => 2.5,
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -37,8 +37,6 @@ class AttributeIP extends Attribute
|
|||
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
|
||||
'default' => null,
|
||||
'example' => '192.0.2.0',
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -29,24 +29,18 @@ class AttributeInteger extends Attribute
|
|||
'description' => 'Minimum value to enforce for new documents.',
|
||||
'default' => null,
|
||||
'example' => 1,
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
->addRule('max', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Maximum value to enforce for new documents.',
|
||||
'default' => null,
|
||||
'example' => 10,
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
->addRule('default', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
|
||||
'default' => null,
|
||||
'example' => 10,
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@ class AttributeString extends Attribute
|
|||
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
|
||||
'default' => null,
|
||||
'example' => 'default',
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -37,8 +37,6 @@ class AttributeURL extends Attribute
|
|||
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
|
||||
'default' => null,
|
||||
'example' => 'http://example.com',
|
||||
'array' => false,
|
||||
'require' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class BaseList extends Model
|
||||
|
@ -10,12 +9,12 @@ class BaseList extends Model
|
|||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name = '';
|
||||
protected string $name = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = '';
|
||||
protected string $type = '';
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
|
|
|
@ -66,9 +66,9 @@ class Deployment extends Model
|
|||
])
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The deployment status.',
|
||||
'description' => 'The deployment status. Possible values are "processing", "building", "pending", "ready", and "failed".',
|
||||
'default' => '',
|
||||
'example' => 'enabled',
|
||||
'example' => 'ready',
|
||||
])
|
||||
->addRule('buildStdout', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
@ -10,7 +10,7 @@ class Domain extends Model
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $public = false;
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ class ErrorDev extends Error
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $public = false;
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
|
@ -41,7 +41,6 @@ class Index extends Model
|
|||
'default' => [],
|
||||
'example' => [],
|
||||
'array' => true,
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ class Key extends Model
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $public = false;
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ class Metric extends Model
|
|||
'default' => -1,
|
||||
'example' => 1,
|
||||
])
|
||||
->addRule('timestamp', [
|
||||
->addRule('date', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The UNIX timestamp at which this metric was aggregated.',
|
||||
'default' => 0,
|
||||
|
|
|
@ -10,7 +10,7 @@ class None extends Model
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $none = true;
|
||||
protected bool $none = true;
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
|
|
|
@ -10,7 +10,7 @@ class Platform extends Model
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $public = false;
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
|
@ -6,11 +6,6 @@ use Appwrite\Utopia\Response;
|
|||
|
||||
class Preferences extends Any
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $any = true;
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
|
|
|
@ -12,7 +12,7 @@ class Project extends Model
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $public = false;
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ class Webhook extends Model
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $public = false;
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
|
@ -107,6 +107,8 @@ trait TeamsBaseClient
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertNotEmpty($response['body']['userId']);
|
||||
$this->assertEquals($name, $response['body']['userName']);
|
||||
$this->assertEquals($email, $response['body']['userEmail']);
|
||||
$this->assertNotEmpty($response['body']['teamId']);
|
||||
$this->assertNotEmpty($response['body']['teamName']);
|
||||
$this->assertCount(2, $response['body']['roles']);
|
||||
|
|
|
@ -57,6 +57,8 @@ trait TeamsBaseServer
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertNotEmpty($response['body']['userId']);
|
||||
$this->assertEquals('Friend User', $response['body']['userName']);
|
||||
$this->assertEquals($email, $response['body']['userEmail']);
|
||||
$this->assertNotEmpty($response['body']['teamId']);
|
||||
$this->assertCount(2, $response['body']['roles']);
|
||||
$this->assertIsInt($response['body']['joined']);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Auth;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Utopia\Database\Document;
|
||||
|
@ -9,10 +9,6 @@ use PHPUnit\Framework\TestCase;
|
|||
|
||||
class AuthTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Roles
|
||||
*/
|
||||
|
@ -22,7 +18,7 @@ class AuthTest extends TestCase
|
|||
Authorization::setRole('role:all');
|
||||
}
|
||||
|
||||
public function testCookieName()
|
||||
public function testCookieName(): void
|
||||
{
|
||||
$name = 'cookie-name';
|
||||
|
||||
|
@ -30,7 +26,7 @@ class AuthTest extends TestCase
|
|||
$this->assertEquals(Auth::$cookieName, $name);
|
||||
}
|
||||
|
||||
public function testEncodeDecodeSession()
|
||||
public function testEncodeDecodeSession(): void
|
||||
{
|
||||
$id = 'id';
|
||||
$secret = 'secret';
|
||||
|
@ -40,13 +36,13 @@ class AuthTest extends TestCase
|
|||
$this->assertEquals(Auth::decodeSession($session), ['id' => $id, 'secret' => $secret]);
|
||||
}
|
||||
|
||||
public function testHash()
|
||||
public function testHash(): void
|
||||
{
|
||||
$secret = 'secret';
|
||||
$this->assertEquals(Auth::hash($secret), '2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b');
|
||||
}
|
||||
|
||||
public function testPassword()
|
||||
public function testPassword(): void
|
||||
{
|
||||
$secret = 'secret';
|
||||
$static = '$2y$08$PDbMtV18J1KOBI9tIYabBuyUwBrtXPGhLxCy9pWP6xkldVOKLrLKy';
|
||||
|
@ -56,19 +52,19 @@ class AuthTest extends TestCase
|
|||
$this->assertEquals(Auth::passwordVerify($secret, $static), true);
|
||||
}
|
||||
|
||||
public function testPasswordGenerator()
|
||||
public function testPasswordGenerator(): void
|
||||
{
|
||||
$this->assertEquals(\mb_strlen(Auth::passwordGenerator()), 40);
|
||||
$this->assertEquals(\mb_strlen(Auth::passwordGenerator(5)), 10);
|
||||
}
|
||||
|
||||
public function testTokenGenerator()
|
||||
public function testTokenGenerator(): void
|
||||
{
|
||||
$this->assertEquals(\mb_strlen(Auth::tokenGenerator()), 256);
|
||||
$this->assertEquals(\mb_strlen(Auth::tokenGenerator(5)), 10);
|
||||
}
|
||||
|
||||
public function testSessionVerify()
|
||||
public function testSessionVerify(): void
|
||||
{
|
||||
$secret = 'secret1';
|
||||
$hash = Auth::hash($secret);
|
||||
|
@ -112,7 +108,7 @@ class AuthTest extends TestCase
|
|||
$this->assertEquals(Auth::sessionVerify($tokens2, 'false-secret'), false);
|
||||
}
|
||||
|
||||
public function testTokenVerify()
|
||||
public function testTokenVerify(): void
|
||||
{
|
||||
$secret = 'secret1';
|
||||
$hash = Auth::hash($secret);
|
||||
|
@ -169,7 +165,7 @@ class AuthTest extends TestCase
|
|||
$this->assertEquals(Auth::tokenVerify($tokens3, Auth::TOKEN_TYPE_RECOVERY, 'false-secret'), false);
|
||||
}
|
||||
|
||||
public function testIsPrivilegedUser()
|
||||
public function testIsPrivilegedUser(): void
|
||||
{
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([]));
|
||||
$this->assertEquals(false, Auth::isPrivilegedUser([Auth::USER_ROLE_GUESTS]));
|
||||
|
@ -186,7 +182,7 @@ class AuthTest extends TestCase
|
|||
$this->assertEquals(true, Auth::isPrivilegedUser([Auth::USER_ROLE_OWNER, Auth::USER_ROLE_ADMIN, Auth::USER_ROLE_DEVELOPER]));
|
||||
}
|
||||
|
||||
public function testIsAppUser()
|
||||
public function testIsAppUser(): void
|
||||
{
|
||||
$this->assertEquals(false, Auth::isAppUser([]));
|
||||
$this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_GUESTS]));
|
||||
|
@ -203,7 +199,7 @@ class AuthTest extends TestCase
|
|||
$this->assertEquals(false, Auth::isAppUser([Auth::USER_ROLE_OWNER, Auth::USER_ROLE_ADMIN, Auth::USER_ROLE_DEVELOPER]));
|
||||
}
|
||||
|
||||
public function testGuestRoles()
|
||||
public function testGuestRoles(): void
|
||||
{
|
||||
$user = new Document([
|
||||
'$id' => ''
|
||||
|
@ -214,7 +210,7 @@ class AuthTest extends TestCase
|
|||
$this->assertContains('guests', $roles);
|
||||
}
|
||||
|
||||
public function testUserRoles()
|
||||
public function testUserRoles(): void
|
||||
{
|
||||
$user = new Document([
|
||||
'$id' => '123',
|
||||
|
@ -247,7 +243,7 @@ class AuthTest extends TestCase
|
|||
$this->assertContains('team:def/guest', $roles);
|
||||
}
|
||||
|
||||
public function testPrivilegedUserRoles()
|
||||
public function testPrivilegedUserRoles(): void
|
||||
{
|
||||
Authorization::setRole(Auth::USER_ROLE_OWNER);
|
||||
$user = new Document([
|
||||
|
@ -281,7 +277,7 @@ class AuthTest extends TestCase
|
|||
$this->assertContains('team:def/guest', $roles);
|
||||
}
|
||||
|
||||
public function testAppUserRoles()
|
||||
public function testAppUserRoles(): void
|
||||
{
|
||||
Authorization::setRole(Auth::USER_ROLE_APP);
|
||||
$user = new Document([
|
||||
|
|
|
@ -1,27 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Auth\Validator;
|
||||
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PasswordTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Password
|
||||
*/
|
||||
protected $object = null;
|
||||
protected ?Password $object = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->object = new Password();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testValues()
|
||||
public function testValues(): void
|
||||
{
|
||||
$this->assertEquals($this->object->isValid(false), false);
|
||||
$this->assertEquals($this->object->isValid(null), false);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Auth\Validator;
|
||||
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -14,11 +14,7 @@ class PhoneTest extends TestCase
|
|||
$this->object = new Phone();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testValues()
|
||||
public function testValues(): void
|
||||
{
|
||||
$this->assertEquals($this->object->isValid(false), false);
|
||||
$this->assertEquals($this->object->isValid(null), false);
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\DSN;
|
||||
|
||||
use Appwrite\DSN\DSN;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DSNTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testSuccess(): void
|
||||
{
|
||||
$dsn = new DSN("mariadb://user:password@localhost:3306/database?charset=utf8&timezone=UTC");
|
||||
|
@ -84,6 +76,6 @@ class DSNTest extends TestCase
|
|||
public function testFail(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$dsn = new DSN("mariadb://");
|
||||
new DSN("mariadb://");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Detector;
|
||||
|
||||
use Appwrite\Detector\Detector;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DetectorTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Detector
|
||||
*/
|
||||
protected $object = null;
|
||||
protected ?Detector $object = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -21,7 +18,7 @@ class DetectorTest extends TestCase
|
|||
{
|
||||
}
|
||||
|
||||
public function testGetOS()
|
||||
public function testGetOS(): void
|
||||
{
|
||||
$this->assertEquals($this->object->getOS(), [
|
||||
'osCode' => 'WIN',
|
||||
|
@ -30,7 +27,7 @@ class DetectorTest extends TestCase
|
|||
]);
|
||||
}
|
||||
|
||||
public function testGetClient()
|
||||
public function testGetClient(): void
|
||||
{
|
||||
$this->assertEquals($this->object->getClient(), [
|
||||
'clientType' => 'browser',
|
||||
|
@ -42,7 +39,7 @@ class DetectorTest extends TestCase
|
|||
]);
|
||||
}
|
||||
|
||||
public function testGetDevice()
|
||||
public function testGetDevice(): void
|
||||
{
|
||||
$this->assertEquals($this->object->getDevice(), [
|
||||
'deviceName' => 'desktop',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Docker;
|
||||
|
||||
use Appwrite\Docker\Compose;
|
||||
use Exception;
|
||||
|
@ -8,11 +8,7 @@ use PHPUnit\Framework\TestCase;
|
|||
|
||||
class ComposeTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Compose
|
||||
*/
|
||||
protected $object = null;
|
||||
|
||||
protected ?Compose $object = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -25,16 +21,12 @@ class ComposeTest extends TestCase
|
|||
$this->object = new Compose($data);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testVersion()
|
||||
public function testVersion(): void
|
||||
{
|
||||
$this->assertEquals('3', $this->object->getVersion());
|
||||
}
|
||||
|
||||
public function testServices()
|
||||
public function testServices(): void
|
||||
{
|
||||
$this->assertCount(17, $this->object->getServices());
|
||||
$this->assertEquals('appwrite-telegraf', $this->object->getService('telegraf')->getContainerName());
|
||||
|
@ -44,12 +36,12 @@ class ComposeTest extends TestCase
|
|||
$this->assertEquals(['2080' => '80', '2443' => '443', '8080' => '8080'], $this->object->getService('traefik')->getPorts());
|
||||
}
|
||||
|
||||
public function testNetworks()
|
||||
public function testNetworks(): void
|
||||
{
|
||||
$this->assertCount(2, $this->object->getNetworks());
|
||||
}
|
||||
|
||||
public function testVolumes()
|
||||
public function testVolumes(): void
|
||||
{
|
||||
$this->assertCount(9, $this->object->getVolumes());
|
||||
$this->assertEquals('appwrite-mariadb', $this->object->getVolumes()[0]);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Docker;
|
||||
|
||||
use Appwrite\Docker\Env;
|
||||
use Exception;
|
||||
|
@ -8,11 +8,7 @@ use PHPUnit\Framework\TestCase;
|
|||
|
||||
class EnvTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Env
|
||||
*/
|
||||
protected $object = null;
|
||||
|
||||
protected ?Env $object = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -25,11 +21,7 @@ class EnvTest extends TestCase
|
|||
$this->object = new Env($data);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testVars()
|
||||
public function testVars(): void
|
||||
{
|
||||
$this->object->setVar('_APP_TEST', 'value4');
|
||||
|
||||
|
@ -39,7 +31,7 @@ class EnvTest extends TestCase
|
|||
$this->assertEquals('value4', $this->object->getVar('_APP_TEST'));
|
||||
}
|
||||
|
||||
public function testExport()
|
||||
public function testExport(): void
|
||||
{
|
||||
$this->assertEquals("_APP_X=value1
|
||||
_APP_Y=value2
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Event;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use InvalidArgumentException;
|
||||
|
@ -9,15 +9,8 @@ use Utopia\App;
|
|||
|
||||
class EventTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Event
|
||||
*/
|
||||
protected $object = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $queue = '';
|
||||
protected ?Event $object = null;
|
||||
protected string $queue = '';
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -29,11 +22,7 @@ class EventTest extends TestCase
|
|||
$this->object = new Event($this->queue, 'TestsV1');
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testQueue()
|
||||
public function testQueue(): void
|
||||
{
|
||||
$this->assertEquals($this->queue, $this->object->getQueue());
|
||||
|
||||
|
@ -44,7 +33,7 @@ class EventTest extends TestCase
|
|||
$this->object->setQueue($this->queue);
|
||||
}
|
||||
|
||||
public function testClass()
|
||||
public function testClass(): void
|
||||
{
|
||||
$this->assertEquals('TestsV1', $this->object->getClass());
|
||||
|
||||
|
@ -55,7 +44,7 @@ class EventTest extends TestCase
|
|||
$this->object->setClass('TestsV1');
|
||||
}
|
||||
|
||||
public function testParams()
|
||||
public function testParams(): void
|
||||
{
|
||||
$this->object
|
||||
->setParam('eventKey1', 'eventValue1')
|
||||
|
@ -69,7 +58,7 @@ class EventTest extends TestCase
|
|||
$this->assertEquals(\Resque::size($this->queue), 1);
|
||||
}
|
||||
|
||||
public function testReset()
|
||||
public function testReset(): void
|
||||
{
|
||||
$this->object
|
||||
->setParam('eventKey1', 'eventValue1')
|
||||
|
@ -85,7 +74,7 @@ class EventTest extends TestCase
|
|||
$this->assertEquals(null, $this->object->getParam('eventKey3'));
|
||||
}
|
||||
|
||||
public function testGenerateEvents()
|
||||
public function testGenerateEvents(): void
|
||||
{
|
||||
$event = Event::generateEvents('users.[userId].create', [
|
||||
'userId' => 'torsten'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Event\Validator;
|
||||
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -20,7 +20,7 @@ class EventValidatorTest extends TestCase
|
|||
{
|
||||
}
|
||||
|
||||
public function testValues()
|
||||
public function testValues(): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\General;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CollectionsTest extends TestCase
|
||||
{
|
||||
protected $collections;
|
||||
protected array $collections;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->collections = require('app/config/collections.php');
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testDuplicateRules()
|
||||
public function testDuplicateRules(): void
|
||||
{
|
||||
foreach ($this->collections as $key => $collection) {
|
||||
if (array_key_exists('attributes', $collection)) {
|
||||
|
|
|
@ -1,116 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\General;
|
||||
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ExtensionsTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
// Core
|
||||
// ctype
|
||||
// curl
|
||||
// date
|
||||
// fileinfo
|
||||
// filter
|
||||
// ftp
|
||||
// hash
|
||||
// iconv
|
||||
// libxml
|
||||
// mysqlnd
|
||||
// pcre
|
||||
// pdo_mysql
|
||||
// pdo_sqlite
|
||||
// Phar
|
||||
// posix
|
||||
// readline
|
||||
// Reflection
|
||||
// session
|
||||
// SimpleXML
|
||||
// sockets
|
||||
// sodium
|
||||
// SPL
|
||||
// sqlite3
|
||||
// standard
|
||||
// tokenizer
|
||||
// xml
|
||||
// xmlreader
|
||||
// xmlwriter
|
||||
// zlib
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testPHPRedis()
|
||||
public function testPHPRedis(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('redis'));
|
||||
}
|
||||
|
||||
public function testSwoole()
|
||||
public function testSwoole(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('swoole'));
|
||||
}
|
||||
|
||||
public function testYAML()
|
||||
public function testYAML(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('yaml'));
|
||||
}
|
||||
|
||||
public function testOPCache()
|
||||
public function testOPCache(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('Zend OPcache'));
|
||||
}
|
||||
|
||||
public function testDOM()
|
||||
public function testDOM(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('dom'));
|
||||
}
|
||||
|
||||
public function testPDO()
|
||||
public function testPDO(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('PDO'));
|
||||
}
|
||||
|
||||
public function testImagick()
|
||||
public function testImagick(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('imagick'));
|
||||
}
|
||||
|
||||
public function testJSON()
|
||||
public function testJSON(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('json'));
|
||||
}
|
||||
|
||||
public function testCURL()
|
||||
public function testCURL(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('curl'));
|
||||
}
|
||||
|
||||
public function testMBString()
|
||||
public function testMBString(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('mbstring'));
|
||||
}
|
||||
|
||||
public function testOPENSSL()
|
||||
public function testOPENSSL(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('openssl'));
|
||||
}
|
||||
|
||||
public function testZLIB()
|
||||
public function testZLIB(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('zlib'));
|
||||
}
|
||||
|
||||
public function testSockets()
|
||||
public function testSockets(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('sockets'));
|
||||
}
|
||||
|
||||
public function testMaxminddb()
|
||||
public function testMaxminddb(): void
|
||||
{
|
||||
$this->assertEquals(true, extension_loaded('maxminddb'));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Messaging;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Utopia\Database\Document;
|
||||
|
@ -106,7 +106,7 @@ class MessagingChannelsTest extends TestCase
|
|||
$this->connectionsCount = 0;
|
||||
}
|
||||
|
||||
public function testSubscriptions()
|
||||
public function testSubscriptions(): void
|
||||
{
|
||||
/**
|
||||
* Check for 1 project.
|
||||
|
@ -148,7 +148,7 @@ class MessagingChannelsTest extends TestCase
|
|||
/**
|
||||
* Tests Wildcard (role:all) Permissions on every channel.
|
||||
*/
|
||||
public function testWildcardPermission()
|
||||
public function testWildcardPermission(): void
|
||||
{
|
||||
foreach ($this->allChannels as $index => $channel) {
|
||||
$event = [
|
||||
|
@ -177,7 +177,7 @@ class MessagingChannelsTest extends TestCase
|
|||
}
|
||||
}
|
||||
|
||||
public function testRolePermissions()
|
||||
public function testRolePermissions(): void
|
||||
{
|
||||
$roles = ['guests', 'users'];
|
||||
foreach ($this->allChannels as $index => $channel) {
|
||||
|
@ -211,7 +211,7 @@ class MessagingChannelsTest extends TestCase
|
|||
}
|
||||
}
|
||||
|
||||
public function testUserPermissions()
|
||||
public function testUserPermissions(): void
|
||||
{
|
||||
foreach ($this->allChannels as $index => $channel) {
|
||||
$permissions = [];
|
||||
|
@ -244,7 +244,7 @@ class MessagingChannelsTest extends TestCase
|
|||
}
|
||||
}
|
||||
|
||||
public function testTeamPermissions()
|
||||
public function testTeamPermissions(): void
|
||||
{
|
||||
foreach ($this->allChannels as $index => $channel) {
|
||||
$permissions = [];
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Messaging;
|
||||
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MessagingGuestTest extends TestCase
|
||||
{
|
||||
public function testGuest()
|
||||
public function testGuest(): void
|
||||
{
|
||||
$realtime = new Realtime();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Messaging;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
|
@ -16,7 +16,7 @@ class MessagingTest extends TestCase
|
|||
{
|
||||
}
|
||||
|
||||
public function testUser()
|
||||
public function testUser(): void
|
||||
{
|
||||
$realtime = new Realtime();
|
||||
|
||||
|
@ -134,7 +134,7 @@ class MessagingTest extends TestCase
|
|||
$this->assertEmpty($realtime->subscriptions);
|
||||
}
|
||||
|
||||
public function testConvertChannelsGuest()
|
||||
public function testConvertChannelsGuest(): void
|
||||
{
|
||||
$user = new Document([
|
||||
'$id' => ''
|
||||
|
@ -157,7 +157,7 @@ class MessagingTest extends TestCase
|
|||
$this->assertArrayNotHasKey('account.456', $channels);
|
||||
}
|
||||
|
||||
public function testConvertChannelsUser()
|
||||
public function testConvertChannelsUser(): void
|
||||
{
|
||||
$user = new Document([
|
||||
'$id' => '123',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Migration;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -34,7 +34,7 @@ abstract class MigrationTest extends TestCase
|
|||
/**
|
||||
* Check versions array integrity.
|
||||
*/
|
||||
public function testMigrationVersions()
|
||||
public function testMigrationVersions(): void
|
||||
{
|
||||
require_once __DIR__ . '/../../../app/init.php';
|
||||
|
||||
|
@ -45,7 +45,7 @@ abstract class MigrationTest extends TestCase
|
|||
$this->assertArrayHasKey(APP_VERSION_STABLE, Migration::$versions);
|
||||
}
|
||||
|
||||
public function testHasDifference()
|
||||
public function testHasDifference(): void
|
||||
{
|
||||
$this->assertFalse(Migration::hasDifference([], []));
|
||||
$this->assertFalse(Migration::hasDifference([
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Migration;
|
||||
|
||||
use ReflectionClass;
|
||||
use Appwrite\Migration\Version\V12;
|
||||
|
@ -16,7 +16,7 @@ class MigrationV12Test extends MigrationTest
|
|||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigrationProjects()
|
||||
public function testMigrationProjects(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'project',
|
||||
|
@ -30,7 +30,7 @@ class MigrationV12Test extends MigrationTest
|
|||
$this->assertEquals($document->getAttribute('search'), 'project Appwrite');
|
||||
}
|
||||
|
||||
public function testMigrationUsers()
|
||||
public function testMigrationUsers(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'user',
|
||||
|
@ -42,7 +42,7 @@ class MigrationV12Test extends MigrationTest
|
|||
$this->assertEquals($document->getAttribute('search'), 'user test@appwrite.io Torsten Dittmann');
|
||||
}
|
||||
|
||||
public function testMigrationTeams()
|
||||
public function testMigrationTeams(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'team',
|
||||
|
@ -53,7 +53,7 @@ class MigrationV12Test extends MigrationTest
|
|||
$this->assertEquals($document->getAttribute('search'), 'team Appwrite');
|
||||
}
|
||||
|
||||
public function testMigrationFunctions()
|
||||
public function testMigrationFunctions(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'function',
|
||||
|
@ -65,7 +65,7 @@ class MigrationV12Test extends MigrationTest
|
|||
$this->assertEquals($document->getAttribute('search'), 'function My Function php-8.0');
|
||||
}
|
||||
|
||||
public function testMigrationExecutions()
|
||||
public function testMigrationExecutions(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'execution',
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Migration;
|
||||
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use ReflectionClass;
|
||||
use Appwrite\Migration\Version\V13;
|
||||
use Utopia\Database\Document;
|
||||
|
@ -17,7 +16,7 @@ class MigrationV13Test extends MigrationTest
|
|||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigrateFunctions()
|
||||
public function testMigrateFunctions(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'func',
|
||||
|
@ -28,7 +27,7 @@ class MigrationV13Test extends MigrationTest
|
|||
$this->assertEquals($document->getAttribute('events'), ['users.*.create']);
|
||||
}
|
||||
|
||||
public function testMigrationWebhooks()
|
||||
public function testMigrationWebhooks(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'webh',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Migration;
|
||||
|
||||
use ReflectionClass;
|
||||
use Appwrite\Migration\Version\V14;
|
||||
|
@ -16,7 +16,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->method->setAccessible(true);
|
||||
}
|
||||
|
||||
public function testMigrateProjects()
|
||||
public function testMigrateProjects(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -28,7 +28,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertEquals($document->getAttribute('version'), '0.15.0');
|
||||
}
|
||||
|
||||
public function testMigrateKeys()
|
||||
public function testMigrateKeys(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -39,7 +39,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertEquals($document->getAttribute('expire'), 0);
|
||||
}
|
||||
|
||||
public function testMigrateWebhooks()
|
||||
public function testMigrateWebhooks(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -50,7 +50,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertEquals(strlen($document->getAttribute('signatureKey')), 128);
|
||||
}
|
||||
|
||||
public function testMigrateUsers()
|
||||
public function testMigrateUsers(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -62,7 +62,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertFalse($document->getAttribute('phoneVerification'));
|
||||
}
|
||||
|
||||
public function testMigratePlatforms()
|
||||
public function testMigratePlatforms(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -77,7 +77,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertEquals($document->getUpdatedAt(), 987654321);
|
||||
}
|
||||
|
||||
public function testMigrateFunctions()
|
||||
public function testMigrateFunctions(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -92,7 +92,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertEquals($document->getUpdatedAt(), 987654321);
|
||||
}
|
||||
|
||||
public function testMigrateDeployments()
|
||||
public function testMigrateDeployments(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -104,7 +104,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertEquals($document->getCreatedAt(), 123456789);
|
||||
}
|
||||
|
||||
public function testMigrateExecutions()
|
||||
public function testMigrateExecutions(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -116,7 +116,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertEquals($document->getCreatedAt(), 123456789);
|
||||
}
|
||||
|
||||
public function testMigrateTeams()
|
||||
public function testMigrateTeams(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -128,7 +128,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertEquals($document->getCreatedAt(), 123456789);
|
||||
}
|
||||
|
||||
public function testMigrateAudits()
|
||||
public function testMigrateAudits(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
@ -151,7 +151,7 @@ class MigrationV14Test extends MigrationTest
|
|||
$this->assertEquals($document->getAttribute('event'), 'databases.default.collections.movies.documents.avatar.create');
|
||||
}
|
||||
|
||||
public function testMigrateStats()
|
||||
public function testMigrateStats(): void
|
||||
{
|
||||
$document = $this->fixDocument(new Document([
|
||||
'$id' => 'appwrite',
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Network\Validators;
|
||||
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CNAMETest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CNAME
|
||||
*/
|
||||
protected $object = null;
|
||||
protected ?CNAME $object = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -21,7 +18,7 @@ class CNAMETest extends TestCase
|
|||
{
|
||||
}
|
||||
|
||||
public function testValues()
|
||||
public function testValues(): void
|
||||
{
|
||||
$this->assertEquals($this->object->isValid(''), false);
|
||||
$this->assertEquals($this->object->isValid(null), false);
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Network\Validators;
|
||||
|
||||
use Appwrite\Network\Validator\Domain;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DomainTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Domain
|
||||
*/
|
||||
protected $domain = null;
|
||||
protected ?Domain $domain = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -22,7 +19,7 @@ class DomainTest extends TestCase
|
|||
$this->domain = null;
|
||||
}
|
||||
|
||||
public function testIsValid()
|
||||
public function testIsValid(): void
|
||||
{
|
||||
// Assertions
|
||||
$this->assertEquals(true, $this->domain->isValid('example.com'));
|
||||
|
|
|
@ -12,16 +12,14 @@
|
|||
* @license The MIT License (MIT) <http://www.opensource.org/licenses/mit-license.php>
|
||||
*/
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
namespace Tests\Unit\Network\Validators;
|
||||
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class EmailTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Email
|
||||
*/
|
||||
protected $email = null;
|
||||
protected ?Email $email = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -33,9 +31,8 @@ class EmailTest extends TestCase
|
|||
$this->email = null;
|
||||
}
|
||||
|
||||
public function testIsValid()
|
||||
public function testIsValid(): void
|
||||
{
|
||||
// Assertions
|
||||
$this->assertEquals(true, $this->email->isValid('email@domain.com'));
|
||||
$this->assertEquals(true, $this->email->isValid('firstname.lastname@domain.com'));
|
||||
$this->assertEquals(true, $this->email->isValid('email@subdomain.domain.com'));
|
||||
|
|
|
@ -12,16 +12,14 @@
|
|||
* @license The MIT License (MIT) <http://www.opensource.org/licenses/mit-license.php>
|
||||
*/
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
namespace Tests\Unit\Network\Validators;
|
||||
|
||||
use Appwrite\Network\Validator\Host;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class HostTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Host
|
||||
*/
|
||||
protected $host = null;
|
||||
protected ?Host $host = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -33,7 +31,7 @@ class HostTest extends TestCase
|
|||
$this->host = null;
|
||||
}
|
||||
|
||||
public function testIsValid()
|
||||
public function testIsValid(): void
|
||||
{
|
||||
// Assertions
|
||||
$this->assertEquals($this->host->isValid('https://appwrite.io/link'), true);
|
||||
|
|
|
@ -12,71 +12,76 @@
|
|||
* @license The MIT License (MIT) <http://www.opensource.org/licenses/mit-license.php>
|
||||
*/
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
namespace Tests\Unit\Network\Validators;
|
||||
|
||||
use Appwrite\Network\Validator\IP;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class IPTest extends TestCase
|
||||
{
|
||||
protected ?IP $validator;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->validator = new IP();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
$this->validator = null;
|
||||
}
|
||||
|
||||
public function testIsValidIP()
|
||||
public function testIsValidIP(): void
|
||||
{
|
||||
$validator = new IP();
|
||||
|
||||
// Assertions
|
||||
$this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true);
|
||||
$this->assertEquals($validator->isValid('109.67.204.101'), true);
|
||||
$this->assertEquals($validator->isValid(23.5), false);
|
||||
$this->assertEquals($validator->isValid('23.5'), false);
|
||||
$this->assertEquals($validator->isValid(null), false);
|
||||
$this->assertEquals($validator->isValid(true), false);
|
||||
$this->assertEquals($validator->isValid(false), false);
|
||||
$this->assertEquals($validator->getType(), 'string');
|
||||
$this->assertEquals($this->validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true);
|
||||
$this->assertEquals($this->validator->isValid('109.67.204.101'), true);
|
||||
$this->assertEquals($this->validator->isValid(23.5), false);
|
||||
$this->assertEquals($this->validator->isValid('23.5'), false);
|
||||
$this->assertEquals($this->validator->isValid(null), false);
|
||||
$this->assertEquals($this->validator->isValid(true), false);
|
||||
$this->assertEquals($this->validator->isValid(false), false);
|
||||
$this->assertEquals($this->validator->getType(), 'string');
|
||||
}
|
||||
|
||||
public function testIsValidIPALL()
|
||||
public function testIsValidIPALL(): void
|
||||
{
|
||||
$validator = new IP(IP::ALL);
|
||||
$this->validator = new IP(IP::ALL);
|
||||
|
||||
// Assertions
|
||||
$this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true);
|
||||
$this->assertEquals($validator->isValid('109.67.204.101'), true);
|
||||
$this->assertEquals($validator->isValid(23.5), false);
|
||||
$this->assertEquals($validator->isValid('23.5'), false);
|
||||
$this->assertEquals($validator->isValid(null), false);
|
||||
$this->assertEquals($validator->isValid(true), false);
|
||||
$this->assertEquals($validator->isValid(false), false);
|
||||
$this->assertEquals($this->validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true);
|
||||
$this->assertEquals($this->validator->isValid('109.67.204.101'), true);
|
||||
$this->assertEquals($this->validator->isValid(23.5), false);
|
||||
$this->assertEquals($this->validator->isValid('23.5'), false);
|
||||
$this->assertEquals($this->validator->isValid(null), false);
|
||||
$this->assertEquals($this->validator->isValid(true), false);
|
||||
$this->assertEquals($this->validator->isValid(false), false);
|
||||
}
|
||||
|
||||
public function testIsValidIPV4()
|
||||
public function testIsValidIPV4(): void
|
||||
{
|
||||
$validator = new IP(IP::V4);
|
||||
$this->validator = new IP(IP::V4);
|
||||
|
||||
// Assertions
|
||||
$this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), false);
|
||||
$this->assertEquals($validator->isValid('109.67.204.101'), true);
|
||||
$this->assertEquals($validator->isValid(23.5), false);
|
||||
$this->assertEquals($validator->isValid('23.5'), false);
|
||||
$this->assertEquals($validator->isValid(null), false);
|
||||
$this->assertEquals($validator->isValid(true), false);
|
||||
$this->assertEquals($validator->isValid(false), false);
|
||||
$this->assertEquals($this->validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), false);
|
||||
$this->assertEquals($this->validator->isValid('109.67.204.101'), true);
|
||||
$this->assertEquals($this->validator->isValid(23.5), false);
|
||||
$this->assertEquals($this->validator->isValid('23.5'), false);
|
||||
$this->assertEquals($this->validator->isValid(null), false);
|
||||
$this->assertEquals($this->validator->isValid(true), false);
|
||||
$this->assertEquals($this->validator->isValid(false), false);
|
||||
}
|
||||
|
||||
public function testIsValidIPV6()
|
||||
public function testIsValidIPV6(): void
|
||||
{
|
||||
$validator = new IP(IP::V6);
|
||||
$this->validator = new IP(IP::V6);
|
||||
|
||||
// Assertions
|
||||
$this->assertEquals($validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true);
|
||||
$this->assertEquals($validator->isValid('109.67.204.101'), false);
|
||||
$this->assertEquals($validator->isValid(23.5), false);
|
||||
$this->assertEquals($validator->isValid('23.5'), false);
|
||||
$this->assertEquals($validator->isValid(null), false);
|
||||
$this->assertEquals($validator->isValid(true), false);
|
||||
$this->assertEquals($validator->isValid(false), false);
|
||||
$this->assertEquals($this->validator->isValid('2001:0db8:85a3:08d3:1319:8a2e:0370:7334'), true);
|
||||
$this->assertEquals($this->validator->isValid('109.67.204.101'), false);
|
||||
$this->assertEquals($this->validator->isValid(23.5), false);
|
||||
$this->assertEquals($this->validator->isValid('23.5'), false);
|
||||
$this->assertEquals($this->validator->isValid(null), false);
|
||||
$this->assertEquals($this->validator->isValid(true), false);
|
||||
$this->assertEquals($this->validator->isValid(false), false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Network\Validators;
|
||||
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class OriginTest extends TestCase
|
||||
{
|
||||
public function testValues()
|
||||
public function testValues(): void
|
||||
{
|
||||
$validator = new Origin([
|
||||
[
|
||||
|
|
|
@ -12,8 +12,9 @@
|
|||
* @license The MIT License (MIT) <http://www.opensource.org/licenses/mit-license.php>
|
||||
*/
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
namespace Tests\Unit\Network\Validators;
|
||||
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class URLTest extends TestCase
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\OpenSSL;
|
||||
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -15,7 +15,7 @@ class OpenSSLTest extends TestCase
|
|||
{
|
||||
}
|
||||
|
||||
public function testEncryptionAndDecryption()
|
||||
public function testEncryptionAndDecryption(): void
|
||||
{
|
||||
$key = 'my-secret-key';
|
||||
$iv = '';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Stats;
|
||||
|
||||
use Appwrite\Stats\Stats;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -28,13 +28,13 @@ class StatsTest extends TestCase
|
|||
{
|
||||
}
|
||||
|
||||
public function testNamespace()
|
||||
public function testNamespace(): void
|
||||
{
|
||||
$this->object->setNamespace('appwritetest.usage');
|
||||
$this->assertEquals('appwritetest.usage', $this->object->getNamespace());
|
||||
}
|
||||
|
||||
public function testParams()
|
||||
public function testParams(): void
|
||||
{
|
||||
$this->object
|
||||
->setParam('projectId', 'appwrite_test')
|
||||
|
@ -50,7 +50,7 @@ class StatsTest extends TestCase
|
|||
$this->assertEquals(null, $this->object->getParam('networkRequestSize'));
|
||||
}
|
||||
|
||||
public function testReset()
|
||||
public function testReset(): void
|
||||
{
|
||||
$this->object
|
||||
->setParam('projectId', 'appwrite_test')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Validator;
|
||||
|
||||
use Appwrite\Task\Validator\Cron;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -21,7 +21,7 @@ class CronTest extends TestCase
|
|||
{
|
||||
}
|
||||
|
||||
public function testValues()
|
||||
public function testValues(): void
|
||||
{
|
||||
$this->assertEquals($this->object->isValid('0 2 * * *'), true); // execute at 2am daily
|
||||
$this->assertEquals($this->object->isValid('0 5,17 * * *'), true); // execute twice a day
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Template;
|
||||
|
||||
use Appwrite\Template\Template;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -24,12 +24,12 @@ class TemplateTest extends TestCase
|
|||
{
|
||||
}
|
||||
|
||||
public function testRender()
|
||||
public function testRender(): void
|
||||
{
|
||||
$this->assertEquals($this->object->render(), 'Hello WORLD');
|
||||
}
|
||||
|
||||
public function testParseURL()
|
||||
public function testParseURL(): void
|
||||
{
|
||||
$url = $this->object->parseURL('https://appwrite.io/demo');
|
||||
|
||||
|
@ -38,7 +38,7 @@ class TemplateTest extends TestCase
|
|||
$this->assertEquals($url['path'], '/demo');
|
||||
}
|
||||
|
||||
public function testUnParseURL()
|
||||
public function testUnParseURL(): void
|
||||
{
|
||||
$url = $this->object->parseURL('https://appwrite.io/demo');
|
||||
|
||||
|
@ -49,18 +49,18 @@ class TemplateTest extends TestCase
|
|||
$this->assertEquals($this->object->unParseURL($url), 'http://example.com/new');
|
||||
}
|
||||
|
||||
public function testMergeQuery()
|
||||
public function testMergeQuery(): void
|
||||
{
|
||||
$this->assertEquals($this->object->mergeQuery('key1=value1&key2=value2', ['key1' => 'value3', 'key4' => 'value4']), 'key1=value3&key2=value2&key4=value4');
|
||||
}
|
||||
|
||||
public function testFromCamelCaseToSnake()
|
||||
public function testFromCamelCaseToSnake(): void
|
||||
{
|
||||
$this->assertEquals('app_write', Template::fromCamelCaseToSnake('appWrite'));
|
||||
$this->assertEquals('app_write', Template::fromCamelCaseToSnake('App Write'));
|
||||
}
|
||||
|
||||
public function testFromCamelCaseToDash()
|
||||
public function testFromCamelCaseToDash(): void
|
||||
{
|
||||
$this->assertEquals('app-write', Template::fromCamelCaseToDash('appWrite'));
|
||||
$this->assertEquals('app-write', Template::fromCamelCaseToDash('App Write'));
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\URL;
|
||||
|
||||
use Appwrite\URL\URL;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class URLTest extends TestCase
|
||||
{
|
||||
public function testParse()
|
||||
public function testParse(): void
|
||||
{
|
||||
$url = URL::parse('https://appwrite.io:8080/path?query=string¶m=value');
|
||||
|
||||
|
@ -28,7 +28,7 @@ class URLTest extends TestCase
|
|||
$this->assertEquals('', $url['query']);
|
||||
}
|
||||
|
||||
public function testUnparse()
|
||||
public function testUnparse(): void
|
||||
{
|
||||
$url = URL::unparse([
|
||||
'scheme' => 'https',
|
||||
|
@ -88,7 +88,7 @@ class URLTest extends TestCase
|
|||
$this->assertEquals('https://eldad:fux@appwrite.io/#bottom', $url);
|
||||
}
|
||||
|
||||
public function testParseQuery()
|
||||
public function testParseQuery(): void
|
||||
{
|
||||
$result = URL::parseQuery('param1=value1¶m2=value2');
|
||||
|
||||
|
@ -96,7 +96,7 @@ class URLTest extends TestCase
|
|||
$this->assertEquals(['param1' => 'value1', 'param2' => 'value2'], $result);
|
||||
}
|
||||
|
||||
public function testUnParseQuery()
|
||||
public function testUnParseQuery(): void
|
||||
{
|
||||
$result = URL::unparseQuery(['param1' => 'value1', 'param2' => 'value2']);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Utopia\Database\Validator;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -21,7 +21,7 @@ class CustomIdTest extends TestCase
|
|||
{
|
||||
}
|
||||
|
||||
public function testValues()
|
||||
public function testValues(): void
|
||||
{
|
||||
$this->assertEquals($this->object->isValid('unique()'), true);
|
||||
$this->assertEquals($this->object->isValid('unique)'), false);
|
||||
|
|
28
tests/unit/Utopia/Lists.php
Normal file
28
tests/unit/Utopia/Lists.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia;
|
||||
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Lists extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('singles', [
|
||||
'type' => 'single',
|
||||
'default' => '',
|
||||
'array' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Lists';
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return 'lists';
|
||||
}
|
||||
}
|
31
tests/unit/Utopia/Nested.php
Normal file
31
tests/unit/Utopia/Nested.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia;
|
||||
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Nested extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('lists', [
|
||||
'type' => 'lists',
|
||||
'default' => '',
|
||||
])
|
||||
->addRule('single', [
|
||||
'type' => 'single',
|
||||
'default' => ''
|
||||
]);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Nested';
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return 'nested';
|
||||
}
|
||||
}
|
|
@ -1,33 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Tests;
|
||||
namespace Tests\Unit\Utopia;
|
||||
|
||||
use Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filters\V11;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class ResponseTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $object = null;
|
||||
protected ?Response $response = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->object = new Response(new SwooleResponse());
|
||||
$this->response = new Response(new SwooleResponse());
|
||||
$this->response->setModel(new Single());
|
||||
$this->response->setModel(new Lists());
|
||||
$this->response->setModel(new Nested());
|
||||
}
|
||||
|
||||
public function testSetFilter()
|
||||
public function testSetFilter(): void
|
||||
{
|
||||
$this->assertEquals($this->object->hasFilter(), false);
|
||||
$this->assertEquals($this->object->getFilter(), null);
|
||||
$this->assertEquals($this->response->hasFilter(), false);
|
||||
$this->assertEquals($this->response->getFilter(), null);
|
||||
|
||||
$filter = new V11();
|
||||
$this->object->setFilter($filter);
|
||||
$this->response->setFilter($filter);
|
||||
|
||||
$this->assertEquals($this->object->hasFilter(), true);
|
||||
$this->assertEquals($this->object->getFilter(), $filter);
|
||||
$this->assertEquals($this->response->hasFilter(), true);
|
||||
$this->assertEquals($this->response->getFilter(), $filter);
|
||||
}
|
||||
|
||||
public function testResponseModel(): void
|
||||
{
|
||||
$output = $this->response->output(new Document([
|
||||
'string' => 'lorem ipsum',
|
||||
'integer' => 123,
|
||||
'boolean' => true,
|
||||
'hidden' => 'secret',
|
||||
]), 'single');
|
||||
|
||||
$this->assertArrayHasKey('string', $output);
|
||||
$this->assertArrayHasKey('integer', $output);
|
||||
$this->assertArrayHasKey('boolean', $output);
|
||||
$this->assertArrayNotHasKey('hidden', $output);
|
||||
}
|
||||
|
||||
public function testResponseModelRequired(): void
|
||||
{
|
||||
$output = $this->response->output(new Document([
|
||||
'string' => 'lorem ipsum',
|
||||
'integer' => 123,
|
||||
'boolean' => true,
|
||||
]), 'single');
|
||||
|
||||
$this->assertArrayHasKey('string', $output);
|
||||
$this->assertArrayHasKey('integer', $output);
|
||||
$this->assertArrayHasKey('boolean', $output);
|
||||
$this->assertArrayHasKey('required', $output);
|
||||
$this->assertEquals('default', $output['required']);
|
||||
}
|
||||
|
||||
public function testResponseModelRequiredException(): void
|
||||
{
|
||||
$this->expectException(Exception::class);
|
||||
$this->response->output(new Document([
|
||||
'integer' => 123,
|
||||
'boolean' => true,
|
||||
]), 'single');
|
||||
}
|
||||
|
||||
public function testResponseModelLists(): void
|
||||
{
|
||||
$output = $this->response->output(new Document([
|
||||
'singles' => [
|
||||
new Document([
|
||||
'string' => 'lorem ipsum',
|
||||
'integer' => 123,
|
||||
'boolean' => true,
|
||||
'hidden' => 'secret'
|
||||
])
|
||||
],
|
||||
'hidden' => 'secret',
|
||||
]), 'lists');
|
||||
|
||||
$this->assertArrayHasKey('singles', $output);
|
||||
$this->assertArrayNotHasKey('hidden', $output);
|
||||
$this->assertCount(1, $output['singles']);
|
||||
|
||||
$single = $output['singles'][0];
|
||||
$this->assertArrayHasKey('string', $single);
|
||||
$this->assertArrayHasKey('integer', $single);
|
||||
$this->assertArrayHasKey('boolean', $single);
|
||||
$this->assertArrayHasKey('required', $single);
|
||||
$this->assertArrayNotHasKey('hidden', $single);
|
||||
}
|
||||
|
||||
public function testResponseModelNested(): void
|
||||
{
|
||||
$output = $this->response->output(new Document([
|
||||
'lists' => new Document([
|
||||
'singles' => [
|
||||
new Document([
|
||||
'string' => 'lorem ipsum',
|
||||
'integer' => 123,
|
||||
'boolean' => true,
|
||||
'hidden' => 'secret'
|
||||
])
|
||||
],
|
||||
'hidden' => 'secret',
|
||||
]),
|
||||
'single' => new Document([
|
||||
'string' => 'lorem ipsum',
|
||||
'integer' => 123,
|
||||
'boolean' => true,
|
||||
'hidden' => 'secret'
|
||||
]),
|
||||
'hidden' => 'secret',
|
||||
]), 'nested');
|
||||
|
||||
$this->assertArrayHasKey('lists', $output);
|
||||
$this->assertArrayHasKey('single', $output);
|
||||
$this->assertArrayNotHasKey('hidden', $output);
|
||||
$this->assertCount(1, $output['lists']['singles']);
|
||||
|
||||
|
||||
$single = $output['single'];
|
||||
$this->assertArrayHasKey('string', $single);
|
||||
$this->assertArrayHasKey('integer', $single);
|
||||
$this->assertArrayHasKey('boolean', $single);
|
||||
$this->assertArrayHasKey('required', $single);
|
||||
$this->assertArrayNotHasKey('hidden', $single);
|
||||
|
||||
$singleFromArray = $output['lists']['singles'][0];
|
||||
$this->assertArrayHasKey('string', $singleFromArray);
|
||||
$this->assertArrayHasKey('integer', $singleFromArray);
|
||||
$this->assertArrayHasKey('boolean', $singleFromArray);
|
||||
$this->assertArrayHasKey('required', $single);
|
||||
$this->assertArrayNotHasKey('hidden', $singleFromArray);
|
||||
}
|
||||
}
|
||||
|
|
43
tests/unit/Utopia/Single.php
Normal file
43
tests/unit/Utopia/Single.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia;
|
||||
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Single extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('string', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'example' => '5e5ea5c16897e',
|
||||
'required' => true
|
||||
])
|
||||
->addRule('integer', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('boolean', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'default' => true,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('required', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'default' => 'default',
|
||||
'required' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Single';
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return 'single';
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue