From 785332f62c8a24bf7807d13f7fab8b11063ffd5a Mon Sep 17 00:00:00 2001 From: Bubka <858858+Bubka@users.noreply.github.com> Date: Thu, 5 Nov 2020 22:54:49 +0100 Subject: [PATCH] Complete phpunit tests --- database/factories/GroupFactory.php | 24 +++ tests/Feature/AccountsGroupTest.php | 294 ++++++++++++++++++++++++++++ tests/Feature/ProtectDbTest.php | 279 ++++++++++++++++++++++++++ tests/Unit/GroupTest.php | 175 +++++++++++++++++ tests/Unit/QrcodeTest.php | 20 +- tests/Unit/TwoFAccountTest.php | 54 +++++ 6 files changed, 838 insertions(+), 8 deletions(-) create mode 100644 database/factories/GroupFactory.php create mode 100644 tests/Feature/AccountsGroupTest.php create mode 100644 tests/Feature/ProtectDbTest.php create mode 100644 tests/Unit/GroupTest.php diff --git a/database/factories/GroupFactory.php b/database/factories/GroupFactory.php new file mode 100644 index 00000000..c030e27a --- /dev/null +++ b/database/factories/GroupFactory.php @@ -0,0 +1,24 @@ +define(Group::class, function (Faker $faker) { + return [ + 'name' => $faker->word, + ]; +}); diff --git a/tests/Feature/AccountsGroupTest.php b/tests/Feature/AccountsGroupTest.php new file mode 100644 index 00000000..d3ef27fc --- /dev/null +++ b/tests/Feature/AccountsGroupTest.php @@ -0,0 +1,294 @@ +user = factory(User::class)->create(); + $this->twofaccounts = factory(Twofaccount::class, 3)->create(); + $this->group = factory(Group::class)->create(); + } + + + /** + * test 2FAccounts creation associated to a user group via API + * + * @test + */ + public function testCreateAccountWhenDefaultGroupIsASpecificOne() + { + + // Set the default group to the existing one + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'defaultGroup' => $this->group->id, + ]) + ->assertStatus(200); + + // Create the account + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/twofaccounts', [ + 'service' => 'testCreation', + 'account' => 'test@example.org', + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test', + 'icon' => 'test.png', + ]) + ->assertStatus(201) + ->assertJsonFragment([ + 'group_id' => $this->group->id + ]); + } + + + /** + * test 2FAccounts creation associated to a user group via API + * + * @test + */ + public function testCreateAccountWhenDefaultGroupIsSetToActiveOne() + { + + // Set the default group as the active one + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'defaultGroup' => -1, + ]) + ->assertStatus(200); + + // Set the active group + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'activeGroup' => 1, + ]) + ->assertStatus(200); + + // Create the account + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/twofaccounts', [ + 'service' => 'testCreation', + 'account' => 'test@example.org', + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test', + 'icon' => 'test.png', + ]) + ->assertStatus(201) + ->assertJsonFragment([ + 'group_id' => 1 + ]); + } + + + /** + * test 2FAccounts creation associated to a user group via API + * + * @test + */ + public function testCreateAccountWhenDefaultIsNoGroup() + { + + // Set the default group to No group + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'defaultGroup' => 0, + ]) + ->assertStatus(200); + + // Create the account + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/twofaccounts', [ + 'service' => 'testCreation', + 'account' => 'test@example.org', + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test', + 'icon' => 'test.png', + ]) + ->assertStatus(201) + ->assertJsonMissing([ + 'group_id' => null + ]); + } + + + /** + * test 2FAccounts creation associated to a user group via API + * + * @test + */ + public function testCreateAccountWhenDefaultGroupDoNotExists() + { + + // Set the default group to a non existing one + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'defaultGroup' => 1000, + ]) + ->assertStatus(200); + + // Create the account + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/twofaccounts', [ + 'service' => 'testCreation', + 'account' => 'test@example.org', + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test', + 'icon' => 'test.png', + ]) + ->assertStatus(201) + ->assertJsonMissing([ + 'group_id' => null + ]); + } + + + /** + * test 2FAccounts association with a user group via API + * + * @test + */ + public function testMoveAccountsToGroup() + { + // We associate all 3 accounts to the user group + $response = $this->actingAs($this->user, 'api') + ->json('PATCH', '/api/group/accounts/', [ + 'groupId' => $this->group->id, + 'accountsIds' => [1,2,3] + ]) + ->assertJsonFragment([ + 'id' => $this->group->id, + 'name' => $this->group->name + ]) + ->assertStatus(200); + + // test if the accounts have the correct foreign key + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/1') + ->assertJsonFragment([ + 'group_id' => (string) $this->group->id + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/2') + ->assertJsonFragment([ + 'group_id' => (string) $this->group->id + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/3') + ->assertJsonFragment([ + 'group_id' => (string) $this->group->id + ]); + + // test the accounts count of the user group + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/groups') + ->assertJsonFragment([ + 'twofaccounts_count' => '3' + ] + ); + } + + + /** + * test 2FAccounts are scoped when an active group is set via API + * + * @test + */ + public function testScopedAccounts() + { + // Set the default group to the existing one + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'activeGroup' => $this->group->id, + ]) + ->assertStatus(200); + + // We associate 2 accounts to the group + $response = $this->actingAs($this->user, 'api') + ->json('PATCH', '/api/group/accounts/', [ + 'groupId' => $this->group->id, + 'accountsIds' => [1,2] + ]) + ->assertStatus(200); + + // Test accounts index is scoped with active group + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts') + ->assertJsonCount(2) + ->assertJsonFragment([ + 'id' => 1, + 'id' => 2 + ]); + } + + + /** + * test 2FAccounts association with a missing group via API + * + * @test + */ + public function testMoveAccountsToMissingGroup() + { + $response = $this->actingAs($this->user, 'api') + ->json('PATCH', '/api/group/accounts/', [ + 'groupId' => '1000', + 'accountsIds' => $this->twofaccounts->keys() + ]) + ->assertStatus(404); + } + + + /** + * test 2FAccounts association with the pseudo group via API + * + * @test + */ + public function testMoveAccountsToPseudoGroup() + { + + $response = $this->actingAs($this->user, 'api') + ->json('PATCH', '/api/group/accounts/', [ + 'groupId' => $this->group->id, + 'accountsIds' => [1,2,3] + ]); + + // We associate the first account to the pseudo group + $response = $this->actingAs($this->user, 'api') + ->json('PATCH', '/api/group/accounts/', [ + 'groupId' => 0, + 'accountsIds' => [1] + ]) + ->assertStatus(200); + + + // test if the forein keys are set to NULL + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/1') + ->assertJsonFragment([ + 'group_id' => null + ]); + + // test the accounts count of the group + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/groups') + ->assertJsonFragment([ + 'twofaccounts_count' => '3', // the 3 accounts for 'all' + 'twofaccounts_count' => '2' // the 2 accounts that remain in the user group + ] + ); + + } + +} \ No newline at end of file diff --git a/tests/Feature/ProtectDbTest.php b/tests/Feature/ProtectDbTest.php new file mode 100644 index 00000000..32abc28d --- /dev/null +++ b/tests/Feature/ProtectDbTest.php @@ -0,0 +1,279 @@ +user = factory(User::class)->create(); + $this->twofaccount = factory(Twofaccount::class,)->create([ + 'service' => 'test', + 'account' => 'test@test.com', + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHVVRBGY7UIW&issuer=test', + ]); + $this->twofaccountAlt = factory(Twofaccount::class,)->create([ + 'service' => 'testAlt', + 'account' => 'testAlt@test.com', + 'uri' => 'otpauth://totp/testAlt@test.com?secret=A4GRFHVVRBGY7UIW&issuer=testAlt', + ]); + } + + + /** + * test db encryption via API + * + * @test + */ + public function testDbEncryption() + { + // Encrypt db + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => true, + ]) + ->assertStatus(200); + + // Get the raw encrypted records + $encrypted = DB::table('twofaccounts')->find($this->twofaccount->id); + $encryptedAlt = DB::table('twofaccounts')->find($this->twofaccountAlt->id); + + // Get the accounts via API and check their consistency with raw data + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/' . $this->twofaccount->id) + ->assertStatus(200) + ->assertJsonFragment([ + 'service' => 'test', + 'account' => Crypt::decryptString($encrypted->account), + 'uri' => Crypt::decryptString($encrypted->uri), + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/' . $this->twofaccountAlt->id) + ->assertStatus(200) + ->assertJsonFragment([ + 'service' => 'testAlt', + 'account' => Crypt::decryptString($encryptedAlt->account), + 'uri' => Crypt::decryptString($encryptedAlt->uri), + ]); + } + + + /** + * test Account update on protected DB via API + * + * @test + */ + public function testTwoFAccountUpdateOnProtectedDB() + { + // Encrypt db + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => true, + ]) + ->assertStatus(200); + + // Only the Account field is encrypted + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/twofaccounts/' . $this->twofaccount->id, [ + 'service' => 'testUpdate', + 'account' => 'testUpdate@test.com', + ]) + ->assertStatus(200) + ->assertJsonFragment([ + 'id' => 1, + 'service' => 'testUpdate', + 'account' => 'testUpdate@test.com', + ]); + } + + + /** + * test db encryption via API + * + * @test + */ + public function testPreventDbEncryptionOnDbAlreadyEncrypted() + { + // Encrypt db + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => true, + ]); + + // Set the option again to force another encryption pass + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => true, + ]); + + // Get the account, it should be readable + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/' . $this->twofaccount->id) + ->assertStatus(200) + ->assertJsonFragment([ + 'service' => 'test', + 'account' => 'test@test.com', + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHVVRBGY7UIW&issuer=test', + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/' . $this->twofaccountAlt->id) + ->assertStatus(200) + ->assertJsonFragment([ + 'service' => 'testAlt', + 'account' => 'testAlt@test.com', + 'uri' => 'otpauth://totp/testAlt@test.com?secret=A4GRFHVVRBGY7UIW&issuer=testAlt', + ]); + } + + + /** + * test db deciphering via API + * + * @test + */ + public function testDbDeciphering() + { + // Encrypt db + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => true, + ]); + + // Decipher db + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => false, + ]) + ->assertStatus(200); + + // Get the accounts, they should be readable + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/' . $this->twofaccount->id) + ->assertStatus(200) + ->assertJsonFragment([ + 'service' => 'test', + 'account' => 'test@test.com', + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHVVRBGY7UIW&issuer=test', + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/' . $this->twofaccountAlt->id) + ->assertStatus(200) + ->assertJsonFragment([ + 'service' => 'testAlt', + 'account' => 'testAlt@test.com', + 'uri' => 'otpauth://totp/testAlt@test.com?secret=A4GRFHVVRBGY7UIW&issuer=testAlt', + ]); + } + + + /** + * test Protect DB option not being persisted if encryption fails via API + * + * @test + */ + public function testAbortEncryptionIfSomethingGoesWrong() + { + // Set no APP_KEY to break Laravel encryption capability + config(['app.key' => '']); + + // Decipher db + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => true, + ]) + ->assertStatus(422); + + // Check ProtectDB option is not active + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/settings/options') + ->assertJsonFragment([ + 'useEncryption' => false + ]); + } + + + /** + * test Protect DB option not being persisted if decyphering fails via API + * + * @test + */ + public function testAbortDecipheringIfSomethingGoesWrong() + { + // Encrypt db + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => true, + ]) + ->assertStatus(200); + + // alter the ciphertext to make deciphering impossible + $affected = DB::table('twofaccounts') + ->where('id', 1) + ->update(['account' => 'xxxxxxxxx', 'uri' => 'yyyyyyyyy']); + + // Decipher db + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => false, + ]) + ->assertStatus(422); + + // Check ProtectDB option has been restored + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/settings/options') + ->assertJsonFragment([ + 'useEncryption' => true + ]); + } + + + /** + * test bad payload don't breaks anything via API + * + * @test + */ + public function testBadPayloadDontBreakEncryptedAccountFetching() + { + // Encrypt db + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/settings/options', [ + 'useEncryption' => true, + ]) + ->assertStatus(200); + + // break the payload + DB::table('twofaccounts') + ->where('id', 1) + ->update([ + 'account' => 'IAmYourFather', + 'uri' => 'YouShallNotPass', + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/1') + ->assertStatus(200) + ->assertJsonFragment([ + 'uri' => '*encrypted*', + 'account' => '*encrypted*', + ]); + } + +} \ No newline at end of file diff --git a/tests/Unit/GroupTest.php b/tests/Unit/GroupTest.php new file mode 100644 index 00000000..a6c91aa9 --- /dev/null +++ b/tests/Unit/GroupTest.php @@ -0,0 +1,175 @@ +user = factory(User::class)->create(); + } + + + /** + * test Group display via API + * + * @test + */ + public function testGroupDisplay() + { + + $group = factory(Group::class)->create([ + 'name' => 'My group', + ]); + + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/groups/' . $group->id) + ->assertStatus(200) + ->assertJsonFragment([ + 'name' => 'My group', + ]); + } + + + /** + * test missing Group display via API + * + * @test + */ + public function testMissingGroupDisplay() + { + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/groups/1000') + ->assertStatus(404); + } + + + /** + * test Group creation via API + * + * @test + */ + public function testGroupCreation() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/groups', [ + 'name' => 'My second group', + ]) + ->assertStatus(201) + ->assertJsonFragment([ + 'name' => 'My second group', + ]); + } + + + /** + * test Group creation when fields are empty via API + * + * @test + */ + public function testGroupCreationWithEmptyRequest() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/groups', [ + 'name' => '', + ]) + ->assertStatus(422); + } + + + /** + * test Group update via API + * + * @test + */ + public function testGroupUpdate() + { + $group = factory(Group::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/groups/' . $group->id, [ + 'name' => 'name updated', + ]) + ->assertStatus(200) + ->assertJsonFragment([ + 'name' => 'name updated', + ]); + } + + + /** + * test Group update via API + * + * @test + */ + public function testGroupUpdateOfMissingGroup() + { + $group = factory(Group::class)->create(); + $id = $group->id; + $group->delete(); + + $response = $this->actingAs($this->user, 'api') + ->json('PUT', '/api/groups/' . $id, [ + 'name' => 'testUpdate', + ]) + ->assertStatus(404); + } + + + /** + * test Group index fetching via API + * + * @test + */ + public function testGroupIndexListing() + { + factory(Group::class, 3)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/groups') + ->assertStatus(200) + ->assertJsonCount(4, $key = null) + ->assertJsonStructure([ + '*' => [ + 'id', + 'name', + 'twofaccounts_count', + 'isActive', + ] + ]) + ->assertJsonFragment([ + 'id' => 0, + 'name' => 'All', + ]); + } + + + /** + * test Group deletion via API + * + * @test + */ + public function testGroupDeletion() + { + $group = factory(Group::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('DELETE', '/api/groups/' . $group->id) + ->assertStatus(204); + } +} diff --git a/tests/Unit/QrcodeTest.php b/tests/Unit/QrcodeTest.php index 9632a0b2..3f7fdacf 100644 --- a/tests/Unit/QrcodeTest.php +++ b/tests/Unit/QrcodeTest.php @@ -2,13 +2,15 @@ namespace Tests\Unit; +use App\User; use Zxing\QrReader; +use Tests\TestCase; +use App\TwoFAccount; +use App\Classes\Options; +use Tests\Classes\LocalFile; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; use Illuminate\Foundation\Testing\WithoutMiddleware; -use Tests\TestCase; -use Tests\Classes\LocalFile; -use App\Classes\Options; class QrcodeTest extends TestCase { @@ -32,7 +34,7 @@ public function testQrcodeDecodeWithMissingImage() /** - * test delete an uploaded icon via API + * test decode an invalid QR code uplloaded via API * * @test */ @@ -43,9 +45,10 @@ public function testDecodeInvalidQrcode() $response = $this->withHeaders([ 'Content-Type' => 'multipart/form-data', ]) - ->json('POST', '/api/qrcode/decode', [ - 'qrcode' => $file - ]); + ->json('POST', '/api/qrcode/decode', [ + 'qrcode' => $file, + 'inputFormat' => 'fileUpload' + ]); $response->assertStatus(422); } @@ -91,7 +94,8 @@ public function testDecodeValidQrcode() $response = $this->withHeaders(['Content-Type' => 'multipart/form-data']) ->json('POST', '/api/qrcode/decode', [ - 'qrcode' => $file + 'qrcode' => $file, + 'inputFormat' => 'fileUpload' ]); $response->assertStatus(200) diff --git a/tests/Unit/TwoFAccountTest.php b/tests/Unit/TwoFAccountTest.php index 0a5af97e..67daaa48 100644 --- a/tests/Unit/TwoFAccountTest.php +++ b/tests/Unit/TwoFAccountTest.php @@ -5,6 +5,7 @@ use App\User; use Tests\TestCase; use App\TwoFAccount; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; class TwoFAccountTest extends TestCase @@ -126,6 +127,37 @@ public function testTwoFAccountCreationWithInvalidTOTP() } + /** + * test otpType is null in case of invalid uri via API + * + * @test + */ + public function testOtpTypeIsNullForAccountWithInvalidUri() + { + $response = $this->actingAs($this->user, 'api') + ->json('POST', '/api/twofaccounts', [ + 'service' => 'testCreation', + 'account' => 'test@example.org', + 'uri' => 'otpauth://totp/test@test.com?secret=A4GRFHZVRBGY7UIW&issuer=test', + 'icon' => 'test.png', + ]) + ->assertStatus(201); + + DB::table('twofaccounts') + ->where('id', 1) + ->update([ + 'uri' => 'iCanHasCheeseBurger', + ]); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/twofaccounts/1') + ->assertStatus(200) + ->assertJsonFragment([ + 'otpType' => null, + ]); + } + + /** * test TOTP generation for a given existing account via API * @@ -318,4 +350,26 @@ public function testTwoFAccountReorder() 'orderedIds' => [3,2,1]]) ->assertStatus(200); } + + + /** + * test show QR code via API + * + * @test + */ + public function testShowQRCode() + { + + $twofaccount = factory(TwoFAccount::class)->create(); + + $response = $this->actingAs($this->user, 'api') + ->json('GET', '/api/qrcode/' . $twofaccount->id) + ->assertJsonStructure([ + 'qrcode', + ]) + ->assertStatus(200); + + $this->assertStringStartsWith('data:image/png;base64', $response->getData()->qrcode); + } + }