2019-05-09 18:54:39 +12:00
|
|
|
<?php
|
2019-10-01 17:57:41 +13:00
|
|
|
|
2021-01-12 10:52:05 +13:00
|
|
|
use Appwrite\Auth\Auth;
|
|
|
|
use Appwrite\Database\Validator\Authorization;
|
2021-06-30 23:36:58 +12:00
|
|
|
use Appwrite\Messaging\Adapter\Realtime;
|
2020-06-29 05:31:21 +12:00
|
|
|
use Utopia\App;
|
2019-11-30 07:23:29 +13:00
|
|
|
use Utopia\Exception;
|
|
|
|
use Utopia\Abuse\Abuse;
|
|
|
|
use Utopia\Abuse\Adapters\TimeLimit;
|
2021-10-08 04:35:17 +13:00
|
|
|
use Utopia\Database\Document;
|
2021-01-22 21:28:33 +13:00
|
|
|
use Utopia\Storage\Device\Local;
|
|
|
|
use Utopia\Storage\Storage;
|
2019-11-30 07:23:29 +13:00
|
|
|
|
2021-12-28 01:45:23 +13:00
|
|
|
App::init(function ($utopia, $request, $response, $project, $user, $events, $audits, $usage, $deletes, $database, $dbForProject, $mode) {
|
2020-07-03 05:37:24 +12:00
|
|
|
/** @var Utopia\App $utopia */
|
2022-01-01 04:50:07 +13:00
|
|
|
/** @var Appwrite\Utopia\Request $request */
|
2020-10-30 02:50:49 +13:00
|
|
|
/** @var Appwrite\Utopia\Response $response */
|
2021-05-16 21:18:34 +12:00
|
|
|
/** @var Utopia\Database\Document $project */
|
|
|
|
/** @var Utopia\Database\Document $user */
|
2020-07-03 05:37:24 +12:00
|
|
|
/** @var Utopia\Registry\Registry $register */
|
2021-01-06 01:22:20 +13:00
|
|
|
/** @var Appwrite\Event\Event $events */
|
|
|
|
/** @var Appwrite\Event\Event $audits */
|
2021-08-08 18:31:20 +12:00
|
|
|
/** @var Appwrite\Stats\Stats $usage */
|
2021-01-06 01:22:20 +13:00
|
|
|
/** @var Appwrite\Event\Event $deletes */
|
2021-06-19 04:13:37 +12:00
|
|
|
/** @var Appwrite\Event\Event $database */
|
2021-01-06 01:22:20 +13:00
|
|
|
/** @var Appwrite\Event\Event $functions */
|
2021-12-28 01:45:23 +13:00
|
|
|
/** @var Utopia\Database\Database $dbForProject */
|
2021-01-06 01:22:20 +13:00
|
|
|
|
|
|
|
Storage::setDevice('files', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId()));
|
|
|
|
Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId()));
|
2021-12-09 04:08:53 +13:00
|
|
|
Storage::setDevice('builds', new Local(APP_STORAGE_BUILDS.'/app-'.$project->getId()));
|
2020-07-03 05:37:24 +12:00
|
|
|
|
2019-11-30 07:23:29 +13:00
|
|
|
$route = $utopia->match($request);
|
|
|
|
|
2021-06-21 01:59:36 +12:00
|
|
|
if ($project->isEmpty() && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
|
2020-06-18 08:08:55 +12:00
|
|
|
throw new Exception('Missing or unknown project ID', 400);
|
|
|
|
}
|
|
|
|
|
2021-06-07 17:17:29 +12:00
|
|
|
/*
|
|
|
|
* Abuse Check
|
|
|
|
*/
|
2021-11-09 23:21:24 +13:00
|
|
|
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
|
|
|
$timeLimitArray = [];
|
2021-06-07 17:17:29 +12:00
|
|
|
|
2021-11-24 22:57:25 +13:00
|
|
|
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
|
2021-06-07 17:17:29 +12:00
|
|
|
|
2021-11-10 03:07:10 +13:00
|
|
|
foreach ($abuseKeyLabel as $abuseKey) {
|
2022-01-04 01:44:28 +13:00
|
|
|
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
2021-11-09 23:21:24 +13:00
|
|
|
$timeLimit
|
|
|
|
->setParam('{userId}', $user->getId())
|
|
|
|
->setParam('{userAgent}', $request->getUserAgent(''))
|
|
|
|
->setParam('{ip}', $request->getIP())
|
2021-11-10 03:07:10 +13:00
|
|
|
->setParam('{url}', $request->getHostname().$route->getPath());
|
2021-11-09 23:21:24 +13:00
|
|
|
$timeLimitArray[] = $timeLimit;
|
2021-06-07 17:17:29 +12:00
|
|
|
}
|
|
|
|
|
2021-11-10 03:52:32 +13:00
|
|
|
$closestLimit = null;
|
2021-06-07 17:17:29 +12:00
|
|
|
|
2021-12-11 06:52:33 +13:00
|
|
|
$roles = Authorization::getRoles();
|
|
|
|
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
|
|
|
$isAppUser = Auth::isAppUser($roles);
|
2021-06-07 17:17:29 +12:00
|
|
|
|
2021-11-09 23:21:24 +13:00
|
|
|
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);
|
|
|
|
}
|
2021-06-24 03:11:23 +12:00
|
|
|
}
|
2021-06-07 17:17:29 +12:00
|
|
|
|
2021-11-09 23:21:24 +13:00
|
|
|
$abuse = new Abuse($timeLimit);
|
2021-06-07 17:17:29 +12:00
|
|
|
|
2021-11-10 03:56:25 +13:00
|
|
|
if ($timeLimit->limit() && ($timeLimit->remaining() < $closestLimit || is_null($closestLimit))) {
|
2021-11-10 03:07:10 +13:00
|
|
|
$closestLimit = $timeLimit->remaining();
|
2021-11-09 23:21:24 +13:00
|
|
|
$response
|
|
|
|
->addHeader('X-RateLimit-Limit', $timeLimit->limit())
|
|
|
|
->addHeader('X-RateLimit-Remaining', $timeLimit->remaining())
|
|
|
|
->addHeader('X-RateLimit-Reset', $timeLimit->time() + $route->getLabel('abuse-time', 3600))
|
|
|
|
;
|
|
|
|
}
|
2021-06-07 17:17:29 +12:00
|
|
|
|
2022-01-21 00:34:50 +13:00
|
|
|
if ((App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled' // Route is rate-limited
|
|
|
|
&& $abuse->check()) // Abuse is not disabled
|
2021-06-07 17:17:29 +12:00
|
|
|
&& (!$isAppUser && !$isPrivilegedUser)) // User is not an admin or API key
|
|
|
|
{
|
2021-11-09 23:21:24 +13:00
|
|
|
throw new Exception('Too many requests', 429);
|
|
|
|
}
|
2021-06-07 17:17:29 +12:00
|
|
|
}
|
2021-01-06 01:22:20 +13:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Background Jobs
|
|
|
|
*/
|
|
|
|
$events
|
|
|
|
->setParam('projectId', $project->getId())
|
2021-05-17 23:32:37 +12:00
|
|
|
->setParam('webhooks', $project->getAttribute('webhooks', []))
|
2021-01-06 01:22:20 +13:00
|
|
|
->setParam('userId', $user->getId())
|
|
|
|
->setParam('event', $route->getLabel('event', ''))
|
2021-03-30 07:00:10 +13:00
|
|
|
->setParam('eventData', [])
|
2021-01-06 01:22:20 +13:00
|
|
|
->setParam('functionId', null)
|
|
|
|
->setParam('executionId', null)
|
|
|
|
->setParam('trigger', 'event')
|
|
|
|
;
|
|
|
|
|
|
|
|
$audits
|
|
|
|
->setParam('projectId', $project->getId())
|
|
|
|
->setParam('userId', $user->getId())
|
2021-08-14 22:13:24 +12:00
|
|
|
->setParam('userEmail', $user->getAttribute('email'))
|
|
|
|
->setParam('userName', $user->getAttribute('name'))
|
|
|
|
->setParam('mode', $mode)
|
2021-01-06 01:22:20 +13:00
|
|
|
->setParam('event', '')
|
|
|
|
->setParam('resource', '')
|
|
|
|
->setParam('userAgent', $request->getUserAgent(''))
|
|
|
|
->setParam('ip', $request->getIP())
|
|
|
|
->setParam('data', [])
|
|
|
|
;
|
|
|
|
|
|
|
|
$usage
|
|
|
|
->setParam('projectId', $project->getId())
|
|
|
|
->setParam('httpRequest', 1)
|
|
|
|
->setParam('httpUrl', $request->getHostname().$request->getURI())
|
|
|
|
->setParam('httpMethod', $request->getMethod())
|
2021-08-24 21:51:45 +12:00
|
|
|
->setParam('httpPath', $route->getPath())
|
2021-01-06 01:22:20 +13:00
|
|
|
->setParam('networkRequestSize', 0)
|
|
|
|
->setParam('networkResponseSize', 0)
|
|
|
|
->setParam('storage', 0)
|
|
|
|
;
|
|
|
|
|
|
|
|
$deletes
|
|
|
|
->setParam('projectId', $project->getId())
|
|
|
|
;
|
|
|
|
|
2021-06-19 04:13:37 +12:00
|
|
|
$database
|
|
|
|
->setParam('projectId', $project->getId())
|
|
|
|
;
|
2021-12-28 01:45:23 +13:00
|
|
|
}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api');
|
2021-01-06 01:22:20 +13:00
|
|
|
|
2021-08-06 22:48:50 +12:00
|
|
|
App::init(function ($utopia, $request, $project) {
|
2021-03-01 07:36:13 +13:00
|
|
|
/** @var Utopia\App $utopia */
|
2022-01-01 04:50:07 +13:00
|
|
|
/** @var Appwrite\Utopia\Request $request */
|
2021-07-26 02:47:18 +12:00
|
|
|
/** @var Utopia\Database\Document $project */
|
2021-03-01 07:36:13 +13:00
|
|
|
|
|
|
|
$route = $utopia->match($request);
|
|
|
|
|
2021-12-11 06:52:33 +13:00
|
|
|
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
|
|
|
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
2021-03-01 07:36:13 +13:00
|
|
|
|
2021-03-02 10:04:53 +13:00
|
|
|
if($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
2021-03-01 07:36:13 +13:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-06 19:42:31 +12:00
|
|
|
$auths = $project->getAttribute('auths', []);
|
2021-03-01 07:36:13 +13:00
|
|
|
switch ($route->getLabel('auth.type', '')) {
|
|
|
|
case 'emailPassword':
|
2021-08-06 20:01:53 +12:00
|
|
|
if(($auths['emailPassword'] ?? true) === false) {
|
2021-03-01 07:36:13 +13:00
|
|
|
throw new Exception('Email / Password authentication is disabled for this project', 501);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-08-31 22:29:54 +12:00
|
|
|
case 'magic-url':
|
|
|
|
if($project->getAttribute('usersAuthMagicURL', true) === false) {
|
|
|
|
throw new Exception('Magic URL authentication is disabled for this project', 501);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-03-01 07:36:13 +13:00
|
|
|
case 'anonymous':
|
2021-08-06 20:01:53 +12:00
|
|
|
if(($auths['anonymous'] ?? true) === false) {
|
2021-03-01 07:36:13 +13:00
|
|
|
throw new Exception('Anonymous authentication is disabled for this project', 501);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'invites':
|
2021-08-06 20:01:53 +12:00
|
|
|
if(($auths['invites'] ?? true) === false) {
|
2021-03-01 07:36:13 +13:00
|
|
|
throw new Exception('Invites authentication is disabled for this project', 501);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'jwt':
|
2021-08-06 20:01:53 +12:00
|
|
|
if(($auths['JWT'] ?? true) === false) {
|
2021-03-01 07:36:13 +13:00
|
|
|
throw new Exception('JWT authentication is disabled for this project', 501);
|
|
|
|
}
|
|
|
|
break;
|
2021-12-07 01:03:12 +13:00
|
|
|
|
2021-03-01 07:36:13 +13:00
|
|
|
default:
|
|
|
|
throw new Exception('Unsupported authentication route');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-08-06 22:48:50 +12:00
|
|
|
}, ['utopia', 'request', 'project'], 'auth');
|
2021-03-01 07:36:13 +13:00
|
|
|
|
2021-09-30 23:32:10 +13:00
|
|
|
App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $database, $mode) {
|
2021-01-06 01:22:20 +13:00
|
|
|
/** @var Utopia\App $utopia */
|
2022-01-01 04:50:07 +13:00
|
|
|
/** @var Appwrite\Utopia\Request $request */
|
2021-01-06 01:22:20 +13:00
|
|
|
/** @var Appwrite\Utopia\Response $response */
|
2021-07-26 02:47:18 +12:00
|
|
|
/** @var Utopia\Database\Document $project */
|
2021-01-06 01:22:20 +13:00
|
|
|
/** @var Appwrite\Event\Event $events */
|
|
|
|
/** @var Appwrite\Event\Event $audits */
|
2021-08-08 18:31:20 +12:00
|
|
|
/** @var Appwrite\Stats\Stats $usage */
|
2021-01-06 01:22:20 +13:00
|
|
|
/** @var Appwrite\Event\Event $deletes */
|
2021-06-19 04:13:37 +12:00
|
|
|
/** @var Appwrite\Event\Event $database */
|
2021-01-06 01:22:20 +13:00
|
|
|
/** @var bool $mode */
|
|
|
|
|
|
|
|
if (!empty($events->getParam('event'))) {
|
2021-12-07 01:03:12 +13:00
|
|
|
if (empty($events->getParam('eventData'))) {
|
2021-03-30 07:00:10 +13:00
|
|
|
$events->setParam('eventData', $response->getPayload());
|
2021-01-06 01:22:20 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
$webhooks = clone $events;
|
|
|
|
$functions = clone $events;
|
|
|
|
|
|
|
|
$webhooks
|
|
|
|
->setQueue('v1-webhooks')
|
|
|
|
->setClass('WebhooksV1')
|
|
|
|
->trigger();
|
|
|
|
|
|
|
|
$functions
|
|
|
|
->setQueue('v1-functions')
|
|
|
|
->setClass('FunctionsV1')
|
|
|
|
->trigger();
|
2021-03-12 05:28:03 +13:00
|
|
|
|
|
|
|
if ($project->getId() !== 'console') {
|
2021-06-30 23:36:58 +12:00
|
|
|
$payload = new Document($response->getPayload());
|
2021-12-17 07:50:06 +13:00
|
|
|
$collection = new Document($events->getParam('collection') ?? []);
|
2021-12-17 07:12:06 +13:00
|
|
|
|
|
|
|
$target = Realtime::fromPayload(
|
|
|
|
event: $events->getParam('event'),
|
|
|
|
payload: $payload,
|
|
|
|
project: $project,
|
|
|
|
collection: $collection
|
|
|
|
);
|
2021-06-30 23:36:58 +12:00
|
|
|
|
|
|
|
Realtime::send(
|
2021-12-07 01:03:12 +13:00
|
|
|
$target['projectId'] ?? $project->getId(),
|
2021-10-08 04:35:17 +13:00
|
|
|
$response->getPayload(),
|
|
|
|
$events->getParam('event'),
|
|
|
|
$target['channels'],
|
|
|
|
$target['roles'],
|
2021-06-30 23:36:58 +12:00
|
|
|
[
|
|
|
|
'permissionsChanged' => $target['permissionsChanged'],
|
|
|
|
'userId' => $events->getParam('userId')
|
|
|
|
]
|
|
|
|
);
|
2021-03-12 05:28:03 +13:00
|
|
|
}
|
2021-01-06 01:22:20 +13:00
|
|
|
}
|
2021-10-08 04:35:17 +13:00
|
|
|
|
2021-01-06 01:22:20 +13:00
|
|
|
if (!empty($audits->getParam('event'))) {
|
|
|
|
$audits->trigger();
|
|
|
|
}
|
2021-10-08 04:35:17 +13:00
|
|
|
|
2021-01-06 01:22:20 +13:00
|
|
|
if (!empty($deletes->getParam('type')) && !empty($deletes->getParam('document'))) {
|
|
|
|
$deletes->trigger();
|
|
|
|
}
|
2021-06-19 04:13:37 +12:00
|
|
|
|
|
|
|
if (!empty($database->getParam('type')) && !empty($database->getParam('document'))) {
|
|
|
|
$database->trigger();
|
|
|
|
}
|
2021-10-08 04:35:17 +13:00
|
|
|
|
2021-01-06 01:22:20 +13:00
|
|
|
$route = $utopia->match($request);
|
2021-01-14 19:05:55 +13:00
|
|
|
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled'
|
|
|
|
&& $project->getId()
|
2021-07-28 02:16:12 +12:00
|
|
|
&& $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin
|
2021-01-06 01:22:20 +13:00
|
|
|
&& !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage on admin mode
|
2021-10-08 04:35:17 +13:00
|
|
|
|
2021-08-08 17:36:08 +12:00
|
|
|
$usage
|
|
|
|
->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
|
|
|
|
->setParam('networkResponseSize', $response->getSize())
|
2021-08-08 18:31:20 +12:00
|
|
|
->submit()
|
2021-08-08 17:36:08 +12:00
|
|
|
;
|
2021-01-06 01:22:20 +13:00
|
|
|
}
|
|
|
|
|
2021-09-30 23:32:10 +13:00
|
|
|
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode'], 'api');
|