2019-05-09 18:54:39 +12:00
< ? php
// Init
2019-10-25 06:53:37 +13:00
require_once __DIR__ . '/init.php' ;
2020-03-29 01:42:16 +13:00
global $utopia , $request , $response , $register , $consoleDB , $project , $service ;
2019-05-09 18:54:39 +12:00
use Utopia\App ;
use Utopia\Request ;
2019-10-25 06:53:37 +13:00
use Utopia\View ;
use Utopia\Exception ;
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 ;
use Appwrite\Database\Database ;
use Appwrite\Database\Document ;
use Appwrite\Database\Validator\Authorization ;
use Appwrite\Event\Event ;
2019-05-09 18:54:39 +12:00
2019-10-01 17:57:41 +13:00
/*
2019-10-25 06:53:37 +13:00
* Configuration files
2019-05-09 18:54:39 +12:00
*/
2019-10-25 06:53:37 +13:00
$roles = include __DIR__ . '/config/roles.php' ; // User roles and scopes
$services = include __DIR__ . '/config/services.php' ; // List of services
2019-08-11 03:24:47 +12:00
2019-10-25 06:53:37 +13:00
$webhook = new Event ( 'v1-webhooks' , 'WebhooksV1' );
$audit = new Event ( 'v1-audits' , 'AuditsV1' );
$usage = new Event ( 'v1-usage' , 'UsageV1' );
2019-10-01 17:57:41 +13:00
2019-10-25 06:53:37 +13:00
/**
* Get All verified client URLs for both console and current projects
* + Filter for duplicated entries
2019-10-25 06:53:11 +13:00
*/
2019-10-25 06:53:37 +13:00
$clientsConsole = array_map ( function ( $node ) {
2020-03-05 11:54:49 +13:00
return $node [ 'hostname' ];
}, array_filter ( $console -> getAttribute ( 'platforms' , []), function ( $node ) {
if ( isset ( $node [ 'type' ]) && $node [ 'type' ] === 'web' && isset ( $node [ 'hostname' ]) && ! empty ( $node [ 'hostname' ])) {
return true ;
}
2019-05-09 18:54:39 +12:00
2020-03-05 11:54:49 +13:00
return false ;
}));
2019-10-25 06:53:37 +13:00
$clients = array_unique ( array_merge ( $clientsConsole , array_map ( function ( $node ) {
2020-03-05 11:54:49 +13:00
return $node [ 'hostname' ];
}, array_filter ( $project -> getAttribute ( 'platforms' , []), function ( $node ) {
if ( isset ( $node [ 'type' ]) && $node [ 'type' ] === 'web' && isset ( $node [ 'hostname' ]) && ! empty ( $node [ 'hostname' ])) {
return true ;
}
2019-05-09 18:54:39 +12:00
2020-03-05 11:54:49 +13:00
return false ;
}))));
2019-10-25 06:53:37 +13:00
2020-03-29 01:42:16 +13:00
$utopia -> init ( function () use ( $utopia , $request , $response , & $user , $project , $roles , $webhook , $audit , $usage , $clients ) {
2019-12-17 17:16:50 +13:00
2019-10-25 06:53:37 +13:00
$route = $utopia -> match ( $request );
$referrer = $request -> getServer ( 'HTTP_REFERER' , '' );
2020-03-05 11:54:49 +13:00
$origin = parse_url ( $request -> getServer ( 'HTTP_ORIGIN' , $referrer ), PHP_URL_HOST );
2020-03-17 08:07:43 +13:00
$protocol = parse_url ( $request -> getServer ( 'HTTP_ORIGIN' , $referrer ), PHP_URL_SCHEME );
$port = parse_url ( $request -> getServer ( 'HTTP_ORIGIN' , $referrer ), PHP_URL_PORT );
2019-10-25 06:53:37 +13:00
2020-03-05 11:54:49 +13:00
$refDomain = $protocol . '://' . (( in_array ( $origin , $clients ))
2020-03-17 08:07:43 +13:00
? $origin : 'localhost' ) . ( ! empty ( $port ) ? ':' . $port : '' );
2019-10-25 06:53:37 +13:00
2020-03-29 01:42:16 +13:00
$selfDomain = new Domain ( Config :: getParam ( 'domain' ));
2020-03-18 00:36:13 +13:00
$endDomain = new Domain ( $origin );
2020-03-29 01:42:16 +13:00
Config :: setParam ( 'domainVerification' , ( $selfDomain -> getRegisterable () === $endDomain -> getRegisterable ()));
2020-03-18 00:36:13 +13:00
2019-10-25 06:53:37 +13:00
/*
* Security Headers
*
* As recommended at :
* @ see https :// www . owasp . org / index . php / List_of_useful_HTTP_headers
*/
$response
-> addHeader ( 'Server' , 'Appwrite' )
-> addHeader ( 'X-XSS-Protection' , '1; mode=block; report=/v1/xss?url=' . urlencode ( $request -> getServer ( 'REQUEST_URI' )))
//->addHeader('X-Frame-Options', ($refDomain == 'http://localhost') ? 'SAMEORIGIN' : 'ALLOW-FROM ' . $refDomain)
-> 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-SDK-Version' )
2020-02-27 22:17:09 +13:00
-> addHeader ( 'Access-Control-Expose-Headers' , 'X-Fallback-Cookies' )
2019-10-25 06:53:37 +13:00
-> addHeader ( 'Access-Control-Allow-Origin' , $refDomain )
-> addHeader ( 'Access-Control-Allow-Credentials' , 'true' )
;
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
/*
* Validate Client Domain - Check to avoid CSRF attack
* Adding Appwrite API domains to allow XDOMAIN communication
2020-01-29 23:49:18 +13:00
* Skip this check for non - web platforms which are not requiredto send an origin header
2019-10-25 06:53:37 +13:00
*/
2020-03-05 11:54:49 +13:00
$origin = parse_url ( $request -> getServer ( 'HTTP_ORIGIN' , $request -> getServer ( 'HTTP_REFERER' , '' )), PHP_URL_HOST );
2019-10-25 06:53:37 +13:00
2020-03-05 11:54:49 +13:00
if ( ! empty ( $origin )
&& ! in_array ( $origin , $clients )
2019-10-25 06:53:37 +13:00
&& in_array ( $request -> getMethod (), [ Request :: METHOD_POST , Request :: METHOD_PUT , Request :: METHOD_PATCH , Request :: METHOD_DELETE ])
2020-03-05 11:54:49 +13:00
&& empty ( $request -> getHeader ( 'X-Appwrite-Key' , '' ))
) {
throw new Exception ( 'Access from this client host is forbidden' , 403 );
2019-10-25 06:53:37 +13:00
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
/*
* ACL Check
*/
$role = ( $user -> isEmpty ()) ? Auth :: USER_ROLE_GUEST : Auth :: USER_ROLE_MEMBER ;
// Add user roles
$membership = $user -> search ( 'teamId' , $project -> getAttribute ( 'teamId' , null ), $user -> getAttribute ( 'memberships' , []));
if ( $membership ) {
foreach ( $membership -> 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-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$scope = $route -> getLabel ( 'scope' , 'none' ); // Allowed scope for chosen route
$scopes = $roles [ $role ][ 'scopes' ]; // Allowed scopes for user role
2020-01-12 10:53:57 +13:00
2019-10-25 06:53:37 +13:00
// Check if given key match project API keys
$key = $project -> search ( 'secret' , $request -> getHeader ( 'X-Appwrite-Key' , '' ), $project -> getAttribute ( 'keys' , []));
2020-01-12 10:53:57 +13:00
2019-10-25 06:53:37 +13:00
/*
* 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 ( null !== $key && $user -> isEmpty ()) {
$user = new Document ([
2020-02-17 20:16:11 +13:00
'$id' => 0 ,
2019-10-25 06:53:37 +13:00
'status' => Auth :: USER_STATUS_ACTIVATED ,
2020-03-29 01:42:16 +13:00
'email' => 'app.' . $project -> getId () . '@service.' . Config :: getParam ( 'domain' ),
2019-10-25 06:53:37 +13:00
'password' => '' ,
'name' => $project -> getAttribute ( 'name' , 'Untitled' ),
]);
$role = Auth :: USER_ROLE_APP ;
2020-01-13 04:20:51 +13:00
$scopes = array_merge ( $roles [ $role ][ 'scopes' ], $key -> getAttribute ( 'scopes' , []));
2019-10-25 06:53:37 +13:00
2020-02-11 19:17:40 +13:00
Authorization :: setDefaultStatus ( false ); // Cancel security segmentation for API keys.
2019-10-25 06:53:37 +13:00
}
2019-05-09 18:54:39 +12:00
2020-02-17 20:16:11 +13:00
Authorization :: setRole ( 'user:' . $user -> getId ());
2019-10-25 06:53:37 +13:00
Authorization :: setRole ( 'role:' . $role );
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
array_map ( function ( $node ) {
if ( isset ( $node [ 'teamId' ]) && isset ( $node [ 'roles' ])) {
Authorization :: setRole ( 'team:' . $node [ 'teamId' ]);
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
foreach ( $node [ 'roles' ] as $nodeRole ) { // Set all team roles
Authorization :: setRole ( 'team:' . $node [ 'teamId' ] . '/' . $nodeRole );
}
}
}, $user -> getAttribute ( 'memberships' , []));
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
// TDOO Check if user is god
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
if ( ! in_array ( $scope , $scopes )) {
2020-02-17 20:16:11 +13:00
if ( empty ( $project -> getId ()) || Database :: SYSTEM_COLLECTION_PROJECTS !== $project -> getCollection ()) { // Check if permission is denied because project is missing
2019-12-17 17:16:50 +13:00
throw new Exception ( 'Project not found' , 404 );
}
2019-10-25 06:53:37 +13:00
throw new Exception ( $user -> getAttribute ( 'email' , 'Guest' ) . ' (role: ' . strtolower ( $roles [ $role ][ 'label' ]) . ') missing scope (' . $scope . ')' , 401 );
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
if ( Auth :: USER_STATUS_BLOCKED == $user -> getAttribute ( 'status' )) { // Account has not been activated
throw new Exception ( 'Invalid credentials. User is blocked' , 401 ); // User is in status blocked
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
if ( $user -> getAttribute ( 'reset' )) {
throw new Exception ( 'Password reset is required' , 412 );
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
/*
* Background Jobs
*/
$webhook
2020-02-17 20:16:11 +13:00
-> setParam ( 'projectId' , $project -> getId ())
2019-10-25 06:53:37 +13:00
-> setParam ( 'event' , $route -> getLabel ( 'webhook' , '' ))
-> setParam ( 'payload' , [])
;
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$audit
2020-02-17 20:16:11 +13:00
-> setParam ( 'projectId' , $project -> getId ())
-> setParam ( 'userId' , $user -> getId ())
2019-10-25 06:53:37 +13:00
-> setParam ( 'event' , '' )
-> setParam ( 'resource' , '' )
-> setParam ( 'userAgent' , $request -> getServer ( 'HTTP_USER_AGENT' , '' ))
-> setParam ( 'ip' , $request -> getIP ())
-> setParam ( 'data' , [])
;
2019-06-09 01:13:19 +12:00
2019-10-25 06:53:37 +13:00
$usage
2020-02-17 20:16:11 +13:00
-> setParam ( 'projectId' , $project -> getId ())
2019-10-25 06:53:37 +13:00
-> setParam ( 'url' , $request -> getServer ( 'HTTP_HOST' , '' ) . $request -> getServer ( 'REQUEST_URI' , '' ))
-> setParam ( 'method' , $request -> getServer ( 'REQUEST_METHOD' , 'UNKNOWN' ))
-> setParam ( 'request' , 0 )
-> setParam ( 'response' , 0 )
-> setParam ( 'storage' , 0 )
;
});
2019-05-09 18:54:39 +12:00
2020-02-13 21:04:52 +13:00
$utopia -> shutdown ( function () use ( $response , $request , $webhook , $audit , $usage , $mode , $project , $utopia ) {
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
/*
* Trigger Events for background jobs
*/
if ( ! empty ( $webhook -> getParam ( 'event' ))) {
$webhook -> trigger ();
}
2020-01-12 02:58:02 +13:00
2019-10-25 06:53:37 +13:00
if ( ! empty ( $audit -> getParam ( 'event' ))) {
$audit -> trigger ();
}
2020-02-13 21:04:52 +13:00
$route = $utopia -> match ( $request );
2019-05-09 18:54:39 +12:00
2020-02-17 20:16:11 +13:00
if ( $project -> getId ()
2020-02-13 21:04:52 +13:00
&& $mode !== APP_MODE_ADMIN
&& ! empty ( $route -> getLabel ( 'sdk.namespace' , null ))) { // Don't calculate console usage and admin mode
$usage
-> setParam ( 'request' , $request -> getSize ())
-> setParam ( 'response' , $response -> getSize ())
-> trigger ()
;
}
2019-10-25 06:53:37 +13:00
});
2019-05-09 18:54:39 +12:00
2020-03-17 08:07:43 +13:00
$utopia -> options ( function () use ( $request , $response ) {
2019-10-25 06:53:37 +13:00
$origin = $request -> getServer ( 'HTTP_ORIGIN' );
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$response
-> addHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, PATCH, DELETE' )
2020-02-27 22:17:09 +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-SDK-Version, X-Fallback-Cookies' )
-> addHeader ( 'Access-Control-Expose-Headers' , 'X-Fallback-Cookies' )
2019-10-25 06:53:37 +13:00
-> addHeader ( 'Access-Control-Allow-Origin' , $origin )
-> addHeader ( 'Access-Control-Allow-Credentials' , 'true' )
-> send ();
});
2019-05-09 18:54:39 +12:00
2020-03-29 01:42:16 +13:00
$utopia -> error ( function ( $error /* @var $error Exception */ ) use ( $request , $response , $utopia , $project ) {
$env = Config :: getParam ( 'env' );
$version = Config :: getParam ( 'version' );
2019-10-25 06:53:37 +13:00
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
2020-01-04 10:01:09 +13:00
case 409 : // Error allowed publicly
2019-10-25 06:53:37 +13:00
case 412 : // Error allowed publicly
case 429 : // Error allowed publicly
$code = $error -> getCode ();
$message = $error -> getMessage ();
break ;
default :
$code = 500 ; // All other errors get the generic 500 server error status code
$message = 'Server Error' ;
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
$output = (( App :: ENV_TYPE_DEVELOPMENT == $env )) ? [
'message' => $error -> getMessage (),
'code' => $error -> getCode (),
'file' => $error -> getFile (),
'line' => $error -> getLine (),
'trace' => $error -> getTrace (),
'version' => $version ,
] : [
'message' => $message ,
'code' => $code ,
'version' => $version ,
];
$response
-> addHeader ( 'Cache-Control' , 'no-cache, no-store, must-revalidate' )
-> addHeader ( 'Expires' , '0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> setStatusCode ( $code )
2019-10-25 06:53:11 +13:00
;
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$route = $utopia -> match ( $request );
$template = ( $route ) ? $route -> getLabel ( 'error' , null ) : null ;
if ( $template ) {
$layout = new View ( __DIR__ . '/views/layouts/default.phtml' );
$comp = new View ( $template );
$comp
-> setParam ( 'projectName' , $project -> getAttribute ( 'name' ))
-> setParam ( 'projectURL' , $project -> getAttribute ( 'url' ))
-> setParam ( 'message' , $error -> getMessage ())
-> setParam ( 'code' , $code )
;
$layout
-> setParam ( 'title' , $project -> getAttribute ( 'name' ) . ' - Error' )
-> setParam ( 'description' , 'No Description' )
-> setParam ( 'body' , $comp )
-> setParam ( 'version' , $version )
-> setParam ( 'litespeed' , false )
;
$response -> send ( $layout -> render ());
2019-10-25 06:53:11 +13:00
}
2019-10-25 06:53:37 +13:00
$response
-> json ( $output )
;
});
$utopia -> get ( '/manifest.json' )
-> desc ( 'Progressive app manifest file' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $response ) {
$response -> json ([
'name' => APP_NAME ,
'short_name' => APP_NAME ,
'start_url' => '.' ,
'url' => 'https://appwrite.io/' ,
'display' => 'standalone' ,
'background_color' => '#fff' ,
'theme_color' => '#f02e65' ,
'description' => 'End to end backend server for frontend and mobile apps. 👩💻👨💻' ,
'icons' => [
[
'src' => 'images/favicon.png' ,
'sizes' => '256x256' ,
'type' => 'image/png' ,
],
],
]);
}
);
$utopia -> get ( '/robots.txt' )
-> desc ( 'Robots.txt File' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $response ) {
2020-02-15 18:22:43 +13:00
$template = new View ( __DIR__ . '/views/general/robots.phtml' );
$response -> text ( $template -> render ( false ));
2019-10-25 06:53:37 +13:00
}
);
$utopia -> get ( '/humans.txt' )
-> desc ( 'Humans.txt File' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $response ) {
2020-02-15 18:22:43 +13:00
$template = new View ( __DIR__ . '/views/general/humans.phtml' );
$response -> text ( $template -> render ( false ));
2019-10-25 06:53:37 +13:00
}
);
2020-02-19 11:13:18 +13:00
$utopia -> get ( '/.well-known/acme-challenge' )
-> desc ( 'SSL Verification' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $request , $response ) {
2020-02-23 21:55:57 +13:00
$base = realpath ( APP_STORAGE_CERTIFICATES );
2020-02-19 11:13:18 +13:00
$path = str_replace ( '/.well-known/acme-challenge/' , '' , $request -> getParam ( 'q' ));
2020-02-23 21:55:57 +13:00
$absolute = realpath ( $base . '/.well-known/acme-challenge/' . $path );
if ( ! $base ) {
throw new Exception ( 'Storage error' , 500 );
}
2020-02-19 11:13:18 +13:00
if ( ! $absolute ) {
2020-02-23 21:55:57 +13:00
throw new Exception ( 'Unknown path' , 404 );
2020-02-19 11:13:18 +13:00
}
if ( ! substr ( $absolute , 0 , strlen ( $base )) === $base ) {
2020-02-23 21:55:57 +13:00
throw new Exception ( 'Invalid path' , 401 );
}
2020-02-24 06:45:51 +13:00
if ( ! file_exists ( $absolute )) {
throw new Exception ( 'Unknown path' , 404 );
}
2020-02-23 21:55:57 +13:00
$content = @ file_get_contents ( $absolute );
if ( ! $content ) {
throw new Exception ( 'Failed to get contents' , 500 );
2020-02-19 11:13:18 +13:00
}
2020-02-23 21:55:57 +13:00
$response -> text ( $content );
2020-02-19 11:13:18 +13:00
}
);
2019-10-25 06:53:37 +13:00
$name = APP_NAME ;
if ( array_key_exists ( $service , $services )) { /** @noinspection PhpIncludeInspection */
include_once $services [ $service ][ 'controller' ];
$name = APP_NAME . ' ' . ucfirst ( $services [ $service ][ 'name' ]);
} else {
/** @noinspection PhpIncludeInspection */
include_once $services [ '/' ][ 'controller' ];
2019-05-09 18:54:39 +12:00
}
2020-03-25 05:24:56 +13:00
$utopia -> run ( $request , $response );