Merge pull request #3641 from appwrite/feat-improve-keys
feat: add new attributes for API Keys
This commit is contained in:
commit
a87724ed1a
|
@ -1003,6 +1003,28 @@ $collections = [
|
|||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('accessedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('sdks'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
@ -1012,6 +1034,13 @@ $collections = [
|
|||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_accessedAt',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['accessedAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
@ -865,6 +865,8 @@ App::post('/v1/projects/:projectId/keys')
|
|||
'name' => $name,
|
||||
'scopes' => $scopes,
|
||||
'expire' => $expire,
|
||||
'sdks' => [],
|
||||
'accessedAt' => null,
|
||||
'secret' => \bin2hex(\random_bytes(128)),
|
||||
]);
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ use Appwrite\Utopia\Request\Filters\V12 as RequestV12;
|
|||
use Appwrite\Utopia\Request\Filters\V13 as RequestV13;
|
||||
use Appwrite\Utopia\Request\Filters\V14 as RequestV14;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
|
@ -47,7 +48,8 @@ App::init()
|
|||
->inject('user')
|
||||
->inject('locale')
|
||||
->inject('clients')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients) {
|
||||
->inject('servers')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients, array $servers) {
|
||||
/*
|
||||
* Request format
|
||||
*/
|
||||
|
@ -303,6 +305,28 @@ App::init()
|
|||
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
|
||||
$accessedAt = $key->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCCESS)) > $accessedAt) {
|
||||
$key->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
|
||||
if ($sdkValidator->isValid($sdk)) {
|
||||
$sdks = $key->getAttribute('sdks', []);
|
||||
if (!in_array($sdk, $sdks)) {
|
||||
array_push($sdks, $sdk);
|
||||
$key->setAttribute('sdks', $sdks);
|
||||
|
||||
/** Update access time as well */
|
||||
$key->setAttribute('accessedAt', Datetime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use Utopia\Database\Document;
|
|||
use Appwrite\Network\Validator\Host;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\App;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Integer;
|
||||
|
@ -395,6 +396,35 @@ App::get('/v1/mock/tests/general/empty')
|
|||
$response->noContent();
|
||||
});
|
||||
|
||||
/** Endpoint to test if required headers are sent from the SDK */
|
||||
App::get('/v1/mock/tests/general/headers')
|
||||
->desc('Get headers')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'headers')
|
||||
->label('sdk.description', 'Return headers from the request')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_MOCK)
|
||||
->label('sdk.mock', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$res = [
|
||||
'x-sdk-name' => $request->getHeader('x-sdk-name'),
|
||||
'x-sdk-platform' => $request->getHeader('x-sdk-platform'),
|
||||
'x-sdk-language' => $request->getHeader('x-sdk-language'),
|
||||
'x-sdk-version' => $request->getHeader('x-sdk-version'),
|
||||
];
|
||||
$res = array_map(function ($key, $value) {
|
||||
return $key . ': ' . $value;
|
||||
}, array_keys($res), $res);
|
||||
$res = implode("; ", $res);
|
||||
|
||||
$response->dynamic(new Document(['result' => $res]), Response::MODEL_MOCK);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/400-error')
|
||||
->desc('400 Error')
|
||||
->groups(['mock'])
|
||||
|
|
12
app/init.php
12
app/init.php
|
@ -90,6 +90,7 @@ const APP_LIMIT_COMPRESSION = 20000000; //20MB
|
|||
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
|
||||
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
|
||||
const APP_LIMIT_SUBQUERY = 1000;
|
||||
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 402;
|
||||
const APP_VERSION_STABLE = '0.15.3';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
|
@ -1015,3 +1016,14 @@ App::setResource('sms', function () {
|
|||
default => null
|
||||
};
|
||||
});
|
||||
|
||||
App::setResource('servers', function () {
|
||||
$platforms = Config::getParam('platforms');
|
||||
$server = $platforms[APP_PLATFORM_SERVER];
|
||||
|
||||
$languages = array_map(function ($language) {
|
||||
return strtolower($language['name']);
|
||||
}, $server['languages']);
|
||||
|
||||
return $languages;
|
||||
});
|
||||
|
|
|
@ -178,6 +178,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
->setLicense($license)
|
||||
->setLicenseContent($licenseContent)
|
||||
->setVersion($language['version'])
|
||||
->setPlatform($key)
|
||||
->setGitURL($language['url'])
|
||||
->setGitRepo($language['gitUrl'])
|
||||
->setGitRepoName($language['gitRepoName'])
|
||||
|
|
|
@ -77,8 +77,8 @@
|
|||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"appwrite/sdk-generator": "dev-feat-new-headers",
|
||||
"ext-fileinfo": "*",
|
||||
"appwrite/sdk-generator": "dev-master as 0.19.5",
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"swoole/ide-helper": "4.8.9",
|
||||
|
|
28
composer.lock
generated
28
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1145ff29befcc4aa21b5002da0b8319c",
|
||||
"content-hash": "039de21eff3a27955696a9f6f645c548",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
@ -2837,7 +2837,7 @@
|
|||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "dev-master",
|
||||
"version": "dev-feat-new-headers",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
|
@ -2861,7 +2861,6 @@
|
|||
"brianium/paratest": "^6.4",
|
||||
"phpunit/phpunit": "^9.5.21"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@ -2882,7 +2881,7 @@
|
|||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/master"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/feat-new-headers"
|
||||
},
|
||||
"time": "2022-08-29T10:43:33+00:00"
|
||||
},
|
||||
|
@ -3534,16 +3533,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "9.2.16",
|
||||
"version": "9.2.17",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "2593003befdcc10db5e213f9f28814f5aa8ac073"
|
||||
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2593003befdcc10db5e213f9f28814f5aa8ac073",
|
||||
"reference": "2593003befdcc10db5e213f9f28814f5aa8ac073",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8",
|
||||
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3599,7 +3598,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.16"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -3607,7 +3606,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-20T05:26:47+00:00"
|
||||
"time": "2022-08-30T12:24:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
|
@ -5356,14 +5355,7 @@
|
|||
"time": "2022-08-12T06:47:24+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "appwrite/sdk-generator",
|
||||
"version": "9999999-dev",
|
||||
"alias": "0.19.5",
|
||||
"alias_normalized": "0.19.5.0"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"appwrite/sdk-generator": 20
|
||||
|
|
|
@ -58,6 +58,19 @@ class Key extends Model
|
|||
'default' => '',
|
||||
'example' => '919c2d18fb5d4...a2ae413da83346ad2',
|
||||
])
|
||||
->addRule('accessedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Most recent access date in Unix timestamp.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE
|
||||
])
|
||||
->addRule('sdks', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'List of SDK user agents that used this key.',
|
||||
'default' => null,
|
||||
'example' => 'appwrite:flutter',
|
||||
'array' => true
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ class User extends Model
|
|||
->addRule('registration', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'User registration date in Datetime.',
|
||||
'default' => null,
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('status', [
|
||||
|
|
|
@ -851,6 +851,7 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-key' => $keySecret,
|
||||
'x-sdk-name' => 'python'
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
@ -859,6 +860,7 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-key' => $keySecret,
|
||||
'x-sdk-name' => 'php'
|
||||
]), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Arsenal'
|
||||
|
@ -866,6 +868,21 @@ class ProjectsConsoleClientTest extends Scope
|
|||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
/** Check that the API key has been updated */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys/' . $keyId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertCount(2, $response['body']['sdks']);
|
||||
$this->assertContains('python', $response['body']['sdks']);
|
||||
$this->assertContains('php', $response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertNotEmpty($response['body']['accessedAt']);
|
||||
|
||||
// Cleanup
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/keys/' . $keyId, array_merge([
|
||||
|
@ -1183,6 +1200,10 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$this->assertContains('teams.read', $response['body']['scopes']);
|
||||
$this->assertContains('teams.write', $response['body']['scopes']);
|
||||
$this->assertNotEmpty($response['body']['secret']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertEmpty($response['body']['accessedAt']);
|
||||
|
||||
$data = array_merge($data, [
|
||||
'keyId' => $response['body']['$id'],
|
||||
|
@ -1252,6 +1273,10 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$this->assertContains('teams.write', $response['body']['scopes']);
|
||||
$this->assertCount(2, $response['body']['scopes']);
|
||||
$this->assertNotEmpty($response['body']['secret']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertEmpty($response['body']['accessedAt']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
|
@ -1360,6 +1385,10 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$this->assertContains('users.write', $response['body']['scopes']);
|
||||
$this->assertContains('collections.read', $response['body']['scopes']);
|
||||
$this->assertCount(3, $response['body']['scopes']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertEmpty($response['body']['accessedAt']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys/' . $keyId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
@ -1374,6 +1403,10 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$this->assertContains('users.write', $response['body']['scopes']);
|
||||
$this->assertContains('collections.read', $response['body']['scopes']);
|
||||
$this->assertCount(3, $response['body']['scopes']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertEmpty($response['body']['accessedAt']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Stats;
|
||||
namespace Tests\Unit\Usage;
|
||||
|
||||
use Appwrite\Usage\Stats;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
|
Loading…
Reference in a new issue