mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-06-25 14:32:09 +02:00
Enhance test coverage
This commit is contained in:
parent
c717e6b279
commit
9c5f18bb46
@ -21,7 +21,7 @@ class WebauthnTwoFAuthUserProvider extends WebAuthnUserProvider
|
|||||||
return $this->validateWebAuthn();
|
return $this->validateWebAuthn();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user disabled the fallback is enabled, we will validate the credential password.
|
// If the user disabled the fallback, we will validate the credential password.
|
||||||
return $user->preferences['useWebauthnOnly'] == false && EloquentUserProvider::validateCredentials($user, $credentials);
|
return $user->preferences['useWebauthnOnly'] == false && EloquentUserProvider::validateCredentials($user, $credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,10 @@ class GroupPolicy
|
|||||||
* @param \App\Models\User $user
|
* @param \App\Models\User $user
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function viewAny(User $user)
|
// public function viewAny(User $user)
|
||||||
{
|
// {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can view the model.
|
* Determine whether the user can view the model.
|
||||||
@ -48,19 +48,19 @@ class GroupPolicy
|
|||||||
* @param \Illuminate\Support\Collection<int, \App\Models\Group> $groups
|
* @param \Illuminate\Support\Collection<int, \App\Models\Group> $groups
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function viewEach(User $user, Group $group, $groups)
|
// public function viewEach(User $user, Group $group, $groups)
|
||||||
{
|
// {
|
||||||
$can = $this->isOwnerOfEach($user, $groups);
|
// $can = $this->isOwnerOfEach($user, $groups);
|
||||||
|
|
||||||
if (! $can) {
|
// if (! $can) {
|
||||||
$ids = $groups->map(function ($group, $key) {
|
// $ids = $groups->map(function ($group, $key) {
|
||||||
return $group->id;
|
// return $group->id;
|
||||||
});
|
// });
|
||||||
Log::notice(sprintf('User ID #%s cannot view all groups in IDs #%s', $user->id, implode(',', $ids->toArray())));
|
// Log::notice(sprintf('User ID #%s cannot view all groups in IDs #%s', $user->id, implode(',', $ids->toArray())));
|
||||||
}
|
// }
|
||||||
|
|
||||||
return $can;
|
// return $can;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can create models.
|
* Determine whether the user can create models.
|
||||||
@ -101,19 +101,19 @@ class GroupPolicy
|
|||||||
* @param \Illuminate\Support\Collection<int, \App\Models\Group> $groups
|
* @param \Illuminate\Support\Collection<int, \App\Models\Group> $groups
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function updateEach(User $user, Group $group, $groups)
|
// public function updateEach(User $user, Group $group, $groups)
|
||||||
{
|
// {
|
||||||
$can = $this->isOwnerOfEach($user, $groups);
|
// $can = $this->isOwnerOfEach($user, $groups);
|
||||||
|
|
||||||
if (! $can) {
|
// if (! $can) {
|
||||||
$ids = $groups->map(function ($group, $key) {
|
// $ids = $groups->map(function ($group, $key) {
|
||||||
return $group->id;
|
// return $group->id;
|
||||||
});
|
// });
|
||||||
Log::notice(sprintf('User ID #%s cannot update all groups in IDs #%s', $user->id, implode(',', $ids->toArray())));
|
// Log::notice(sprintf('User ID #%s cannot update all groups in IDs #%s', $user->id, implode(',', $ids->toArray())));
|
||||||
}
|
// }
|
||||||
|
|
||||||
return $can;
|
// return $can;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can delete the model.
|
* Determine whether the user can delete the model.
|
||||||
@ -141,19 +141,19 @@ class GroupPolicy
|
|||||||
* @param \Illuminate\Support\Collection<int, \App\Models\Group> $groups
|
* @param \Illuminate\Support\Collection<int, \App\Models\Group> $groups
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function deleteEach(User $user, Group $group, $groups)
|
// public function deleteEach(User $user, Group $group, $groups)
|
||||||
{
|
// {
|
||||||
$can = $this->isOwnerOfEach($user, $groups);
|
// $can = $this->isOwnerOfEach($user, $groups);
|
||||||
|
|
||||||
if (! $can) {
|
// if (! $can) {
|
||||||
$ids = $groups->map(function ($group, $key) {
|
// $ids = $groups->map(function ($group, $key) {
|
||||||
return $group->id;
|
// return $group->id;
|
||||||
});
|
// });
|
||||||
Log::notice(sprintf('User ID #%s cannot delete all groups in IDs #%s', $user->id, implode(',', $ids->toArray())));
|
// Log::notice(sprintf('User ID #%s cannot delete all groups in IDs #%s', $user->id, implode(',', $ids->toArray())));
|
||||||
}
|
// }
|
||||||
|
|
||||||
return $can;
|
// return $can;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can restore the model.
|
* Determine whether the user can restore the model.
|
||||||
@ -162,10 +162,10 @@ class GroupPolicy
|
|||||||
* @param \App\Models\Group $group
|
* @param \App\Models\Group $group
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function restore(User $user, Group $group)
|
// public function restore(User $user, Group $group)
|
||||||
{
|
// {
|
||||||
return $this->isOwnerOf($user, $group);
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can permanently delete the model.
|
* Determine whether the user can permanently delete the model.
|
||||||
@ -174,8 +174,8 @@ class GroupPolicy
|
|||||||
* @param \App\Models\Group $group
|
* @param \App\Models\Group $group
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function forceDelete(User $user, Group $group)
|
// public function forceDelete(User $user, Group $group)
|
||||||
{
|
// {
|
||||||
return $this->isOwnerOf($user, $group);
|
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,10 @@ class TwoFAccountPolicy
|
|||||||
* @param \App\Models\User $user
|
* @param \App\Models\User $user
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function viewAny(User $user)
|
// public function viewAny(User $user)
|
||||||
{
|
// {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can view the model.
|
* Determine whether the user can view the model.
|
||||||
@ -162,10 +162,10 @@ class TwoFAccountPolicy
|
|||||||
* @param \App\Models\TwoFAccount $twofaccount
|
* @param \App\Models\TwoFAccount $twofaccount
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function restore(User $user, TwoFAccount $twofaccount)
|
// public function restore(User $user, TwoFAccount $twofaccount)
|
||||||
{
|
// {
|
||||||
return $this->isOwnerOf($user, $twofaccount);
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the user can permanently delete the model.
|
* Determine whether the user can permanently delete the model.
|
||||||
@ -174,8 +174,8 @@ class TwoFAccountPolicy
|
|||||||
* @param \App\Models\TwoFAccount $twofaccount
|
* @param \App\Models\TwoFAccount $twofaccount
|
||||||
* @return \Illuminate\Auth\Access\Response|bool
|
* @return \Illuminate\Auth\Access\Response|bool
|
||||||
*/
|
*/
|
||||||
public function forceDelete(User $user, TwoFAccount $twofaccount)
|
// public function forceDelete(User $user, TwoFAccount $twofaccount)
|
||||||
{
|
// {
|
||||||
return $this->isOwnerOf($user, $twofaccount);
|
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ class TwoFAuthMigrator extends Migrator
|
|||||||
|
|
||||||
if (is_null($json)) {
|
if (is_null($json)) {
|
||||||
Log::error('2FAuth JSON migration data cannot be read');
|
Log::error('2FAuth JSON migration data cannot be read');
|
||||||
throw new InvalidMigrationDataException('2FAS Auth');
|
throw new InvalidMigrationDataException('2FAuth');
|
||||||
}
|
}
|
||||||
|
|
||||||
$twofaccounts = [];
|
$twofaccounts = [];
|
||||||
|
@ -10,6 +10,9 @@ use Tests\FeatureTestCase;
|
|||||||
/**
|
/**
|
||||||
* @covers \App\Api\v1\Controllers\GroupController
|
* @covers \App\Api\v1\Controllers\GroupController
|
||||||
* @covers \App\Api\v1\Resources\GroupResource
|
* @covers \App\Api\v1\Resources\GroupResource
|
||||||
|
* @covers \App\Listeners\ResetUsersPreference
|
||||||
|
* @covers \App\Policies\GroupPolicy
|
||||||
|
* @covers \App\Models\Group
|
||||||
*/
|
*/
|
||||||
class GroupControllerTest extends FeatureTestCase
|
class GroupControllerTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
@ -444,4 +447,27 @@ class GroupControllerTest extends FeatureTestCase
|
|||||||
'message',
|
'message',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_destroy_group_resets_user_preferences()
|
||||||
|
{
|
||||||
|
// Set the default group to a specific one
|
||||||
|
$this->user['preferences->defaultGroup'] = $this->userGroupA->id;
|
||||||
|
// Set the active group
|
||||||
|
$this->user['preferences->activeGroup'] = $this->userGroupA->id;
|
||||||
|
$this->user->save();
|
||||||
|
|
||||||
|
$this->assertEquals($this->userGroupA->id, $this->user->preferences['defaultGroup']);
|
||||||
|
$this->assertEquals($this->userGroupA->id, $this->user->preferences['activeGroup']);
|
||||||
|
|
||||||
|
$this->actingAs($this->user, 'api-guard')
|
||||||
|
->json('DELETE', '/api/v1/groups/' . $this->userGroupA->id);
|
||||||
|
|
||||||
|
$this->user->refresh();
|
||||||
|
|
||||||
|
$this->assertEquals(0, $this->user->preferences['defaultGroup']);
|
||||||
|
$this->assertEquals(0, $this->user->preferences['activeGroup']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,14 @@ use Tests\FeatureTestCase;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\Api\v1\Controllers\TwoFAccountController
|
* @covers \App\Api\v1\Controllers\TwoFAccountController
|
||||||
|
* @covers \App\Api\v1\Resources\TwoFAccountCollection
|
||||||
* @covers \App\Api\v1\Resources\TwoFAccountReadResource
|
* @covers \App\Api\v1\Resources\TwoFAccountReadResource
|
||||||
* @covers \App\Api\v1\Resources\TwoFAccountStoreResource
|
* @covers \App\Api\v1\Resources\TwoFAccountStoreResource
|
||||||
|
* @covers \App\Api\v1\Resources\TwoFAccountExportResource
|
||||||
|
* @covers \App\Api\v1\Resources\TwoFAccountExportCollection
|
||||||
* @covers \App\Providers\MigrationServiceProvider
|
* @covers \App\Providers\MigrationServiceProvider
|
||||||
* @covers \App\Providers\TwoFAuthServiceProvider
|
* @covers \App\Providers\TwoFAuthServiceProvider
|
||||||
|
* @covers \App\Policies\TwoFAccountPolicy
|
||||||
*/
|
*/
|
||||||
class TwoFAccountControllerTest extends FeatureTestCase
|
class TwoFAccountControllerTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
@ -91,6 +95,27 @@ class TwoFAccountControllerTest extends FeatureTestCase
|
|||||||
'counter',
|
'counter',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private const VALID_EXPORT_STRUTURE = [
|
||||||
|
'app',
|
||||||
|
'schema',
|
||||||
|
'datetime',
|
||||||
|
'data' => [
|
||||||
|
'*' => [
|
||||||
|
'otp_type',
|
||||||
|
'account',
|
||||||
|
'service',
|
||||||
|
'icon',
|
||||||
|
'icon_mime',
|
||||||
|
'icon_file',
|
||||||
|
'secret',
|
||||||
|
'digits',
|
||||||
|
'algorithm',
|
||||||
|
'period',
|
||||||
|
'counter',
|
||||||
|
'legacy_uri',
|
||||||
|
], ],
|
||||||
|
];
|
||||||
|
|
||||||
private const JSON_FRAGMENTS_FOR_CUSTOM_TOTP = [
|
private const JSON_FRAGMENTS_FOR_CUSTOM_TOTP = [
|
||||||
'service' => OtpTestData::SERVICE,
|
'service' => OtpTestData::SERVICE,
|
||||||
'account' => OtpTestData::ACCOUNT,
|
'account' => OtpTestData::ACCOUNT,
|
||||||
@ -868,6 +893,65 @@ class TwoFAccountControllerTest extends FeatureTestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_export_returns_json_migration_resource()
|
||||||
|
{
|
||||||
|
$this->twofaccountA = TwoFAccount::factory()->for($this->user)->create(self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP);
|
||||||
|
$this->twofaccountB = TwoFAccount::factory()->for($this->user)->create(self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP);
|
||||||
|
|
||||||
|
$this->actingAs($this->user, 'api-guard')
|
||||||
|
->json('GET', '/api/v1/twofaccounts/export?ids=' . $this->twofaccountA->id . ',' . $this->twofaccountB->id)
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonStructure(self::VALID_EXPORT_STRUTURE)
|
||||||
|
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP)
|
||||||
|
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_export_too_many_ids_returns_bad_request()
|
||||||
|
{
|
||||||
|
TwoFAccount::factory()->count(102)->for($this->user)->create();
|
||||||
|
|
||||||
|
$ids = DB::table('twofaccounts')->where('user_id', $this->user->id)->pluck('id')->implode(',');
|
||||||
|
|
||||||
|
$response = $this->actingAs($this->user, 'api-guard')
|
||||||
|
->json('GET', '/api/v1/twofaccounts/export?ids=' . $ids)
|
||||||
|
->assertStatus(400)
|
||||||
|
->assertJsonStructure([
|
||||||
|
'message',
|
||||||
|
'reason',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_export_missing_twofaccount_returns_existing_ones_only()
|
||||||
|
{
|
||||||
|
$this->twofaccountA = TwoFAccount::factory()->for($this->user)->create(self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP);
|
||||||
|
|
||||||
|
$response = $this->actingAs($this->user, 'api-guard')
|
||||||
|
->json('GET', '/api/v1/twofaccounts/export?ids=' . $this->twofaccountA->id . ',1000')
|
||||||
|
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_export_twofaccount_of_another_user_is_forbidden()
|
||||||
|
{
|
||||||
|
$response = $this->actingAs($this->user, 'api-guard')
|
||||||
|
->json('GET', '/api/v1/twofaccounts/export?ids=' . $this->twofaccountC->id)
|
||||||
|
->assertForbidden()
|
||||||
|
->assertJsonStructure([
|
||||||
|
'message',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
@ -1155,8 +1239,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
|
|||||||
{
|
{
|
||||||
TwoFAccount::factory()->count(3)->for($this->user)->create();
|
TwoFAccount::factory()->count(3)->for($this->user)->create();
|
||||||
|
|
||||||
$ids = DB::table('twofaccounts')->where('user_id', $this->user->id)->pluck('id')->implode(',');
|
|
||||||
|
|
||||||
$response = $this->actingAs($this->user, 'api-guard')
|
$response = $this->actingAs($this->user, 'api-guard')
|
||||||
->json('DELETE', '/api/v1/twofaccounts?ids=' . $this->twofaccountA->id . ',' . $this->twofaccountB->id)
|
->json('DELETE', '/api/v1/twofaccounts?ids=' . $this->twofaccountA->id . ',' . $this->twofaccountB->id)
|
||||||
->assertNoContent();
|
->assertNoContent();
|
||||||
|
@ -106,7 +106,7 @@ class MigrationTestData
|
|||||||
"name": "' . OtpTestData::ACCOUNT . '",
|
"name": "' . OtpTestData::ACCOUNT . '",
|
||||||
"issuer": "' . OtpTestData::SERVICE . '",
|
"issuer": "' . OtpTestData::SERVICE . '",
|
||||||
"note": "",
|
"note": "",
|
||||||
"icon": "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPg0KICAgPGNpcmNsZSBjeD0iNTEyIiBjeT0iNTEyIiByPSI1MTIiIHN0eWxlPSJmaWxsOiMwMDBlOWMiLz4NCiAgIDxwYXRoIGQ9Im03MDAuMiA0NjYuNSA2MS4yLTEwNi4zYzIzLjYgNDEuNiAzNy4yIDg5LjggMzcuMiAxNDEuMSAwIDY4LjgtMjQuMyAxMzEuOS02NC43IDE4MS40SDU3NS44bDQ4LjctODQuNmgtNjQuNGw3NS44LTEzMS43IDY0LjMuMXptLTU1LjQtMTI1LjJMNDQ4LjMgNjgyLjVsLjEuMkgyOTAuMWMtNDAuNS00OS41LTY0LjctMTEyLjYtNjQuNy0xODEuNCAwLTUxLjQgMTMuNi05OS42IDM3LjMtMTQxLjNsMTAyLjUgMTc4LjIgMTEzLjMtMTk3aDE2Ni4zeiIgc3R5bGU9ImZpbGw6I2ZmZiIvPg0KPC9zdmc+DQo=",
|
"icon": "' . OtpTestData::ICON_SVG_DATA_ENCODED . '",
|
||||||
"icon_mime": "image\/svg+xml",
|
"icon_mime": "image\/svg+xml",
|
||||||
"info": {
|
"info": {
|
||||||
"secret": "' . OtpTestData::SECRET . '",
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
@ -463,4 +463,249 @@ class MigrationTestData
|
|||||||
},
|
},
|
||||||
"db": "1rX0ajzsxNbhN2hvnNCMBNooLlzqwz\/LMT3bNEIJjPH+zIvIbA6GVVPHLpna+yvjxLPKVkt1OQig=="
|
"db": "1rX0ajzsxNbhN2hvnNCMBNooLlzqwz\/LMT3bNEIJjPH+zIvIbA6GVVPHLpna+yvjxLPKVkt1OQig=="
|
||||||
}';
|
}';
|
||||||
|
|
||||||
|
const VALID_2FAUTH_JSON_MIGRATION_PAYLOAD = '
|
||||||
|
{
|
||||||
|
"app": "2fauth_v3.4.1",
|
||||||
|
"schema": 1,
|
||||||
|
"datetime": "2022-12-14T14:53:06.173939Z",
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"otp_type": "totp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": null,
|
||||||
|
"icon_mime": null,
|
||||||
|
"icon_file": null,
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"otp_type": "hotp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": null,
|
||||||
|
"icon_mime": null,
|
||||||
|
"icon_file": null,
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": null,
|
||||||
|
"counter": ' . OtpTestData::COUNTER_CUSTOM . ',
|
||||||
|
"legacy_uri": "' . OtpTestData::HOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"otp_type": "steamtotp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::STEAM . '",
|
||||||
|
"icon": null,
|
||||||
|
"icon_mime": null,
|
||||||
|
"icon_file": null,
|
||||||
|
"secret": "' . OtpTestData::STEAM_SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_STEAM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::STEAM_TOTP_URI . '"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}';
|
||||||
|
|
||||||
|
const VALID_2FAUTH_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE = '
|
||||||
|
{
|
||||||
|
"app": "2fauth_v3.4.1",
|
||||||
|
"schema": 1,
|
||||||
|
"datetime": "2022-12-14T14:53:06.173939Z",
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"otp_type": "totp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": null,
|
||||||
|
"icon_mime": null,
|
||||||
|
"icon_file": null,
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"otp_type": "Xotp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": null,
|
||||||
|
"icon_mime": null,
|
||||||
|
"icon_file": null,
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}';
|
||||||
|
|
||||||
|
const VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_SVG_ICON = '
|
||||||
|
{
|
||||||
|
"app": "2fauth_v3.4.1",
|
||||||
|
"schema": 1,
|
||||||
|
"datetime": "2022-12-14T14:53:06.173939Z",
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"otp_type": "totp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": "' . OtpTestData::ICON_SVG . '",
|
||||||
|
"icon_mime": "image\/svg+xml",
|
||||||
|
"icon_file": "' . OtpTestData::ICON_SVG_DATA_ENCODED . '",
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}';
|
||||||
|
|
||||||
|
const VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_JPG_ICON = '
|
||||||
|
{
|
||||||
|
"app": "2fauth_v3.4.1",
|
||||||
|
"schema": 1,
|
||||||
|
"datetime": "2022-12-14T14:53:06.173939Z",
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"otp_type": "totp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": "' . OtpTestData::ICON_JPEG . '",
|
||||||
|
"icon_mime": "image\/svg+xml",
|
||||||
|
"icon_file": "' . OtpTestData::ICON_JPEG_DATA . '",
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}';
|
||||||
|
|
||||||
|
const VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_PNG_ICON = '
|
||||||
|
{
|
||||||
|
"app": "2fauth_v3.4.1",
|
||||||
|
"schema": 1,
|
||||||
|
"datetime": "2022-12-14T14:53:06.173939Z",
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"otp_type": "totp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": "' . OtpTestData::ICON_PNG . '",
|
||||||
|
"icon_mime": "image\/svg+xml",
|
||||||
|
"icon_file": "' . OtpTestData::ICON_PNG_DATA . '",
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}';
|
||||||
|
|
||||||
|
const VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_BMP_ICON = '
|
||||||
|
{
|
||||||
|
"app": "2fauth_v3.4.1",
|
||||||
|
"schema": 1,
|
||||||
|
"datetime": "2022-12-14T14:53:06.173939Z",
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"otp_type": "totp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": "' . OtpTestData::ICON_BMP . '",
|
||||||
|
"icon_mime": "image\/svg+xml",
|
||||||
|
"icon_file": "' . OtpTestData::ICON_BMP_DATA . '",
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}';
|
||||||
|
|
||||||
|
const VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_WEBP_ICON = '
|
||||||
|
{
|
||||||
|
"app": "2fauth_v3.4.1",
|
||||||
|
"schema": 1,
|
||||||
|
"datetime": "2022-12-14T14:53:06.173939Z",
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"otp_type": "totp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": "' . OtpTestData::ICON_WEBP . '",
|
||||||
|
"icon_mime": "image\/svg+xml",
|
||||||
|
"icon_file": "' . OtpTestData::ICON_WEBP_DATA . '",
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}';
|
||||||
|
|
||||||
|
const VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_ICON = '
|
||||||
|
{
|
||||||
|
"app": "2fauth_v3.4.1",
|
||||||
|
"schema": 1,
|
||||||
|
"datetime": "2022-12-14T14:53:06.173939Z",
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"otp_type": "totp",
|
||||||
|
"account": "' . OtpTestData::ACCOUNT . '",
|
||||||
|
"service": "' . OtpTestData::SERVICE . '",
|
||||||
|
"icon": "' . OtpTestData::ICON_PNG . '",
|
||||||
|
"icon_mime": "image\/gif",
|
||||||
|
"icon_file": "' . OtpTestData::ICON_PNG_DATA . '",
|
||||||
|
"secret": "' . OtpTestData::SECRET . '",
|
||||||
|
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
|
||||||
|
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
|
||||||
|
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
|
||||||
|
"counter": null,
|
||||||
|
"legacy_uri": "' . OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . '"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}';
|
||||||
|
|
||||||
|
const INVALID_2FAUTH_JSON_MIGRATION_PAYLOAD = '
|
||||||
|
{
|
||||||
|
"app": "2fauth_v3.4.1",
|
||||||
|
"schema": 1,
|
||||||
|
"datetime": "2022-12-14T14:53:06.173939Z",
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
,
|
||||||
|
]
|
||||||
|
}';
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,8 @@ class OtpTestData
|
|||||||
|
|
||||||
const ICON_SVG_DATA = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><circle cx="512" cy="512" r="512" style="fill:#000e9c"/><path d="m700.2 466.5 61.2-106.3c23.6 41.6 37.2 89.8 37.2 141.1 0 68.8-24.3 131.9-64.7 181.4H575.8l48.7-84.6h-64.4l75.8-131.7 64.3.1zm-55.4-125.2L448.3 682.5l.1.2H290.1c-40.5-49.5-64.7-112.6-64.7-181.4 0-51.4 13.6-99.6 37.3-141.3l102.5 178.2 113.3-197h166.3z" style="fill:#fff"/></svg>';
|
const ICON_SVG_DATA = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><circle cx="512" cy="512" r="512" style="fill:#000e9c"/><path d="m700.2 466.5 61.2-106.3c23.6 41.6 37.2 89.8 37.2 141.1 0 68.8-24.3 131.9-64.7 181.4H575.8l48.7-84.6h-64.4l75.8-131.7 64.3.1zm-55.4-125.2L448.3 682.5l.1.2H290.1c-40.5-49.5-64.7-112.6-64.7-181.4 0-51.4 13.6-99.6 37.3-141.3l102.5 178.2 113.3-197h166.3z" style="fill:#fff"/></svg>';
|
||||||
|
|
||||||
|
const ICON_SVG_DATA_ENCODED = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPg0KICAgPGNpcmNsZSBjeD0iNTEyIiBjeT0iNTEyIiByPSI1MTIiIHN0eWxlPSJmaWxsOiMwMDBlOWMiLz4NCiAgIDxwYXRoIGQ9Im03MDAuMiA0NjYuNSA2MS4yLTEwNi4zYzIzLjYgNDEuNiAzNy4yIDg5LjggMzcuMiAxNDEuMSAwIDY4LjgtMjQuMyAxMzEuOS02NC43IDE4MS40SDU3NS44bDQ4LjctODQuNmgtNjQuNGw3NS44LTEzMS43IDY0LjMuMXptLTU1LjQtMTI1LjJMNDQ4LjMgNjgyLjVsLjEuMkgyOTAuMWMtNDAuNS00OS41LTY0LjctMTEyLjYtNjQuNy0xODEuNCAwLTUxLjQgMTMuNi05OS42IDM3LjMtMTQxLjNsMTAyLjUgMTc4LjIgMTEzLjMtMTk3aDE2Ni4zeiIgc3R5bGU9ImZpbGw6I2ZmZiIvPg0KPC9zdmc+DQo=';
|
||||||
|
|
||||||
const TOTP_FULL_CUSTOM_URI_NO_IMG = 'otpauth://totp/' . self::SERVICE . ':' . self::ACCOUNT . '?secret=' . self::SECRET . '&issuer=' . self::SERVICE . '&digits=' . self::DIGITS_CUSTOM . '&period=' . self::PERIOD_CUSTOM . '&algorithm=' . self::ALGORITHM_CUSTOM;
|
const TOTP_FULL_CUSTOM_URI_NO_IMG = 'otpauth://totp/' . self::SERVICE . ':' . self::ACCOUNT . '?secret=' . self::SECRET . '&issuer=' . self::SERVICE . '&digits=' . self::DIGITS_CUSTOM . '&period=' . self::PERIOD_CUSTOM . '&algorithm=' . self::ALGORITHM_CUSTOM;
|
||||||
|
|
||||||
const TOTP_FULL_CUSTOM_URI = self::TOTP_FULL_CUSTOM_URI_NO_IMG . '&image=' . self::IMAGE;
|
const TOTP_FULL_CUSTOM_URI = self::TOTP_FULL_CUSTOM_URI_NO_IMG . '&image=' . self::IMAGE;
|
||||||
|
@ -7,6 +7,7 @@ use Tests\FeatureTestCase;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\Http\Controllers\Auth\RegisterController
|
* @covers \App\Http\Controllers\Auth\RegisterController
|
||||||
|
* @covers \App\Http\Requests\UserStoreRequest
|
||||||
*/
|
*/
|
||||||
class RegisterControllerTest extends FeatureTestCase
|
class RegisterControllerTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace Tests\Feature\Http\Auth;
|
namespace Tests\Feature\Http\Auth;
|
||||||
|
|
||||||
use App\Facades\Settings;
|
|
||||||
use App\Models\Group;
|
use App\Models\Group;
|
||||||
use App\Models\TwoFAccount;
|
use App\Models\TwoFAccount;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@ -12,6 +11,7 @@ use Tests\FeatureTestCase;
|
|||||||
/**
|
/**
|
||||||
* @covers \App\Http\Controllers\Auth\UserController
|
* @covers \App\Http\Controllers\Auth\UserController
|
||||||
* @covers \App\Http\Middleware\RejectIfDemoMode
|
* @covers \App\Http\Middleware\RejectIfDemoMode
|
||||||
|
* @covers \App\Http\Requests\UserUpdateRequest
|
||||||
*/
|
*/
|
||||||
class UserControllerTest extends FeatureTestCase
|
class UserControllerTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
@ -63,6 +63,33 @@ class UserControllerTest extends FeatureTestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_update_user_with_uppercased_email_returns_success()
|
||||||
|
{
|
||||||
|
$response = $this->actingAs($this->user, 'web-guard')
|
||||||
|
->json('PUT', '/user', [
|
||||||
|
'name' => self::NEW_USERNAME,
|
||||||
|
'email' => strtoupper(self::NEW_EMAIL),
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertExactJson([
|
||||||
|
'name' => self::NEW_USERNAME,
|
||||||
|
'id' => $this->user->id,
|
||||||
|
'email' => self::NEW_EMAIL,
|
||||||
|
'is_admin' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('users', [
|
||||||
|
'name' => self::NEW_USERNAME,
|
||||||
|
'id' => $this->user->id,
|
||||||
|
'email' => self::NEW_EMAIL,
|
||||||
|
'is_admin' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
@ -70,7 +97,7 @@ class UserControllerTest extends FeatureTestCase
|
|||||||
{
|
{
|
||||||
Config::set('2fauth.config.isDemoApp', true);
|
Config::set('2fauth.config.isDemoApp', true);
|
||||||
|
|
||||||
$name = $this->user->name;
|
$name = $this->user->name;
|
||||||
$email = $this->user->email;
|
$email = $this->user->email;
|
||||||
|
|
||||||
$response = $this->actingAs($this->user, 'web-guard')
|
$response = $this->actingAs($this->user, 'web-guard')
|
||||||
@ -88,9 +115,9 @@ class UserControllerTest extends FeatureTestCase
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertDatabaseHas('users', [
|
$this->assertDatabaseHas('users', [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'id' => $this->user->id,
|
'id' => $this->user->id,
|
||||||
'email' => $email,
|
'email' => $email,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ use Tests\FeatureTestCase;
|
|||||||
/**
|
/**
|
||||||
* @covers \App\Http\Controllers\Auth\WebAuthnLoginController
|
* @covers \App\Http\Controllers\Auth\WebAuthnLoginController
|
||||||
* @covers \App\Models\User
|
* @covers \App\Models\User
|
||||||
|
* @covers \App\Extensions\WebauthnTwoFAuthUserProvider
|
||||||
*/
|
*/
|
||||||
class WebAuthnLoginControllerTest extends FeatureTestCase
|
class WebAuthnLoginControllerTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
@ -120,8 +121,8 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
|
|||||||
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE)
|
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE)
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonFragment([
|
->assertJsonFragment([
|
||||||
'message' => 'authenticated',
|
'message' => 'authenticated',
|
||||||
'name' => $this->user->name,
|
'name' => $this->user->name,
|
||||||
])
|
])
|
||||||
->assertJsonStructure([
|
->assertJsonStructure([
|
||||||
'message',
|
'message',
|
||||||
@ -175,6 +176,26 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_legacy_login_is_rejected_when_webauthn_only_is_enable()
|
||||||
|
{
|
||||||
|
$this->user = User::factory()->create([
|
||||||
|
'email' => self::EMAIL,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Set to webauthn only
|
||||||
|
$this->user['preferences->useWebauthnOnly'] = true;
|
||||||
|
$this->user->save();
|
||||||
|
|
||||||
|
$response = $this->json('POST', '/user/login', [
|
||||||
|
'email' => self::EMAIL,
|
||||||
|
'password' => 'password',
|
||||||
|
])
|
||||||
|
->assertUnauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*
|
*
|
||||||
@ -215,8 +236,8 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
|
|||||||
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE)
|
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE)
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonFragment([
|
->assertJsonFragment([
|
||||||
'message' => 'authenticated',
|
'message' => 'authenticated',
|
||||||
'name' => $this->user->name,
|
'name' => $this->user->name,
|
||||||
])
|
])
|
||||||
->assertJsonStructure([
|
->assertJsonStructure([
|
||||||
'message',
|
'message',
|
||||||
@ -289,7 +310,7 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
|
|||||||
false,
|
false,
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
for ($i=0; $i < $throttle - 1; $i++) {
|
for ($i = 0; $i < $throttle - 1; $i++) {
|
||||||
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID);
|
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
54
tests/Feature/Http/Middlewares/AdminOnlyMiddlewareTest.php
Normal file
54
tests/Feature/Http/Middlewares/AdminOnlyMiddlewareTest.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Http\Middlewares;
|
||||||
|
|
||||||
|
use App\Http\Middleware\AdminOnly;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\AuthorizationException;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Tests\FeatureTestCase;
|
||||||
|
|
||||||
|
class AdminOnlyMiddlewareTest extends FeatureTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_users_are_rejected()
|
||||||
|
{
|
||||||
|
$this->expectException(AuthorizationException::class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
|
||||||
|
*/
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user);
|
||||||
|
|
||||||
|
$request = Request::create('/admin', 'GET');
|
||||||
|
$middleware = new AdminOnly;
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_admins_pass()
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
|
||||||
|
*/
|
||||||
|
$admin = User::factory()->administrator()->create();
|
||||||
|
|
||||||
|
$this->actingAs($admin);
|
||||||
|
|
||||||
|
$request = Request::create('/admin', 'GET');
|
||||||
|
$middleware = new AdminOnly;
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->assertNull($response);
|
||||||
|
}
|
||||||
|
}
|
80
tests/Feature/Http/Requests/WebauthnAssertedRequestTest.php
Normal file
80
tests/Feature/Http/Requests/WebauthnAssertedRequestTest.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Http\Requests;
|
||||||
|
|
||||||
|
use App\Http\Requests\WebauthnAssertedRequest;
|
||||||
|
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Http\Requests\WebauthnAssertedRequest
|
||||||
|
*/
|
||||||
|
class WebauthnAssertedRequestTest extends TestCase
|
||||||
|
{
|
||||||
|
use WithoutMiddleware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideValidData
|
||||||
|
*/
|
||||||
|
public function test_valid_data(array $data) : void
|
||||||
|
{
|
||||||
|
$request = new WebauthnAssertedRequest();
|
||||||
|
$validator = Validator::make($data, $request->rules());
|
||||||
|
|
||||||
|
$this->assertFalse($validator->fails());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide Valid data for validation test
|
||||||
|
*/
|
||||||
|
public function provideValidData() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[[
|
||||||
|
'id' => 'string',
|
||||||
|
'rawId' => 'string',
|
||||||
|
'type' => 'string',
|
||||||
|
'response' => [
|
||||||
|
'clientDataJSON' => 'string',
|
||||||
|
'authenticatorData' => 'string',
|
||||||
|
'signature' => 'string',
|
||||||
|
'userHandle' => null,
|
||||||
|
],
|
||||||
|
'email' => 'valid@email.com',
|
||||||
|
]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideInvalidData
|
||||||
|
*/
|
||||||
|
public function test_invalid_data(array $data) : void
|
||||||
|
{
|
||||||
|
$request = new WebauthnAssertedRequest();
|
||||||
|
$validator = Validator::make($data, $request->rules());
|
||||||
|
|
||||||
|
$this->assertTrue($validator->fails());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide invalid data for validation test
|
||||||
|
*/
|
||||||
|
public function provideInvalidData() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[[
|
||||||
|
'email' => '', // required
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'email' => true, // email
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'email' => 0, // email
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'email' => 'sdfsdf@', // email
|
||||||
|
]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
35
tests/Feature/Models/UserModelTest.php
Normal file
35
tests/Feature/Models/UserModelTest.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Models;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Tests\FeatureTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Models\User
|
||||||
|
*/
|
||||||
|
class UserModelTest extends FeatureTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_admin_scope_returns_only_admin()
|
||||||
|
{
|
||||||
|
User::factory()->count(4)->create();
|
||||||
|
|
||||||
|
$firstAdmin = User::factory()->administrator()->create([
|
||||||
|
'name' => 'first',
|
||||||
|
]);
|
||||||
|
$secondAdmin = User::factory()->administrator()->create([
|
||||||
|
'name' => 'secondAdmin',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$admins = User::admins()->get();
|
||||||
|
|
||||||
|
$this->assertCount(2, $admins);
|
||||||
|
$this->assertEquals($admins[0]->is_admin, true);
|
||||||
|
$this->assertEquals($admins[1]->is_admin, true);
|
||||||
|
$this->assertEquals($admins[0]->name, $firstAdmin->name);
|
||||||
|
$this->assertEquals($admins[1]->name, $secondAdmin->name);
|
||||||
|
}
|
||||||
|
}
|
@ -174,4 +174,29 @@ class HandlerTest extends TestCase
|
|||||||
'message',
|
'message',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_AccessDeniedException_returns_forbidden_json_response()
|
||||||
|
{
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$instance = new Handler($this->createMock(Container::class));
|
||||||
|
$class = new \ReflectionClass(Handler::class);
|
||||||
|
|
||||||
|
$method = $class->getMethod('render');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$mockException = $this->createMock(\Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException::class);
|
||||||
|
|
||||||
|
$response = $method->invokeArgs($instance, [$request, $mockException]);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||||
|
|
||||||
|
$response = \Illuminate\Testing\TestResponse::fromBaseResponse($response);
|
||||||
|
$response->assertStatus(403)
|
||||||
|
->assertJsonStructure([
|
||||||
|
'message',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,4 +135,98 @@ class HelpersTest extends TestCase
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @dataProvider commaSeparatedToArrayProvider
|
||||||
|
*/
|
||||||
|
public function test_commaSeparatedToArray_returns_ids_in_array($str, $expected)
|
||||||
|
{
|
||||||
|
$array = Helpers::commaSeparatedToArray($str);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide data for cleanVersionNumber() tests
|
||||||
|
*/
|
||||||
|
public function commaSeparatedToArrayProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'NOMINAL' => [
|
||||||
|
'1,2,3',
|
||||||
|
[1, 2, 3],
|
||||||
|
],
|
||||||
|
'DUPLICATE' => [
|
||||||
|
'1,2,2,3',
|
||||||
|
[1, 2, 2, 3],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @dataProvider invalidCommaSeparatedToArrayProvider
|
||||||
|
*/
|
||||||
|
public function test_commaSeparatedToArray_returns_unchanged_ids($str, $expected)
|
||||||
|
{
|
||||||
|
$array = Helpers::commaSeparatedToArray($str);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide data for cleanVersionNumber() tests
|
||||||
|
*/
|
||||||
|
public function invalidCommaSeparatedToArrayProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'INVALID_IDS_LEADING_SPACES' => [
|
||||||
|
'1, 2,3',
|
||||||
|
'1, 2,3',
|
||||||
|
],
|
||||||
|
'INVALID_IDS_TRAILING_SPACES' => [
|
||||||
|
'1,2 ,3',
|
||||||
|
'1,2 ,3',
|
||||||
|
],
|
||||||
|
'INVALID_IDS_BAD_SEPARATOR' => [
|
||||||
|
'1/2/3',
|
||||||
|
'1/2/3',
|
||||||
|
],
|
||||||
|
'INVALID_IDS_NOT_DIGIT' => [
|
||||||
|
'a,b,c',
|
||||||
|
'a,b,c',
|
||||||
|
],
|
||||||
|
'INVALID_IDS_MISSING_DIGIT' => [
|
||||||
|
'1,,3',
|
||||||
|
'1,,3',
|
||||||
|
],
|
||||||
|
'INVALID_IDS_LEADING_COMMA' => [
|
||||||
|
',2,3',
|
||||||
|
',2,3',
|
||||||
|
],
|
||||||
|
'INVALID_IDS_TRAILING_COMMA' => [
|
||||||
|
'1,2,',
|
||||||
|
'1,2,',
|
||||||
|
],
|
||||||
|
'NOT_STRING_BOOLEAN' => [
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
'NOT_STRING_INT' => [
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
'NOT_STRING_ARRAY' => [
|
||||||
|
[1],
|
||||||
|
[1],
|
||||||
|
],
|
||||||
|
'NOT_STRING_NULL' => [
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ use App\Services\Migrators\GoogleAuthMigrator;
|
|||||||
use App\Services\Migrators\Migrator;
|
use App\Services\Migrators\Migrator;
|
||||||
use App\Services\Migrators\PlainTextMigrator;
|
use App\Services\Migrators\PlainTextMigrator;
|
||||||
use App\Services\Migrators\TwoFASMigrator;
|
use App\Services\Migrators\TwoFASMigrator;
|
||||||
|
use App\Services\Migrators\TwoFAuthMigrator;
|
||||||
use App\Services\SettingService;
|
use App\Services\SettingService;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
@ -30,6 +31,7 @@ use Tests\TestCase;
|
|||||||
* @covers \App\Services\Migrators\TwoFASMigrator
|
* @covers \App\Services\Migrators\TwoFASMigrator
|
||||||
* @covers \App\Services\Migrators\PlainTextMigrator
|
* @covers \App\Services\Migrators\PlainTextMigrator
|
||||||
* @covers \App\Services\Migrators\GoogleAuthMigrator
|
* @covers \App\Services\Migrators\GoogleAuthMigrator
|
||||||
|
* @covers \App\Services\Migrators\TwoFAuthMigrator
|
||||||
*
|
*
|
||||||
* @uses \App\Models\TwoFAccount
|
* @uses \App\Models\TwoFAccount
|
||||||
*/
|
*/
|
||||||
@ -208,6 +210,12 @@ class MigratorTest extends TestCase
|
|||||||
'gauth',
|
'gauth',
|
||||||
$hasSteam = false,
|
$hasSteam = false,
|
||||||
],
|
],
|
||||||
|
'2FAUTH_MIGRATION_PAYLOAD' => [
|
||||||
|
new TwoFAuthMigrator(),
|
||||||
|
MigrationTestData::VALID_2FAUTH_JSON_MIGRATION_PAYLOAD,
|
||||||
|
'custom',
|
||||||
|
$hasSteam = true,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,6 +281,10 @@ class MigratorTest extends TestCase
|
|||||||
new GoogleAuthMigrator(),
|
new GoogleAuthMigrator(),
|
||||||
MigrationTestData::GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA,
|
MigrationTestData::GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA,
|
||||||
],
|
],
|
||||||
|
'INVALID_2FAUTH_JSON_MIGRATION_PAYLOAD' => [
|
||||||
|
new TwoFAuthMigrator(),
|
||||||
|
MigrationTestData::INVALID_2FAUTH_JSON_MIGRATION_PAYLOAD,
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -313,6 +325,10 @@ class MigratorTest extends TestCase
|
|||||||
new TwoFASMigrator(),
|
new TwoFASMigrator(),
|
||||||
MigrationTestData::VALID_2FAS_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE,
|
MigrationTestData::VALID_2FAS_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE,
|
||||||
],
|
],
|
||||||
|
'VALID_2FAUTH_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE' => [
|
||||||
|
new TwoFAuthMigrator(),
|
||||||
|
MigrationTestData::VALID_2FAUTH_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,12 +410,71 @@ class MigratorTest extends TestCase
|
|||||||
Storage::disk('icons')->assertDirectoryEmpty('/');
|
Storage::disk('icons')->assertDirectoryEmpty('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*
|
||||||
|
* @dataProvider TwoFAuthWithIconMigrationProvider
|
||||||
|
*/
|
||||||
|
public function test_migrate_2fauth_payload_with_icon_sets_and_stores_the_icon($migration)
|
||||||
|
{
|
||||||
|
Storage::fake('icons');
|
||||||
|
|
||||||
|
$migrator = new TwoFAuthMigrator();
|
||||||
|
$accounts = $migrator->migrate($migration);
|
||||||
|
|
||||||
|
$this->assertContainsOnlyInstancesOf(TwoFAccount::class, $accounts);
|
||||||
|
$this->assertCount(1, $accounts);
|
||||||
|
|
||||||
|
Storage::disk('icons')->assertExists($accounts->first()->icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide data for TwoFAccount store tests
|
||||||
|
*/
|
||||||
|
public function TwoFAuthWithIconMigrationProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'SVG' => [
|
||||||
|
MigrationTestData::VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_SVG_ICON,
|
||||||
|
],
|
||||||
|
'PNG' => [
|
||||||
|
MigrationTestData::VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_PNG_ICON,
|
||||||
|
],
|
||||||
|
'JPG' => [
|
||||||
|
MigrationTestData::VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_JPG_ICON,
|
||||||
|
],
|
||||||
|
'BMP' => [
|
||||||
|
MigrationTestData::VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_BMP_ICON,
|
||||||
|
],
|
||||||
|
'WEBP' => [
|
||||||
|
MigrationTestData::VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_WEBP_ICON,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_migrate_2fauth_payload_with_unsupported_icon_does_not_fail()
|
||||||
|
{
|
||||||
|
Storage::fake('icons');
|
||||||
|
|
||||||
|
$migrator = new TwoFAuthMigrator();
|
||||||
|
$accounts = $migrator->migrate(MigrationTestData::VALID_2FAUTH_JSON_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_ICON);
|
||||||
|
|
||||||
|
$this->assertContainsOnlyInstancesOf(TwoFAccount::class, $accounts);
|
||||||
|
$this->assertCount(1, $accounts);
|
||||||
|
|
||||||
|
$this->assertNull($this->fakeTwofaccount->icon);
|
||||||
|
Storage::disk('icons')->assertDirectoryEmpty('/');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*
|
*
|
||||||
* @dataProvider factoryProvider
|
* @dataProvider factoryProvider
|
||||||
*/
|
*/
|
||||||
public function test_factory_returns_plain_text_migrator($payload, $migratorClass)
|
public function test_factory_returns_relevant_migrator($payload, $migratorClass)
|
||||||
{
|
{
|
||||||
$factory = new MigratorFactory();
|
$factory = new MigratorFactory();
|
||||||
|
|
||||||
@ -434,6 +509,10 @@ class MigratorTest extends TestCase
|
|||||||
MigrationTestData::GOOGLE_AUTH_MIGRATION_URI,
|
MigrationTestData::GOOGLE_AUTH_MIGRATION_URI,
|
||||||
GoogleAuthMigrator::class,
|
GoogleAuthMigrator::class,
|
||||||
],
|
],
|
||||||
|
'2FAUTH_MIGRATION_URI' => [
|
||||||
|
MigrationTestData::VALID_2FAUTH_JSON_MIGRATION_PAYLOAD,
|
||||||
|
TwoFAuthMigrator::class,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user