2025-02-11 04:49:22 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Appwrite\Auth;
|
|
|
|
|
|
|
|
|
|
use Ahc\Jwt\JWT;
|
|
|
|
|
use Ahc\Jwt\JWTException;
|
|
|
|
|
use Appwrite\Extend\Exception;
|
|
|
|
|
use Utopia\Config\Config;
|
|
|
|
|
use Utopia\Database\DateTime;
|
|
|
|
|
use Utopia\Database\Document;
|
|
|
|
|
use Utopia\System\System;
|
|
|
|
|
|
|
|
|
|
class Key
|
|
|
|
|
{
|
|
|
|
|
public function __construct(
|
|
|
|
|
protected string $projectId,
|
|
|
|
|
protected string $type,
|
|
|
|
|
protected string $role,
|
|
|
|
|
protected array $scopes,
|
|
|
|
|
protected string $name,
|
2025-02-12 08:22:08 +00:00
|
|
|
protected bool $expired = false,
|
2025-02-12 11:19:51 +00:00
|
|
|
protected array $disabledMetrics = [],
|
2025-02-20 12:44:22 +00:00
|
|
|
protected bool $hostnameOverride = false,
|
2025-02-21 19:50:43 +00:00
|
|
|
protected bool $bannerDisabled = false,
|
|
|
|
|
protected bool $projectCheckDisabled = false,
|
2025-03-08 11:57:55 +00:00
|
|
|
protected bool $previewAuthDisabled = false,
|
2025-03-26 11:59:05 +00:00
|
|
|
protected bool $deploymentStatusIgnored = false,
|
2025-02-11 04:53:40 +00:00
|
|
|
) {
|
2025-02-11 04:49:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getProjectId(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->projectId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getType(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getRole(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->role;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getScopes(): array
|
|
|
|
|
{
|
|
|
|
|
return $this->scopes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getName(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->name;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-12 08:22:08 +00:00
|
|
|
public function isExpired(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->expired;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-12 11:19:51 +00:00
|
|
|
public function getDisabledMetrics(): array
|
2025-02-11 04:49:22 +00:00
|
|
|
{
|
2025-02-12 11:19:51 +00:00
|
|
|
return $this->disabledMetrics;
|
2025-02-11 04:49:22 +00:00
|
|
|
}
|
|
|
|
|
|
2025-02-20 12:44:22 +00:00
|
|
|
|
|
|
|
|
public function getHostnameOverride(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->hostnameOverride;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-21 19:50:43 +00:00
|
|
|
|
|
|
|
|
public function isBannerDisabled(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->bannerDisabled;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 11:57:55 +00:00
|
|
|
public function isPreviewAuthDisabled(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->previewAuthDisabled;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-26 11:59:05 +00:00
|
|
|
public function isDeploymentStatusIgnored(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->deploymentStatusIgnored;
|
|
|
|
|
}
|
2025-03-08 11:57:55 +00:00
|
|
|
|
2025-02-21 19:50:43 +00:00
|
|
|
public function isProjectCheckDisabled(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->projectCheckDisabled;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-11 09:02:20 +00:00
|
|
|
/**
|
|
|
|
|
* Decode the given secret key into a Key object, containing the project ID, type, role, scopes, and name.
|
|
|
|
|
* Can be a stored API key or a dynamic key (JWT).
|
|
|
|
|
*
|
|
|
|
|
* @param Document $project
|
|
|
|
|
* @param string $key
|
|
|
|
|
* @return Key
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
2025-02-11 04:49:22 +00:00
|
|
|
public static function decode(
|
|
|
|
|
Document $project,
|
|
|
|
|
string $key
|
2025-02-11 04:53:40 +00:00
|
|
|
): Key {
|
2025-02-11 04:49:22 +00:00
|
|
|
if (\str_contains($key, '_')) {
|
|
|
|
|
[$type, $secret] = \explode('_', $key, 2);
|
|
|
|
|
} else {
|
|
|
|
|
$type = API_KEY_STANDARD;
|
|
|
|
|
$secret = $key;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-17 20:44:31 +00:00
|
|
|
$role = USER_ROLE_APPS;
|
2025-02-11 04:49:22 +00:00
|
|
|
$roles = Config::getParam('roles', []);
|
2025-03-17 20:44:31 +00:00
|
|
|
$scopes = $roles[USER_ROLE_APPS]['scopes'] ?? [];
|
2025-02-12 08:22:08 +00:00
|
|
|
$expired = false;
|
2025-02-11 04:49:22 +00:00
|
|
|
|
|
|
|
|
$guestKey = new Key(
|
|
|
|
|
$project->getId(),
|
|
|
|
|
$type,
|
2025-03-17 20:44:31 +00:00
|
|
|
USER_ROLE_GUESTS,
|
|
|
|
|
$roles[USER_ROLE_GUESTS]['scopes'] ?? [],
|
2025-02-11 04:49:22 +00:00
|
|
|
'UNKNOWN'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
switch ($type) {
|
|
|
|
|
case API_KEY_DYNAMIC:
|
|
|
|
|
$jwtObj = new JWT(
|
|
|
|
|
key: System::getEnv('_APP_OPENSSL_KEY_V1'),
|
|
|
|
|
algo: 'HS256',
|
|
|
|
|
maxAge: 86400,
|
|
|
|
|
leeway: 0
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$payload = $jwtObj->decode($secret);
|
|
|
|
|
} catch (JWTException) {
|
2025-02-12 08:22:08 +00:00
|
|
|
$expired = true;
|
2025-02-11 04:49:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$name = $payload['name'] ?? 'Dynamic Key';
|
|
|
|
|
$projectId = $payload['projectId'] ?? '';
|
2025-02-12 11:19:51 +00:00
|
|
|
$disabledMetrics = $payload['disabledMetrics'] ?? [];
|
2025-02-20 12:44:22 +00:00
|
|
|
$hostnameOverride = $payload['hostnameOverride'] ?? false;
|
2025-02-21 19:50:43 +00:00
|
|
|
$bannerDisabled = $payload['bannerDisabled'] ?? false;
|
|
|
|
|
$projectCheckDisabled = $payload['projectCheckDisabled'] ?? false;
|
2025-03-08 11:57:55 +00:00
|
|
|
$previewAuthDisabled = $payload['previewAuthDisabled'] ?? false;
|
2025-03-26 11:59:05 +00:00
|
|
|
$deploymentStatusIgnored = $payload['deploymentStatusIgnored'] ?? false;
|
2025-02-11 04:49:22 +00:00
|
|
|
$scopes = \array_merge($payload['scopes'] ?? [], $scopes);
|
|
|
|
|
|
2025-02-21 19:50:43 +00:00
|
|
|
if (!$projectCheckDisabled && $projectId !== $project->getId()) {
|
2025-02-11 04:49:22 +00:00
|
|
|
return $guestKey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Key(
|
|
|
|
|
$projectId,
|
|
|
|
|
$type,
|
|
|
|
|
$role,
|
|
|
|
|
$scopes,
|
|
|
|
|
$name,
|
2025-02-12 08:22:08 +00:00
|
|
|
$expired,
|
2025-02-20 12:44:22 +00:00
|
|
|
$disabledMetrics,
|
2025-02-21 19:50:43 +00:00
|
|
|
$hostnameOverride,
|
|
|
|
|
$bannerDisabled,
|
2025-03-08 11:57:55 +00:00
|
|
|
$projectCheckDisabled,
|
2025-03-26 11:59:05 +00:00
|
|
|
$previewAuthDisabled,
|
|
|
|
|
$deploymentStatusIgnored
|
2025-02-11 04:49:22 +00:00
|
|
|
);
|
|
|
|
|
case API_KEY_STANDARD:
|
|
|
|
|
$key = $project->find(
|
|
|
|
|
key: 'secret',
|
|
|
|
|
find: $key,
|
|
|
|
|
subject: 'keys'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!$key) {
|
|
|
|
|
return $guestKey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$expire = $key->getAttribute('expire');
|
|
|
|
|
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
|
2025-02-12 08:22:08 +00:00
|
|
|
$expired = true;
|
2025-02-11 04:49:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$name = $key->getAttribute('name', 'UNKNOWN');
|
|
|
|
|
$scopes = \array_merge($key->getAttribute('scopes', []), $scopes);
|
|
|
|
|
|
|
|
|
|
return new Key(
|
|
|
|
|
$project->getId(),
|
|
|
|
|
$type,
|
|
|
|
|
$role,
|
|
|
|
|
$scopes,
|
2025-02-12 08:22:08 +00:00
|
|
|
$name,
|
|
|
|
|
$expired
|
2025-02-11 04:49:22 +00:00
|
|
|
);
|
|
|
|
|
default:
|
|
|
|
|
return $guestKey;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-11 04:53:40 +00:00
|
|
|
}
|