appwrite/tests/unit/Utopia/Database/Documents/UserTest.php
Claude 7aff75ae1c
refactor: convert User::isApp() and User::isPrivileged() from static to instance methods
All call sites now use $user->isApp() and $user->isPrivileged() instance
syntax instead of static User::isApp() / $user::isPrivileged() calls.
Added setUser() to Request class for consistency with Response.

https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
2026-03-26 02:47:56 +00:00

367 lines
15 KiB
PHP

<?php
namespace Tests\Unit\Utopia\Database\Documents;
use Appwrite\Utopia\Database\Documents\User;
use PHPUnit\Framework\TestCase;
use Utopia\Auth\Proofs\Token;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Roles;
class UserTest extends TestCase
{
private $authorization;
public function getAuthorization(): Authorization
{
if (isset($this->authorization)) {
return $this->authorization;
}
$this->authorization = new Authorization();
return $this->authorization;
}
/**
* Reset Roles
*/
public function tearDown(): void
{
$this->getAuthorization()->cleanRoles();
$this->getAuthorization()->addRole(Role::any()->toString());
}
public function testSessionVerify(): void
{
$proofForToken = new Token();
$expireTime1 = 60 * 60 * 24;
$secret = 'secret1';
$hash = $proofForToken->hash($secret);
$tokens1 = [
new Document([
'$id' => ID::custom('token1'),
'secret' => $hash,
'provider' => SESSION_PROVIDER_EMAIL,
'providerUid' => 'test@example.com',
'expire' => DateTime::addSeconds(new \DateTime(), $expireTime1),
]),
new Document([
'$id' => ID::custom('token2'),
'secret' => 'secret2',
'provider' => SESSION_PROVIDER_EMAIL,
'providerUid' => 'test@example.com',
'expire' => DateTime::addSeconds(new \DateTime(), $expireTime1),
]),
];
$expireTime2 = -60 * 60 * 24;
$tokens2 = [
new Document([ // Correct secret and type time, wrong expire time
'$id' => ID::custom('token1'),
'secret' => $hash,
'provider' => SESSION_PROVIDER_EMAIL,
'providerUid' => 'test@example.com',
'expire' => DateTime::addSeconds(new \DateTime(), $expireTime2),
]),
new Document([
'$id' => ID::custom('token2'),
'secret' => 'secret2',
'provider' => SESSION_PROVIDER_EMAIL,
'providerUid' => 'test@example.com',
'expire' => DateTime::addSeconds(new \DateTime(), $expireTime2),
]),
];
$user1 = new User([
'$id' => ID::custom('user1'),
'sessions' => $tokens1,
]);
$user2 = new User([
'$id' => ID::custom('user2'),
'sessions' => $tokens2,
]);
$this->assertEquals('token1', $user1->sessionVerify($secret, $proofForToken));
$this->assertEquals($user1->sessionVerify('false-secret', $proofForToken), false);
$this->assertEquals($user2->sessionVerify($secret, $proofForToken), false);
$this->assertEquals($user2->sessionVerify('false-secret', $proofForToken), false);
}
public function testTokenVerify(): void
{
$proofForToken = new Token();
$secret = 'secret1';
$hash = $proofForToken->hash($secret);
$tokens1 = [
new Document([
'$id' => ID::custom('token1'),
'type' => TOKEN_TYPE_RECOVERY,
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), 60 * 60 * 24)),
'secret' => $hash,
]),
new Document([
'$id' => ID::custom('token2'),
'type' => TOKEN_TYPE_RECOVERY,
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)),
'secret' => 'secret2',
]),
];
$tokens2 = [
new Document([ // Correct secret and type time, wrong expire time
'$id' => ID::custom('token1'),
'type' => TOKEN_TYPE_RECOVERY,
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)),
'secret' => $hash,
]),
new Document([
'$id' => ID::custom('token2'),
'type' => TOKEN_TYPE_RECOVERY,
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)),
'secret' => 'secret2',
]),
];
$tokens3 = [ // Correct secret and expire time, wrong type
new Document([
'$id' => ID::custom('token1'),
'type' => TOKEN_TYPE_INVITE,
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), 60 * 60 * 24)),
'secret' => $hash,
]),
new Document([
'$id' => ID::custom('token2'),
'type' => TOKEN_TYPE_RECOVERY,
'expire' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -60 * 60 * 24)),
'secret' => 'secret2',
]),
];
$user1 = new User([
'$id' => ID::custom('user1'),
'tokens' => $tokens1,
]);
$user2 = new User([
'$id' => ID::custom('user2'),
'tokens' => $tokens2,
]);
$user3 = new User([
'$id' => ID::custom('user3'),
'tokens' => $tokens3,
]);
$this->assertEquals($user1->tokenVerify(TOKEN_TYPE_RECOVERY, $secret, $proofForToken), $tokens1[0]);
$this->assertEquals($user1->tokenVerify(null, $secret, $proofForToken), $tokens1[0]);
$this->assertEquals($user1->tokenVerify(TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false);
$this->assertEquals($user2->tokenVerify(TOKEN_TYPE_RECOVERY, $secret, $proofForToken), false);
$this->assertEquals($user2->tokenVerify(TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false);
$this->assertEquals($user3->tokenVerify(TOKEN_TYPE_RECOVERY, $secret, $proofForToken), false);
$this->assertEquals($user3->tokenVerify(TOKEN_TYPE_RECOVERY, 'false-secret', $proofForToken), false);
}
public function testIsPrivilegedUser(): void
{
$user = new User();
$this->assertEquals(false, $user->isPrivileged([]));
$this->assertEquals(false, $user->isPrivileged([Role::guests()->toString()]));
$this->assertEquals(false, $user->isPrivileged([Role::users()->toString()]));
$this->assertEquals(true, $user->isPrivileged([User::ROLE_ADMIN]));
$this->assertEquals(true, $user->isPrivileged([User::ROLE_DEVELOPER]));
$this->assertEquals(true, $user->isPrivileged([User::ROLE_OWNER]));
$this->assertEquals(false, $user->isPrivileged([User::ROLE_APPS]));
$this->assertEquals(false, $user->isPrivileged([User::ROLE_SYSTEM]));
$this->assertEquals(false, $user->isPrivileged([User::ROLE_APPS, User::ROLE_APPS]));
$this->assertEquals(false, $user->isPrivileged([User::ROLE_APPS, Role::guests()->toString()]));
$this->assertEquals(true, $user->isPrivileged([User::ROLE_OWNER, Role::guests()->toString()]));
$this->assertEquals(true, $user->isPrivileged([User::ROLE_OWNER, User::ROLE_ADMIN, User::ROLE_DEVELOPER]));
}
public function testIsAppUser(): void
{
$user = new User();
$this->assertEquals(false, $user->isApp([]));
$this->assertEquals(false, $user->isApp([Role::guests()->toString()]));
$this->assertEquals(false, $user->isApp([Role::users()->toString()]));
$this->assertEquals(false, $user->isApp([User::ROLE_ADMIN]));
$this->assertEquals(false, $user->isApp([User::ROLE_DEVELOPER]));
$this->assertEquals(false, $user->isApp([User::ROLE_OWNER]));
$this->assertEquals(true, $user->isApp([User::ROLE_APPS]));
$this->assertEquals(false, $user->isApp([User::ROLE_SYSTEM]));
$this->assertEquals(true, $user->isApp([User::ROLE_APPS, User::ROLE_APPS]));
$this->assertEquals(true, $user->isApp([User::ROLE_APPS, Role::guests()->toString()]));
$this->assertEquals(false, $user->isApp([User::ROLE_OWNER, Role::guests()->toString()]));
$this->assertEquals(false, $user->isApp([User::ROLE_OWNER, User::ROLE_ADMIN, User::ROLE_DEVELOPER]));
}
public function testGuestRoles(): void
{
$user = new User([
'$id' => ''
]);
$roles = $user->getRoles($this->getAuthorization());
$this->assertCount(1, $roles);
$this->assertContains(Role::guests()->toString(), $roles);
}
public function testUserRoles(): void
{
$user = new User([
'$id' => ID::custom('123'),
'labels' => [
'vip',
'admin'
],
'emailVerification' => true,
'phoneVerification' => true,
'memberships' => [
[
'$id' => ID::custom('456'),
'teamId' => ID::custom('abc'),
'confirm' => true,
'roles' => [
'administrator',
'moderator'
]
],
[
'$id' => ID::custom('abc'),
'teamId' => ID::custom('def'),
'confirm' => true,
'roles' => [
'guest'
]
]
]
]);
$roles = $user->getRoles($this->getAuthorization());
$this->assertCount(13, $roles);
$this->assertContains(Role::users()->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'))->toString(), $roles);
$this->assertContains(Role::users(Roles::DIMENSION_VERIFIED)->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_VERIFIED)->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'administrator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'moderator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'), 'guest')->toString(), $roles);
$this->assertContains(Role::member(ID::custom('456'))->toString(), $roles);
$this->assertContains(Role::member(ID::custom('abc'))->toString(), $roles);
$this->assertContains('label:vip', $roles);
$this->assertContains('label:admin', $roles);
// Disable all verification
$user['emailVerification'] = false;
$user['phoneVerification'] = false;
$roles = $user->getRoles($this->getAuthorization());
$this->assertContains(Role::users(Roles::DIMENSION_UNVERIFIED)->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_UNVERIFIED)->toString(), $roles);
// Enable single verification type
$user['emailVerification'] = true;
$roles = $user->getRoles($this->getAuthorization());
$this->assertContains(Role::users(Roles::DIMENSION_VERIFIED)->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_VERIFIED)->toString(), $roles);
}
public function testPrivilegedUserRoles(): void
{
$this->getAuthorization()->addRole(User::ROLE_OWNER);
$user = new User([
'$id' => ID::custom('123'),
'emailVerification' => true,
'phoneVerification' => true,
'memberships' => [
[
'$id' => ID::custom('def'),
'teamId' => ID::custom('abc'),
'confirm' => true,
'roles' => [
'administrator',
'moderator'
]
],
[
'$id' => ID::custom('abc'),
'teamId' => ID::custom('def'),
'confirm' => true,
'roles' => [
'guest'
]
]
]
]);
$roles = $user->getRoles($this->getAuthorization());
$this->assertCount(11, $roles);
$this->assertContains(Role::users()->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'))->toString(), $roles);
$this->assertContains(Role::users(Roles::DIMENSION_VERIFIED)->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_VERIFIED)->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'administrator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'moderator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'), 'guest')->toString(), $roles);
$this->assertContains(Role::member(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::member(ID::custom('abc'))->toString(), $roles);
}
public function testAppUserRoles(): void
{
$this->getAuthorization()->addRole(User::ROLE_APPS);
$user = new User([
'$id' => ID::custom('123'),
'memberships' => [
[
'$id' => ID::custom('def'),
'teamId' => ID::custom('abc'),
'confirm' => true,
'roles' => [
'administrator',
'moderator'
]
],
[
'$id' => ID::custom('abc'),
'teamId' => ID::custom('def'),
'confirm' => true,
'roles' => [
'guest'
]
]
]
]);
$roles = $user->getRoles($this->getAuthorization());
$this->assertCount(7, $roles);
$this->assertNotContains(Role::users()->toString(), $roles);
$this->assertNotContains(Role::user(ID::custom('123'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'administrator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('abc'), 'moderator')->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::team(ID::custom('def'), 'guest')->toString(), $roles);
$this->assertContains(Role::member(ID::custom('def'))->toString(), $roles);
$this->assertContains(Role::member(ID::custom('abc'))->toString(), $roles);
}
}