Merge pull request #11339 from appwrite/fix-org-keys

fix: org keys auth
This commit is contained in:
Matej Bačo
2026-02-16 18:22:12 +01:00
committed by GitHub
5 changed files with 82 additions and 5 deletions
+34 -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();
+3
View File
@@ -1304,6 +1304,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];
@@ -1318,6 +1319,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
@@ -738,6 +738,7 @@ services:
depends_on:
- redis
- maildev
- ${_APP_DB_HOST:-mariadb}
# - smtp
environment:
- _APP_ENV
@@ -750,6 +751,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
@@ -5376,7 +5376,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,
@@ -5384,8 +5384,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