1
0
Fork 0
mirror of synced 2024-07-01 12:40:34 +12:00

Merge pull request #3641 from appwrite/feat-improve-keys

feat: add new attributes for API Keys
This commit is contained in:
Christy Jacob 2022-08-30 20:46:50 +02:00 committed by GitHub
commit a87724ed1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 158 additions and 22 deletions

View file

@ -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' => [],
],
],
],

View file

@ -865,6 +865,8 @@ App::post('/v1/projects/:projectId/keys')
'name' => $name,
'scopes' => $scopes,
'expire' => $expire,
'sdks' => [],
'accessedAt' => null,
'secret' => \bin2hex(\random_bytes(128)),
]);

View file

@ -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());
}
}
}
}

View file

@ -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'])

View file

@ -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;
});

View file

@ -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'])

View file

@ -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
View file

@ -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

View file

@ -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
])
;
}

View file

@ -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', [

View file

@ -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

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Unit\Stats;
namespace Tests\Unit\Usage;
use Appwrite\Usage\Stats;
use PHPUnit\Framework\TestCase;