2023-03-09 07:30:01 +13:00
< ? php
use Appwrite\Event\Certificate ;
use Appwrite\Event\Delete ;
use Appwrite\Event\Event ;
use Appwrite\Extend\Exception ;
use Appwrite\Network\Validator\CNAME ;
use Appwrite\Utopia\Database\Validator\Queries\Rules ;
use Appwrite\Utopia\Response ;
use Utopia\App ;
use Utopia\Database\Database ;
use Utopia\Database\Document ;
2023-08-06 20:51:53 +12:00
use Utopia\Database\Helpers\ID ;
2023-03-09 07:30:01 +13:00
use Utopia\Database\Query ;
use Utopia\Database\Validator\UID ;
use Utopia\Domains\Domain ;
2023-08-06 20:51:53 +12:00
use Utopia\Validator\Domain as ValidatorDomain ;
2023-03-09 07:30:01 +13:00
use Utopia\Validator\Text ;
use Utopia\Validator\WhiteList ;
App :: post ( '/v1/proxy/rules' )
-> groups ([ 'api' , 'proxy' ])
-> desc ( 'Create Rule' )
-> label ( 'scope' , 'rules.write' )
-> label ( 'event' , 'rules.[ruleId].create' )
-> label ( 'audits.event' , 'rule.create' )
-> label ( 'audits.resource' , 'rule/{response.$id}' )
2023-08-30 23:28:43 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-03-09 07:30:01 +13:00
-> label ( 'sdk.namespace' , 'proxy' )
-> label ( 'sdk.method' , 'createRule' )
-> label ( 'sdk.description' , '/docs/references/proxy/create-rule.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_CREATED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_PROXY_RULE )
2023-08-06 20:51:53 +12:00
-> param ( 'domain' , null , new ValidatorDomain (), 'Domain name.' )
2023-08-20 06:26:47 +12:00
-> param ( 'resourceType' , null , new WhiteList ([ 'api' , 'function' ]), 'Action definition for the rule. Possible values are "api", "function"' )
-> param ( 'resourceId' , '' , new UID (), 'ID of resource for the action type. If resourceType is "api", leave empty. If resourceType is "function", provide ID of the function.' , true )
2023-03-09 07:30:01 +13:00
-> inject ( 'response' )
-> inject ( 'project' )
2023-10-20 04:28:01 +13:00
-> inject ( 'queueForCertificates' )
-> inject ( 'queueForEvents' )
2023-03-09 07:30:01 +13:00
-> inject ( 'dbForConsole' )
-> inject ( 'dbForProject' )
2023-10-20 04:28:01 +13:00
-> action ( function ( string $domain , string $resourceType , string $resourceId , Response $response , Document $project , Certificate $queueForCertificates , Event $queueForEvents , Database $dbForConsole , Database $dbForProject ) {
2023-08-22 05:43:03 +12:00
$mainDomain = App :: getEnv ( '_APP_DOMAIN' , '' );
2023-08-23 06:14:20 +12:00
if ( $domain === $mainDomain ) {
throw new Exception ( Exception :: GENERAL_ARGUMENT_INVALID , 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.' );
}
if ( $domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL ) {
2023-08-22 20:16:17 +12:00
throw new Exception ( Exception :: GENERAL_ARGUMENT_INVALID , 'This domain name is not allowed. Please pick another one.' );
2023-08-22 05:43:03 +12:00
}
2023-08-23 01:18:07 +12:00
2023-03-09 07:30:01 +13:00
$document = $dbForConsole -> findOne ( 'rules' , [
Query :: equal ( 'domain' , [ $domain ]),
]);
if ( $document && ! $document -> isEmpty ()) {
2023-03-15 08:31:23 +13:00
if ( $document -> getAttribute ( 'projectId' ) === $project -> getId ()) {
2023-03-11 02:36:31 +13:00
$resourceType = $document -> getAttribute ( 'resourceType' );
$resourceId = $document -> getAttribute ( 'resourceId' );
$message = " Domain already assigned to ' { $resourceType } ' service " ;
2023-03-15 08:31:23 +13:00
if ( ! empty ( $resourceId )) {
2023-03-11 02:36:31 +13:00
$message .= " with ID ' { $resourceId } ' " ;
}
$message .= '.' ;
} else {
2023-08-18 18:55:44 +12:00
$message = 'Domain already assigned to different project.' ;
2023-03-11 02:36:31 +13:00
}
2023-03-15 08:31:23 +13:00
2023-03-11 02:36:31 +13:00
throw new Exception ( Exception :: RULE_ALREADY_EXISTS , $message );
2023-03-09 07:30:01 +13:00
}
$resourceInternalId = '' ;
2023-03-15 08:31:23 +13:00
if ( $resourceType == 'function' ) {
if ( empty ( $resourceId )) {
2023-07-28 20:27:16 +12:00
throw new Exception ( Exception :: FUNCTION_NOT_FOUND );
2023-03-09 07:30:01 +13:00
}
$function = $dbForProject -> getDocument ( 'functions' , $resourceId );
if ( $function -> isEmpty ()) {
2023-07-28 19:56:07 +12:00
throw new Exception ( Exception :: RULE_RESOURCE_NOT_FOUND );
2023-03-09 07:30:01 +13:00
}
$resourceInternalId = $function -> getInternalId ();
}
$domain = new Domain ( $domain );
$ruleId = ID :: unique ();
2023-07-12 22:55:33 +12:00
$rule = new Document ([
2023-03-09 07:30:01 +13:00
'$id' => $ruleId ,
'projectId' => $project -> getId (),
'projectInternalId' => $project -> getInternalId (),
'domain' => $domain -> get (),
'resourceType' => $resourceType ,
'resourceId' => $resourceId ,
'resourceInternalId' => $resourceInternalId ,
'certificateId' => '' ,
2023-07-12 22:55:33 +12:00
]);
$status = 'created' ;
2023-07-25 01:12:36 +12:00
$functionsDomain = App :: getEnv ( '_APP_DOMAIN_FUNCTIONS' );
if ( ! empty ( $functionsDomain ) && \str_ends_with ( $domain -> get (), $functionsDomain )) {
2023-07-12 22:55:33 +12:00
$status = 'verified' ;
}
if ( $status === 'created' ) {
$target = new Domain ( App :: getEnv ( '_APP_DOMAIN_TARGET' , '' ));
$validator = new CNAME ( $target -> get ()); // Verify Domain with DNS records
if ( $validator -> isValid ( $domain -> get ())) {
$status = 'verifying' ;
2023-10-20 04:28:01 +13:00
$queueForCertificates
2023-07-12 22:55:33 +12:00
-> setDomain ( new Document ([
'domain' => $rule -> getAttribute ( 'domain' )
]))
-> trigger ();
}
}
$rule -> setAttribute ( 'status' , $status );
$rule = $dbForConsole -> createDocument ( 'rules' , $rule );
2023-03-09 07:30:01 +13:00
2023-10-20 04:28:01 +13:00
$queueForEvents -> setParam ( 'ruleId' , $rule -> getId ());
2023-03-09 07:30:01 +13:00
2023-03-14 02:35:34 +13:00
$rule -> setAttribute ( 'logs' , '' );
2023-03-09 07:30:01 +13:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
2023-03-10 20:42:52 +13:00
-> dynamic ( $rule , Response :: MODEL_PROXY_RULE );
2023-03-09 07:30:01 +13:00
});
App :: get ( '/v1/proxy/rules' )
-> groups ([ 'api' , 'proxy' ])
-> desc ( 'List Rules' )
-> label ( 'scope' , 'rules.read' )
2023-08-30 23:28:43 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-03-09 07:30:01 +13:00
-> label ( 'sdk.namespace' , 'proxy' )
-> label ( 'sdk.method' , 'listRules' )
-> label ( 'sdk.description' , '/docs/references/proxy/list-rules.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_PROXY_RULE_LIST )
-> param ( 'queries' , [], new Rules (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Rules :: ALLOWED_ATTRIBUTES ), true )
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'dbForConsole' )
-> action ( function ( array $queries , string $search , Response $response , Document $project , Database $dbForConsole ) {
$queries = Query :: parseQueries ( $queries );
if ( ! empty ( $search )) {
$queries [] = Query :: search ( 'search' , $search );
}
$queries [] = Query :: equal ( 'projectInternalId' , [ $project -> getInternalId ()]);
// Get cursor document if there was a cursor query
2023-08-06 20:51:53 +12:00
$cursor = Query :: getByType ( $queries , [ Query :: TYPE_CURSORAFTER , Query :: TYPE_CURSORBEFORE ]);
2023-03-09 07:30:01 +13:00
$cursor = reset ( $cursor );
if ( $cursor ) {
/** @var Query $cursor */
$ruleId = $cursor -> getValue ();
$cursorDocument = $dbForConsole -> getDocument ( 'rules' , $ruleId );
if ( $cursorDocument -> isEmpty ()) {
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Rule ' { $ruleId } ' for the 'cursor' value not found. " );
}
$cursor -> setValue ( $cursorDocument );
}
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2023-03-14 02:35:34 +13:00
$rules = $dbForConsole -> find ( 'rules' , $queries );
foreach ( $rules as $rule ) {
$certificate = $dbForConsole -> getDocument ( 'certificates' , $rule -> getAttribute ( 'certificateId' , '' ));
$rule -> setAttribute ( 'logs' , $certificate -> getAttribute ( 'logs' , '' ));
2023-06-09 03:24:27 +12:00
$rule -> setAttribute ( 'renewAt' , $certificate -> getAttribute ( 'renewDate' , '' ));
2023-03-14 02:35:34 +13:00
}
2023-03-09 07:30:01 +13:00
$response -> dynamic ( new Document ([
2023-03-14 02:35:34 +13:00
'rules' => $rules ,
2023-03-09 07:30:01 +13:00
'total' => $dbForConsole -> count ( 'rules' , $filterQueries , APP_LIMIT_COUNT ),
]), Response :: MODEL_PROXY_RULE_LIST );
});
App :: get ( '/v1/proxy/rules/:ruleId' )
-> groups ([ 'api' , 'proxy' ])
-> desc ( 'Get Rule' )
-> label ( 'scope' , 'rules.read' )
2023-08-30 23:28:43 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-03-09 07:30:01 +13:00
-> label ( 'sdk.namespace' , 'proxy' )
-> label ( 'sdk.method' , 'getRule' )
-> label ( 'sdk.description' , '/docs/references/proxy/get-rule.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_PROXY_RULE )
-> param ( 'ruleId' , '' , new UID (), 'Rule ID.' )
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'dbForConsole' )
-> action ( function ( string $ruleId , Response $response , Document $project , Database $dbForConsole ) {
2023-03-10 20:42:52 +13:00
$rule = $dbForConsole -> getDocument ( 'rules' , $ruleId );
2023-03-09 07:30:01 +13:00
if ( $rule -> isEmpty () || $rule -> getAttribute ( 'projectInternalId' ) !== $project -> getInternalId ()) {
throw new Exception ( Exception :: RULE_NOT_FOUND );
}
2023-03-14 02:35:34 +13:00
$certificate = $dbForConsole -> getDocument ( 'certificates' , $rule -> getAttribute ( 'certificateId' , '' ));
$rule -> setAttribute ( 'logs' , $certificate -> getAttribute ( 'logs' , '' ));
2023-06-09 03:24:27 +12:00
$rule -> setAttribute ( 'renewAt' , $certificate -> getAttribute ( 'renewDate' , '' ));
2023-03-14 02:35:34 +13:00
2023-03-09 07:30:01 +13:00
$response -> dynamic ( $rule , Response :: MODEL_PROXY_RULE );
});
App :: delete ( '/v1/proxy/rules/:ruleId' )
-> groups ([ 'api' , 'proxy' ])
-> desc ( 'Delete Rule' )
-> label ( 'scope' , 'rules.write' )
-> label ( 'event' , 'rules.[ruleId].delete' )
2023-07-13 23:42:04 +12:00
-> label ( 'audits.event' , 'rules.delete' )
2023-03-09 07:30:01 +13:00
-> label ( 'audits.resource' , 'rule/{request.ruleId}' )
2023-08-30 23:28:43 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-03-09 07:30:01 +13:00
-> label ( 'sdk.namespace' , 'proxy' )
-> label ( 'sdk.method' , 'deleteRule' )
-> label ( 'sdk.description' , '/docs/references/proxy/delete-rule.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_NOCONTENT )
-> label ( 'sdk.response.model' , Response :: MODEL_NONE )
-> param ( 'ruleId' , '' , new UID (), 'Rule ID.' )
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'dbForConsole' )
2023-10-20 04:28:01 +13:00
-> inject ( 'queueForDeletes' )
-> inject ( 'queueForEvents' )
-> action ( function ( string $ruleId , Response $response , Document $project , Database $dbForConsole , Delete $queueForDeletes , Event $queueForEvents ) {
2023-03-09 07:30:01 +13:00
$rule = $dbForConsole -> getDocument ( 'rules' , $ruleId );
if ( $rule -> isEmpty () || $rule -> getAttribute ( 'projectInternalId' ) !== $project -> getInternalId ()) {
throw new Exception ( Exception :: RULE_NOT_FOUND );
}
2023-07-28 20:27:16 +12:00
$dbForConsole -> deleteDocument ( 'rules' , $rule -> getId ());
2023-03-09 07:30:01 +13:00
2023-10-20 04:28:01 +13:00
$queueForDeletes
2023-03-09 07:30:01 +13:00
-> setType ( DELETE_TYPE_DOCUMENT )
-> setDocument ( $rule );
2023-10-20 04:28:01 +13:00
$queueForEvents -> setParam ( 'ruleId' , $rule -> getId ());
2023-03-09 07:30:01 +13:00
$response -> noContent ();
});
App :: patch ( '/v1/proxy/rules/:ruleId/verification' )
-> desc ( 'Update Rule Verification Status' )
-> groups ([ 'api' , 'proxy' ])
-> label ( 'scope' , 'rules.write' )
2023-03-11 01:20:24 +13:00
-> label ( 'event' , 'rules.[ruleId].update' )
-> label ( 'audits.event' , 'rule.update' )
-> label ( 'audits.resource' , 'rule/{response.$id}' )
2023-08-30 23:28:43 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-03-09 07:30:01 +13:00
-> label ( 'sdk.namespace' , 'proxy' )
-> label ( 'sdk.method' , 'updateRuleVerification' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_PROXY_RULE )
-> param ( 'ruleId' , '' , new UID (), 'Rule ID.' )
-> inject ( 'response' )
2023-10-20 04:28:01 +13:00
-> inject ( 'queueForCertificates' )
-> inject ( 'queueForEvents' )
2023-03-09 07:30:01 +13:00
-> inject ( 'project' )
-> inject ( 'dbForConsole' )
2023-10-20 04:28:01 +13:00
-> action ( function ( string $ruleId , Response $response , Certificate $queueForCertificates , Event $queueForEvents , Document $project , Database $dbForConsole ) {
2023-03-09 07:30:01 +13:00
$rule = $dbForConsole -> getDocument ( 'rules' , $ruleId );
if ( $rule -> isEmpty () || $rule -> getAttribute ( 'projectInternalId' ) !== $project -> getInternalId ()) {
throw new Exception ( Exception :: RULE_NOT_FOUND );
}
$target = new Domain ( App :: getEnv ( '_APP_DOMAIN_TARGET' , '' ));
if ( ! $target -> isKnown () || $target -> isTest ()) {
2023-07-28 20:27:16 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Domain target must be configured as environment variable.' );
2023-03-09 07:30:01 +13:00
}
if ( $rule -> getAttribute ( 'verification' ) === true ) {
return $response -> dynamic ( $rule , Response :: MODEL_PROXY_RULE );
}
$validator = new CNAME ( $target -> get ()); // Verify Domain with DNS records
2023-03-10 20:42:52 +13:00
$domain = new Domain ( $rule -> getAttribute ( 'domain' , '' ));
2023-03-09 07:30:01 +13:00
2023-03-10 20:42:52 +13:00
if ( ! $validator -> isValid ( $domain -> get ())) {
2023-03-09 07:30:01 +13:00
throw new Exception ( Exception :: RULE_VERIFICATION_FAILED );
}
2023-03-09 08:50:51 +13:00
$dbForConsole -> updateDocument ( 'rules' , $rule -> getId (), $rule -> setAttribute ( 'status' , 'verifying' ));
2023-03-09 07:30:01 +13:00
// Issue a TLS certificate when domain is verified
2023-10-20 04:28:01 +13:00
$queueForCertificates
2023-03-09 07:30:01 +13:00
-> setDomain ( new Document ([
'domain' => $rule -> getAttribute ( 'domain' )
]))
-> trigger ();
2023-10-20 04:28:01 +13:00
$queueForEvents -> setParam ( 'ruleId' , $rule -> getId ());
2023-03-11 01:20:24 +13:00
2023-03-14 02:35:34 +13:00
$certificate = $dbForConsole -> getDocument ( 'certificates' , $rule -> getAttribute ( 'certificateId' , '' ));
$rule -> setAttribute ( 'logs' , $certificate -> getAttribute ( 'logs' , '' ));
2023-03-09 07:30:01 +13:00
$response -> dynamic ( $rule , Response :: MODEL_PROXY_RULE );
2023-03-15 08:31:23 +13:00
});