diff --git a/src/app/Console/Commands/UserCreate.php b/src/app/Console/Commands/UserCreate.php new file mode 100644 index 0000000..4d1a4fe --- /dev/null +++ b/src/app/Console/Commands/UserCreate.php @@ -0,0 +1,39 @@ + $this->argument('name'), + 'password' => Hash::make($this->argument('password')), + ]); + $this->info("Created {$user->id}"); + return Command::SUCCESS; + } +} diff --git a/src/app/Console/Commands/UserDelete.php b/src/app/Console/Commands/UserDelete.php new file mode 100644 index 0000000..3097b58 --- /dev/null +++ b/src/app/Console/Commands/UserDelete.php @@ -0,0 +1,36 @@ +argument('id')); + $this->info("Deleting {$user->name}"); + $user->delete(); + return Command::SUCCESS; + } +} diff --git a/src/app/Console/Commands/UserList.php b/src/app/Console/Commands/UserList.php new file mode 100644 index 0000000..ca02a60 --- /dev/null +++ b/src/app/Console/Commands/UserList.php @@ -0,0 +1,37 @@ +each( function ($user) { + $this->info($user); + }); + return Command::SUCCESS; + } +} diff --git a/src/app/Models/License.php b/src/app/Models/License.php index 94bb66b..c2f5bb2 100644 --- a/src/app/Models/License.php +++ b/src/app/Models/License.php @@ -1,94 +1,93 @@ */ protected $fillable = [ 'key_id', 'product_id', 'num_licenses', 'idempotency_key', 'creation_date', 'expiration_date', 'body', 'owner_id', 'nfr', 'test_license', 'status' ]; /** * The attributes that should be hidden for serialization. * * @var array */ protected $hidden = [ - // 'password', - // 'remember_token', + 'body', ]; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'creation_date' => 'datetime', 'expiration_date' => 'datetime', ]; protected $dates = [ 'created_at', 'updated_at', 'deleted_at', 'creation_date', 'expiration_date' ]; public function shortId() { return str_pad($this->key_id, 10, "0", STR_PAD_LEFT); } public static function commentFromBody($body) { $body_parsed = openssl_x509_parse($body); return json_decode( base64_decode($body_parsed['extensions']['nsComment']), true ); } public static function usersFromBody($body) { return intval(self::commentFromBody($body)['users']); } public static function nfrFromBody($body) { $comments = self::commentFromBody($body); if (!array_key_exists('nfr', $comments)) { return false; } return (bool)intval($comments['nfr']); } public static function expirationDateFromBody($body) { $body_parsed = openssl_x509_parse($body); return new Carbon("@{$body_parsed['validTo_time_t']}"); } } diff --git a/src/app/Models/User.php b/src/app/Models/User.php index 23b4063..1b12656 100644 --- a/src/app/Models/User.php +++ b/src/app/Models/User.php @@ -1,44 +1,27 @@ */ protected $fillable = [ 'name', - 'email', 'password', ]; /** * The attributes that should be hidden for serialization. * * @var array */ protected $hidden = [ - 'password', - 'remember_token', - ]; - - /** - * The attributes that should be cast. - * - * @var array - */ - protected $casts = [ - 'email_verified_at' => 'datetime', + 'password' ]; } diff --git a/src/database/factories/UserFactory.php b/src/database/factories/UserFactory.php deleted file mode 100644 index 41f8ae8..0000000 --- a/src/database/factories/UserFactory.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ -class UserFactory extends Factory -{ - /** - * Define the model's default state. - * - * @return array - */ - public function definition() - { - return [ - 'name' => fake()->name(), - 'email' => fake()->unique()->safeEmail(), - 'email_verified_at' => now(), - 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password - 'remember_token' => Str::random(10), - ]; - } - - /** - * Indicate that the model's email address should be unverified. - * - * @return static - */ - public function unverified() - { - return $this->state(fn (array $attributes) => [ - 'email_verified_at' => null, - ]); - } -} diff --git a/src/database/migrations/2014_10_12_000000_create_users_table.php b/src/database/migrations/2014_10_12_000000_create_users_table.php index cf6b776..041b91e 100644 --- a/src/database/migrations/2014_10_12_000000_create_users_table.php +++ b/src/database/migrations/2014_10_12_000000_create_users_table.php @@ -1,36 +1,33 @@ id(); $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); $table->string('password'); - $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } }; diff --git a/src/routes/api.php b/src/routes/api.php index 0f6f3c7..681c8c7 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -1,31 +1,40 @@ 'v2/plesk' ], function () { Route::get('health-check', [Controller::class, 'healthcheck']); // Backwards compat for deployment Route::get('healthcheck', [Controller::class, 'healthcheck']); + } +); + +Route::group( + [ + 'prefix' => 'v2/plesk', + 'middleware' => ['auth.basic:,name'] + ], + function () { Route::post('key', [Controller::class, 'createKey']); Route::get('key/{id}', [Controller::class, 'getKey']); Route::put('key/{id}', [Controller::class, 'updateKey']); Route::delete('key/{id}', [Controller::class, 'deleteKey']); } ); diff --git a/src/tests/Feature/ApiTest.php b/src/tests/Feature/ApiTest.php index e1c9c52..ae9441e 100644 --- a/src/tests/Feature/ApiTest.php +++ b/src/tests/Feature/ApiTest.php @@ -1,404 +1,439 @@ user = User::create([ + 'name' => "test", + 'password' => "simple123", + ]); \App\Models\License::truncate(); } /** * {@inheritDoc} */ public function tearDown(): void { \App\Models\License::truncate(); + User::truncate(); parent::tearDown(); } public function testHealth() { $response = $this->get("api/v2/plesk/health-check"); $response->assertStatus(200); $json = $response->json(); $this->assertSame($json['status'], 'OK'); \config([ 'app.ca_cert' => "invalid", 'app.ca_private_key' => "invalid" ]); $response = $this->get("api/v2/plesk/health-check"); $response->assertStatus(500); $json = $response->json(); $this->assertSame($json['status'], 'FAILURE'); \config([ 'app.ca_cert' => null, 'app.ca_private_key' => null ]); } + public function testCreate() { $post = [ "keyId" => 123456789, "productId" => "PLESK-10", "items" => [ "item" => "cpu_cores", "value" => '5' ], "body" => null, "status" => 'active', "creationDate" => "2016-08-29T09:12:33.000Z", "operationDate" => "2016-08-29T09:12:33.000Z", "expirationDate" => "2016-09-29T09:12:33.000Z", "owner" => [ "id" => 12345 ], "nfr" => false, "activationData" => "" ]; - $response = $this->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); $response->assertStatus(200); // Validate license $license = \App\Models\License::where('key_id', $post['keyId'])->first(); $this->assertNotNull($license); $this->assertSame(\App\Models\License::usersFromBody($license->body), 10); $this->assertSame(\App\Models\License::nfrFromBody($license->body), false); // We can't mock the "now" used by openssl, so we make sure the offset matches instead. Carbon::setTestNow(); $this->assertSame((new Carbon($post['expirationDate']))->diffInDays(\App\Models\License::expirationDateFromBody($license->body), false), Carbon::now()->diffInDays(Carbon::createFromDate(2016, 8, 29))); Carbon::setTestNow(Carbon::createFromDate(2016, 8, 29)); $this->assertSame($license->key_id, $post['keyId']); $this->assertSame($license->product_id, $post['productId']); $this->assertSame($license->num_licenses, 10); $this->assertSame($license->idempotency_key, "123e4567-e89b-12d3-a456-426614174000"); $this->assertTrue($license->creation_date->eq(new Carbon($post['creationDate']))); $this->assertTrue($license->expiration_date->eq(new Carbon($post['expirationDate']))); $this->assertSame($license->owner_id, $post['owner']['id']); $this->assertSame($license->test_license, 0); // Validate returned json object $json = $response->json(); $this->assertSame($json['keyId'], $post['keyId']); $this->assertSame($json['productId'], $post['productId']); // $this->assertSame($json['items'], $post['items']); $this->assertSame($json['body'], $license->body); $this->assertSame($json['status'], $post['status']); $this->assertSame($json['creationDate'], $post['creationDate']); $this->assertSame($json['operationDate'], $post['operationDate']); $this->assertSame($json['expirationDate'], $post['expirationDate']); $this->assertSame($json['owner']['id'], $post['owner']['id']); $this->assertSame($json['nfr'], $post['nfr']); $this->assertSame($json['activationData'], null); // Test idempotency - $response = $this->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); $response->assertStatus(200); // Test recreate key - $response = $this->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174123", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174123", $post); $response->assertStatus(500); $this->assertSame("Error while saving", $response->json()['message']); } + public function testRequireAuth() + { + $post = [ + "keyId" => 123456789, + "productId" => "PLESK-10", + "items" => [ + "item" => "cpu_cores", + "value" => '5' + ], + "body" => null, + "status" => 'active', + "creationDate" => "2016-08-29T09:12:33.000Z", + "operationDate" => "2016-08-29T09:12:33.000Z", + "expirationDate" => "2016-09-29T09:12:33.000Z", + "owner" => [ + "id" => 12345 + ], + "nfr" => false, + "activationData" => "" + ]; + + $response = $this->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); + $response->assertStatus(401); + } + public function testTestMode() { $post = [ "keyId" => 7, "productId" => "PLESK-10", "items" => [ "item" => "cpu_cores", "value" => '5' ], "body" => null, "status" => 'active', "creationDate" => "2016-08-29T09:12:33.000Z", "operationDate" => "2016-08-29T09:12:33.000Z", "expirationDate" => "2016-09-29T09:12:33.000Z", "owner" => [ "id" => 12345 ], "nfr" => false, "activationData" => "" ]; - $response = $this->postJson("api/v2/plesk/key?testMode=true&idempotencyKey=123e4567-e89b-12d3-a456-426614174123", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=true&idempotencyKey=123e4567-e89b-12d3-a456-426614174123", $post); $response->assertStatus(200); $json = $response->json(); $this->assertArrayHasKey('body', $json); $this->assertSame(\App\Models\License::usersFromBody($json['body']), 10); $license = \App\Models\License::where('key_id', $post['keyId'])->first(); $this->assertNotNull($license); $this->assertSame($license->test_license, 1); $response = $this->get("api/v2/plesk/key/{$post['keyId']}?testMode=true"); $response->assertStatus(200); $response = $this->get("api/v2/plesk/key/{$post['keyId']}?testMode=false"); $response->assertStatus(404); } public function testNFRLicense() { $post = [ "keyId" => 7, "productId" => "PLESK-10", "items" => [ "item" => "cpu_cores", "value" => '5' ], "body" => null, "status" => 'active', "creationDate" => "2016-08-29T09:12:33.000Z", "operationDate" => "2016-08-29T09:12:33.000Z", "expirationDate" => "2016-09-29T09:12:33.000Z", "owner" => [ "id" => 12345 ], "nfr" => true, "activationData" => "" ]; - $response = $this->postJson("api/v2/plesk/key?testMode=true&idempotencyKey=123e4567-e89b-12d3-a456-426614174123", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=true&idempotencyKey=123e4567-e89b-12d3-a456-426614174123", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(\App\Models\License::nfrFromBody($json['body']), true); $this->assertSame($json['nfr'], true); } public function testGet() { $post = [ "keyId" => 123456789, "productId" => "PLESK-10", "items" => [ "item" => "cpu_cores", "value" => '5' ], "body" => null, "status" => 'active', "creationDate" => "2016-08-29T09:12:33.000Z", "operationDate" => "2016-08-29T09:12:33.000Z", "expirationDate" => "2016-09-29T09:12:33.000Z", "owner" => [ "id" => 12345 ], "nfr" => false, "activationData" => "" ]; - $response = $this->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); $response->assertStatus(200); $license = \App\Models\License::where('key_id', $post['keyId'])->first(); // Test get $response = $this->get("api/v2/plesk/key/{$post['keyId']}"); $response->assertStatus(200); // Validate returned json object $json = $response->json(); $this->assertSame($json['keyId'], $post['keyId']); $this->assertSame($json['productId'], $post['productId']); $this->assertSame($json['body'], $license->body); $this->assertSame($json['status'], $post['status']); //TODO we're loosing subsecond accuracy because of the PDO interface (I think) $this->assertSame($json['creationDate'], $post['creationDate']); // $this->assertSame($json['operationDate'], $post['operationDate']); $this->assertSame($json['expirationDate'], $post['expirationDate']); $this->assertSame($json['owner']['id'], $post['owner']['id']); $this->assertSame($json['nfr'], $post['nfr']); // $this->assertSame($json['activationData'], null); $response = $this->get("api/v2/plesk/key/invalid"); $response->assertStatus(404); $response = $this->get("api/v2/plesk/key/77777"); $response->assertStatus(404); } public function testTerminate() { $post = [ "keyId" => 123456789, "productId" => "PLESK-10", "items" => [ "item" => "cpu_cores", "value" => '5' ], "body" => null, "status" => 'active', "creationDate" => "2016-08-29T09:12:33.000Z", "operationDate" => "2016-08-29T09:12:33.000Z", "expirationDate" => "2016-09-29T09:12:33.000Z", "owner" => [ "id" => 12345 ], "nfr" => false, "activationData" => "" ]; - $response = $this->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); $response->assertStatus(200); $response = $this->delete("api/v2/plesk/key/{$post['keyId']}"); $response->assertStatus(200); $license = \App\Models\License::withTrashed()->where('key_id', $post['keyId'])->first(); $this->assertSame($license->status, "terminated"); $this->assertTrue($license->trashed()); $response = $this->delete("api/v2/plesk/key/invalid"); $response->assertStatus(404); $response = $this->delete("api/v2/plesk/key/77777"); $response->assertStatus(404); } public function testTerminateTestMode() { $post = [ "keyId" => 123456789, "productId" => "PLESK-10", "items" => [ "item" => "cpu_cores", "value" => '5' ], "body" => null, "status" => 'active', "creationDate" => "2016-08-29T09:12:33.000Z", "operationDate" => "2016-08-29T09:12:33.000Z", "expirationDate" => "2016-09-29T09:12:33.000Z", "owner" => [ "id" => 12345 ], "nfr" => false, "activationData" => "" ]; - $response = $this->postJson("api/v2/plesk/key?testMode=true&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=true&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); $response->assertStatus(200); - $response = $this->delete("api/v2/plesk/key/{$post['keyId']}?testMode=true"); + $response = $this->actingAs($this->user)->delete("api/v2/plesk/key/{$post['keyId']}?testMode=true"); $response->assertStatus(200); $license = \App\Models\License::withTrashed()->where('key_id', $post['keyId'])->first(); $this->assertSame($license->status, "terminated"); $this->assertTrue($license->trashed()); $response = $this->delete("api/v2/plesk/key/invalid?testMode=true"); $response->assertStatus(404); $response = $this->delete("api/v2/plesk/key/77777?testMode=true"); $response->assertStatus(404); } public function testUpdate() { $post = [ "keyId" => 123456789, "productId" => "PLESK-10", "items" => [ "item" => "cpu_cores", "value" => '5' ], "body" => null, "status" => 'active', "creationDate" => "2016-08-29T09:12:33.000Z", "operationDate" => "2016-08-29T09:12:33.000Z", "expirationDate" => "2016-09-29T09:12:33.000Z", "owner" => [ "id" => 12345 ], "nfr" => false, "activationData" => "" ]; - $response = $this->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); $response->assertStatus(200); // The upgrade $post['productId'] = "PLESK-100"; $response = $this->putJson("api/v2/plesk/key/{$post['keyId']}?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174001", $post); $response->assertStatus(200); // Idempotency $response = $this->putJson("api/v2/plesk/key/{$post['keyId']}?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174001", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame($json['productId'], $post['productId']); $license = \App\Models\License::where('key_id', $post['keyId'])->first(); $this->assertSame($license->num_licenses, 100); // Suspend $post['status'] = "suspended"; $response = $this->putJson("api/v2/plesk/key/{$post['keyId']}?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174002", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame($json['status'], $post['status']); $license = \App\Models\License::where('key_id', $post['keyId'])->first(); $this->assertSame($license->status, "suspended"); // Renew after delete $response = $this->delete("api/v2/plesk/key/{$post['keyId']}"); $response->assertStatus(200); $post['status'] = "active"; $response = $this->putJson("api/v2/plesk/key/{$post['keyId']}?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174003", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame($json['status'], $post['status']); $license = \App\Models\License::where('key_id', $post['keyId'])->first(); $this->assertSame($license->status, "active"); } public function testSignature() { $post = [ "keyId" => 123456789, "productId" => "PLESK-10", "items" => [ "item" => "cpu_cores", "value" => '5' ], "body" => null, "status" => 'active', "creationDate" => "2016-08-29T09:12:33.000Z", "operationDate" => "2016-08-29T09:12:33.000Z", "expirationDate" => "2016-09-29T09:12:33.000Z", "owner" => [ "id" => 12345 ], "nfr" => false, "activationData" => "" ]; - $response = $this->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); + $response = $this->actingAs($this->user)->postJson("api/v2/plesk/key?testMode=false&idempotencyKey=123e4567-e89b-12d3-a456-426614174000", $post); $response->assertStatus(200); // Validate license $license = \App\Models\License::where('key_id', $post['keyId'])->first(); $body = $license->body; // $body_parsed = openssl_x509_parse($body); // var_export($body_parsed); $publicKey = openssl_get_publickey(\config("app.ca_cert")); $ret = openssl_x509_verify($body, $publicKey); $this->assertSame($ret, 1); } }