Merge remote-tracking branch 'origin/1.8.x' into feat-mongodb

This commit is contained in:
Jake Barnby
2026-02-17 16:32:46 +13:00
13 changed files with 166 additions and 17 deletions
+35 -2
View File
@@ -30,6 +30,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Roles;
use Utopia\Http\Http;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
@@ -169,8 +170,10 @@ Http::init()
// Handle special app role case
if ($apiKey->getRole() === User::ROLE_APPS) {
// Disable authorization checks for API keys
$authorization->setDefaultStatus(false);
// Disable authorization checks for project API keys
if (($apiKey->getType() === API_KEY_STANDARD || $apiKey->getType() === API_KEY_DYNAMIC) && $apiKey->getProjectId() === $project->getId()) {
$authorization->setDefaultStatus(false);
}
$user = new User([
'$id' => '',
@@ -247,6 +250,35 @@ Http::init()
$queueForAudits->setUser($user);
}
// Apply permission
if ($apiKey->getType() === API_KEY_ORGANIZATION) {
$authorization->addRole(Role::team($team->getId())->toString());
$authorization->addRole(Role::team($team->getId(), 'owner')->toString());
} elseif ($apiKey->getType() === API_KEY_ACCOUNT) {
$authorization->addRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::users()->toString());
if ($user->getAttribute('emailVerification', false) || $user->getAttribute('phoneVerification', false)) {
$authorization->addRole(Role::user($user->getId(), Roles::DIMENSION_VERIFIED)->toString());
$authorization->addRole(Role::users(Roles::DIMENSION_VERIFIED)->toString());
} else {
$authorization->addRole(Role::user($user->getId(), Roles::DIMENSION_UNVERIFIED)->toString());
$authorization->addRole(Role::users(Roles::DIMENSION_UNVERIFIED)->toString());
}
foreach (\array_filter($user->getAttribute('memberships', []), fn ($membership) => ($membership['confirm'] ?? false) === true) as $nodeMembership) {
$authorization->addRole(Role::team($nodeMembership['teamId'])->toString());
$authorization->addRole(Role::member($nodeMembership->getId())->toString());
foreach (($nodeMembership['roles'] ?? []) as $nodeRole) {
$authorization->addRole(Role::team($nodeMembership['teamId'], $nodeRole)->toString());
}
}
foreach ($user->getAttribute('labels', []) as $nodeLabel) {
$authorization->addRole('label:' . $nodeLabel);
}
}
} // Admin User Authentication
elseif (($project->getId() === 'console' && !$team->isEmpty() && !$user->isEmpty()) || ($project->getId() !== 'console' && !$user->isEmpty() && $mode === APP_MODE_ADMIN)) {
$teamId = $team->getId();
@@ -498,6 +530,7 @@ Http::init()
$queueForMessaging->setProject($project);
$queueForFunctions->setProject($project);
$queueForBuilds->setProject($project);
$queueForMails->setProject($project);
/* Auto-set platforms */
$queueForFunctions->setPlatform($platform);
+3
View File
@@ -1298,6 +1298,7 @@ Http::setResource('team', function (Document $project, Database $dbForPlatform,
} else {
$route = $utopia->match($request);
$path = !empty($route) ? $route->getPath() : $request->getURI();
$orgHeader = $request->getHeader('x-appwrite-organization', '');
if (str_starts_with($path, '/v1/projects/:projectId')) {
$uri = $request->getURI();
$pid = explode('/', $uri)[3];
@@ -1312,6 +1313,8 @@ Http::setResource('team', function (Document $project, Database $dbForPlatform,
$team = $authorization->skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
return $team;
} elseif (!empty($orgHeader)) {
return $authorization->skip(fn () => $dbForPlatform->getDocument('teams', $orgHeader));
}
}
+7
View File
@@ -748,6 +748,7 @@ services:
depends_on:
- redis
- maildev
- ${_APP_DB_HOST:-mariadb}
# - smtp
environment:
- _APP_ENV
@@ -760,6 +761,12 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
@@ -932,16 +932,6 @@ class Builds extends Action
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
}
/** Screenshot site */
if ($resource->getCollection() === 'sites') {
$queueForScreenshots
->setDeploymentId($deployment->getId())
->setProject($project)
->trigger();
Console::log('Site screenshot queued');
}
/** Set auto deploy */
$activateBuild = false;
if ($deployment->getAttribute('activate') === true) {
@@ -1023,6 +1013,16 @@ class Builds extends Action
Console::log('Deployment activated');
}
/** Screenshot site */
if ($resource->getCollection() === 'sites') {
$queueForScreenshots
->setDeploymentId($deployment->getId())
->setProject($project)
->trigger();
Console::log('Site screenshot queued');
}
$this->afterDeploymentSuccess(
$project,
$deployment,
+3 -1
View File
@@ -6,6 +6,7 @@ use Appwrite\Template\Template;
use Exception;
use PHPMailer\PHPMailer\PHPMailer;
use Swoole\Runtime;
use Utopia\Database\Document;
use Utopia\Logger\Log;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
@@ -32,6 +33,7 @@ class Mails extends Action
$this
->desc('Mails worker')
->inject('message')
->inject('project')
->inject('register')
->inject('log')
->callback($this->action(...));
@@ -53,7 +55,7 @@ class Mails extends Action
* @return void
* @throws Exception
*/
public function action(Message $message, Registry $register, Log $log): void
public function action(Message $message, Document $project, Registry $register, Log $log): void
{
Runtime::setHookFlags(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_TCP);
$payload = $message->getPayload() ?? [];
@@ -735,6 +735,7 @@ class Migrations extends Action
];
$queueForMails
->setProject($project)
->setSubject($subject)
->setPreview($preview)
->setBody($emailBody)
@@ -253,6 +253,7 @@ class Webhooks extends Action
->setParam('{{year}}', date("Y"));
$queueForMails
->setProject($project)
->setSubject($subject)
->setPreview($preview)
->setBody($body->render());
+3 -1
View File
@@ -456,6 +456,8 @@ class Response extends SwooleResponse
foreach ($data[$key] as $index => $item) {
if ($item instanceof Document) {
$ruleType = null;
if (\is_array($rule['type'])) {
foreach ($rule['type'] as $type) {
$condition = false;
@@ -474,7 +476,7 @@ class Response extends SwooleResponse
$ruleType = $rule['type'];
}
if (!self::hasModel($ruleType)) {
if ($ruleType === null || !self::hasModel($ruleType)) {
throw new Exception('Missing model for rule: ' . $ruleType);
}
@@ -30,6 +30,10 @@ class AttributeList extends Model
Response::MODEL_ATTRIBUTE_POINT,
Response::MODEL_ATTRIBUTE_LINE,
Response::MODEL_ATTRIBUTE_POLYGON,
Response::MODEL_ATTRIBUTE_VARCHAR,
Response::MODEL_ATTRIBUTE_TEXT,
Response::MODEL_ATTRIBUTE_MEDIUMTEXT,
Response::MODEL_ATTRIBUTE_LONGTEXT,
Response::MODEL_ATTRIBUTE_STRING // needs to be last, since its condition would dominate any other string attribute
],
'description' => 'List of attributes.',
@@ -73,6 +73,10 @@ class Collection extends Model
Response::MODEL_ATTRIBUTE_POINT,
Response::MODEL_ATTRIBUTE_LINE,
Response::MODEL_ATTRIBUTE_POLYGON,
Response::MODEL_ATTRIBUTE_VARCHAR,
Response::MODEL_ATTRIBUTE_TEXT,
Response::MODEL_ATTRIBUTE_MEDIUMTEXT,
Response::MODEL_ATTRIBUTE_LONGTEXT,
Response::MODEL_ATTRIBUTE_STRING, // needs to be last, since its condition would dominate any other string attribute
],
'description' => 'Collection attributes.',
@@ -514,6 +514,63 @@ class DatabasesStringTypesTest extends Scope
/**
* @depends testCreateLongtextAttribute
*/
public function testListStringTypeAttributes(array $data): array
{
$databaseId = $data['databaseId'];
$collectionId = $data['collectionId'];
// Wait for attributes to be created
sleep(2);
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$attributes = $response['body']['attributes'];
$types = array_column($attributes, 'type');
$this->assertContains('varchar', $types);
$this->assertContains('text', $types);
$this->assertContains('mediumtext', $types);
$this->assertContains('longtext', $types);
return $data;
}
/**
* @depends testListStringTypeAttributes
*/
public function testGetCollectionWithStringTypeAttributes(array $data): array
{
$databaseId = $data['databaseId'];
$collectionId = $data['collectionId'];
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$attributes = $response['body']['attributes'];
$types = array_column($attributes, 'type');
$this->assertContains('varchar', $types);
$this->assertContains('text', $types);
$this->assertContains('mediumtext', $types);
$this->assertContains('longtext', $types);
return $data;
}
/**
* @depends testGetCollectionWithStringTypeAttributes
*/
public function testUpdateVarcharAttribute(array $data): array
{
$this->markTestSkipped('Skipped until utopia-php/database updateAttribute supports VARCHAR type');
@@ -5377,7 +5377,7 @@ class ProjectsConsoleClientTest extends Scope
]);
$this->assertEquals(400, $response['headers']['status-code']);
/** Test oauth2 with devKey and now get oauth2 is disabled */
/** Test oauth2 with devKey and now flow works with untrusted URL too */
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
@@ -5385,8 +5385,37 @@ class ProjectsConsoleClientTest extends Scope
], [
'success' => 'https://example.com',
'failure' => 'https://example.com'
]);
$this->assertEquals(200, $response['headers']['status-code']);
], followRedirects: false);
$this->assertEquals(301, $response['headers']['status-code']);
$this->assertArrayHasKey('location', $response['headers']);
$location = $response['headers']['location'];
$locationClient = new Client();
$locationClient->setEndpoint('');
$locationClient->addHeader('x-appwrite-dev-key', $devKey['secret']);
$response = $locationClient->call(Client::METHOD_GET, $location, followRedirects: false);
$this->assertEquals(301, $response['headers']['status-code']);
$this->assertArrayHasKey('location', $response['headers']);
$location = $response['headers']['location'];
$this->assertStringStartsWith('http://appwrite:/v1/account/sessions/oauth2/callback/mock/', $response['headers']['location']);
$response = $locationClient->call(Client::METHOD_GET, $location, followRedirects: false);
$this->assertEquals(301, $response['headers']['status-code']);
$this->assertArrayHasKey('location', $response['headers']);
$location = $response['headers']['location'];
$this->assertStringStartsWith('http://appwrite:/v1/account/sessions/oauth2/mock/redirect', $response['headers']['location']);
$response = $locationClient->call(Client::METHOD_GET, $location, followRedirects: false);
$this->assertEquals(301, $response['headers']['status-code']);
$this->assertSame('https://example.com/#', $response['headers']['location']);
/** Ensure any hostname is allowed */
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, [
@@ -279,6 +279,12 @@ services:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_ADAPTER
- _APP_SMTP_HOST
- _APP_SMTP_PORT