2019-05-09 18:54:39 +12:00
< ? php
2022-05-24 02:54:50 +12:00
require_once __DIR__ . '/../init.php' ;
2019-10-25 06:53:37 +13:00
2019-05-09 18:54:39 +12:00
use Utopia\App ;
2022-12-15 05:04:06 +13:00
use Utopia\Database\Helpers\Role ;
2022-05-27 02:46:08 +12:00
use Utopia\Locale\Locale ;
use Utopia\Logger\Logger ;
2021-11-24 03:24:25 +13:00
use Utopia\Logger\Log ;
use Utopia\Logger\Log\User ;
2023-02-10 03:27:11 +13:00
use Swoole\Http\Request as SwooleRequest ;
use Utopia\Cache\Cache ;
use Utopia\Pools\Group ;
2021-12-31 05:17:01 +13:00
use Appwrite\Utopia\Request ;
2020-06-29 19:22:53 +12:00
use Appwrite\Utopia\Response ;
2021-12-14 22:11:34 +13:00
use Appwrite\Utopia\View ;
2022-05-27 22:36:06 +12:00
use Appwrite\Extend\Exception as AppwriteException ;
2020-03-29 01:42:16 +13:00
use Utopia\Config\Config ;
2020-03-18 00:36:13 +13:00
use Utopia\Domains\Domain ;
2020-03-25 06:56:32 +13:00
use Appwrite\Auth\Auth ;
2022-04-14 00:39:31 +12:00
use Appwrite\Event\Certificate ;
2020-06-12 07:36:10 +12:00
use Appwrite\Network\Validator\Origin ;
2022-03-01 09:55:18 +13:00
use Appwrite\Utopia\Response\Filters\V11 as ResponseV11 ;
2022-03-01 09:54:20 +13:00
use Appwrite\Utopia\Response\Filters\V12 as ResponseV12 ;
2022-05-17 19:56:28 +12:00
use Appwrite\Utopia\Response\Filters\V13 as ResponseV13 ;
2022-06-28 21:15:27 +12:00
use Appwrite\Utopia\Response\Filters\V14 as ResponseV14 ;
2022-08-16 03:09:09 +12:00
use Appwrite\Utopia\Response\Filters\V15 as ResponseV15 ;
2020-10-30 11:04:53 +13:00
use Utopia\CLI\Console ;
2022-05-27 02:46:08 +12:00
use Utopia\Database\Database ;
2022-07-13 01:32:39 +12:00
use Utopia\Database\DateTime ;
2021-05-16 21:18:34 +12:00
use Utopia\Database\Document ;
2021-07-16 09:14:52 +12:00
use Utopia\Database\Query ;
2021-07-26 02:51:04 +12:00
use Utopia\Database\Validator\Authorization ;
2022-04-20 21:31:17 +12:00
use Utopia\Validator\Hostname ;
2022-03-01 09:55:18 +13:00
use Appwrite\Utopia\Request\Filters\V12 as RequestV12 ;
use Appwrite\Utopia\Request\Filters\V13 as RequestV13 ;
2022-05-17 19:56:28 +12:00
use Appwrite\Utopia\Request\Filters\V14 as RequestV14 ;
2022-08-18 01:08:09 +12:00
use Appwrite\Utopia\Request\Filters\V15 as RequestV15 ;
2022-02-11 21:44:04 +13:00
use Utopia\Validator\Text ;
2022-08-06 00:08:04 +12:00
use Utopia\Validator\WhiteList ;
2019-05-09 18:54:39 +12:00
2020-06-29 08:45:36 +12:00
Config :: setParam ( 'domainVerification' , false );
2020-07-01 18:35:57 +12:00
Config :: setParam ( 'cookieDomain' , 'localhost' );
Config :: setParam ( 'cookieSamesite' , Response :: COOKIE_SAMESITE_NONE );
2020-06-30 09:43:34 +12:00
2023-07-28 19:56:07 +12:00
function router ( App $utopia , Database $dbForConsole , SwooleRequest $swooleRequest , Request $request , Response $response )
2023-03-15 08:31:23 +13:00
{
2023-07-11 20:53:40 +12:00
$utopia -> getRoute () -> label ( 'error' , __DIR__ . '/../views/general/error.phtml' );
2023-07-28 19:56:07 +12:00
$host = $request -> getHostname () ? ? '' ;
2023-03-14 02:35:34 +13:00
2023-02-28 01:43:20 +13:00
$route = Authorization :: skip (
2023-08-07 01:11:30 +12:00
fn () => $dbForConsole -> find ( 'rules' , [
2023-02-28 01:43:20 +13:00
Query :: equal ( 'domain' , [ $host ]),
Query :: limit ( 1 )
])
)[ 0 ] ? ? null ;
2023-03-15 08:31:23 +13:00
if ( $route === null ) {
2023-08-04 17:59:23 +12:00
$mainDomain = App :: getEnv ( '_APP_DOMAIN' , '' );
2023-08-04 20:56:46 +12:00
if ( $mainDomain === 'localhost' ) {
2023-08-21 00:14:05 +12:00
throw new AppwriteException ( AppwriteException :: ROUTER_DOMAIN_NOT_CONFIGURED );
2023-08-04 17:59:23 +12:00
} else {
throw new AppwriteException ( AppwriteException :: ROUTER_HOST_NOT_FOUND );
}
2023-02-28 01:43:20 +13:00
}
2023-03-11 01:20:24 +13:00
$projectId = $route -> getAttribute ( 'projectId' );
$project = Authorization :: skip (
2023-08-07 01:11:30 +12:00
fn () => $dbForConsole -> getDocument ( 'projects' , $projectId )
2023-03-11 01:20:24 +13:00
);
2023-03-15 08:31:23 +13:00
if ( array_key_exists ( 'proxy' , $project -> getAttribute ( 'services' , []))) {
2023-03-11 01:20:24 +13:00
$status = $project -> getAttribute ( 'services' , [])[ 'proxy' ];
2023-03-15 08:31:23 +13:00
if ( ! $status ) {
2023-03-11 01:20:24 +13:00
throw new AppwriteException ( AppwriteException :: GENERAL_SERVICE_DISABLED );
}
}
2023-03-14 02:35:34 +13:00
// Skip Appwrite Router for ACME challenge. Nessessary for certificate generation
2023-08-07 01:11:30 +12:00
$path = ( $swooleRequest -> server [ 'request_uri' ] ? ? '/' );
2023-03-15 08:31:23 +13:00
if ( \str_starts_with ( $path , '/.well-known/acme-challenge' )) {
2023-03-14 02:35:34 +13:00
return false ;
}
2023-02-28 01:43:20 +13:00
$type = $route -> getAttribute ( 'resourceType' );
2023-03-15 08:31:23 +13:00
if ( $type === 'function' ) {
2023-02-28 01:43:20 +13:00
$functionId = $route -> getAttribute ( 'resourceId' );
$projectId = $route -> getAttribute ( 'projectId' );
2023-08-07 01:11:30 +12:00
$path = ( $swooleRequest -> server [ 'request_uri' ] ? ? '/' );
2023-02-28 01:43:20 +13:00
$query = ( $swooleRequest -> server [ 'query_string' ] ? ? '' );
2023-03-15 08:31:23 +13:00
if ( ! empty ( $query )) {
2023-02-28 01:43:20 +13:00
$path .= '?' . $query ;
}
$body = \json_encode ([
'async' => false ,
'body' => $swooleRequest -> getContent () ? ? '' ,
'method' => $swooleRequest -> server [ 'request_method' ],
'path' => $path ,
'headers' => $swooleRequest -> header
]);
$headers = [
'Content-Type: application/json' ,
'Content-Length: ' . \strlen ( $body ),
'X-Appwrite-Project: ' . $projectId
];
$ch = \curl_init ();
\curl_setopt ( $ch , CURLOPT_URL , " http://localhost/v1/functions/ { $functionId } /executions " );
\curl_setopt ( $ch , CURLOPT_CUSTOMREQUEST , " POST " );
\curl_setopt ( $ch , CURLOPT_POSTFIELDS , $body );
\curl_setopt ( $ch , CURLOPT_HTTPHEADER , $headers );
\curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
// \curl_setopt($ch, CURLOPT_HEADER, true);
\curl_setopt ( $ch , CURLOPT_CONNECTTIMEOUT , 10 );
$executionResponse = \curl_exec ( $ch );
$statusCode = \curl_getinfo ( $ch , CURLINFO_HTTP_CODE );
$error = \curl_error ( $ch );
$errNo = \curl_errno ( $ch );
\curl_close ( $ch );
if ( $errNo !== 0 ) {
2023-07-11 20:53:40 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_ARGUMENT_INVALID , " Internal error: " . $error );
2023-02-28 01:43:20 +13:00
}
if ( $statusCode >= 400 ) {
$error = \json_decode ( $executionResponse , true )[ 'message' ];
2023-07-11 20:53:40 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_ARGUMENT_INVALID , " Execution error: " . $error );
2023-02-28 01:43:20 +13:00
}
$execution = \json_decode ( $executionResponse , true );
2023-08-07 01:42:18 +12:00
$contentType = 'text/plain' ;
2023-07-31 01:12:55 +12:00
foreach ( $execution [ 'responseHeaders' ] as $header ) {
2023-08-07 01:42:18 +12:00
if ( \strtolower ( $header [ 'name' ]) === 'content-type' ) {
$contentType = $header [ 'value' ];
}
2023-08-07 01:11:30 +12:00
$response -> setHeader ( $header [ 'name' ], $header [ 'value' ]);
2023-02-28 01:43:20 +13:00
}
2023-07-31 01:12:55 +12:00
$body = $execution [ 'responseBody' ] ? ? '' ;
2023-02-28 01:43:20 +13:00
2023-08-07 01:11:30 +12:00
$encodingKey = \array_search ( 'x-open-runtimes-encoding' , \array_column ( $execution [ 'responseHeaders' ], 'name' ));
2023-08-11 21:19:05 +12:00
if ( $encodingKey !== false ) {
if (( $execution [ 'responseHeaders' ][ $encodingKey ][ 'value' ] ? ? '' ) === 'base64' ) {
2023-08-07 01:11:30 +12:00
$body = \base64_decode ( $body );
}
2023-02-28 01:43:20 +13:00
}
2023-08-07 01:42:18 +12:00
$response
-> setContentType ( $contentType )
-> setStatusCode ( $execution [ 'responseStatusCode' ] ? ? 200 )
-> send ( $body );
2023-02-28 01:43:20 +13:00
return true ;
2023-03-15 08:31:23 +13:00
} elseif ( $type === 'api' ) {
2023-03-11 01:20:24 +13:00
return false ;
2023-02-28 01:43:20 +13:00
} else {
2023-07-28 20:27:16 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_SERVER_ERROR , 'Unknown resource type ' . $type );
2023-02-28 01:43:20 +13:00
}
return false ;
}
2022-07-22 18:00:42 +12:00
App :: init ()
2023-08-07 01:42:18 +12:00
-> groups ([ 'api' , 'web' ])
2022-07-22 18:00:42 +12:00
-> inject ( 'utopia' )
2023-02-10 03:27:11 +13:00
-> inject ( 'swooleRequest' )
2022-07-22 18:00:42 +12:00
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'console' )
-> inject ( 'project' )
-> inject ( 'dbForConsole' )
-> inject ( 'user' )
-> inject ( 'locale' )
2023-04-17 14:10:17 +12:00
-> inject ( 'localeCodes' )
2022-07-22 18:00:42 +12:00
-> inject ( 'clients' )
2022-08-11 01:45:04 +12:00
-> inject ( 'servers' )
2023-08-06 20:51:53 +12:00
-> action ( function ( App $utopia , SwooleRequest $swooleRequest , Request $request , Response $response , Document $console , Document $project , Database $dbForConsole , Document $user , Locale $locale , array $localeCodes , array $clients , array $servers ) {
2023-02-10 03:27:11 +13:00
/*
* Appwrite Router
*/
2023-07-31 01:30:30 +12:00
2023-07-28 19:56:07 +12:00
$host = $request -> getHostname () ? ? '' ;
2023-02-23 04:07:34 +13:00
$mainDomain = App :: getEnv ( '_APP_DOMAIN' , '' );
// Only run Router when external domain
2023-08-17 06:08:24 +12:00
if ( $host !== $mainDomain && $host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL ) {
2023-07-28 19:56:07 +12:00
if ( router ( $utopia , $dbForConsole , $swooleRequest , $request , $response )) {
2023-02-28 01:43:20 +13:00
return ;
2023-02-21 23:30:16 +13:00
}
}
2023-02-21 23:31:55 +13:00
2022-07-22 18:00:42 +12:00
/*
* Request format
*/
2023-02-20 00:04:12 +13:00
$route = $utopia -> getRoute ();
2022-07-22 18:00:42 +12:00
Request :: setRoute ( $route );
2023-02-21 23:31:55 +13:00
if ( $route === null ) {
2023-08-18 18:55:44 +12:00
return $response -> setStatusCode ( 404 ) -> send ( 'Not Found' );
2023-02-15 02:58:13 +13:00
}
2022-07-22 18:00:42 +12:00
$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 ;
2022-09-14 08:42:45 +12:00
case version_compare ( $requestFormat , '0.15.3' , '<' ) :
2022-08-18 01:08:09 +12:00
Request :: setFilter ( new RequestV15 ());
break ;
2022-07-22 18:00:42 +12:00
default :
Request :: setFilter ( null );
}
2021-05-11 22:47:02 +12:00
} else {
2022-07-22 18:00:42 +12:00
Request :: setFilter ( null );
}
2021-05-11 22:47:02 +12:00
2022-07-22 18:00:42 +12:00
$localeParam = ( string ) $request -> getParam ( 'locale' , $request -> getHeader ( 'x-appwrite-locale' , '' ));
2023-04-17 14:10:17 +12:00
if ( \in_array ( $localeParam , $localeCodes )) {
2022-07-22 18:00:42 +12:00
$locale -> setDefault ( $localeParam );
2021-05-11 22:47:02 +12:00
}
2022-07-22 18:00:42 +12:00
if ( $project -> isEmpty ()) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: PROJECT_NOT_FOUND );
2021-01-03 04:35:21 +13:00
}
2022-03-22 03:23:56 +13:00
2022-07-22 18:00:42 +12:00
if ( ! empty ( $route -> getLabel ( 'sdk.auth' , [])) && $project -> isEmpty () && ( $route -> getLabel ( 'scope' , '' ) !== 'public' )) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: PROJECT_UNKNOWN );
2020-06-02 07:58:58 +12:00
}
2022-07-22 18:00:42 +12:00
$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 ()) &&
2023-08-07 01:11:30 +12:00
$endDomain -> getRegisterable () !== ''
2022-07-22 18:00:42 +12:00
);
2023-07-21 22:08:34 +12:00
$isLocalHost = $request -> getHostname () === 'localhost' || $request -> getHostname () === 'localhost:' . $request -> getPort ();
$isIpAddress = filter_var ( $request -> getHostname (), FILTER_VALIDATE_IP ) !== false ;
$isConsoleProject = $project -> getAttribute ( '$id' , '' ) === 'console' ;
$isConsoleRootSession = App :: getEnv ( '_APP_CONSOLE_ROOT_SESSION' , 'disabled' ) === 'enabled' ;
Config :: setParam (
'cookieDomain' ,
$isLocalHost || $isIpAddress
? null
: ( $isConsoleProject && $isConsoleRootSession
? '.' . $selfDomain -> getRegisterable ()
: '.' . $request -> getHostname ()
)
);
2022-07-22 18:00:42 +12:00
/*
* 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 ());
2019-10-25 06:53:37 +13:00
break ;
2022-07-22 18:00:42 +12:00
case version_compare ( $responseFormat , '0.13.4' , '<=' ) :
Response :: setFilter ( new ResponseV13 ());
2019-10-25 06:53:37 +13:00
break ;
2022-07-22 18:00:42 +12:00
case version_compare ( $responseFormat , '0.14.0' , '<=' ) :
Response :: setFilter ( new ResponseV14 ());
2019-10-25 06:53:37 +13:00
break ;
2022-09-14 08:42:45 +12:00
case version_compare ( $responseFormat , '0.15.3' , '<=' ) :
2022-08-16 03:09:09 +12:00
Response :: setFilter ( new ResponseV15 ());
break ;
2022-07-22 18:00:42 +12:00
default :
Response :: setFilter ( null );
2019-10-25 06:53:37 +13:00
}
2022-07-22 18:00:42 +12:00
} else {
Response :: setFilter ( null );
2019-10-25 06:53:37 +13:00
}
2021-08-18 22:20:49 +12:00
2020-12-29 06:03:47 +13:00
/*
2022-07-22 18:00:42 +12:00
* 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
2023-08-22 01:16:02 +12:00
if ( $request -> getProtocol () !== 'https' && ( $swooleRequest -> header [ 'host' ] ? ? '' ) !== 'localhost' && ( $swooleRequest -> header [ 'host' ] ? ? '' ) !== APP_HOSTNAME_INTERNAL ) { // Localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
2022-07-22 18:00:42 +12:00
if ( $request -> getMethod () !== Request :: METHOD_GET ) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_PROTOCOL_UNSUPPORTED , 'Method unsupported over HTTP.' );
2022-07-22 18:00:42 +12:00
}
return $response -> redirect ( 'https://' . $request -> getHostname () . $request -> getURI ());
2022-06-03 21:12:19 +12:00
}
2022-11-21 16:49:45 +13:00
}
2022-06-01 03:41:12 +12:00
2022-11-21 16:49:45 +13:00
if ( $request -> getProtocol () === 'https' ) {
2022-07-22 18:00:42 +12:00
$response -> addHeader ( 'Strict-Transport-Security' , 'max-age=' . ( 60 * 60 * 24 * 126 )); // 126 days
2020-12-29 06:03:47 +13:00
}
2019-05-09 18:54:39 +12:00
2022-07-22 18:00:42 +12:00
$response
-> addHeader ( 'Server' , 'Appwrite' )
-> addHeader ( 'X-Content-Type-Options' , 'nosniff' )
-> addHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, PATCH, DELETE' )
2023-01-20 13:36:17 +13:00
-> 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-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma' )
2022-07-22 18:00:42 +12:00
-> addHeader ( 'Access-Control-Expose-Headers' , 'X-Fallback-Cookies' )
-> addHeader ( 'Access-Control-Allow-Origin' , $refDomain )
2023-08-07 01:11:30 +12:00
-> addHeader ( 'Access-Control-Allow-Credentials' , 'true' );
2019-05-09 18:54:39 +12:00
2022-07-22 18:00:42 +12:00
/*
* 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' , [])));
2019-05-09 18:54:39 +12:00
2022-05-31 23:35:59 +12:00
if (
2022-07-22 18:00:42 +12:00
! $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' , '' ))
2022-05-31 23:35:59 +12:00
) {
2022-08-14 18:56:12 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_UNKNOWN_ORIGIN , $originValidator -> getDescription ());
2021-07-29 22:28:17 +12:00
}
2019-05-09 18:54:39 +12:00
2022-07-22 18:00:42 +12:00
/*
* ACL Check
*/
2022-08-19 16:05:00 +12:00
$role = ( $user -> isEmpty ())
? Role :: guests () -> toString ()
2022-08-19 16:04:33 +12:00
: Role :: users () -> toString ();
2022-07-22 18:00:42 +12:00
// 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 ;
}
}
2019-12-17 17:16:50 +13:00
}
2021-08-18 22:20:49 +12:00
2022-07-22 18:00:42 +12:00
$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' ),
]);
2022-08-15 19:20:10 +12:00
$role = Auth :: USER_ROLE_APPS ;
2022-07-22 18:00:42 +12:00
$scopes = \array_merge ( $roles [ $role ][ 'scopes' ], $key -> getAttribute ( 'scopes' , []));
2022-08-13 15:21:50 +12:00
$expire = $key -> getAttribute ( 'expire' );
2022-08-16 05:37:26 +12:00
if ( ! empty ( $expire ) && $expire < DateTime :: formatTz ( DateTime :: now ())) {
2023-08-07 01:11:30 +12:00
throw new AppwriteException ( AppwriteException :: PROJECT_KEY_EXPIRED );
2022-07-22 18:00:42 +12:00
}
2022-08-15 19:20:10 +12:00
Authorization :: setRole ( Auth :: USER_ROLE_APPS );
2022-07-22 18:00:42 +12:00
Authorization :: setDefaultStatus ( false ); // Cancel security segmentation for API keys.
2022-08-06 00:08:04 +12:00
2022-08-20 19:41:07 +12:00
$accessedAt = $key -> getAttribute ( 'accessedAt' , '' );
2022-08-20 02:52:38 +12:00
if ( DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime (), - APP_KEY_ACCCESS )) > $accessedAt ) {
$key -> setAttribute ( 'accessedAt' , DateTime :: now ());
2022-08-06 00:08:04 +12:00
$dbForConsole -> updateDocument ( 'keys' , $key -> getId (), $key );
$dbForConsole -> deleteCachedDocument ( 'projects' , $project -> getId ());
}
2022-08-09 18:13:34 +12:00
2022-08-11 01:45:04 +12:00
$sdkValidator = new WhiteList ( $servers , true );
2022-08-11 01:00:57 +12:00
$sdk = $request -> getHeader ( 'x-sdk-name' , 'UNKNOWN' );
2022-08-09 18:13:34 +12:00
if ( $sdkValidator -> isValid ( $sdk )) {
$sdks = $key -> getAttribute ( 'sdks' , []);
if ( ! in_array ( $sdk , $sdks )) {
2022-08-11 01:49:56 +12:00
array_push ( $sdks , $sdk );
2022-08-09 18:13:34 +12:00
$key -> setAttribute ( 'sdks' , $sdks );
2022-08-11 01:49:56 +12:00
2022-08-09 20:52:30 +12:00
/** Update access time as well */
2022-08-20 02:52:38 +12:00
$key -> setAttribute ( 'accessedAt' , Datetime :: now ());
2022-08-09 18:13:34 +12:00
$dbForConsole -> updateDocument ( 'keys' , $key -> getId (), $key );
$dbForConsole -> deleteCachedDocument ( 'projects' , $project -> getId ());
}
}
2021-12-27 23:35:51 +13:00
}
2022-07-22 18:00:42 +12:00
}
2021-12-27 23:35:51 +13:00
2022-08-14 02:31:06 +12:00
Authorization :: setRole ( $role );
2021-11-24 03:24:25 +13:00
2022-07-22 18:00:42 +12:00
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 ()))
) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_SERVICE_DISABLED );
2021-12-07 02:14:55 +13:00
}
2022-07-22 18:00:42 +12:00
}
2021-11-24 03:24:25 +13:00
2022-07-22 18:00:42 +12:00
if ( ! \in_array ( $scope , $scopes )) {
if ( $project -> isEmpty ()) { // Check if permission is denied because project is missing
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: PROJECT_NOT_FOUND );
2021-12-07 02:14:55 +13:00
}
2021-11-24 03:24:25 +13:00
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_UNAUTHORIZED_SCOPE , $user -> getAttribute ( 'email' , 'User' ) . ' (role: ' . \strtolower ( $roles [ $role ][ 'label' ]) . ') missing scope (' . $scope . ')' );
2021-12-07 02:14:55 +13:00
}
2019-05-09 18:54:39 +12:00
2022-07-22 18:00:42 +12:00
if ( false === $user -> getAttribute ( 'status' )) { // Account is blocked
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: USER_BLOCKED );
2022-07-22 18:00:42 +12:00
}
2022-03-02 05:16:42 +13:00
2022-07-22 18:00:42 +12:00
if ( $user -> getAttribute ( 'reset' )) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: USER_PASSWORD_RESET_REQUIRED );
2022-07-22 18:00:42 +12:00
}
});
2022-03-02 03:19:47 +13:00
2022-07-22 18:00:42 +12:00
App :: options ()
2023-07-11 20:53:40 +12:00
-> inject ( 'utopia' )
2023-02-28 01:43:20 +13:00
-> inject ( 'swooleRequest' )
2022-07-22 18:00:42 +12:00
-> inject ( 'request' )
-> inject ( 'response' )
2023-02-28 01:43:20 +13:00
-> inject ( 'dbForConsole' )
2023-07-11 20:53:40 +12:00
-> action ( function ( App $utopia , SwooleRequest $swooleRequest , Request $request , Response $response , Database $dbForConsole ) {
2023-02-28 01:43:20 +13:00
/*
* Appwrite Router
*/
2023-07-28 19:56:07 +12:00
$host = $request -> getHostname () ? ? '' ;
2023-02-28 01:43:20 +13:00
$mainDomain = App :: getEnv ( '_APP_DOMAIN' , '' );
// Only run Router when external domain
2023-08-17 06:08:24 +12:00
if ( $host !== $mainDomain && $host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL ) {
2023-07-28 19:56:07 +12:00
if ( router ( $utopia , $dbForConsole , $swooleRequest , $request , $response )) {
2023-02-28 01:43:20 +13:00
return ;
}
}
2022-07-22 18:00:42 +12:00
$origin = $request -> getOrigin ();
$response
-> addHeader ( 'Server' , 'Appwrite' )
-> addHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, PATCH, DELETE' )
2023-01-20 13:36:17 +13:00
-> 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-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies' )
2022-07-22 18:00:42 +12:00
-> 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 ( 'project' )
-> inject ( 'logger' )
-> inject ( 'loggerBreadcrumbs' )
2022-11-18 01:37:59 +13:00
-> action ( function ( Throwable $error , App $utopia , Request $request , Response $response , Document $project , ? Logger $logger , array $loggerBreadcrumbs ) {
2022-07-22 18:00:42 +12:00
$version = App :: getEnv ( '_APP_VERSION' , 'UNKNOWN' );
2023-02-20 00:04:12 +13:00
$route = $utopia -> getRoute ();
2022-07-22 18:00:42 +12:00
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 ());
2023-06-15 23:21:18 +12:00
$log -> addTag ( 'database' , $project -> getAttribute ( 'database' , 'console' ));
2022-07-22 18:00:42 +12:00
$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 ());
2023-05-30 18:17:28 +12:00
$log -> addExtra ( 'roles' , Authorization :: getRoles ());
2022-07-22 18:00:42 +12:00
$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 ) {
2022-08-09 02:44:07 +12:00
$error = new AppwriteException ( AppwriteException :: GENERAL_UNKNOWN , $message , $code , $error );
2022-07-22 18:00:42 +12:00
switch ( $code ) {
case 400 :
$error -> setType ( AppwriteException :: GENERAL_ARGUMENT_INVALID );
break ;
case 404 :
$error -> setType ( AppwriteException :: GENERAL_ROUTE_NOT_FOUND );
break ;
}
2023-01-20 13:36:17 +13:00
} elseif ( $error instanceof Utopia\Database\Exception\Conflict ) {
$error = new AppwriteException ( AppwriteException :: DOCUMENT_UPDATE_CONFLICT , null , null , $error );
$code = $error -> getCode ();
$message = $error -> getMessage ();
2022-07-22 18:00:42 +12:00
}
/** Wrap all exceptions inside Appwrite\Extend\Exception */
if ( ! ( $error instanceof AppwriteException )) {
2022-08-09 02:44:07 +12:00
$error = new AppwriteException ( AppwriteException :: GENERAL_UNKNOWN , $message , $code , $error );
2022-07-22 18:00:42 +12:00
}
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
2022-02-13 22:12:16 +13:00
break ;
2022-07-22 18:00:42 +12:00
default :
$code = 500 ; // All other errors get the generic 500 server error status code
$message = 'Server Error' ;
2022-02-13 22:12:16 +13:00
}
2019-10-25 06:53:37 +13:00
2022-07-22 18:00:42 +12:00
//$_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' )
2023-08-07 01:11:30 +12:00
-> setStatusCode ( $code );
2019-10-25 06:53:37 +13:00
2022-07-22 18:00:42 +12:00
$template = ( $route ) ? $route -> getLabel ( 'error' , null ) : null ;
if ( $template ) {
2022-11-18 01:37:59 +13:00
$layout = new View ( $template );
2022-07-22 18:00:42 +12:00
2022-11-18 01:37:59 +13:00
$layout
-> setParam ( 'title' , $project -> getAttribute ( 'name' ) . ' - Error' )
2022-07-22 18:00:42 +12:00
-> setParam ( 'development' , App :: isDevelopment ())
-> setParam ( 'projectName' , $project -> getAttribute ( 'name' ))
-> setParam ( 'projectURL' , $project -> getAttribute ( 'url' ))
-> setParam ( 'message' , $error -> getMessage ())
2023-08-01 07:55:02 +12:00
-> setParam ( 'type' , $type )
2022-07-22 18:00:42 +12:00
-> setParam ( 'code' , $code )
2023-08-07 01:11:30 +12:00
-> setParam ( 'trace' , $trace );
2022-07-22 18:00:42 +12:00
$response -> html ( $layout -> render ());
}
2019-10-25 06:53:37 +13:00
2022-07-22 18:00:42 +12:00
$response -> dynamic (
new Document ( $output ),
$utopia -> isDevelopment () ? Response :: MODEL_ERROR_DEV : Response :: MODEL_ERROR
);
});
2019-10-25 06:53:37 +13:00
2020-06-29 05:31:21 +12:00
App :: get ( '/robots.txt' )
2019-10-25 06:53:37 +13:00
-> desc ( 'Robots.txt File' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
2020-12-27 01:19:46 +13:00
-> inject ( 'response' )
2022-05-27 02:46:08 +12:00
-> action ( function ( Response $response ) {
2022-05-31 23:35:59 +12:00
$template = new View ( __DIR__ . '/../views/general/robots.phtml' );
2020-06-30 09:43:34 +12:00
$response -> text ( $template -> render ( false ));
2020-12-27 01:19:46 +13:00
});
2019-10-25 06:53:37 +13:00
2020-06-29 05:31:21 +12:00
App :: get ( '/humans.txt' )
2019-10-25 06:53:37 +13:00
-> desc ( 'Humans.txt File' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
2020-12-27 01:19:46 +13:00
-> inject ( 'response' )
2022-05-27 02:46:08 +12:00
-> action ( function ( Response $response ) {
2022-05-31 23:35:59 +12:00
$template = new View ( __DIR__ . '/../views/general/humans.phtml' );
2020-06-30 09:43:34 +12:00
$response -> text ( $template -> render ( false ));
2020-12-27 01:19:46 +13:00
});
2019-10-25 06:53:37 +13:00
2023-06-02 23:18:34 +12:00
App :: get ( '/.well-known/acme-challenge/*' )
2020-02-19 11:13:18 +13:00
-> desc ( 'SSL Verification' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
2020-12-27 01:19:46 +13:00
-> inject ( 'request' )
-> inject ( 'response' )
2022-05-27 02:46:08 +12:00
-> action ( function ( Request $request , Response $response ) {
2022-02-11 21:44:04 +13:00
$uriChunks = \explode ( '/' , $request -> getURI ());
$token = $uriChunks [ \count ( $uriChunks ) - 1 ];
2023-03-02 01:00:36 +13:00
$validator = new Text ( 100 , allowList : [
2022-02-11 21:44:04 +13:00
... Text :: NUMBERS ,
... Text :: ALPHABET_LOWER ,
... Text :: ALPHABET_UPPER ,
'-' ,
'_'
]);
2022-02-01 04:04:30 +13:00
2022-02-11 21:44:04 +13:00
if ( ! $validator -> isValid ( $token ) || \count ( $uriChunks ) !== 4 ) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_ARGUMENT_INVALID , 'Invalid challenge token.' );
2022-02-01 04:04:30 +13:00
}
2020-06-30 09:43:34 +12:00
$base = \realpath ( APP_STORAGE_CERTIFICATES );
2022-05-24 02:54:50 +12:00
$absolute = \realpath ( $base . '/.well-known/acme-challenge/' . $token );
2020-02-23 21:55:57 +13:00
2020-10-28 08:46:15 +13:00
if ( ! $base ) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_SERVER_ERROR , 'Storage error' );
2020-06-30 09:43:34 +12:00
}
2020-02-19 11:13:18 +13:00
2020-10-28 08:46:15 +13:00
if ( ! $absolute ) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_ROUTE_NOT_FOUND , 'Unknown path' );
2020-06-30 09:43:34 +12:00
}
2020-02-23 21:55:57 +13:00
2020-10-28 08:46:15 +13:00
if ( ! \substr ( $absolute , 0 , \strlen ( $base )) === $base ) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_UNAUTHORIZED_SCOPE , 'Invalid path' );
2020-06-30 09:43:34 +12:00
}
2020-02-24 06:45:51 +13:00
2020-10-28 08:46:15 +13:00
if ( ! \file_exists ( $absolute )) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_ROUTE_NOT_FOUND , 'Unknown path' );
2020-06-30 09:43:34 +12:00
}
2020-02-23 21:55:57 +13:00
2020-06-30 09:43:34 +12:00
$content = @ \file_get_contents ( $absolute );
2020-02-19 11:13:18 +13:00
2020-10-28 08:46:15 +13:00
if ( ! $content ) {
2022-08-09 02:44:07 +12:00
throw new AppwriteException ( AppwriteException :: GENERAL_SERVER_ERROR , 'Failed to get contents' );
2020-02-19 11:13:18 +13:00
}
2020-06-30 09:43:34 +12:00
$response -> text ( $content );
2020-12-27 01:19:46 +13:00
});
2020-02-19 11:13:18 +13:00
2020-07-29 19:29:34 +12:00
include_once __DIR__ . '/shared/api.php' ;
2022-11-24 20:53:52 +13:00
include_once __DIR__ . '/shared/api/auth.php' ;
2020-06-26 07:53:36 +12:00
2023-02-16 03:50:18 +13:00
App :: wildcard ()
2023-08-07 01:11:30 +12:00
-> groups ([ 'api' ])
2023-02-21 23:31:55 +13:00
-> action ( function () {
2023-02-16 03:50:18 +13:00
throw new AppwriteException ( AppwriteException :: GENERAL_ROUTE_NOT_FOUND );
});
2020-10-28 08:46:15 +13:00
foreach ( Config :: getParam ( 'services' , []) as $service ) {
2020-06-26 18:14:54 +12:00
include_once $service [ 'controller' ];
2021-05-12 17:17:34 +12:00
}