mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 00:49:02 +00:00
Merge pull request #9395 from appwrite/fix-apex-rules
Feat: Enhance rules
This commit is contained in:
commit
ac9ac3e279
36 changed files with 2110 additions and 350 deletions
2
.env
2
.env
|
|
@ -23,7 +23,7 @@ _APP_OPENSSL_KEY_V1=your-secret-key
|
|||
_APP_DOMAIN=traefik
|
||||
_APP_DOMAIN_FUNCTIONS=functions.localhost
|
||||
_APP_DOMAIN_SITES=sites.localhost
|
||||
_APP_DOMAIN_TARGET=localhost
|
||||
_APP_DOMAIN_TARGET=test.appwrite.io
|
||||
_APP_RULES_FORMAT=md5
|
||||
_APP_REDIS_HOST=redis
|
||||
_APP_REDIS_PORT=6379
|
||||
|
|
|
|||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -146,6 +146,7 @@ jobs:
|
|||
Projects,
|
||||
Realtime,
|
||||
Sites,
|
||||
Proxy,
|
||||
Storage,
|
||||
Teams,
|
||||
Users,
|
||||
|
|
@ -212,6 +213,7 @@ jobs:
|
|||
Projects,
|
||||
Realtime,
|
||||
Sites,
|
||||
Proxy,
|
||||
Storage,
|
||||
Teams,
|
||||
Users,
|
||||
|
|
|
|||
|
|
@ -1013,10 +1013,10 @@ return [
|
|||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('resourceType'),
|
||||
'$id' => ID::custom('type'), // 'api', 'redirect', 'deployment' (site or function)
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 100,
|
||||
'size' => 32,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
|
|
@ -1024,24 +1024,28 @@ return [
|
|||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('resourceInternalId'),
|
||||
// If 'api', then (empty)
|
||||
// If 'redirect', then URL
|
||||
// If 'deployment', then deployment ID
|
||||
'$id' => ID::custom('value'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'size' => 512,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('resourceId'),
|
||||
// Examples: branch=main
|
||||
'$id' => ID::custom('automation'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
|
|
@ -1066,9 +1070,27 @@ return [
|
|||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_domain'),
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
|
|
@ -1091,24 +1113,24 @@ return [
|
|||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_resourceInternalId',
|
||||
'$id' => '_key_type',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'attributes' => ['type'],
|
||||
'lengths' => [32],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_resourceId',
|
||||
'$id' => '_key_value',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'attributes' => ['value'],
|
||||
'lengths' => [512],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_resourceType',
|
||||
'$id' => '_key_automation',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceType'],
|
||||
'lengths' => [],
|
||||
'attributes' => ['automation'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -23683,7 +23683,7 @@
|
|||
"parameters": [
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "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 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: domain, resourceType, resourceId, url",
|
||||
"description": "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 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: domain, type, value, automation, url",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
|
|
@ -23706,10 +23706,12 @@
|
|||
"in": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
"\/proxy\/rules\/api": {
|
||||
"post": {
|
||||
"summary": "Create rule",
|
||||
"operationId": "proxyCreateRule",
|
||||
"summary": "Create API rule",
|
||||
"operationId": "proxyCreateAPIRule",
|
||||
"tags": [
|
||||
"proxy"
|
||||
],
|
||||
|
|
@ -23727,13 +23729,79 @@
|
|||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createRule",
|
||||
"method": "createAPIRule",
|
||||
"weight": 423,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "proxy\/create-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule.",
|
||||
"demo": "proxy\/create-a-p-i-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule for serving Appwrite's API on custom domain.",
|
||||
"rate-limit": 10,
|
||||
"rate-time": 60,
|
||||
"rate-key": "userId:{userId}, url:{url}",
|
||||
"scope": "rules.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"description": "Domain name.",
|
||||
"x-example": null
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"domain"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"\/proxy\/rules\/function": {
|
||||
"post": {
|
||||
"summary": "Create function rule",
|
||||
"operationId": "proxyCreateFunctionRule",
|
||||
"tags": [
|
||||
"proxy"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Rule",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/proxyRule"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFunctionRule",
|
||||
"weight": 425,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "proxy\/create-function-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule for executing Appwrite Function on custom domain.",
|
||||
"rate-limit": 10,
|
||||
"rate-time": 60,
|
||||
"rate-key": "userId:{userId}, url:{url}",
|
||||
|
|
@ -23762,27 +23830,159 @@
|
|||
"description": "Domain name.",
|
||||
"x-example": null
|
||||
},
|
||||
"resourceType": {
|
||||
"functionId": {
|
||||
"type": "string",
|
||||
"description": "Action definition for the rule. Possible values are \"api\", \"function\" and \"site\"",
|
||||
"x-example": "api",
|
||||
"enum": [
|
||||
"api",
|
||||
"function",
|
||||
"site"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
},
|
||||
"resourceId": {
|
||||
"type": "string",
|
||||
"description": "ID of resource for the action type. If resourceType is \"api\", leave empty. If resourceType is \"function\", provide ID of the function.",
|
||||
"x-example": "<RESOURCE_ID>"
|
||||
"description": "ID of function to be executed.",
|
||||
"x-example": "<FUNCTION_ID>"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"domain",
|
||||
"resourceType"
|
||||
"functionId"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"\/proxy\/rules\/redirect": {
|
||||
"post": {
|
||||
"summary": "Create Redirect rule",
|
||||
"operationId": "proxyCreateRedirectRule",
|
||||
"tags": [
|
||||
"proxy"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Rule",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/proxyRule"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createRedirectRule",
|
||||
"weight": 426,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "proxy\/create-redirect-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule for to redirect from custom domain to another domain.",
|
||||
"rate-limit": 10,
|
||||
"rate-time": 60,
|
||||
"rate-key": "userId:{userId}, url:{url}",
|
||||
"scope": "rules.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"description": "Domain name.",
|
||||
"x-example": null
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target URL of redirection",
|
||||
"x-example": "https:\/\/example.com"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"domain",
|
||||
"target"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"\/proxy\/rules\/site": {
|
||||
"post": {
|
||||
"summary": "Create site rule",
|
||||
"operationId": "proxyCreateSiteRule",
|
||||
"tags": [
|
||||
"proxy"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Rule",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/proxyRule"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSiteRule",
|
||||
"weight": 424,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "proxy\/create-site-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule for serving Appwrite Site on custom domain.",
|
||||
"rate-limit": 10,
|
||||
"rate-time": 60,
|
||||
"rate-key": "userId:{userId}, url:{url}",
|
||||
"scope": "rules.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"description": "Domain name.",
|
||||
"x-example": null
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string",
|
||||
"description": "ID of site to be executed.",
|
||||
"x-example": "<SITE_ID>"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"domain",
|
||||
"siteId"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -39780,15 +39980,20 @@
|
|||
"description": "Domain name.",
|
||||
"x-example": "appwrite.company.com"
|
||||
},
|
||||
"resourceType": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Action definition for the rule. Possible values are \"api\", \"function\", or \"redirect\"",
|
||||
"x-example": "function"
|
||||
"description": "Action definition for the rule. Possible values are \"api\", \"deployment\", or \"redirect\"",
|
||||
"x-example": "deployment"
|
||||
},
|
||||
"resourceId": {
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "ID of resource for the action type. If resourceType is \"api\" or \"url\", it is empty. If resourceType is \"function\", it is ID of the function.",
|
||||
"x-example": "myAwesomeFunction"
|
||||
"description": "Detail specification for the type. If type is \"api\", this is empty. If type is \"redirect\", this is URL. If type is \"deployment\", this is deployment ID.",
|
||||
"x-example": "67a9cf1a00150ee93abd"
|
||||
},
|
||||
"automation": {
|
||||
"type": "string",
|
||||
"description": "Action that result in update of rule. If VCS branch, value can be of syntax \"branch=[name]\"",
|
||||
"x-example": "branch=dev"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
|
|
@ -39811,8 +40016,9 @@
|
|||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"domain",
|
||||
"resourceType",
|
||||
"resourceId",
|
||||
"type",
|
||||
"value",
|
||||
"automation",
|
||||
"status",
|
||||
"logs",
|
||||
"renewAt"
|
||||
|
|
@ -39944,6 +40150,11 @@
|
|||
"type": "string",
|
||||
"description": "Defines if HTTPS is enforced for all requests.",
|
||||
"x-example": "enabled"
|
||||
},
|
||||
"_APP_DOMAINS_NAMESERVERS": {
|
||||
"type": "string",
|
||||
"description": "Comma-separated list of nameservers.",
|
||||
"x-example": "ns1.example.com,ns2.example.com"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -39955,7 +40166,8 @@
|
|||
"_APP_DOMAIN_ENABLED",
|
||||
"_APP_ASSISTANT_ENABLED",
|
||||
"_APP_DOMAIN_SITES",
|
||||
"_APP_OPTIONS_FORCE_HTTPS"
|
||||
"_APP_OPTIONS_FORCE_HTTPS",
|
||||
"_APP_DOMAINS_NAMESERVERS"
|
||||
]
|
||||
},
|
||||
"mfaChallenge": {
|
||||
|
|
|
|||
|
|
@ -24168,7 +24168,7 @@
|
|||
"parameters": [
|
||||
{
|
||||
"name": "queries",
|
||||
"description": "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 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: domain, resourceType, resourceId, url",
|
||||
"description": "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 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: domain, type, value, automation, url",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"collectionFormat": "multi",
|
||||
|
|
@ -24188,10 +24188,12 @@
|
|||
"in": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
"\/proxy\/rules\/api": {
|
||||
"post": {
|
||||
"summary": "Create rule",
|
||||
"operationId": "proxyCreateRule",
|
||||
"summary": "Create API rule",
|
||||
"operationId": "proxyCreateAPIRule",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
],
|
||||
|
|
@ -24211,13 +24213,82 @@
|
|||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createRule",
|
||||
"method": "createAPIRule",
|
||||
"weight": 423,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "proxy\/create-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule.",
|
||||
"demo": "proxy\/create-a-p-i-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule for serving Appwrite's API on custom domain.",
|
||||
"rate-limit": 10,
|
||||
"rate-time": 60,
|
||||
"rate-key": "userId:{userId}, url:{url}",
|
||||
"scope": "rules.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"description": "Domain name.",
|
||||
"default": null,
|
||||
"x-example": null
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"domain"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/proxy\/rules\/function": {
|
||||
"post": {
|
||||
"summary": "Create function rule",
|
||||
"operationId": "proxyCreateFunctionRule",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
],
|
||||
"produces": [
|
||||
"application\/json"
|
||||
],
|
||||
"tags": [
|
||||
"proxy"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Rule",
|
||||
"schema": {
|
||||
"$ref": "#\/definitions\/proxyRule"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFunctionRule",
|
||||
"weight": 425,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "proxy\/create-function-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule for executing Appwrite Function on custom domain.",
|
||||
"rate-limit": 10,
|
||||
"rate-time": 60,
|
||||
"rate-key": "userId:{userId}, url:{url}",
|
||||
|
|
@ -24248,29 +24319,168 @@
|
|||
"default": null,
|
||||
"x-example": null
|
||||
},
|
||||
"resourceType": {
|
||||
"functionId": {
|
||||
"type": "string",
|
||||
"description": "Action definition for the rule. Possible values are \"api\", \"function\" and \"site\"",
|
||||
"description": "ID of function to be executed.",
|
||||
"default": null,
|
||||
"x-example": "api",
|
||||
"enum": [
|
||||
"api",
|
||||
"function",
|
||||
"site"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
},
|
||||
"resourceId": {
|
||||
"type": "string",
|
||||
"description": "ID of resource for the action type. If resourceType is \"api\", leave empty. If resourceType is \"function\", provide ID of the function.",
|
||||
"default": "",
|
||||
"x-example": "<RESOURCE_ID>"
|
||||
"x-example": "<FUNCTION_ID>"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"domain",
|
||||
"resourceType"
|
||||
"functionId"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/proxy\/rules\/redirect": {
|
||||
"post": {
|
||||
"summary": "Create Redirect rule",
|
||||
"operationId": "proxyCreateRedirectRule",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
],
|
||||
"produces": [
|
||||
"application\/json"
|
||||
],
|
||||
"tags": [
|
||||
"proxy"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Rule",
|
||||
"schema": {
|
||||
"$ref": "#\/definitions\/proxyRule"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createRedirectRule",
|
||||
"weight": 426,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "proxy\/create-redirect-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule for to redirect from custom domain to another domain.",
|
||||
"rate-limit": 10,
|
||||
"rate-time": 60,
|
||||
"rate-key": "userId:{userId}, url:{url}",
|
||||
"scope": "rules.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"description": "Domain name.",
|
||||
"default": null,
|
||||
"x-example": null
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target URL of redirection",
|
||||
"default": null,
|
||||
"x-example": "https:\/\/example.com"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"domain",
|
||||
"target"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/proxy\/rules\/site": {
|
||||
"post": {
|
||||
"summary": "Create site rule",
|
||||
"operationId": "proxyCreateSiteRule",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
],
|
||||
"produces": [
|
||||
"application\/json"
|
||||
],
|
||||
"tags": [
|
||||
"proxy"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Rule",
|
||||
"schema": {
|
||||
"$ref": "#\/definitions\/proxyRule"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSiteRule",
|
||||
"weight": 424,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "proxy\/create-site-rule.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a new proxy rule for serving Appwrite Site on custom domain.",
|
||||
"rate-limit": 10,
|
||||
"rate-time": 60,
|
||||
"rate-key": "userId:{userId}, url:{url}",
|
||||
"scope": "rules.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"description": "Domain name.",
|
||||
"default": null,
|
||||
"x-example": null
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string",
|
||||
"description": "ID of site to be executed.",
|
||||
"default": null,
|
||||
"x-example": "<SITE_ID>"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"domain",
|
||||
"siteId"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -40434,15 +40644,20 @@
|
|||
"description": "Domain name.",
|
||||
"x-example": "appwrite.company.com"
|
||||
},
|
||||
"resourceType": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Action definition for the rule. Possible values are \"api\", \"function\", or \"redirect\"",
|
||||
"x-example": "function"
|
||||
"description": "Action definition for the rule. Possible values are \"api\", \"deployment\", or \"redirect\"",
|
||||
"x-example": "deployment"
|
||||
},
|
||||
"resourceId": {
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "ID of resource for the action type. If resourceType is \"api\" or \"url\", it is empty. If resourceType is \"function\", it is ID of the function.",
|
||||
"x-example": "myAwesomeFunction"
|
||||
"description": "Detail specification for the type. If type is \"api\", this is empty. If type is \"redirect\", this is URL. If type is \"deployment\", this is deployment ID.",
|
||||
"x-example": "67a9cf1a00150ee93abd"
|
||||
},
|
||||
"automation": {
|
||||
"type": "string",
|
||||
"description": "Action that result in update of rule. If VCS branch, value can be of syntax \"branch=[name]\"",
|
||||
"x-example": "branch=dev"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
|
|
@ -40465,8 +40680,9 @@
|
|||
"$createdAt",
|
||||
"$updatedAt",
|
||||
"domain",
|
||||
"resourceType",
|
||||
"resourceId",
|
||||
"type",
|
||||
"value",
|
||||
"automation",
|
||||
"status",
|
||||
"logs",
|
||||
"renewAt"
|
||||
|
|
@ -40598,6 +40814,11 @@
|
|||
"type": "string",
|
||||
"description": "Defines if HTTPS is enforced for all requests.",
|
||||
"x-example": "enabled"
|
||||
},
|
||||
"_APP_DOMAINS_NAMESERVERS": {
|
||||
"type": "string",
|
||||
"description": "Comma-separated list of nameservers.",
|
||||
"x-example": "ns1.example.com,ns2.example.com"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -40609,7 +40830,8 @@
|
|||
"_APP_DOMAIN_ENABLED",
|
||||
"_APP_ASSISTANT_ENABLED",
|
||||
"_APP_DOMAIN_SITES",
|
||||
"_APP_OPTIONS_FORCE_HTTPS"
|
||||
"_APP_OPTIONS_FORCE_HTTPS",
|
||||
"_APP_DOMAINS_NAMESERVERS"
|
||||
]
|
||||
},
|
||||
"mfaChallenge": {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ App::get('/v1/console/variables')
|
|||
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
|
||||
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
|
||||
'_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'),
|
||||
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS')
|
||||
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
|
||||
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
|
||||
]);
|
||||
|
||||
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use Utopia\Config\Config;
|
|||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
|
|
@ -231,10 +232,10 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
'activate' => $activate,
|
||||
]));
|
||||
|
||||
// Preview deployments for sites
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
$projectId = $project->getId();
|
||||
|
||||
// Deployment preview
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
|
|
@ -244,13 +245,61 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentId,
|
||||
'resourceInternalId' => $deployment->getInternalId(),
|
||||
'type' => 'deployment',
|
||||
'value' => $deployment->getId(),
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
]))
|
||||
);
|
||||
|
||||
// VCS branch preview
|
||||
if (!empty($providerBranch)) {
|
||||
$domain = "branch-{$providerBranch}-{$resource->getId()}-{$project->getId()}.{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'type' => 'deployment',
|
||||
'value' => $deployment->getId(),
|
||||
'automation' => 'branch=' . $providerBranch,
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
]))
|
||||
);
|
||||
} catch (Duplicate $err) {
|
||||
// Ignore, rule already exists; will be updated by builds worker
|
||||
}
|
||||
}
|
||||
|
||||
// VCS commit preview
|
||||
if (!empty($providerCommitHash)) {
|
||||
$domain = "commit-{$providerCommitHash}-{$resource->getId()}-{$project->getId()}.{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'type' => 'deployment',
|
||||
'value' => $deployment->getId(),
|
||||
'automation' => 'commit=' . $providerCommitHash,
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
]))
|
||||
);
|
||||
} catch (Duplicate $err) {
|
||||
// Ignore, rule already exists; will be updated by builds worker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($providerCommitHash) && $resource->getAttribute('providerSilentMode', false) === false) {
|
||||
|
|
|
|||
|
|
@ -123,17 +123,9 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
return false;
|
||||
}
|
||||
|
||||
$type = $rule->getAttribute('resourceType');
|
||||
$type = $rule->getAttribute('type', '');
|
||||
|
||||
if ($type === 'function' || $type === 'site' || $type === 'deployment') {
|
||||
$resourceCollection = match ($type) {
|
||||
'function' => 'functions',
|
||||
'site' => 'sites',
|
||||
'deployment' => 'deployments',
|
||||
};
|
||||
}
|
||||
|
||||
if ($type === 'function' || $type === 'site' || $type === 'deployment') {
|
||||
if ($type === 'deployment') {
|
||||
$method = $utopia->getRoute()?->getLabel('sdk', null);
|
||||
|
||||
if (empty($method)) {
|
||||
|
|
@ -167,8 +159,22 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
}
|
||||
}
|
||||
|
||||
$resourceId = $rule->getAttribute('resourceId');
|
||||
$projectId = $rule->getAttribute('projectId');
|
||||
/** @var Database $dbForProject */
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('value')));
|
||||
|
||||
if ($deployment->getAttribute('resourceType', '') === 'functions') {
|
||||
$type = 'function';
|
||||
} elseif ($deployment->getAttribute('resourceType', '') === 'sites') {
|
||||
$type = 'site';
|
||||
}
|
||||
|
||||
$resource = $type === 'function' ?
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
|
||||
|
||||
$isPreview = $type === 'function' ? false : (!\str_starts_with($rule->getAttribute('automation', ''), 'site='));
|
||||
|
||||
$path = ($swooleRequest->server['request_uri'] ?? '/');
|
||||
$query = ($swooleRequest->server['query_string'] ?? '');
|
||||
|
|
@ -181,30 +187,17 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
|
||||
$requestHeaders = $request->getHeaders();
|
||||
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
|
||||
/** @var Database $dbForProject */
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
if ($resourceCollection === 'deployments') {
|
||||
$subResource = Authorization::skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
|
||||
$resource = Authorization::skip(fn () => $dbForProject->getDocument($subResource->getAttribute('resourceType'), $subResource->getAttribute('resourceId')));
|
||||
} else {
|
||||
$resource = Authorization::skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
|
||||
}
|
||||
|
||||
if ($resource->isEmpty() || !$resource->getAttribute('enabled')) {
|
||||
throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $resourceId)) {
|
||||
if ($isResourceBlocked($project, $type === 'function' ? RESOURCE_TYPE_FUNCTIONS : RESOURCE_TYPE_SITES, $resource->getId())) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_RESOURCE_BLOCKED);
|
||||
}
|
||||
|
||||
$version = match ($type) {
|
||||
'function' => $resource->getAttribute('version', 'v2'),
|
||||
'site' => 'v4',
|
||||
'deployment' => 'v4'
|
||||
};
|
||||
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
|
|
@ -213,34 +206,13 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
$runtime = match ($type) {
|
||||
'function' => $runtimes[$resource->getAttribute('runtime')] ?? null,
|
||||
'site' => $runtimes[$resource->getAttribute('buildRuntime')] ?? null,
|
||||
'deployment' => $runtimes[$resource->getAttribute('buildRuntime')] ?? null,
|
||||
default => null
|
||||
};
|
||||
|
||||
if ($resource->getAttribute('adapter', '') === 'static') {
|
||||
$runtime = $runtimes['static-1'] ?? null;
|
||||
}
|
||||
|
||||
if (\is_null($runtime)) {
|
||||
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
|
||||
}
|
||||
|
||||
$deploymentId = match ($type) {
|
||||
'function' => $resource->getAttribute('deployment', ''),
|
||||
'site' => $resource->getAttribute('deploymentId', ''),
|
||||
'deployment' => $subResource->getId()
|
||||
};
|
||||
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $deploymentId));
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $resource->getId()) {
|
||||
throw new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
|
||||
}
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
|
||||
}
|
||||
|
||||
/** Check if build has completed */
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
if ($build->isEmpty()) {
|
||||
|
|
@ -251,10 +223,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
throw new AppwriteException(AppwriteException::BUILD_NOT_READY);
|
||||
}
|
||||
|
||||
//todo: figure out for sites/functions
|
||||
if ($type === 'function') {
|
||||
$permissions = $resource->getAttribute('execute');
|
||||
|
||||
if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) {
|
||||
throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"');
|
||||
}
|
||||
|
|
@ -266,18 +236,15 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
$headers['x-appwrite-continent-code'] = '';
|
||||
$headers['x-appwrite-continent-eu'] = 'false';
|
||||
|
||||
//todo: check if this would work for sites
|
||||
if ($type === 'function') {
|
||||
$jwtExpiry = $resource->getAttribute('timeout', 900);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
|
||||
$jwtKey = $jwtObj->encode([
|
||||
'projectId' => $project->getId(),
|
||||
'scopes' => $resource->getAttribute('scopes', [])
|
||||
]);
|
||||
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $jwtKey;
|
||||
$headers['x-appwrite-trigger'] = 'http';
|
||||
$headers['x-appwrite-user-jwt'] = '';
|
||||
}
|
||||
$jwtExpiry = $resource->getAttribute('timeout', 900);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
|
||||
$jwtKey = $jwtObj->encode([
|
||||
'projectId' => $project->getId(),
|
||||
'scopes' => $resource->getAttribute('scopes', [])
|
||||
]);
|
||||
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $jwtKey;
|
||||
$headers['x-appwrite-trigger'] = 'http';
|
||||
$headers['x-appwrite-user-jwt'] = '';
|
||||
|
||||
$ip = $headers['x-real-ip'] ?? '';
|
||||
if (!empty($ip)) {
|
||||
|
|
@ -316,21 +283,26 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
'errors' => '',
|
||||
'logs' => '',
|
||||
'duration' => 0.0,
|
||||
'search' => implode(' ', [$resourceId, $executionId]),
|
||||
'search' => implode(' ', [$resource->getId(), $executionId]),
|
||||
]);
|
||||
|
||||
if ($type === 'function') {
|
||||
$execution->setAttribute('resourceType', 'functions');
|
||||
$execution->setAttribute('trigger', 'http'); // http / schedule / event
|
||||
$execution->setAttribute('status', 'processing'); // waiting / processing / completed / failed
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $resource->getId())
|
||||
->setParam('executionId', $execution->getId())
|
||||
->setContext('function', $resource);
|
||||
} elseif ($type === 'site') {
|
||||
$execution->setAttribute('resourceType', 'sites');
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $resource->getId())
|
||||
->setParam('executionId', $execution->getId())
|
||||
->setContext('function', $resource);
|
||||
$queueForEvents
|
||||
->setParam('siteId', $resource->getId())
|
||||
->setParam('executionId', $execution->getId())
|
||||
->setContext('site', $resource);
|
||||
}
|
||||
|
||||
$durationStart = \microtime(true);
|
||||
|
||||
|
|
@ -363,7 +335,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
// Appwrite vars
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
|
||||
'APPWRITE_FUNCTION_ID' => $resourceId,
|
||||
'APPWRITE_FUNCTION_ID' => $resource->getId(),
|
||||
'APPWRITE_FUNCTION_NAME' => $resource->getAttribute('name'),
|
||||
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
|
||||
|
|
@ -394,12 +366,10 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
$version = match ($type) {
|
||||
'function' => $resource->getAttribute('version', 'v2'),
|
||||
'site' => 'v4',
|
||||
'deployment' => 'v4'
|
||||
};
|
||||
$entrypoint = match ($type) {
|
||||
'function' => $deployment->getAttribute('entrypoint', ''),
|
||||
'site' => '',
|
||||
'deployment' => ''
|
||||
};
|
||||
|
||||
if ($type === 'function') {
|
||||
|
|
@ -407,7 +377,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
'v2' => '',
|
||||
default => 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $runtime['startCommand'] . '"'
|
||||
};
|
||||
} elseif ($type === 'site' || $type === 'deployment') {
|
||||
} elseif ($type === 'site') {
|
||||
$frameworks = Config::getParam('frameworks', []);
|
||||
$framework = $frameworks[$resource->getAttribute('framework', '')] ?? null;
|
||||
|
||||
|
|
@ -426,7 +396,6 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
$entrypoint = match ($type) {
|
||||
'function' => $deployment->getAttribute('entrypoint', ''),
|
||||
'site' => '',
|
||||
'deployment' => ''
|
||||
};
|
||||
|
||||
$executionResponse = $executor->createExecution(
|
||||
|
|
@ -455,7 +424,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
$transformation->addAdapter(new Preview());
|
||||
$transformation->setInput($executionResponse['body']);
|
||||
$transformation->setTraits($executionResponse['headers']);
|
||||
if ($type === 'deployment' && $transformation->transform()) {
|
||||
if ($isPreview && $transformation->transform()) {
|
||||
$executionResponse['body'] = $transformation->getOutput();
|
||||
|
||||
foreach ($executionResponse['headers'] as $key => $value) {
|
||||
|
|
@ -475,9 +444,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
|
||||
/** Update execution status */
|
||||
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
|
||||
if ($type === 'function') {
|
||||
$execution->setAttribute('status', $status);
|
||||
}
|
||||
$execution->setAttribute('status', $status);
|
||||
$execution->setAttribute('logs', $executionResponse['logs']);
|
||||
$execution->setAttribute('errors', $executionResponse['errors']);
|
||||
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
|
||||
|
|
@ -566,6 +533,17 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
} elseif ($type === 'api') {
|
||||
$utopia->getRoute()?->label('error', '');
|
||||
return false;
|
||||
} elseif ($type === 'redirect') {
|
||||
$path = ($swooleRequest->server['request_uri'] ?? '/');
|
||||
$query = ($swooleRequest->server['query_string'] ?? '');
|
||||
if (!empty($query)) {
|
||||
$path .= '?' . $query;
|
||||
}
|
||||
|
||||
$url = 'https://' . $rule->getAttribute('value', '') . $path;
|
||||
|
||||
$response->redirect($url);
|
||||
return true;
|
||||
} else {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type);
|
||||
}
|
||||
|
|
@ -698,14 +676,16 @@ App::init()
|
|||
}
|
||||
|
||||
if ($domainDocument->isEmpty()) {
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
$domainDocument = new Document([
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
'$id' => System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(),
|
||||
'$id' => $ruleId,
|
||||
'domain' => $domain->get(),
|
||||
'resourceType' => 'api',
|
||||
'status' => 'verifying',
|
||||
'projectId' => 'console',
|
||||
'projectInternalId' => 'console'
|
||||
'projectInternalId' => 'console',
|
||||
'search' => implode(' ', [$ruleId, $domain->get()]),
|
||||
]);
|
||||
|
||||
$domainDocument = $dbForPlatform->createDocument('rules', $domainDocument);
|
||||
|
|
@ -742,7 +722,7 @@ App::init()
|
|||
} elseif (!empty($origin)) {
|
||||
// Auto-allow domains with linked rule
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin)));
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? '')));
|
||||
} else {
|
||||
$rule = Authorization::skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
|
|
@ -1311,13 +1291,7 @@ App::get('/v1/ping')
|
|||
App::wildcard()
|
||||
->groups(['api'])
|
||||
->label('scope', 'global')
|
||||
->inject('utopia')
|
||||
->action(function (App $utopia) {
|
||||
$handeledByRouter = $utopia->getRoute()?->getLabel('router', false);
|
||||
if ($handeledByRouter === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
->action(function () {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -519,7 +519,6 @@ $http->on('Task', function () use ($register, $domains) {
|
|||
if ($lastSyncUpdate != null) {
|
||||
$queries[] = Query::greaterThanEqual('$updatedAt', $lastSyncUpdate);
|
||||
}
|
||||
$queries[] = Query::equal('resourceType', ['function']);
|
||||
$results = [];
|
||||
try {
|
||||
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
"utopia-php/database": "0.59.0",
|
||||
"utopia-php/domains": "0.5.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "dev-fix-prevent-duplicate-compression as 0.33.99",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/fetch": "0.3.*",
|
||||
"utopia-php/image": "0.7.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
|
|
|
|||
27
composer.lock
generated
27
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "236b24287cd5bdcf705df586f12c560c",
|
||||
"content-hash": "6883b3e81cfb0c5355997def668d5df2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
|
@ -3919,16 +3919,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/framework",
|
||||
"version": "dev-fix-prevent-duplicate-compression",
|
||||
"version": "0.33.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/http.git",
|
||||
"reference": "a1efe3e10038afe4109af833ce7a25a8ec4b5ed2"
|
||||
"reference": "e91d4c560d1b809e25faa63d564fef034363b50f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/a1efe3e10038afe4109af833ce7a25a8ec4b5ed2",
|
||||
"reference": "a1efe3e10038afe4109af833ce7a25a8ec4b5ed2",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/e91d4c560d1b809e25faa63d564fef034363b50f",
|
||||
"reference": "e91d4c560d1b809e25faa63d564fef034363b50f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -3960,9 +3960,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/http/issues",
|
||||
"source": "https://github.com/utopia-php/http/tree/fix-prevent-duplicate-compression"
|
||||
"source": "https://github.com/utopia-php/http/tree/0.33.16"
|
||||
},
|
||||
"time": "2025-02-03T12:02:35+00:00"
|
||||
"time": "2025-01-16T15:58:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
|
|
@ -8804,18 +8804,9 @@
|
|||
"time": "2024-03-07T20:33:40+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "utopia-php/framework",
|
||||
"version": "dev-fix-prevent-duplicate-compression",
|
||||
"alias": "0.33.99",
|
||||
"alias_normalized": "0.33.99.0"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"utopia-php/framework": 20
|
||||
},
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
|
|
|||
|
|
@ -181,11 +181,11 @@ class Base extends Action
|
|||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentId,
|
||||
'resourceInternalId' => $deployment->getInternalId(),
|
||||
'type' => 'deployment',
|
||||
'value' => $deployment->getId(),
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
]))
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -724,8 +724,8 @@ class Builds extends Action
|
|||
try {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
Query::equal("projectInternalId", [$project->getInternalId()]),
|
||||
Query::equal("resourceType", ["deployment"]),
|
||||
Query::equal("resourceInternalId", [$deployment->getInternalId()])
|
||||
Query::equal("type", ["deployment"]),
|
||||
Query::equal("value", [$deployment->getId()])
|
||||
]));
|
||||
|
||||
if ($rule->isEmpty()) {
|
||||
|
|
@ -831,14 +831,51 @@ class Builds extends Action
|
|||
case 'functions':
|
||||
$resource->setAttribute('deployment', $deployment->getId());
|
||||
$resource = $dbForProject->updateDocument('functions', $resource->getId(), $resource);
|
||||
|
||||
$this->listRules($project, [
|
||||
Query::equal("automation", ["function=" . $resource->getId()]),
|
||||
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
|
||||
$rule = $rule->setAttribute('value', $deployment->getId());
|
||||
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
|
||||
});
|
||||
break;
|
||||
case 'sites':
|
||||
$resource->setAttribute('deploymentId', $deployment->getId());
|
||||
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
|
||||
|
||||
$this->listRules($project, [
|
||||
Query::equal("automation", ["site=" . $resource->getId()]),
|
||||
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
|
||||
$rule = $rule->setAttribute('value', $deployment->getId());
|
||||
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
|
||||
});
|
||||
|
||||
// VCS branch
|
||||
$branchName = $deployment->getAttribute('providerBranch');
|
||||
if (!empty($branchName)) {
|
||||
$this->listRules($project, [
|
||||
Query::equal("automation", ["branch=" . $branchName]),
|
||||
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
|
||||
$rule = $rule->setAttribute('value', $deployment->getId());
|
||||
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
|
||||
});
|
||||
}
|
||||
|
||||
// VCS commit
|
||||
$commitHash = $deployment->getAttribute('providerCommitHash', '');
|
||||
if (!empty($commitHash)) {
|
||||
$this->listRules($project, [
|
||||
Query::equal("automation", ["commit=" . $commitHash]),
|
||||
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
|
||||
$rule = $rule->setAttribute('value', $deployment->getId());
|
||||
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
|
||||
Console::info('Build has been canceled');
|
||||
return;
|
||||
|
|
@ -1105,8 +1142,8 @@ class Builds extends Action
|
|||
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
Query::equal("projectInternalId", [$project->getInternalId()]),
|
||||
Query::equal("resourceType", ["deployment"]),
|
||||
Query::equal("resourceInternalId", [$deployment->getInternalId()])
|
||||
Query::equal("type", ["deployment"]),
|
||||
Query::equal("value", [$deployment->getId()])
|
||||
]));
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
|
|
@ -1131,4 +1168,38 @@ class Builds extends Action
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function listRules(Document $project, array $queries, Database $database, callable $callback = null): void
|
||||
{
|
||||
$limit = 100;
|
||||
$cursor = null;
|
||||
|
||||
do {
|
||||
$queries = \array_merge([
|
||||
Query::limit($limit),
|
||||
Query::equal("projectInternalId", [$project->getInternalId()])
|
||||
], $queries);
|
||||
|
||||
if ($cursor !== null) {
|
||||
$queries[] = Query::cursorAfter($cursor);
|
||||
}
|
||||
|
||||
$results = $database->find('rules', $queries);
|
||||
|
||||
$total = \count($results);
|
||||
if ($total > 0) {
|
||||
$cursor = $results[$total - 1];
|
||||
}
|
||||
|
||||
if ($total < $limit) {
|
||||
$cursor = null;
|
||||
}
|
||||
|
||||
foreach ($results as $document) {
|
||||
if (is_callable($callback)) {
|
||||
$callback($document);
|
||||
}
|
||||
}
|
||||
} while (!\is_null($cursor));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
152
src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php
Normal file
152
src/Appwrite/Platform/Modules/Proxy/Http/Rules/API/Create.php
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\API;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createAPIRule';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/proxy/rules/api')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('Create API rule')
|
||||
->label('scope', 'rules.write')
|
||||
->label('event', 'rules.[ruleId].create')
|
||||
->label('audits.event', 'rule.create')
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
name: 'createAPIRule',
|
||||
description: <<<EOT
|
||||
Create a new proxy rule for serving Appwrite's API on custom domain.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PROXY_RULE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}, url:{url}')
|
||||
->label('abuse-time', 60)
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
|
||||
{
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$deniedDomains = [
|
||||
$mainDomain,
|
||||
$sitesDomain,
|
||||
$functionsDomain,
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL,
|
||||
];
|
||||
|
||||
if (\in_array($domain, $deniedDomains)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
}
|
||||
|
||||
if (\str_starts_with($domain, 'commit-') || \str_starts_with($domain, 'branch-')) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
}
|
||||
|
||||
try {
|
||||
$domain = new Domain($domain);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
// Apex domain prevention due to CNAME limitations
|
||||
if (empty(App::getEnv('_APP_DOMAINS_NAMESERVERS', ''))) {
|
||||
if ($domain->get() === $domain->getRegisterable()) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'The instance does not allow root-level (apex) domains.');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
|
||||
$status = 'created';
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
$status = 'verified';
|
||||
}
|
||||
if ($status === 'created') {
|
||||
$target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
$validator = new CNAME($target->get());
|
||||
if ($validator->isValid($domain->get())) {
|
||||
$status = 'verifying';
|
||||
}
|
||||
}
|
||||
|
||||
$rule = new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain->get(),
|
||||
'status' => $status,
|
||||
'type' => 'api',
|
||||
'value' => '',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain->get()]),
|
||||
]);
|
||||
|
||||
try {
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
} catch (Duplicate $e) {
|
||||
throw new Exception(Exception::RULE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('status', '') === 'verifying') {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
'domain' => $rule->getAttribute('domain')
|
||||
]))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules;
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Function;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
|
|
@ -10,8 +10,10 @@ use Appwrite\SDK\AuthType;
|
|||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
|
|
@ -19,7 +21,6 @@ use Utopia\Platform\Action;
|
|||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
|
|
@ -27,25 +28,25 @@ class Create extends Action
|
|||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createRule';
|
||||
return 'createFunctionRule';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/proxy/rules')
|
||||
->setHttpPath('/v1/proxy/rules/function')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('Create rule')
|
||||
->desc('Create function rule')
|
||||
->label('scope', 'rules.write')
|
||||
->label('event', 'rules.[ruleId].create')
|
||||
->label('audits.event', 'rule.create')
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
name: 'createRule',
|
||||
name: 'createFunctionRule',
|
||||
description: <<<EOT
|
||||
Create a new proxy rule.
|
||||
Create a new proxy rule for executing Appwrite Function on custom domain.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
|
|
@ -59,8 +60,7 @@ class Create extends Action
|
|||
->label('abuse-key', 'userId:{userId}, url:{url}')
|
||||
->label('abuse-time', 60)
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->param('resourceType', null, new WhiteList(['api', 'function', 'site']), 'Action definition for the rule. Possible values are "api", "function" and "site"')
|
||||
->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)
|
||||
->param('functionId', '', new UID(), 'ID of function to be executed.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
|
|
@ -70,7 +70,7 @@ class Create extends Action
|
|||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
public function action(string $domain, string $functionId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
{
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
|
|
@ -84,91 +84,69 @@ class Create extends Action
|
|||
APP_HOSTNAME_INTERNAL,
|
||||
];
|
||||
|
||||
if (in_array($domain, $deniedDomains, true)) {
|
||||
if (\in_array($domain, $deniedDomains)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
}
|
||||
|
||||
$resourceInternalId = '';
|
||||
|
||||
switch ($resourceType) {
|
||||
case 'function':
|
||||
case 'site':
|
||||
if (empty($resourceId)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'resourceId cannot be empty for resourceType "' . $resourceType . '".');
|
||||
}
|
||||
|
||||
$expectedDomain = ($resourceType === 'function') ? $functionsDomain : $sitesDomain;
|
||||
if (!\str_ends_with($domain, $expectedDomain)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must end with ' . $expectedDomain . ' for resourceType "' . $resourceType . '".');
|
||||
}
|
||||
|
||||
$collection = ($resourceType === 'function') ? 'functions' : 'sites';
|
||||
$document = $dbForProject->getDocument($collection, $resourceId);
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$resourceInternalId = $document->getInternalId();
|
||||
break;
|
||||
case 'api':
|
||||
if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must not end with ' . $functionsDomain . ' or ' . $sitesDomain . ' for resourceType "api".');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
$domain = new Domain($domain);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
// Apex domain prevention due to CNAME limitations
|
||||
if (empty(App::getEnv('_APP_DOMAINS_NAMESERVERS', ''))) {
|
||||
if ($domain->get() === $domain->getRegisterable()) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'The instance does not allow root-level (apex) domains.');
|
||||
}
|
||||
}
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
|
||||
try {
|
||||
$rule = new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain->get(),
|
||||
'resourceType' => $resourceType,
|
||||
'resourceId' => $resourceId,
|
||||
'resourceInternalId' => $resourceInternalId,
|
||||
'certificateId' => '',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
if ($e->getCode() === Exception::DOCUMENT_ALREADY_EXISTS) {
|
||||
throw new Exception(Exception::RULE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'An unexpected error occurred: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$status = 'created';
|
||||
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
$status = 'verified';
|
||||
}
|
||||
|
||||
if ($status === 'created') {
|
||||
$target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
$validator = new CNAME($target->get()); // Verify Domain with DNS records
|
||||
|
||||
$validator = new CNAME($target->get());
|
||||
if ($validator->isValid($domain->get())) {
|
||||
$status = 'verifying';
|
||||
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
'domain' => $rule->getAttribute('domain')
|
||||
]))
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
$rule->setAttribute('status', $status);
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
$rule = new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain->get(),
|
||||
'status' => $status,
|
||||
'type' => 'deployment',
|
||||
'value' => $function->getAttribute('deployment', ''),
|
||||
'certificateId' => '',
|
||||
'automation' => 'function=' . $function->getId(),
|
||||
'search' => implode(' ', [$ruleId, $domain->get()]),
|
||||
]);
|
||||
|
||||
try {
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
} catch (Duplicate $e) {
|
||||
throw new Exception(Exception::RULE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('status', '') === 'verifying') {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
'domain' => $rule->getAttribute('domain')
|
||||
]))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Redirect;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createRedirectRule';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/proxy/rules/redirect')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('Create Redirect rule')
|
||||
->label('scope', 'rules.write')
|
||||
->label('event', 'rules.[ruleId].create')
|
||||
->label('audits.event', 'rule.create')
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
name: 'createRedirectRule',
|
||||
description: <<<EOT
|
||||
Create a new proxy rule for to redirect from custom domain to another domain.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PROXY_RULE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}, url:{url}')
|
||||
->label('abuse-time', 60)
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->param('target', null, new ValidatorDomain(), 'Target domain (hostname) of redirection')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $domain, string $target, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
|
||||
{
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$deniedDomains = [
|
||||
$mainDomain,
|
||||
$sitesDomain,
|
||||
$functionsDomain,
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL,
|
||||
];
|
||||
|
||||
if (\in_array($domain, $deniedDomains)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
}
|
||||
|
||||
try {
|
||||
$domain = new Domain($domain);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
try {
|
||||
$target = new Domain($target);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Target may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
// Apex domain prevention due to CNAME limitations
|
||||
if (empty(App::getEnv('_APP_DOMAINS_NAMESERVERS', ''))) {
|
||||
if ($domain->get() === $domain->getRegisterable()) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'The instance does not allow root-level (apex) domains.');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
|
||||
$status = 'created';
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
$status = 'verified';
|
||||
}
|
||||
if ($status === 'created') {
|
||||
$dnsTarget = new Domain(System::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
$validator = new CNAME($dnsTarget->get());
|
||||
if ($validator->isValid($domain->get())) {
|
||||
$status = 'verifying';
|
||||
}
|
||||
}
|
||||
|
||||
$rule = new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain->get(),
|
||||
'status' => $status,
|
||||
'type' => 'redirect',
|
||||
'value' => $target->get(),
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain->get()]),
|
||||
]);
|
||||
|
||||
try {
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
} catch (Duplicate $e) {
|
||||
throw new Exception(Exception::RULE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('status', '') === 'verifying') {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
'domain' => $rule->getAttribute('domain')
|
||||
]))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
}
|
||||
}
|
||||
159
src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php
Normal file
159
src/Appwrite/Platform/Modules/Proxy/Http/Rules/Site/Create.php
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Site;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\CNAME;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createSiteRule';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/proxy/rules/site')
|
||||
->groups(['api', 'proxy'])
|
||||
->desc('Create site rule')
|
||||
->label('scope', 'rules.write')
|
||||
->label('event', 'rules.[ruleId].create')
|
||||
->label('audits.event', 'rule.create')
|
||||
->label('audits.resource', 'rule/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'proxy',
|
||||
name: 'createSiteRule',
|
||||
description: <<<EOT
|
||||
Create a new proxy rule for serving Appwrite Site on custom domain.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PROXY_RULE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}, url:{url}')
|
||||
->label('abuse-time', 60)
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->param('siteId', '', new UID(), 'ID of site to be executed.')
|
||||
->param('branch', '', new Text(255, 0), 'Name of VCS branch to deploy changes automatically', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
{
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$deniedDomains = [
|
||||
$mainDomain,
|
||||
$sitesDomain,
|
||||
$functionsDomain,
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL,
|
||||
];
|
||||
|
||||
if (\in_array($domain, $deniedDomains)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
}
|
||||
|
||||
try {
|
||||
$domain = new Domain($domain);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
// Apex domain prevention due to CNAME limitations
|
||||
if (empty(App::getEnv('_APP_DOMAINS_NAMESERVERS', ''))) {
|
||||
if ($domain->get() === $domain->getRegisterable()) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'The instance does not allow root-level (apex) domains.');
|
||||
}
|
||||
}
|
||||
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
if ($site->isEmpty()) {
|
||||
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
|
||||
$status = 'created';
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
$status = 'verified';
|
||||
}
|
||||
if ($status === 'created') {
|
||||
$target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', ''));
|
||||
$validator = new CNAME($target->get());
|
||||
if ($validator->isValid($domain->get())) {
|
||||
$status = 'verifying';
|
||||
}
|
||||
}
|
||||
|
||||
$rule = new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain->get(),
|
||||
'status' => $status,
|
||||
'type' => 'deployment',
|
||||
'value' => $site->getAttribute('deploymentId', ''),
|
||||
'certificateId' => '',
|
||||
'automation' => !empty($branch) ? ('branch=' . $branch) : ('site=' . $site->getId()),
|
||||
'search' => implode(' ', [$ruleId, $domain->get()]),
|
||||
]);
|
||||
|
||||
try {
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
} catch (Duplicate $e) {
|
||||
throw new Exception(Exception::RULE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('status', '') === 'verifying') {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
'domain' => $rule->getAttribute('domain')
|
||||
]))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Services;
|
||||
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\Create as CreateRule;
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\API\Create as CreateAPIRule;
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\Function\Create as CreateFunctionRule;
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\Redirect\Create as CreateRedirectRule;
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\Site\Create as CreateSiteRule;
|
||||
use Utopia\Platform\Service;
|
||||
|
||||
class Http extends Service
|
||||
|
|
@ -10,7 +13,11 @@ class Http extends Service
|
|||
public function __construct()
|
||||
{
|
||||
$this->type = Service::TYPE_HTTP;
|
||||
|
||||
// Rules
|
||||
$this->addAction(CreateRule::getName(), new CreateRule());
|
||||
$this->addAction(CreateAPIRule::getName(), new CreateAPIRule());
|
||||
$this->addAction(CreateSiteRule::getName(), new CreateSiteRule());
|
||||
$this->addAction(CreateFunctionRule::getName(), new CreateFunctionRule());
|
||||
$this->addAction(CreateRedirectRule::getName(), new CreateRedirectRule());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,11 +112,11 @@ class Create extends Action
|
|||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentId,
|
||||
'resourceInternalId' => $deployment->getInternalId(),
|
||||
'type' => 'deployment',
|
||||
'value' => $deployment->getId(),
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
]))
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -237,11 +237,11 @@ class Create extends Action
|
|||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentId,
|
||||
'resourceInternalId' => $deployment->getInternalId(),
|
||||
'type' => 'deployment',
|
||||
'value' => $deployment->getId(),
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
]))
|
||||
);
|
||||
} else {
|
||||
|
|
@ -288,11 +288,11 @@ class Create extends Action
|
|||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentId,
|
||||
'resourceInternalId' => $deployment->getInternalId(),
|
||||
'type' => 'deployment',
|
||||
'value' => $deployment->getId(),
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
]))
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class Delete extends Action
|
|||
|
||||
if ($site->getAttribute('deployment') === $deployment->getId()) { // Reset site deployment
|
||||
$site = $dbForProject->updateDocument('sites', $site->getId(), new Document(array_merge($site->getArrayCopy(), [
|
||||
'deployment' => '',
|
||||
'deploymentId' => '',
|
||||
'deploymentInternalId' => '',
|
||||
])));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,11 +148,11 @@ class Create extends Base
|
|||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentId,
|
||||
'resourceInternalId' => $deployment->getInternalId(),
|
||||
'type' => 'deployment',
|
||||
'value' => $deployment->getId(),
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
]))
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -761,8 +761,8 @@ class Deletes extends Action
|
|||
*/
|
||||
Console::info("Deleting rules for site " . $siteId);
|
||||
$this->deleteByGroup('rules', [
|
||||
Query::equal('resourceType', ['site']),
|
||||
Query::equal('resourceInternalId', [$siteInternalId]),
|
||||
Query::equal('type', ['deployment']),
|
||||
Query::equal('automation', ['site=' . $siteId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
|
||||
$this->deleteRule($dbForPlatform, $document, $certificates);
|
||||
|
|
@ -782,23 +782,25 @@ class Deletes extends Action
|
|||
*/
|
||||
Console::info("Deleting deployments for site " . $siteId);
|
||||
$deploymentInternalIds = [];
|
||||
$deploymentIds = [];
|
||||
$this->deleteByGroup('deployments', [
|
||||
Query::equal('resourceInternalId', [$siteInternalId])
|
||||
], $dbForProject, function (Document $document) use ($deviceForFunctions, $deviceForFiles, $dbForPlatform, &$deploymentInternalIds) {
|
||||
], $dbForProject, function (Document $document) use ($project, $certificates, $deviceForFunctions, $deviceForFiles, $dbForPlatform, &$deploymentInternalIds) {
|
||||
$deploymentInternalIds[] = $document->getInternalId();
|
||||
$deploymentIds[] = $document->getId();
|
||||
$this->deleteDeploymentFiles($deviceForFunctions, $document);
|
||||
$this->deleteDeploymentScreenshots($deviceForFiles, $dbForPlatform, $document);
|
||||
$this->deleteDeploymentRules($dbForPlatform, $document, $project, $certificates);
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete rules for all deployments of the site
|
||||
*/
|
||||
//TODO: If functions also have previews in the future, change the logic here to use unique identifier for sites and functions
|
||||
foreach ($deploymentInternalIds as $deploymentInternalId) {
|
||||
Console::info("Deleting rules for site " . $siteId . "'s deployment " . $deploymentInternalId);
|
||||
foreach ($deploymentIds as $deploymentId) {
|
||||
Console::info("Deleting rules for site " . $siteId . "'s deployment " . $deploymentId);
|
||||
$this->deleteByGroup('rules', [
|
||||
Query::equal('resourceType', ['deployment']),
|
||||
Query::equal('resourceInternalId', [$deploymentInternalId]),
|
||||
Query::equal('type', ['deployment']),
|
||||
Query::equal('value', [$deploymentId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
|
||||
$this->deleteRule($dbForPlatform, $document, $certificates);
|
||||
|
|
@ -856,8 +858,8 @@ class Deletes extends Action
|
|||
*/
|
||||
Console::info("Deleting rules for function " . $functionId);
|
||||
$this->deleteByGroup('rules', [
|
||||
Query::equal('resourceType', ['function']),
|
||||
Query::equal('resourceInternalId', [$functionInternalId]),
|
||||
Query::equal('type', ['deployment']),
|
||||
Query::equal('automation', ['function=' . $functionId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
], $dbForPlatform, function (Document $document) use ($project, $dbForPlatform, $certificates) {
|
||||
$this->deleteRule($dbForPlatform, $document, $certificates);
|
||||
|
|
@ -880,9 +882,10 @@ class Deletes extends Action
|
|||
$deploymentInternalIds = [];
|
||||
$this->deleteByGroup('deployments', [
|
||||
Query::equal('resourceInternalId', [$functionInternalId])
|
||||
], $dbForProject, function (Document $document) use ($deviceForFunctions, &$deploymentInternalIds) {
|
||||
], $dbForProject, function (Document $document) use ($dbForPlatform, $project, $certificates, $deviceForFunctions, &$deploymentInternalIds) {
|
||||
$deploymentInternalIds[] = $document->getInternalId();
|
||||
$this->deleteDeploymentFiles($deviceForFunctions, $document);
|
||||
$this->deleteDeploymentRules($dbForPlatform, $document, $project, $certificates);
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -930,6 +933,18 @@ class Deletes extends Action
|
|||
$this->deleteRuntimes($getProjectDB, $document, $project);
|
||||
}
|
||||
|
||||
private function deleteDeploymentRules(Database $dbForPlatform, Document $deployment, Document $project, CertificatesAdapter $certificates): void
|
||||
{
|
||||
Console::info("Deleting rules for site " . $deployment->getId());
|
||||
$this->deleteByGroup('rules', [
|
||||
Query::equal('type', ['deployment']),
|
||||
Query::equal('value', [$deployment->getId()]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
|
||||
$this->deleteRule($dbForPlatform, $document, $certificates);
|
||||
});
|
||||
}
|
||||
|
||||
private function deleteDeploymentScreenshots(Device $deviceForFiles, Database $dbForPlatform, Document $deployment): void
|
||||
{
|
||||
$screenshotIds = [];
|
||||
|
|
@ -1081,8 +1096,8 @@ class Deletes extends Action
|
|||
*/
|
||||
Console::info("Deleting rules for deployment " . $deploymentId);
|
||||
$this->deleteByGroup('rules', [
|
||||
Query::equal('resourceType', ['deployment']),
|
||||
Query::equal('resourceInternalId', [$deploymentInternalId]),
|
||||
Query::equal('type', ['deployment']),
|
||||
Query::equal('value', [$deploymentId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
|
||||
$this->deleteRule($dbForPlatform, $document, $certificates);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ class Rules extends Base
|
|||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'domain',
|
||||
'resourceType',
|
||||
'resourceId',
|
||||
'type',
|
||||
'value',
|
||||
'automation',
|
||||
'url'
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,15 @@ class ConsoleVariables extends Model
|
|||
'default' => '',
|
||||
'example' => 'enabled',
|
||||
]
|
||||
)
|
||||
->addRule(
|
||||
'_APP_DOMAINS_NAMESERVERS',
|
||||
[
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Comma-separated list of nameservers.',
|
||||
'default' => '',
|
||||
'example' => 'ns1.example.com,ns2.example.com',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,17 +34,24 @@ class Rule extends Model
|
|||
'default' => '',
|
||||
'example' => 'appwrite.company.com',
|
||||
])
|
||||
->addRule('resourceType', [
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Action definition for the rule. Possible values are "api", "function", or "redirect"',
|
||||
'description' => 'Action definition for the rule. Possible values are "api", "deployment", or "redirect"',
|
||||
'default' => '',
|
||||
'example' => 'function',
|
||||
'example' => 'deployment',
|
||||
])
|
||||
->addRule('resourceId', [
|
||||
->addRule('value', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'ID of resource for the action type. If resourceType is "api" or "url", it is empty. If resourceType is "function", it is ID of the function.',
|
||||
'description' => 'Detail specification for the type. If type is "api", this is empty. If type is "redirect", this is URL. If type is "deployment", this is deployment ID.',
|
||||
'default' => '',
|
||||
'example' => 'myAwesomeFunction',
|
||||
'example' => '67a9cf1a00150ee93abd',
|
||||
])
|
||||
->addRule('automation', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Action that results in a rule update. If VCS branch, value can be of syntax "branch=[name]"',
|
||||
'array' => false,
|
||||
'default' => '',
|
||||
'example' => 'branch=dev',
|
||||
])
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -1101,15 +1101,14 @@ class UsageTest extends Scope
|
|||
|
||||
$rule = $this->client->call(
|
||||
Client::METHOD_POST,
|
||||
'/proxy/rules',
|
||||
'/proxy/rules/function',
|
||||
array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()),
|
||||
[
|
||||
'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'),
|
||||
'resourceType' => 'function',
|
||||
'resourceId' => $functionId,
|
||||
'functionId' => $functionId,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class ConsoleConsoleClientTest extends Scope
|
|||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertCount(9, $response['body']);
|
||||
$this->assertCount(10, $response['body']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET']);
|
||||
$this->assertIsInt($response['body']['_APP_STORAGE_LIMIT']);
|
||||
$this->assertIsInt($response['body']['_APP_COMPUTE_SIZE_LIMIT']);
|
||||
|
|
@ -34,5 +34,7 @@ class ConsoleConsoleClientTest extends Scope
|
|||
$this->assertIsBool($response['body']['_APP_ASSISTANT_ENABLED']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAIN_SITES']);
|
||||
$this->assertIsString($response['body']['_APP_OPTIONS_FORCE_HTTPS']);
|
||||
$this->assertIsString($response['body']['_APP_DOMAINS_NAMESERVERS']);
|
||||
// When adding new keys, dont forget to update count a few lines above
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,13 +274,12 @@ trait FunctionsBase
|
|||
protected function setupFunctionDomain(string $functionId, string $subdomain = ''): string
|
||||
{
|
||||
$subdomain = $subdomain ? $subdomain : ID::unique();
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/function', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''),
|
||||
'resourceType' => 'function',
|
||||
'resourceId' => $functionId,
|
||||
'functionId' => $functionId,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
|
|
@ -299,8 +298,8 @@ trait FunctionsBase
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('resourceId', [$functionId])->toString(),
|
||||
Query::equal('resourceType', ['function'])->toString(),
|
||||
Query::equal('automation', ['function=' . $functionId])->toString(),
|
||||
Query::equal('type', ['deployment'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ class FunctionsServerTest extends Scope
|
|||
|
||||
$deployment = $deployment['body']['data']['functionsGetDeployment'];
|
||||
$this->assertEquals('ready', $deployment['status']);
|
||||
});
|
||||
}, 30000);
|
||||
return $deployment;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,14 +24,13 @@ class ProjectsCustomServerTest extends Scope
|
|||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
|
||||
'resourceType' => 'api',
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', $headers, [
|
||||
'domain' => 'api.appwrite.test',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', $headers, [
|
||||
'resourceType' => 'api',
|
||||
'domain' => 'abc.test.io',
|
||||
]);
|
||||
|
|
@ -39,8 +38,7 @@ class ProjectsCustomServerTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// duplicate rule
|
||||
$response2 = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
|
||||
'resourceType' => 'api',
|
||||
$response2 = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', $headers, [
|
||||
'domain' => 'abc.test.io',
|
||||
]);
|
||||
|
||||
|
|
@ -52,8 +50,7 @@ class ProjectsCustomServerTest extends Scope
|
|||
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
|
||||
'resourceType' => 'api',
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', $headers, [
|
||||
'domain' => $functionsDomain,
|
||||
]);
|
||||
|
||||
|
|
@ -62,24 +59,21 @@ class ProjectsCustomServerTest extends Scope
|
|||
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
|
||||
'resourceType' => 'api',
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', $headers, [
|
||||
'domain' => $sitesDomain,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// prevent functions domain
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
|
||||
'resourceType' => 'function',
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules/function', $headers, [
|
||||
'domain' => $functionsDomain,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// prevent sites domain
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
|
||||
'resourceType' => 'site',
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', $headers, [
|
||||
'domain' => $sitesDomain,
|
||||
]);
|
||||
|
||||
|
|
@ -98,8 +92,7 @@ class ProjectsCustomServerTest extends Scope
|
|||
];
|
||||
|
||||
foreach ($deniedDomains as $deniedDomain) {
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
|
||||
'resourceType' => 'api',
|
||||
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', $headers, [
|
||||
'domain' => $deniedDomain,
|
||||
]);
|
||||
|
||||
|
|
|
|||
295
tests/e2e/Services/Proxy/ProxyBase.php
Normal file
295
tests/e2e/Services/Proxy/ProxyBase.php
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Proxy;
|
||||
|
||||
use Appwrite\ID;
|
||||
use Appwrite\Tests\Async;
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\CLI\Console;
|
||||
|
||||
trait ProxyBase
|
||||
{
|
||||
use Async;
|
||||
|
||||
protected function listRules(array $params = []): mixed
|
||||
{
|
||||
$rule = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), $params);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
protected function createAPIRule(string $domain): mixed
|
||||
{
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'domain' => $domain,
|
||||
]);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
protected function updateRuleVerification(string $ruleId): mixed
|
||||
{
|
||||
$rule = $this->client->call(Client::METHOD_PATCH, '/proxy/rules/' . $ruleId . '/verification', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
protected function createSiteRule(string $domain, string $siteId, string $branch = ''): mixed
|
||||
{
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'domain' => $domain,
|
||||
'siteId' => $siteId,
|
||||
'branch' => $branch,
|
||||
]);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
protected function getRule(string $ruleId): mixed
|
||||
{
|
||||
$rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
protected function createRedirectRule(string $domain, string $target): mixed
|
||||
{
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/redirect', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'domain' => $domain,
|
||||
'target' => $target,
|
||||
]);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
protected function createFunctionRule(string $domain, string $functionId): mixed
|
||||
{
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/function', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'domain' => $domain,
|
||||
'functionId' => $functionId,
|
||||
]);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
protected function deleteRule(string $ruleId): mixed
|
||||
{
|
||||
$rule = $this->client->call(Client::METHOD_DELETE, '/proxy/rules/' . $ruleId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
protected function setupAPIRule(string $domain): string
|
||||
{
|
||||
$rule = $this->createAPIRule($domain);
|
||||
|
||||
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
|
||||
|
||||
return $rule['body']['$id'];
|
||||
}
|
||||
|
||||
protected function setupRedirectRule(string $domain, string $target): string
|
||||
{
|
||||
$rule = $this->createRedirectRule($domain, $target);
|
||||
|
||||
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
|
||||
|
||||
return $rule['body']['$id'];
|
||||
}
|
||||
|
||||
protected function setupFunctionRule(string $domain, string $functionId): string
|
||||
{
|
||||
$rule = $this->createFunctionRule($domain, $functionId);
|
||||
|
||||
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
|
||||
|
||||
return $rule['body']['$id'];
|
||||
}
|
||||
|
||||
protected function setupSiteRule(string $domain, string $siteId, string $branch = ''): string
|
||||
{
|
||||
$rule = $this->createSiteRule($domain, $siteId, $branch);
|
||||
|
||||
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));
|
||||
|
||||
return $rule['body']['$id'];
|
||||
}
|
||||
|
||||
protected function cleanupRule(string $ruleId): void
|
||||
{
|
||||
$rule = $this->deleteRule($ruleId);
|
||||
$this->assertEquals(204, $rule['headers']['status-code'], 'Failed to cleanup rule: ' . \json_encode($rule));
|
||||
}
|
||||
|
||||
protected function cleanupSite(string $siteId): void
|
||||
{
|
||||
$site = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $site['headers']['status-code'], 'Failed to cleanup site: ' . \json_encode($site));
|
||||
}
|
||||
|
||||
protected function cleanupFunction(string $functionId): void
|
||||
{
|
||||
$function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $function['headers']['status-code'], 'Failed to cleanup function: ' . \json_encode($function));
|
||||
}
|
||||
|
||||
protected function setupSite(): mixed
|
||||
{
|
||||
// Site
|
||||
$site = $this->client->call(Client::METHOD_POST, '/sites', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]), [
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Proxy site',
|
||||
'framework' => 'other',
|
||||
'adapter' => 'static',
|
||||
'buildRuntime' => 'static-1',
|
||||
'outputDirectory' => './',
|
||||
'buildCommand' => '',
|
||||
'installCommand' => '',
|
||||
'fallbackFile' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals($site['headers']['status-code'], 201, 'Setup site failed with status code: ' . $site['headers']['status-code'] . ' and response: ' . json_encode($site['body'], JSON_PRETTY_PRINT));
|
||||
|
||||
$siteId = $site['body']['$id'];
|
||||
|
||||
// Deployment
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]), [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
|
||||
$this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEventually(function () use ($siteId, $deploymentId) {
|
||||
$site = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]));
|
||||
$this->assertEquals($deploymentId, $site['body']['deploymentId'], 'Deployment is not activated, deployment: ' . json_encode($site['body'], JSON_PRETTY_PRINT));
|
||||
}, 100000, 500);
|
||||
|
||||
return ['siteId' => $siteId, 'deploymentId' => $deploymentId];
|
||||
}
|
||||
|
||||
protected function setupFunction(): mixed
|
||||
{
|
||||
// Function
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]), [
|
||||
'functionId' => ID::unique(),
|
||||
'runtime' => 'node-18.0',
|
||||
'name' => 'Proxy Function',
|
||||
'entrypoint' => 'index.js',
|
||||
'commands' => '',
|
||||
'execute' => ['any']
|
||||
]);
|
||||
|
||||
$this->assertEquals($function['headers']['status-code'], 201, 'Setup function failed with status code: ' . $function['headers']['status-code'] . ' and response: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
|
||||
|
||||
$functionId = $function['body']['$id'];
|
||||
|
||||
// Deployment
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]), [
|
||||
'code' => $this->packageFunction('node'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
|
||||
$this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEventually(function () use ($functionId, $deploymentId) {
|
||||
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]));
|
||||
$this->assertEquals($deploymentId, $function['body']['deployment'], 'Deployment is not activated, deployment: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
|
||||
}, 100000, 500);
|
||||
|
||||
return ['functionId' => $functionId, 'deploymentId' => $deploymentId];
|
||||
}
|
||||
|
||||
private function packageSite(string $site): CURLFile
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
$folderPath = realpath(__DIR__ . '/../../../resources/sites') . "/$site";
|
||||
$tarPath = "$folderPath/code.tar.gz";
|
||||
|
||||
Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr);
|
||||
|
||||
if (filesize($tarPath) > 1024 * 1024 * 5) {
|
||||
throw new \Exception('Code package is too large. Use the chunked upload method instead.');
|
||||
}
|
||||
|
||||
return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
|
||||
}
|
||||
|
||||
private function packageFunction(string $function): CURLFile
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
$folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function";
|
||||
$tarPath = "$folderPath/code.tar.gz";
|
||||
|
||||
Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr);
|
||||
|
||||
if (filesize($tarPath) > 1024 * 1024 * 5) {
|
||||
throw new \Exception('Code package is too large. Use the chunked upload method instead.');
|
||||
}
|
||||
|
||||
return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
|
||||
}
|
||||
}
|
||||
436
tests/e2e/Services/Proxy/ProxyCustomServerTest.php
Normal file
436
tests/e2e/Services/Proxy/ProxyCustomServerTest.php
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Proxy;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class ProxyCustomServerTest extends Scope
|
||||
{
|
||||
use ProxyBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
|
||||
public function testCreateRule(): void
|
||||
{
|
||||
$domain = \uniqid() . '-api.myapp.com';
|
||||
$rule = $this->createAPIRule($domain);
|
||||
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
$this->assertEquals($domain, $rule['body']['domain']);
|
||||
$this->assertArrayHasKey('$id', $rule['body']);
|
||||
$this->assertArrayHasKey('type', $rule['body']);
|
||||
$this->assertArrayHasKey('value', $rule['body']);
|
||||
$this->assertArrayHasKey('automation', $rule['body']);
|
||||
$this->assertArrayHasKey('status', $rule['body']);
|
||||
$this->assertArrayHasKey('logs', $rule['body']);
|
||||
$this->assertArrayHasKey('renewAt', $rule['body']);
|
||||
|
||||
$ruleId = $rule['body']['$id'];
|
||||
|
||||
$rule = $this->createAPIRule($domain);
|
||||
$this->assertEquals(409, $rule['headers']['status-code']);
|
||||
|
||||
$rule = $this->deleteRule($ruleId);
|
||||
|
||||
$this->assertEquals(204, $rule['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateRuleSetup(): void
|
||||
{
|
||||
$ruleId = $this->setupAPIRule(\uniqid() . '-api2.myapp.com');
|
||||
$this->cleanupRule($ruleId);
|
||||
}
|
||||
|
||||
public function testCreateRuleApex(): void
|
||||
{
|
||||
$rule = $this->createAPIRule('myapp.com');
|
||||
$this->assertEquals(400, $rule['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateRuleVcs(): void
|
||||
{
|
||||
$domain = \uniqid() . '-vcs.myapp.com';
|
||||
|
||||
$rule = $this->createAPIRule('commit-' . $domain);
|
||||
$this->assertEquals(400, $rule['headers']['status-code']);
|
||||
|
||||
$rule = $this->createAPIRule('branch-' . $domain);
|
||||
$this->assertEquals(400, $rule['headers']['status-code']);
|
||||
|
||||
$rule = $this->createAPIRule('anything-' . $domain);
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
$this->cleanupRule($rule['body']['$id']);
|
||||
}
|
||||
|
||||
public function testCreateAPIRule(): void
|
||||
{
|
||||
$domain = \uniqid() . '-api.custom.localhost';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
// We should ideally assert 400, but server allows unknown domains, and serves API by default
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/versions');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(APP_VERSION_STABLE, $response['body']['server']);
|
||||
|
||||
$ruleId = $this->setupAPIRule($domain);
|
||||
|
||||
$this->assertNotEmpty($ruleId);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/versions');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(APP_VERSION_STABLE, $response['body']['server']);
|
||||
|
||||
$this->cleanupRule($ruleId);
|
||||
|
||||
$rule = $this->createAPIRule('http://' . $domain);
|
||||
$this->assertEquals(400, $rule['headers']['status-code']);
|
||||
|
||||
$rule = $this->createAPIRule('https://' . $domain);
|
||||
$this->assertEquals(400, $rule['headers']['status-code']);
|
||||
|
||||
// Unexpected I would say, but it is the current behaviour
|
||||
$rule = $this->createAPIRule('wss://' . $domain);
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
$this->cleanupRule($rule['body']['$id']);
|
||||
|
||||
// Unexpected I would say, but it is the current behaviour
|
||||
$rule = $this->createAPIRule($domain . '/some-path');
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
$this->cleanupRule($rule['body']['$id']);
|
||||
}
|
||||
|
||||
public function testCreateRedirectRule(): void
|
||||
{
|
||||
$domain = \uniqid() . '-redirect.custom.localhost';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://appwrite');
|
||||
$proxyClient->addHeader('x-appwrite-hostname', $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
$ruleId = $this->setupRedirectRule($domain, 'jsonplaceholder.typicode.com');
|
||||
$this->assertNotEmpty($ruleId);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(1, $response['body']['id']);
|
||||
|
||||
$this->cleanupRule($ruleId);
|
||||
}
|
||||
|
||||
public function testCreateFunctionRule(): void
|
||||
{
|
||||
$domain = \uniqid() . '-function.custom.localhost';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://appwrite');
|
||||
$proxyClient->addHeader('x-appwrite-hostname', $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ping');
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
$setup = $this->setupFunction();
|
||||
$functionId = $setup['functionId'];
|
||||
$deploymentId = $setup['deploymentId'];
|
||||
|
||||
$this->assertNotEmpty($functionId);
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$ruleId = $this->setupFunctionRule($domain, $functionId);
|
||||
$this->assertNotEmpty($ruleId);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ping');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($functionId, $response['body']['APPWRITE_FUNCTION_ID']);
|
||||
|
||||
$this->cleanupRule($ruleId);
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
|
||||
$this->assertEventually(function () use ($functionId, $deploymentId) {
|
||||
$rules = $this->listRules([
|
||||
'queries' => [
|
||||
Query::limit(1)->toString(),
|
||||
Query::equal('type', ['deployment'])->toString(),
|
||||
Query::equal('automation', ['function=' . $functionId])->toString()
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(0, $rules['body']['total']);
|
||||
$this->assertCount(0, $rules['body']['rules']);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'queries' => [
|
||||
Query::limit(1)->toString(),
|
||||
Query::equal('type', ['deployment'])->toString(),
|
||||
Query::equal('value', [$deploymentId])->toString()
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(0, $rules['body']['total']);
|
||||
$this->assertCount(0, $rules['body']['rules']);
|
||||
});
|
||||
}
|
||||
|
||||
public function testCreateSiteRule(): void
|
||||
{
|
||||
$domain = \uniqid() . '-site.custom.localhost';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://appwrite');
|
||||
$proxyClient->addHeader('x-appwrite-hostname', $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/contact');
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
$setup = $this->setupSite();
|
||||
$siteId = $setup['siteId'];
|
||||
$deploymentId = $setup['deploymentId'];
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$ruleId = $this->setupSiteRule($domain, $siteId);
|
||||
$this->assertNotEmpty($ruleId);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/contact');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString('Contact page', $response['body']);
|
||||
|
||||
$this->cleanupRule($ruleId);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
|
||||
$this->assertEventually(function () use ($siteId, $deploymentId) {
|
||||
$rules = $this->listRules([
|
||||
'queries' => [
|
||||
Query::limit(1)->toString(),
|
||||
Query::equal('type', ['deployment'])->toString(),
|
||||
Query::equal('automation', ['site=' . $siteId])->toString()
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(0, $rules['body']['total']);
|
||||
$this->assertCount(0, $rules['body']['rules']);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'queries' => [
|
||||
Query::limit(1)->toString(),
|
||||
Query::equal('type', ['deployment'])->toString(),
|
||||
Query::equal('value', [$deploymentId])->toString()
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(0, $rules['body']['total']);
|
||||
$this->assertCount(0, $rules['body']['rules']);
|
||||
});
|
||||
}
|
||||
|
||||
public function testCreatSiteBranchRule(): void
|
||||
{
|
||||
$domain = \uniqid() . '-site-branch.custom.localhost';
|
||||
|
||||
$setup = $this->setupSite();
|
||||
$siteId = $setup['siteId'];
|
||||
$deploymentId = $setup['deploymentId'];
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$ruleId = $this->setupSiteRule($domain, $siteId, 'dev');
|
||||
$this->assertNotEmpty($ruleId);
|
||||
|
||||
$rule = $this->getRule($ruleId);
|
||||
$this->assertEquals(200, $rule['headers']['status-code']);
|
||||
$this->assertEquals('branch=dev', $rule['body']['automation']);
|
||||
|
||||
$this->cleanupRule($ruleId);
|
||||
}
|
||||
|
||||
public function testUpdateRule(): void
|
||||
{
|
||||
// Create function appwrite-network domain
|
||||
$domain = \uniqid() . '-cname-api.' . App::getEnv('_APP_DOMAIN_FUNCTIONS');
|
||||
|
||||
$rule = $this->createAPIRule($domain);
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
$this->assertEquals('verified', $rule['body']['status']);
|
||||
|
||||
$this->cleanupRule($rule['body']['$id']);
|
||||
|
||||
// Create site appwrite-network domain
|
||||
$domain = \uniqid() . '-cname-api.' . App::getEnv('_APP_DOMAIN_SITES');
|
||||
|
||||
$rule = $this->createAPIRule($domain);
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
$this->assertEquals('verified', $rule['body']['status']);
|
||||
|
||||
$this->cleanupRule($rule['body']['$id']);
|
||||
|
||||
// Create + update
|
||||
$domain = \uniqid() . '-cname-api.custom.localhost';
|
||||
|
||||
$rule = $this->createAPIRule($domain);
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
$this->assertEquals('created', $rule['body']['status']);
|
||||
|
||||
$ruleId = $rule['body']['$id'];
|
||||
|
||||
$rule = $this->updateRuleVerification($ruleId);
|
||||
$this->assertEquals(401, $rule['headers']['status-code']);
|
||||
|
||||
$this->cleanupRule($ruleId);
|
||||
}
|
||||
|
||||
public function testGetRule()
|
||||
{
|
||||
$domain = \uniqid() . '-get.custom.localhost';
|
||||
$ruleId = $this->setupAPIRule($domain);
|
||||
|
||||
$this->assertNotEmpty($ruleId);
|
||||
|
||||
$rule = $this->getRule($ruleId);
|
||||
$this->assertEquals(200, $rule['headers']['status-code']);
|
||||
$this->assertEquals($domain, $rule['body']['domain']);
|
||||
$this->assertArrayHasKey('$id', $rule['body']);
|
||||
$this->assertArrayHasKey('type', $rule['body']);
|
||||
$this->assertArrayHasKey('value', $rule['body']);
|
||||
$this->assertArrayHasKey('automation', $rule['body']);
|
||||
$this->assertArrayHasKey('status', $rule['body']);
|
||||
$this->assertArrayHasKey('logs', $rule['body']);
|
||||
$this->assertArrayHasKey('renewAt', $rule['body']);
|
||||
|
||||
$this->cleanupRule($ruleId);
|
||||
}
|
||||
|
||||
public function testListRules()
|
||||
{
|
||||
$rules = $this->listRules();
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
foreach ($rules['body']['rules'] as $rule) {
|
||||
$rule = $this->deleteRule($rule['$id']);
|
||||
$this->assertEquals(204, $rule['headers']['status-code']);
|
||||
}
|
||||
|
||||
$rules = $this->listRules();
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(0, $rules['body']['total']);
|
||||
$this->assertCount(0, $rules['body']['rules']);
|
||||
|
||||
$rule1Domain = \uniqid() . '-list1.custom.localhost';
|
||||
$rule1Id = $this->setupAPIRule($rule1Domain);
|
||||
$this->assertNotEmpty($rule1Id);
|
||||
|
||||
$rules = $this->listRules();
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(1, $rules['body']['total']);
|
||||
$this->assertCount(1, $rules['body']['rules']);
|
||||
$this->assertEquals($rule1Domain, $rules['body']['rules'][0]['domain']);
|
||||
$this->assertArrayHasKey('$id', $rules['body']['rules'][0]);
|
||||
$this->assertArrayHasKey('type', $rules['body']['rules'][0]);
|
||||
$this->assertArrayHasKey('value', $rules['body']['rules'][0]);
|
||||
$this->assertArrayHasKey('automation', $rules['body']['rules'][0]);
|
||||
$this->assertArrayHasKey('status', $rules['body']['rules'][0]);
|
||||
$this->assertArrayHasKey('logs', $rules['body']['rules'][0]);
|
||||
$this->assertArrayHasKey('renewAt', $rules['body']['rules'][0]);
|
||||
|
||||
$rule2Domain = \uniqid() . '-list1.custom.localhost';
|
||||
$rule2Id = $this->setupAPIRule($rule2Domain);
|
||||
$this->assertNotEmpty($rule2Id);
|
||||
|
||||
$rules = $this->listRules();
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(2, $rules['body']['total']);
|
||||
$this->assertCount(2, $rules['body']['rules']);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'queries' => [
|
||||
Query::limit(1)->toString()
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(2, $rules['body']['total']);
|
||||
$this->assertCount(1, $rules['body']['rules']);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'queries' => [
|
||||
Query::equal('$id', [$rule1Id])->toString()
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertCount(1, $rules['body']['rules']);
|
||||
$this->assertEquals($rule1Domain, $rules['body']['rules'][0]['domain']);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'queries' => [
|
||||
Query::orderDesc('$id')->toString()
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertCount(2, $rules['body']['rules']);
|
||||
$this->assertEquals($rule2Id, $rules['body']['rules'][0]['$id']);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'queries' => [
|
||||
Query::equal('domain', [$rule2Domain])->toString()
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertCount(1, $rules['body']['rules']);
|
||||
$this->assertEquals($rule2Id, $rules['body']['rules'][0]['$id']);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'search' => $rule1Domain,
|
||||
'queries' => [ Query::orderDesc('$createdAt') ]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$ruleIds = \array_column($rules['body']['rules'], '$id');
|
||||
$this->assertContains($rule1Id, $ruleIds);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'search' => $rule2Domain,
|
||||
'queries' => [ Query::orderDesc('$createdAt') ]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$ruleIds = \array_column($rules['body']['rules'], '$id');
|
||||
$this->assertContains($rule2Id, $ruleIds);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'search' => $rule1Id,
|
||||
'queries' => [ Query::orderDesc('$createdAt') ]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$ruleDomains = \array_column($rules['body']['rules'], 'domain');
|
||||
$this->assertContains($rule1Domain, $ruleDomains);
|
||||
|
||||
$rules = $this->listRules([
|
||||
'search' => $rule2Id,
|
||||
'queries' => [ Query::orderDesc('$createdAt') ]
|
||||
]);
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$ruleDomains = \array_column($rules['body']['rules'], 'domain');
|
||||
$this->assertContains($rule2Domain, $ruleDomains);
|
||||
|
||||
$rules = $this->listRules();
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
foreach ($rules['body']['rules'] as $rule) {
|
||||
$rule = $this->deleteRule($rule['$id']);
|
||||
$this->assertEquals(204, $rule['headers']['status-code']);
|
||||
}
|
||||
|
||||
$rules = $this->listRules();
|
||||
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||
$this->assertEquals(0, $rules['body']['total']);
|
||||
$this->assertCount(0, $rules['body']['rules']);
|
||||
}
|
||||
}
|
||||
|
|
@ -284,13 +284,12 @@ trait SitesBase
|
|||
protected function setupSiteDomain(string $siteId, string $subdomain = ''): string
|
||||
{
|
||||
$subdomain = $subdomain ? $subdomain : ID::unique();
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_SITES', ''),
|
||||
'resourceType' => 'site',
|
||||
'resourceId' => $siteId,
|
||||
'siteId' => $siteId,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $rule['headers']['status-code']);
|
||||
|
|
@ -309,8 +308,8 @@ trait SitesBase
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('resourceId', [$siteId])->toString(),
|
||||
Query::equal('resourceType', ['site'])->toString(),
|
||||
Query::equal('automation', ['site=' . $siteId])->toString(),
|
||||
Query::equal('type', ['deployment'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
|
|
@ -324,7 +323,6 @@ trait SitesBase
|
|||
return $domain;
|
||||
}
|
||||
|
||||
|
||||
protected function getDeploymentDomain(string $deploymentId): string
|
||||
{
|
||||
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||
|
|
@ -332,8 +330,9 @@ trait SitesBase
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('resourceId', [$deploymentId])->toString(),
|
||||
Query::equal('resourceType', ['deployment'])->toString(),
|
||||
Query::equal('value', [$deploymentId])->toString(),
|
||||
Query::equal('type', ['deployment'])->toString(),
|
||||
Query::equal('automation', [''])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class SitesCustomServerTest extends Scope
|
|||
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$rule = $this->setupSiteDomain($siteId);
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/console/resources', [
|
||||
'origin' => 'http://localhost',
|
||||
|
|
@ -88,7 +88,7 @@ class SitesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'type' => 'rules',
|
||||
'value' => $rule,
|
||||
'value' => $domain,
|
||||
]);
|
||||
|
||||
$this->assertEquals(409, $response['headers']['status-code']); // domain unavailable
|
||||
|
|
@ -115,7 +115,7 @@ class SitesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('resourceId', [$siteId])
|
||||
Query::equal('automation', ['site=' . $siteId])
|
||||
]
|
||||
]);
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ class SitesCustomServerTest extends Scope
|
|||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'type' => 'rules',
|
||||
'value' => $rule,
|
||||
'value' => $domain,
|
||||
]);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']); // domain available as site is deleted
|
||||
|
|
@ -273,6 +273,8 @@ class SitesCustomServerTest extends Scope
|
|||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
// This is first Sites test with Proxy
|
||||
// If this fails, it may not be related to variables; but Router flow failing
|
||||
public function testVariablesE2E(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
|
|
@ -1310,17 +1312,15 @@ class SitesCustomServerTest extends Scope
|
|||
|
||||
$siteId2 = $site2['body']['$id'];
|
||||
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([
|
||||
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_SITES', ''),
|
||||
'resourceType' => 'site',
|
||||
'resourceId' => $siteId2,
|
||||
'siteId' => $siteId2,
|
||||
]);
|
||||
|
||||
$this->assertEquals(409, $rule['headers']['status-code']);
|
||||
$this->assertStringContainsString("Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.", $rule['body']['message']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
|
||||
|
|
|
|||
11
tests/resources/sites/static/contact.html
Normal file
11
tests/resources/sites/static/contact.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Contact page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Contact page</h1>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue