Merge branch '1.9.x' into migration-refractor

This commit is contained in:
premtsd-code
2026-04-28 20:32:54 +05:30
committed by GitHub
3 changed files with 107 additions and 7 deletions
+5 -3
View File
@@ -327,9 +327,11 @@ return function (Container $container): void {
}
}
$impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', '');
$impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', '');
$impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', '');
// Query params mirror the header fallback pattern used by ?project= and ?devKey=,
// allowing Console to embed impersonation in direct file/image URLs where headers cannot be set.
$impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', (string)$request->getParam('impersonateUserId', ''));
$impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', (string)$request->getParam('impersonateEmail', ''));
$impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', (string)$request->getParam('impersonatePhone', ''));
if (!$user->isEmpty() && $user->getAttribute('impersonator', false)) {
$userDb = ($mode === APP_MODE_ADMIN || $project->getId() === 'console') ? $dbForPlatform : $dbForProject;
+6 -4
View File
@@ -571,10 +571,12 @@ return function (Container $container): void {
}
}
// Impersonation: if current user has impersonator capability and headers are set, act as another user
$impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', '');
$impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', '');
$impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', '');
// Impersonation: if current user has impersonator capability and headers/params are set, act as another user
// Query params mirror the header fallback pattern used by ?project= and ?devKey=,
// allowing Console to embed impersonation in direct file/image URLs where headers cannot be set.
$impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', (string)$request->getParam('impersonateUserId', ''));
$impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', (string)$request->getParam('impersonateEmail', ''));
$impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', (string)$request->getParam('impersonatePhone', ''));
if (!$user->isEmpty() && $user->getAttribute('impersonator', false)) {
$userDb = (APP_MODE_ADMIN === $mode || $project->getId() === 'console') ? $dbForPlatform : $dbForProject;
$targetUser = null;
+96
View File
@@ -2708,6 +2708,102 @@ trait UsersBase
$this->assertIsArray($response['body']['users']);
}
/**
* Test impersonation via URL query params — mirrors the ?project= and ?devKey= pattern.
* Allows Console to embed impersonation in direct file/image URLs where headers cannot be set.
*/
public function testImpersonateByQueryParams(): void
{
$projectId = $this->getProject()['$id'];
$headers = array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders());
$emailA = 'queryparam-impersonator-' . \uniqid() . '@appwrite.io';
$emailB = 'queryparam-target-' . \uniqid() . '@appwrite.io';
$emailC = 'queryparam-target-c-' . \uniqid() . '@appwrite.io';
$phone = '+1' . \rand(1000000000, 9999999999);
$userA = $this->client->call(Client::METHOD_POST, '/users', $headers, [
'userId' => ID::unique(),
'email' => $emailA,
'password' => 'password',
'name' => 'Query Param Impersonator',
]);
$this->assertEquals(201, $userA['headers']['status-code']);
$idA = $userA['body']['$id'];
$userB = $this->client->call(Client::METHOD_POST, '/users', $headers, [
'userId' => ID::unique(),
'email' => $emailB,
'password' => 'password',
'name' => 'Query Param Target',
]);
$this->assertEquals(201, $userB['headers']['status-code']);
$idB = $userB['body']['$id'];
$patch = $this->client->call(Client::METHOD_PATCH, '/users/' . $idA . '/impersonator', $headers, ['impersonator' => true]);
$this->assertEquals(200, $patch['headers']['status-code']);
$session = $this->client->call(Client::METHOD_POST, '/users/' . $idA . '/sessions', $headers);
$this->assertEquals(201, $session['headers']['status-code']);
$sessionSecret = $session['body']['secret'];
$sessionHeaders = [
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
'x-appwrite-session' => $sessionSecret,
];
// Impersonate by user ID via query param
$account = $this->client->call(Client::METHOD_GET, '/account', $sessionHeaders, [
'impersonateUserId' => $idB,
]);
$this->assertEquals(200, $account['headers']['status-code']);
$this->assertEquals($idB, $account['body']['$id']);
$this->assertEquals('Query Param Target', $account['body']['name']);
$this->assertEquals($idA, $account['body']['impersonatorUserId']);
// Impersonate by email via query param
$accountByEmail = $this->client->call(Client::METHOD_GET, '/account', $sessionHeaders, [
'impersonateEmail' => $emailB,
]);
$this->assertEquals(200, $accountByEmail['headers']['status-code']);
$this->assertEquals($idB, $accountByEmail['body']['$id']);
$this->assertEquals($idA, $accountByEmail['body']['impersonatorUserId']);
// Impersonate by phone via query param (update target user with a phone first)
$this->client->call(Client::METHOD_PATCH, '/users/' . $idB . '/phone', $headers, [
'number' => $phone,
]);
$accountByPhone = $this->client->call(Client::METHOD_GET, '/account', $sessionHeaders, [
'impersonatePhone' => $phone,
]);
$this->assertEquals(200, $accountByPhone['headers']['status-code']);
$this->assertEquals($idB, $accountByPhone['body']['$id']);
$this->assertEquals($idA, $accountByPhone['body']['impersonatorUserId']);
// Header takes priority over query param when both are present
$userC = $this->client->call(Client::METHOD_POST, '/users', $headers, [
'userId' => ID::unique(),
'email' => $emailC,
'password' => 'password',
'name' => 'Query Param Target C',
]);
$this->assertEquals(201, $userC['headers']['status-code']);
$idC = $userC['body']['$id'];
$accountHeaderPriority = $this->client->call(
Client::METHOD_GET,
'/account',
array_merge($sessionHeaders, ['x-appwrite-impersonate-user-id' => $idC]),
['impersonateUserId' => $idB]
);
$this->assertEquals(200, $accountHeaderPriority['headers']['status-code']);
$this->assertEquals($idC, $accountHeaderPriority['body']['$id'], 'header must take priority over query param');
}
/**
* Test PATCH /users/:userId/impersonator for non-existent user returns 404
*/