Update tests & minor fixes

This commit is contained in:
Bubka 2022-12-09 10:52:17 +01:00
parent 7ce7067380
commit 05a39b6501
61 changed files with 2773 additions and 526 deletions

View File

@ -35,6 +35,8 @@ class TwoFAccountDynamicRequest extends FormRequest
/**
* Prepare the data for validation.
*
* @codeCoverageIgnore
*
* @return void
*/
protected function prepareForValidation()

View File

@ -40,6 +40,8 @@ class TwoFAccountStoreRequest extends FormRequest
/**
* Prepare the data for validation.
*
* @codeCoverageIgnore
*
* @return void
*/
protected function prepareForValidation()

View File

@ -40,6 +40,8 @@ class TwoFAccountUpdateRequest extends FormRequest
/**
* Prepare the data for validation.
*
* @codeCoverageIgnore
*
* @return void
*/
protected function prepareForValidation()

View File

@ -33,6 +33,8 @@ class TwoFAccountUriRequest extends FormRequest
/**
* Prepare the data for validation.
*
* @codeCoverageIgnore
*
* @return void
*/
protected function prepareForValidation()

View File

@ -31,7 +31,7 @@ class WebauthnCredentialBroker extends PasswordBroker
$token = $this->tokens->create($user);
if ($callback) {
$callback($user, $token);
$callback($user, $token); // @codeCoverageIgnore
} else {
$user->sendWebauthnRecoveryNotification($token);
}

View File

@ -25,6 +25,7 @@ class Helpers
*/
public static function cleanVersionNumber(?string $release) : string|false
{
return preg_match('/([[0-9][0-9\.]*[0-9])/', $release, $version) ? $version[0] : false;
// We use the regex for semver detection (see https://semver.org/)
return preg_match('/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?/', $release, $version) ? $version[0] : false;
}
}

View File

@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Log;
use Laragear\WebAuthn\Http\Requests\AssertedRequest;
use Laragear\WebAuthn\Http\Requests\AssertionRequest;
use Laragear\WebAuthn\WebAuthn;
use Illuminate\Support\Arr;
class WebAuthnLoginController extends Controller
{
@ -33,7 +34,7 @@ class WebAuthnLoginController extends Controller
*/
public function options(AssertionRequest $request) : Responsable|JsonResponse
{
switch (env('WEBAUTHN_USER_VERIFICATION')) {
switch (config('webauthn.user_verification')) {
case WebAuthn::USER_VERIFICATION_DISCOURAGED:
$request = $request->fastLogin(); // Makes the authenticator to only check for user presence on registration
break;
@ -69,7 +70,7 @@ class WebAuthnLoginController extends Controller
// Some authenticators do not send a userHandle so we hack the response to be compliant
// with Larapass/webauthn-lib implementation that waits for a userHandle
if (! $response['userHandle']) {
if (!Arr::exists($response, 'userHandle') || blank($response['userHandle'])) {
$response['userHandle'] = User::getFromCredentialId($request->id)?->userHandle();
$request->merge(['response' => $response]);
}

View File

@ -19,7 +19,7 @@ class WebAuthnRegisterController extends Controller
*/
public function options(AttestationRequest $request) : Responsable
{
switch (env('WEBAUTHN_USER_VERIFICATION')) {
switch (config('webauthn.user_verification')) {
case WebAuthn::USER_VERIFICATION_DISCOURAGED:
$request = $request->fastRegistration(); // Makes the authenticator to only check for user presence on registration
break;

View File

@ -4,6 +4,9 @@ namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
/**
* @codeCoverageIgnore
*/
class TrustHosts extends Middleware
{
/**

View File

@ -60,8 +60,6 @@ class TwoFAccount extends Model implements Sortable
const FAKE_ID = -2;
private const IMAGELINK_STORAGE_PATH = 'imagesLink/';
/**
* List of OTP types supported by 2FAuth
*/
@ -376,10 +374,6 @@ class TwoFAccount extends Model implements Sortable
$this->enforceAsSteam();
}
if (! $this->icon && $skipIconFetching) {
$this->icon = $this->getDefaultIcon();
}
if (! $this->icon && Settings::get('getOfficialIcons') && ! $skipIconFetching) {
$this->icon = $this->getDefaultIcon();
}
@ -441,6 +435,22 @@ class TwoFAccount extends Model implements Sortable
return $this;
}
/**
* Compare 2 TwoFAccounts
*/
public function equals(self $other): bool
{
return $this->service === $other->service &&
$this->account === $other->account &&
$this->icon === $other->icon &&
$this->otp_type === $other->otp_type &&
$this->secret === $other->secret &&
$this->digits === $other->digits &&
$this->algorithm === $other->algorithm &&
$this->period === $other->period &&
$this->counter === $other->counter;
}
/**
* Sets model attributes to STEAM values
*/
@ -534,7 +544,6 @@ class TwoFAccount extends Model implements Sortable
try {
$path_parts = pathinfo($url);
$newFilename = Helpers::getUniqueFilename($path_parts['extension']);
$imageFile = self::IMAGELINK_STORAGE_PATH . $newFilename;
try {
$response = Http::retry(3, 100)->get($url);
@ -546,8 +555,10 @@ class TwoFAccount extends Model implements Sortable
Log::error(sprintf('Cannot fetch imageLink at "%s"', $url));
}
if (in_array(Storage::mimeType($imageFile), ['image/png', 'image/jpeg', 'image/webp', 'image/bmp'])
&& getimagesize(storage_path() . '/app/' . $imageFile)) {
if (
in_array(Storage::disk('imagesLink')->mimeType($newFilename), ['image/png', 'image/jpeg', 'image/webp', 'image/bmp'])
&& getimagesize(Storage::disk('imagesLink')->path($newFilename))
) {
// Should be a valid image, we move it to the icons disk
if (Storage::disk('icons')->put($newFilename, Storage::disk('imagesLink')->get($newFilename))) {
Storage::disk('imagesLink')->delete($newFilename);
@ -555,10 +566,8 @@ class TwoFAccount extends Model implements Sortable
Log::info(sprintf('Icon file %s stored', $newFilename));
} else {
// @codeCoverageIgnoreStart
Storage::disk('imagesLink')->delete($newFilename);
throw new \Exception('Unsupported mimeType or missing image on storage');
// @codeCoverageIgnoreEnd
}
return $newFilename;

View File

@ -49,6 +49,7 @@ class TwoFAuthServiceProvider extends ServiceProvider implements DeferrableProvi
/**
* Get the services provided by the provider.
*
* @codeCoverageIgnore
* @return array
*/
public function provides()

View File

@ -59,7 +59,7 @@ class GoogleAuthMigrator extends Migrator
// The token failed to generate a valid account so we create a fake account to be returned.
$fakeAccount = new TwoFAccount();
$fakeAccount->id = -2;
$fakeAccount->id = TwoFAccount::FAKE_ID;
$fakeAccount->otp_type = $fakeAccount::TOTP;
// Only basic fields are filled to limit the risk of another exception.
$fakeAccount->account = $otp_parameters->getName() ?? __('twofaccounts.import.invalid_account');

View File

@ -39,7 +39,7 @@ class PlainTextMigrator extends Migrator
// The token failed to generate a valid account so we create a fake account to be returned.
$fakeAccount = new TwoFAccount();
$fakeAccount->id = -2;
$fakeAccount->id = TwoFAccount::FAKE_ID;
$fakeAccount->otp_type = substr($uri, 10, 4);
// Only basic fields are filled to limit the risk of another exception.
$fakeAccount->account = __('twofaccounts.import.invalid_account');

View File

@ -89,8 +89,12 @@ class TwoFASMigrator extends Migrator
$parameters['secret'] = $this->padToValidBase32Secret($otp_parameters['secret']);
$parameters['algorithm'] = $otp_parameters['otp']['algorithm'];
$parameters['digits'] = $otp_parameters['otp']['digits'];
$parameters['counter'] = $otp_parameters['otp']['counter'] ?? null;
$parameters['period'] = $otp_parameters['otp']['period'] ?? null;
$parameters['counter'] = strtolower($parameters['otp_type']) === 'hotp' && $otp_parameters['otp']['counter'] > 0
? $otp_parameters['otp']['counter']
: null;
$parameters['period'] = strtolower($parameters['otp_type']) === 'totp' && $otp_parameters['otp']['period'] > 0
? $otp_parameters['otp']['period']
: null;
try {
$twofaccounts[$key] = new TwoFAccount;

View File

@ -16,7 +16,7 @@ class ReleaseRadarService
*/
public function scheduledScan() : void
{
if ((Settings::get('lastRadarScan') + 604800) < time()) {
if ((Settings::get('lastRadarScan') + (60 * 60 * 24 * 7)) < time()) {
$this->newRelease();
}
}
@ -39,18 +39,19 @@ class ReleaseRadarService
protected function newRelease() : false|string
{
if ($latestReleaseData = json_decode($this->getLatestReleaseData())) {
$githubVersion = Helpers::cleanVersionNumber($latestReleaseData->tag_name);
$installedVersion = Helpers::cleanVersionNumber(config('2fauth.version'));
if ($githubVersion > $installedVersion && $latestReleaseData->prerelease == false && $latestReleaseData->draft == false) {
Settings::set('latestRelease', $latestReleaseData->tag_name);
if ($githubVersion && $installedVersion) {
if ($githubVersion > $installedVersion && $latestReleaseData->prerelease == false && $latestReleaseData->draft == false) {
Settings::set('latestRelease', $latestReleaseData->tag_name);
return $latestReleaseData->tag_name;
} else {
Settings::delete('latestRelease');
return $latestReleaseData->tag_name;
} else {
Settings::delete('latestRelease');
}
}
Settings::set('lastRadarScan', time());
}
return false;
@ -68,6 +69,8 @@ class ReleaseRadarService
->get(config('2fauth.latestReleaseUrl'));
if ($response->successful()) {
Settings::set('lastRadarScan', time());
return $response->body();
}
} catch (\Exception $exception) {

View File

@ -2,6 +2,8 @@
return [
'user_verification' => env('WEBAUTHN_USER_VERIFICATION', 'discouraged'),
/*
|--------------------------------------------------------------------------
| Relaying Party

View File

@ -5,6 +5,10 @@ namespace Tests\Api\v1\Controllers\Auth;
use App\Models\User;
use Tests\FeatureTestCase;
/**
* @covers \App\Api\v1\Controllers\UserController
* @covers \App\Api\v1\Resources\UserResource
*/
class UserControllerTest extends FeatureTestCase
{
/**
@ -15,7 +19,7 @@ class UserControllerTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();

View File

@ -21,7 +21,7 @@ class GroupControllerTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();

View File

@ -40,6 +40,31 @@ class IconControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
public function test_fetch_logo_returns_filename()
{
$response = $this->json('POST', '/api/v1/icons/default', [
'service' => 'twitter',
])
->assertStatus(201)
->assertJsonStructure([
'filename',
]);
}
/**
* @test
*/
public function test_fetch_unknown_logo_returns_nothing()
{
$response = $this->json('POST', '/api/v1/icons/default', [
'service' => 'unknown_company',
])
->assertNoContent();
}
/**
* @test
*/

View File

@ -9,13 +9,16 @@ use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Tests\Classes\LocalFile;
use Tests\Classes\OtpTestData;
use Tests\Data\OtpTestData;
use Tests\FeatureTestCase;
use Tests\Data\MigrationTestData;
/**
* @covers \App\Api\v1\Controllers\TwoFAccountController
* @covers \App\Api\v1\Resources\TwoFAccountReadResource
* @covers \App\Api\v1\Resources\TwoFAccountStoreResource
* @covers \App\Providers\MigrationServiceProvider
* @covers \App\Providers\TwoFAuthServiceProvider
*/
class TwoFAccountControllerTest extends FeatureTestCase
{
@ -122,7 +125,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
@ -447,7 +450,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
{
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/migration', [
'payload' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI,
'payload' => MigrationTestData::GOOGLE_AUTH_MIGRATION_URI,
'withSecret' => 1,
])
->assertOk()
@ -483,7 +486,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
{
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/migration', [
'uri' => OtpTestData::INVALID_GOOGLE_AUTH_MIGRATION_URI,
'uri' => MigrationTestData::INVALID_GOOGLE_AUTH_MIGRATION_URI,
])
->assertStatus(422);
}
@ -507,7 +510,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/migration', [
'payload' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI,
'payload' => MigrationTestData::GOOGLE_AUTH_MIGRATION_URI,
])
->assertOk()
->assertJsonFragment([
@ -524,7 +527,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
{
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/migration', [
'payload' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA,
'payload' => MigrationTestData::GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA,
])
->assertStatus(400)
->assertJsonStructure([
@ -546,22 +549,11 @@ class TwoFAccountControllerTest extends FeatureTestCase
'withSecret' => 1,
])
->assertOk()
->assertJsonCount(5, $key = null)
->assertJsonCount(3, $key = null)
->assertJsonFragment([
'id' => 0,
'service' => OtpTestData::SERVICE . '_totp',
'account' => OtpTestData::ACCOUNT . '_totp',
'otp_type' => 'totp',
'secret' => OtpTestData::SECRET,
'digits' => OtpTestData::DIGITS_DEFAULT,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'counter' => null,
])
->assertJsonFragment([
'id' => 0,
'service' => OtpTestData::SERVICE . '_totp_custom',
'account' => OtpTestData::ACCOUNT . '_totp_custom',
'service' => OtpTestData::SERVICE,
'account' => OtpTestData::ACCOUNT,
'otp_type' => 'totp',
'secret' => OtpTestData::SECRET,
'digits' => OtpTestData::DIGITS_CUSTOM,
@ -571,21 +563,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
])
->assertJsonFragment([
'id' => 0,
'service' => OtpTestData::SERVICE . '_hotp',
'account' => OtpTestData::ACCOUNT . '_hotp',
'service' => OtpTestData::SERVICE,
'account' => OtpTestData::ACCOUNT,
'otp_type' => 'hotp',
'secret' => OtpTestData::SECRET,
'digits' => OtpTestData::DIGITS_DEFAULT,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'period' => null,
'counter' => OtpTestData::COUNTER_DEFAULT,
])
->assertJsonFragment([
'id' => 0,
'service' => OtpTestData::SERVICE . '_hotp_custom',
'account' => OtpTestData::ACCOUNT . '_hotp_custom',
'otp_type' => 'totp',
'secret' => OtpTestData::SECRET,
'digits' => OtpTestData::DIGITS_CUSTOM,
'algorithm' => OtpTestData::ALGORITHM_CUSTOM,
'period' => null,
@ -594,7 +575,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonFragment([
'id' => 0,
'service' => OtpTestData::STEAM,
'account' => OtpTestData::ACCOUNT . '_steam',
'account' => OtpTestData::ACCOUNT,
'otp_type' => 'steamtotp',
'secret' => OtpTestData::STEAM_SECRET,
'digits' => OtpTestData::DIGITS_STEAM,
@ -625,10 +606,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
public function invalidAegisJsonFileProvider()
{
return [
'validPlainTextFile' => [
'encryptedAegisJsonFile' => [
LocalFile::fake()->encryptedAegisJsonFile(),
],
'validPlainTextFileWithNewLines' => [
'invalidAegisJsonFile' => [
LocalFile::fake()->invalidAegisJsonFile(),
],
];
@ -720,16 +701,16 @@ class TwoFAccountControllerTest extends FeatureTestCase
public function invalidPlainTextFileProvider()
{
return [
'validPlainTextFile' => [
'invalidPlainTextFileEmpty' => [
LocalFile::fake()->invalidPlainTextFileEmpty(),
],
'validPlainTextFileWithNewLines' => [
'invalidPlainTextFileNoUri' => [
LocalFile::fake()->invalidPlainTextFileNoUri(),
],
'validPlainTextFileWithNewLines' => [
'invalidPlainTextFileWithInvalidUri' => [
LocalFile::fake()->invalidPlainTextFileWithInvalidUri(),
],
'validPlainTextFileWithNewLines' => [
'invalidPlainTextFileWithInvalidLine' => [
LocalFile::fake()->invalidPlainTextFileWithInvalidLine(),
],
];
@ -744,7 +725,8 @@ class TwoFAccountControllerTest extends FeatureTestCase
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/reorder', [
'orderedIds' => [3, 2, 1], ])
'orderedIds' => [3, 2, 1],
])
->assertStatus(200)
->assertJsonStructure([
'message',
@ -760,7 +742,8 @@ class TwoFAccountControllerTest extends FeatureTestCase
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/reorder', [
'orderedIds' => '3,2,1', ])
'orderedIds' => '3,2,1',
])
->assertStatus(422);
}

View File

@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\GroupAssignRequest
*/
class GroupAssignRequestTest extends TestCase
{
use WithoutMiddleware;
@ -18,8 +21,8 @@ class GroupAssignRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new GroupAssignRequest();
@ -29,7 +32,7 @@ class GroupAssignRequestTest extends TestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new GroupAssignRequest();
$validator = Validator::make($data, $request->rules());
@ -40,7 +43,7 @@ class GroupAssignRequestTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -54,7 +57,7 @@ class GroupAssignRequestTest extends TestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$request = new GroupAssignRequest();
$validator = Validator::make($data, $request->rules());
@ -65,7 +68,7 @@ class GroupAssignRequestTest extends TestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -9,6 +9,9 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\FeatureTestCase;
/**
* @covers \App\Api\v1\Requests\GroupStoreRequest
*/
class GroupStoreRequestTest extends FeatureTestCase
{
use WithoutMiddleware;
@ -21,8 +24,8 @@ class GroupStoreRequestTest extends FeatureTestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new GroupStoreRequest();
@ -32,7 +35,7 @@ class GroupStoreRequestTest extends FeatureTestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new GroupStoreRequest();
$validator = Validator::make($data, $request->rules());
@ -43,7 +46,7 @@ class GroupStoreRequestTest extends FeatureTestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -55,7 +58,7 @@ class GroupStoreRequestTest extends FeatureTestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$group = new Group([
'name' => $this->uniqueGroupName,
@ -72,7 +75,7 @@ class GroupStoreRequestTest extends FeatureTestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -9,6 +9,9 @@ use Illuminate\Support\Facades\Validator;
use Tests\Classes\LocalFile;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\QrCodeDecodeRequest
*/
class QrCodeDecodeRequestTest extends TestCase
{
use WithoutMiddleware;
@ -19,8 +22,8 @@ class QrCodeDecodeRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new QrCodeDecodeRequest();
@ -30,7 +33,7 @@ class QrCodeDecodeRequestTest extends TestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new QrCodeDecodeRequest();
$validator = Validator::make($data, $request->rules());
@ -41,7 +44,7 @@ class QrCodeDecodeRequestTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
$file = LocalFile::fake()->validQrcode();
@ -55,7 +58,7 @@ class QrCodeDecodeRequestTest extends TestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$request = new QrCodeDecodeRequest();
$validator = Validator::make($data, $request->rules());
@ -66,7 +69,7 @@ class QrCodeDecodeRequestTest extends TestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -9,6 +9,9 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\FeatureTestCase;
/**
* @covers \App\Api\v1\Requests\SettingStoreRequest
*/
class SettingStoreRequestTest extends FeatureTestCase
{
use WithoutMiddleware;
@ -21,8 +24,8 @@ class SettingStoreRequestTest extends FeatureTestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new SettingStoreRequest();
@ -32,7 +35,7 @@ class SettingStoreRequestTest extends FeatureTestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new SettingStoreRequest();
$validator = Validator::make($data, $request->rules());
@ -43,7 +46,7 @@ class SettingStoreRequestTest extends FeatureTestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -64,7 +67,7 @@ class SettingStoreRequestTest extends FeatureTestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
Settings::set($this->uniqueKey, 'uniqueValue');
@ -77,7 +80,7 @@ class SettingStoreRequestTest extends FeatureTestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\SettingUpdateRequest
*/
class SettingUpdateRequestTest extends TestCase
{
use WithoutMiddleware;
@ -18,8 +21,8 @@ class SettingUpdateRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new SettingUpdateRequest();
@ -29,7 +32,7 @@ class SettingUpdateRequestTest extends TestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new SettingUpdateRequest();
$validator = Validator::make($data, $request->rules());
@ -40,7 +43,7 @@ class SettingUpdateRequestTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -58,7 +61,7 @@ class SettingUpdateRequestTest extends TestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$request = new SettingUpdateRequest();
$validator = Validator::make($data, $request->rules());
@ -69,7 +72,7 @@ class SettingUpdateRequestTest extends TestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\TwoFAccountBatchRequest
*/
class TwoFAccountBatchRequestTest extends TestCase
{
use WithoutMiddleware;
@ -18,8 +21,8 @@ class TwoFAccountBatchRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new TwoFAccountBatchRequest();
@ -29,7 +32,7 @@ class TwoFAccountBatchRequestTest extends TestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new TwoFAccountBatchRequest();
$validator = Validator::make($data, $request->rules());
@ -40,7 +43,7 @@ class TwoFAccountBatchRequestTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -55,7 +58,7 @@ class TwoFAccountBatchRequestTest extends TestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$request = new TwoFAccountBatchRequest();
$validator = Validator::make($data, $request->rules());
@ -66,7 +69,7 @@ class TwoFAccountBatchRequestTest extends TestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -9,6 +9,9 @@ use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Auth;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\TwoFAccountDynamicRequest
*/
class TwoFAccountDynamicRequestTest extends TestCase
{
use WithoutMiddleware;
@ -19,8 +22,8 @@ class TwoFAccountDynamicRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new TwoFAccountDynamicRequest();

View File

@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\TwoFAccountImportRequest
*/
class TwoFAccountImportRequestTest extends TestCase
{
use WithoutMiddleware;
@ -18,8 +21,8 @@ class TwoFAccountImportRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new TwoFAccountImportRequest();
@ -29,7 +32,7 @@ class TwoFAccountImportRequestTest extends TestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new TwoFAccountImportRequest();
$validator = Validator::make($data, $request->rules());
@ -40,7 +43,7 @@ class TwoFAccountImportRequestTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -52,7 +55,7 @@ class TwoFAccountImportRequestTest extends TestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$request = new TwoFAccountImportRequest();
$validator = Validator::make($data, $request->rules());
@ -63,7 +66,7 @@ class TwoFAccountImportRequestTest extends TestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\TwoFAccountReorderRequest
*/
class TwoFAccountReorderRequestTest extends TestCase
{
use WithoutMiddleware;
@ -18,8 +21,8 @@ class TwoFAccountReorderRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new TwoFAccountReorderRequest();
@ -29,7 +32,7 @@ class TwoFAccountReorderRequestTest extends TestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new TwoFAccountReorderRequest();
$validator = Validator::make($data, $request->rules());
@ -40,7 +43,7 @@ class TwoFAccountReorderRequestTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -55,7 +58,7 @@ class TwoFAccountReorderRequestTest extends TestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$request = new TwoFAccountReorderRequest();
$validator = Validator::make($data, $request->rules());
@ -66,7 +69,7 @@ class TwoFAccountReorderRequestTest extends TestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -8,6 +8,10 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\TwoFAccountStoreRequest
* @covers \App\Rules\IsBase32Encoded
*/
class TwoFAccountStoreRequestTest extends TestCase
{
use WithoutMiddleware;
@ -18,8 +22,8 @@ class TwoFAccountStoreRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new TwoFAccountStoreRequest();
@ -29,7 +33,7 @@ class TwoFAccountStoreRequestTest extends TestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new TwoFAccountStoreRequest();
$validator = Validator::make($data, $request->rules());
@ -40,7 +44,7 @@ class TwoFAccountStoreRequestTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -98,7 +102,7 @@ class TwoFAccountStoreRequestTest extends TestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$request = new TwoFAccountStoreRequest();
$validator = Validator::make($data, $request->rules());
@ -109,7 +113,7 @@ class TwoFAccountStoreRequestTest extends TestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -8,6 +8,10 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\TwoFAccountUpdateRequest
* @covers \App\Rules\IsBase32Encoded
*/
class TwoFAccountUpdateRequestTest extends TestCase
{
use WithoutMiddleware;
@ -18,8 +22,8 @@ class TwoFAccountUpdateRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new TwoFAccountUpdateRequest();
@ -29,7 +33,7 @@ class TwoFAccountUpdateRequestTest extends TestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new TwoFAccountUpdateRequest();
$validator = Validator::make($data, $request->rules());
@ -40,7 +44,7 @@ class TwoFAccountUpdateRequestTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -80,7 +84,7 @@ class TwoFAccountUpdateRequestTest extends TestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$request = new TwoFAccountUpdateRequest();
$validator = Validator::make($data, $request->rules());
@ -91,7 +95,7 @@ class TwoFAccountUpdateRequestTest extends TestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
/**
* @covers \App\Api\v1\Requests\TwoFAccountUriRequest
*/
class TwoFAccountUriRequestTest extends TestCase
{
use WithoutMiddleware;
@ -18,8 +21,8 @@ class TwoFAccountUriRequestTest extends TestCase
public function test_user_is_authorized()
{
Auth::shouldReceive('check')
->once()
->andReturn(true);
->once()
->andReturn(true);
$request = new TwoFAccountUriRequest();
@ -29,7 +32,7 @@ class TwoFAccountUriRequestTest extends TestCase
/**
* @dataProvider provideValidData
*/
public function test_valid_data(array $data) : void
public function test_valid_data(array $data): void
{
$request = new TwoFAccountUriRequest();
$validator = Validator::make($data, $request->rules());
@ -40,7 +43,7 @@ class TwoFAccountUriRequestTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideValidData() : array
public function provideValidData(): array
{
return [
[[
@ -59,7 +62,7 @@ class TwoFAccountUriRequestTest extends TestCase
/**
* @dataProvider provideInvalidData
*/
public function test_invalid_data(array $data) : void
public function test_invalid_data(array $data): void
{
$request = new TwoFAccountUriRequest();
$validator = Validator::make($data, $request->rules());
@ -70,7 +73,7 @@ class TwoFAccountUriRequestTest extends TestCase
/**
* Provide invalid data for validation test
*/
public function provideInvalidData() : array
public function provideInvalidData(): array
{
return [
[[

View File

@ -3,6 +3,8 @@
namespace Tests\Classes;
use Illuminate\Http\Testing\File;
use Tests\Data\MigrationTestData;
use Tests\Data\OtpTestData;
class LocalFileFactory
{
@ -58,7 +60,7 @@ class LocalFileFactory
return new File('validAegisMigration.json', tap(tmpfile(), function ($temp) {
ob_start();
echo OtpTestData::AEGIS_JSON_MIGRATION_PAYLOAD;
echo MigrationTestData::VALID_AEGIS_JSON_MIGRATION_PAYLOAD;
fwrite($temp, ob_get_clean());
}));
@ -74,7 +76,7 @@ class LocalFileFactory
return new File('invalidAegisMigration.json', tap(tmpfile(), function ($temp) {
ob_start();
echo OtpTestData::INVALID_AEGIS_JSON_MIGRATION_PAYLOAD;
echo MigrationTestData::INVALID_AEGIS_JSON_MIGRATION_PAYLOAD;
fwrite($temp, ob_get_clean());
}));
@ -90,7 +92,7 @@ class LocalFileFactory
return new File('encryptedAegisJsonFile.txt', tap(tmpfile(), function ($temp) {
ob_start();
echo OtpTestData::ENCRYPTED_AEGIS_JSON_MIGRATION_PAYLOAD;
echo MigrationTestData::ENCRYPTED_AEGIS_JSON_MIGRATION_PAYLOAD;
fwrite($temp, ob_get_clean());
}));

View File

@ -1,257 +0,0 @@
<?php
namespace Tests\Classes;
class OtpTestData
{
const ACCOUNT = 'account';
const SERVICE = 'service';
const STEAM = 'Steam';
const SECRET = 'A4GRFHVVRBGY7UIW';
const STEAM_SECRET = 'XJGTDRUUKZH3X7TQN2QZUGCGXZCC5LXE';
const ALGORITHM_DEFAULT = 'sha1';
const ALGORITHM_CUSTOM = 'sha256';
const DIGITS_DEFAULT = 6;
const DIGITS_CUSTOM = 7;
const DIGITS_STEAM = 5;
const PERIOD_DEFAULT = 30;
const PERIOD_CUSTOM = 40;
const COUNTER_DEFAULT = 0;
const COUNTER_CUSTOM = 5;
const IMAGE = 'https%3A%2F%2Fen.opensuse.org%2Fimages%2F4%2F44%2FButton-filled-colour.png';
const ICON = 'test.png';
const TOTP_FULL_CUSTOM_URI = 'otpauth://totp/' . self::SERVICE . ':' . self::ACCOUNT . '?secret=' . self::SECRET . '&issuer=' . self::SERVICE . '&digits=' . self::DIGITS_CUSTOM . '&period=' . self::PERIOD_CUSTOM . '&algorithm=' . self::ALGORITHM_CUSTOM . '&image=' . self::IMAGE;
const HOTP_FULL_CUSTOM_URI = 'otpauth://hotp/' . self::SERVICE . ':' . self::ACCOUNT . '?secret=' . self::SECRET . '&issuer=' . self::SERVICE . '&digits=' . self::DIGITS_CUSTOM . '&counter=' . self::COUNTER_CUSTOM . '&algorithm=' . self::ALGORITHM_CUSTOM . '&image=' . self::IMAGE;
const TOTP_SHORT_URI = 'otpauth://totp/' . self::ACCOUNT . '?secret=' . self::SECRET;
const HOTP_SHORT_URI = 'otpauth://hotp/' . self::ACCOUNT . '?secret=' . self::SECRET;
const TOTP_URI_WITH_UNREACHABLE_IMAGE = 'otpauth://totp/service:account?secret=A4GRFHVVRBGY7UIW&image=https%3A%2F%2Fen.opensuse.org%2Fimage.png';
const INVALID_OTPAUTH_URI = 'otpauth://Xotp/' . self::ACCOUNT . '?secret=' . self::SECRET;
const STEAM_TOTP_URI = 'otpauth://totp/' . self::STEAM . ':' . self::ACCOUNT . '?secret=' . self::STEAM_SECRET . '&issuer=' . self::STEAM . '&digits=' . self::DIGITS_STEAM . '&period=30&algorithm=' . self::ALGORITHM_DEFAULT;
const ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP = [
'service' => self::SERVICE,
'account' => self::ACCOUNT,
'icon' => self::ICON,
'otp_type' => 'totp',
'secret' => self::SECRET,
'digits' => self::DIGITS_CUSTOM,
'algorithm' => self::ALGORITHM_CUSTOM,
'period' => self::PERIOD_CUSTOM,
'counter' => null,
];
const ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_TOTP = [
'account' => self::ACCOUNT,
'otp_type' => 'totp',
'secret' => self::SECRET,
];
const ARRAY_OF_PARAMETERS_FOR_UNSUPPORTED_OTP_TYPE = [
'account' => self::ACCOUNT,
'otp_type' => 'Xotp',
'secret' => self::SECRET,
];
const ARRAY_OF_INVALID_PARAMETERS_FOR_TOTP = [
'account' => self::ACCOUNT,
'otp_type' => 'totp',
'secret' => 0,
];
const ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP = [
'service' => self::SERVICE,
'account' => self::ACCOUNT,
'icon' => self::ICON,
'otp_type' => 'hotp',
'secret' => self::SECRET,
'digits' => self::DIGITS_CUSTOM,
'algorithm' => self::ALGORITHM_CUSTOM,
'period' => null,
'counter' => self::COUNTER_CUSTOM,
];
const ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_HOTP = [
'account' => self::ACCOUNT,
'otp_type' => 'hotp',
'secret' => self::SECRET,
];
const ARRAY_OF_FULL_VALID_PARAMETERS_FOR_STEAM_TOTP = [
'service' => self::STEAM,
'account' => self::ACCOUNT,
'otp_type' => 'steamtotp',
'secret' => self::STEAM_SECRET,
'digits' => self::DIGITS_STEAM,
'algorithm' => self::ALGORITHM_DEFAULT,
'period' => self::PERIOD_DEFAULT,
'counter' => null,
];
const GOOGLE_AUTH_MIGRATION_URI = 'otpauth-migration://offline?data=CiQKCgcNEp61iE2P0RYSB2FjY291bnQaB3NlcnZpY2UgASgBMAIKLAoKBw0SnrWITY/RFhILYWNjb3VudF9iaXMaC3NlcnZpY2VfYmlzIAEoATACEAEYASAA';
const INVALID_GOOGLE_AUTH_MIGRATION_URI = 'otpauthmigration://offline?data=CiQKCgcNEp61iE2P0RYSB2FjY291bnQaB3NlcnZpY2UgASgBMAIKLAoKBw0SnrWITY/RFhILYWNjb3VudF9iaXMaC3NlcnZpY2VfYmlzIAEoATACEAEYASAA';
const GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA = 'otpauth-migration://offline?data=CiQKCgcNEp61iE2P0RYSB2FjY291bnQaB3NlcnZpY';
const AEGIS_JSON_MIGRATION_PAYLOAD = '
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 2,
"entries": [
{
"type": "totp",
"uuid": "5be1b189-260d-4fe1-930a-a78cb669dd86",
"name": "' . self::ACCOUNT . '_totp",
"issuer": "' . self::SERVICE . '_totp",
"note": "",
"icon": null,
"info": {
"secret": "' . self::SECRET . '",
"algo": "' . self::ALGORITHM_DEFAULT . '",
"digits": ' . self::DIGITS_DEFAULT . ',
"period": ' . self::PERIOD_DEFAULT . '
}
},
{
"type": "totp",
"uuid": "fb2ebd05-9d71-4b2e-9d4e-b7f8d2942bfb",
"name": "' . self::ACCOUNT . '_totp_custom",
"issuer": "' . self::SERVICE . '_totp_custom",
"note": "",
"icon": null,
"info": {
"secret": "' . self::SECRET . '",
"algo": "' . self::ALGORITHM_CUSTOM . '",
"digits": ' . self::DIGITS_CUSTOM . ',
"period": ' . self::PERIOD_CUSTOM . '
}
},
{
"type": "hotp",
"uuid": "90a2af2e-2857-4515-bb18-52c4fa823f6f",
"name": "' . self::ACCOUNT . '_hotp",
"issuer": "' . self::SERVICE . '_hotp",
"note": "",
"icon": null,
"info": {
"secret": "' . self::SECRET . '",
"algo": "' . self::ALGORITHM_DEFAULT . '",
"digits": ' . self::DIGITS_DEFAULT . ',
"counter": ' . self::COUNTER_DEFAULT . '
}
},
{
"type": "hotp",
"uuid": "e1b3f683-d5fe-4126-b616-8c8abd8ad97c",
"name": "' . self::ACCOUNT . '_hotp_custom",
"issuer": "' . self::SERVICE . '_hotp_custom",
"note": "",
"icon": null,
"info": {
"secret": "' . self::SECRET . '",
"algo": "' . self::ALGORITHM_CUSTOM . '",
"digits": ' . self::DIGITS_CUSTOM . ',
"counter": ' . self::COUNTER_CUSTOM . '
}
},
{
"type": "steamtotp",
"uuid": "9fb06143-421d-46e1-a7e9-4aafe44b0e72",
"name": "' . self::ACCOUNT . '_steam",
"issuer": "' . self::STEAM . '",
"note": "",
"icon": "null",
"info": {
"secret": "' . self::STEAM_SECRET . '",
"algo": "' . self::ALGORITHM_DEFAULT . '",
"digits": ' . self::DIGITS_STEAM . ',
"period": ' . self::PERIOD_DEFAULT . '
}
}
]
}
}';
const INVALID_AEGIS_JSON_MIGRATION_PAYLOAD = '
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 2,
"thisIsNotTheCorrectKeyName": [
{
"type": "totp",
"uuid": "5be1b189-260d-4fe1-930a-a78cb669dd86",
"name": "' . self::ACCOUNT . '",
"issuer": "' . self::SERVICE . '",
"note": "",
"icon": null,
"info": {
"secret": "' . self::SECRET . '",
"algo": "' . self::ALGORITHM_DEFAULT . '",
"digits": ' . self::DIGITS_DEFAULT . ',
"period": ' . self::PERIOD_DEFAULT . '
}
}
]
}
}';
const ENCRYPTED_AEGIS_JSON_MIGRATION_PAYLOAD = '
{
"version": 1,
"header": {
"slots": [
{
"type": 1,
"uuid": "1f447956-c71c-4be4-8192-97197dc67df7",
"key": "d742967686cae462c5732023a72d59245d8q7c5c93a66aeb2q2a350bb8b6a7ae",
"key_params": {
"nonce": "77a8ff6d84265efd2a3ed9b7",
"tag": "cc13fb4a5baz3fd27bc97f5eacaa00d0"
},
"n": 32768,
"r": 8,
"p": 1,
"salt": "1c245b2696b948dt040c30c538aeb6f9620b054d9ff182f33dd4b285b67bed51",
"repaired": true
}
],
"params": {
"nonce": "f31675d9966d2z588bd07788",
"tag": "ad5729fa135dc6d6sw87e0c955932661"
}
},
"db": "1rX0ajzsxNbhN2hvnNCMBNooLlzqwz\/LMT3bNEIJjPH+zIvIbA6GVVPHLpna+yvjxLPKVkt1OQig=="
}';
}

View File

@ -0,0 +1,161 @@
<?php
namespace Tests\Data;
class HttpRequestTestData
{
const TAG_NAME = 'v3.4.1';
const NEW_TAG_NAME = 'v3.4.2';
const SVG_LOGO_BODY = '<svg xmlns="http://www.w3.org/2000/svg" class="r-k200y r-13gxpu9 r-4qtqp9 r-yyyyoo r-np7d94 r-dnmrzs r-bnwqim r-1plcrui r-lrvibr" width="22.706" height="22.706"><path d="M22.706 4.311c-.835.37-1.732.62-2.675.733a4.67 4.67 0 002.048-2.578 9.3 9.3 0 01-2.958 1.13 4.66 4.66 0 00-7.938 4.25 13.229 13.229 0 01-9.602-4.868c-.4.69-.63 1.49-.63 2.342a4.66 4.66 0 002.072 3.878 4.647 4.647 0 01-2.11-.583v.06a4.66 4.66 0 003.737 4.568 4.692 4.692 0 01-2.104.08 4.661 4.661 0 004.352 3.234 9.348 9.348 0 01-5.786 1.995A9.5 9.5 0 010 18.487a13.175 13.175 0 007.14 2.093c8.57 0 13.255-7.098 13.255-13.254 0-.2-.005-.402-.014-.602a9.47 9.47 0 002.323-2.41z" fill="#1da1f2"/></svg>';
const TFA_JSON_BODY = '
[
[
"Twitch",
{
"domain": "twitch.tv",
"url": "https://www.twitch.tv/",
"tfa":
[
"sms",
"custom-software",
"totp"
],
"custom-software":
[
"Authy"
],
"documentation": "https://help.twitch.tv/s/article/two-factor-authentication",
"notes": "To activate two factor authentication, you must provide a mobile phone number.",
"keywords":
[
"entertainment"
]
}
],
[
"Twitter",
{
"domain": "twitter.com",
"tfa":
[
"sms",
"totp",
"u2f"
],
"documentation": "https://help.twitter.com/en/managing-your-account/two-factor-authentication",
"recovery": "https://help.twitter.com/en/managing-your-account/issues-with-login-authentication",
"notes": "SMS only available on select providers.",
"keywords":
[
"social"
]
}
],
[
"Txbit",
{
"domain": "txbit.io",
"tfa":
[
"totp"
],
"documentation": "https://support.txbit.io/support/solutions/articles/44000447137",
"keywords":
[
"cryptocurrencies"
]
}
]
]';
const LATEST_RELEASE_BODY_NO_NEW_RELEASE = '
{
"url": "https://api.github.com/repos/Bubka/2FAuth/releases/84186611",
"assets_url": "https://api.github.com/repos/Bubka/2FAuth/releases/84186611/assets",
"upload_url": "https://uploads.github.com/repos/Bubka/2FAuth/releases/84186611/assets{?name,label}",
"html_url": "https://github.com/Bubka/2FAuth/releases/tag/' . self::TAG_NAME . '",
"id": 84186611,
"author": {
"login": "Bubka",
"id": 858858,
"node_id": "MDQ6VXNlcjg1ODg1OA==",
"avatar_url": "https://avatars.githubusercontent.com/u/858858?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/Bubka",
"html_url": "https://github.com/Bubka",
"followers_url": "https://api.github.com/users/Bubka/followers",
"following_url": "https://api.github.com/users/Bubka/following{/other_user}",
"gists_url": "https://api.github.com/users/Bubka/gists{/gist_id}",
"starred_url": "https://api.github.com/users/Bubka/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/Bubka/subscriptions",
"organizations_url": "https://api.github.com/users/Bubka/orgs",
"repos_url": "https://api.github.com/users/Bubka/repos",
"events_url": "https://api.github.com/users/Bubka/events{/privacy}",
"received_events_url": "https://api.github.com/users/Bubka/received_events",
"type": "User",
"site_admin": false
},
"node_id": "RE_kwDOCyNVx84FBJXz",
"tag_name": "' . self::TAG_NAME . '",
"target_commitish": "master",
"name": "' . self::TAG_NAME . '",
"draft": false,
"prerelease": false,
"created_at": "2022-11-25T13:31:45Z",
"published_at": "2022-11-25T13:44:10Z",
"assets": [
],
"tarball_url": "https://api.github.com/repos/Bubka/2FAuth/tarball/' . self::TAG_NAME . '",
"zipball_url": "https://api.github.com/repos/Bubka/2FAuth/zipball/' . self::TAG_NAME . '",
"body": "### Fixed\r\n\r\n- [issue #140](https://github.com/Bubka/2FAuth/issues/140) Bad regex for Period field (advanced form)\r\n- [issue #141](https://github.com/Bubka/2FAuth/issues/141) Digits field is missing in advanced form"
}';
const LATEST_RELEASE_BODY_NEW_RELEASE = '
{
"url": "https://api.github.com/repos/Bubka/2FAuth/releases/84186611",
"assets_url": "https://api.github.com/repos/Bubka/2FAuth/releases/84186611/assets",
"upload_url": "https://uploads.github.com/repos/Bubka/2FAuth/releases/84186611/assets{?name,label}",
"html_url": "https://github.com/Bubka/2FAuth/releases/tag/' . self::NEW_TAG_NAME . '",
"id": 84186611,
"author": {
"login": "Bubka",
"id": 858858,
"node_id": "MDQ6VXNlcjg1ODg1OA==",
"avatar_url": "https://avatars.githubusercontent.com/u/858858?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/Bubka",
"html_url": "https://github.com/Bubka",
"followers_url": "https://api.github.com/users/Bubka/followers",
"following_url": "https://api.github.com/users/Bubka/following{/other_user}",
"gists_url": "https://api.github.com/users/Bubka/gists{/gist_id}",
"starred_url": "https://api.github.com/users/Bubka/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/Bubka/subscriptions",
"organizations_url": "https://api.github.com/users/Bubka/orgs",
"repos_url": "https://api.github.com/users/Bubka/repos",
"events_url": "https://api.github.com/users/Bubka/events{/privacy}",
"received_events_url": "https://api.github.com/users/Bubka/received_events",
"type": "User",
"site_admin": false
},
"node_id": "RE_kwDOCyNVx84FBJXz",
"tag_name": "' . self::NEW_TAG_NAME . '",
"target_commitish": "master",
"name": "' . self::NEW_TAG_NAME . '",
"draft": false,
"prerelease": false,
"created_at": "2022-12-25T13:31:45Z",
"published_at": "2022-12-25T13:44:10Z",
"assets": [
],
"tarball_url": "https://api.github.com/repos/Bubka/2FAuth/tarball/' . self::NEW_TAG_NAME . '",
"zipball_url": "https://api.github.com/repos/Bubka/2FAuth/zipball/' . self::NEW_TAG_NAME . '",
"body": "### Fixed\r\n\r\n- [issue #140](https://github.com/Bubka/2FAuth/issues/140) Bad regex for Period field (advanced form)\r\n- [issue #141](https://github.com/Bubka/2FAuth/issues/141) Digits field is missing in advanced form"
}';
const ICON_PNG = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAsUlEQVR4AWN44aVBEhoCGl4GGLzND/nYW/Fpdsf7urTX8Q74NLwtjf7z+vl/VPDzwvFX4eYIDUhm6//99AGi6PfDOz9OH4Tr+TSrHYuG1/GOn+f3AtGnOV0vvLXeZPr8+/IJouHbthU4nJQfAtQANBuuFJ+GDx2F///9g6gAMn5dOfP34zt8Gr7tWQ838n1DBlDk973r+DS8Sff+snQKBL2KsQOKfJzSAOFC9EPQcEhLAD5LqIU3S31+AAAAAElFTkSuQmCC';
}

View File

@ -0,0 +1,466 @@
<?php
namespace Tests\Data;
class MigrationTestData
{
const GOOGLE_AUTH_MIGRATION_URI = 'otpauth-migration://offline?data=CiQKCgcNEp61iE2P0RYSB2FjY291bnQaB3NlcnZpY2UgASgBMAIKLAoKBw0SnrWITY/RFhILYWNjb3VudF9iaXMaC3NlcnZpY2VfYmlzIAEoATACEAEYASAA';
const INVALID_GOOGLE_AUTH_MIGRATION_URI = 'otpauthmigration://offline?data=CiQKCgcNEp61iE2P0RYSB2FjY291bnQaB3NlcnZpY2UgASgBMAIKLAoKBw0SnrWITY/RFhILYWNjb3VudF9iaXMaC3NlcnZpY2VfYmlzIAEoATACEAEYASAA';
const GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA = 'otpauth-migration://offline?data=CiQKCgcNEp61iE2P0RYSB2FjY291bnQaB3NlcnZpY';
const VALID_PLAIN_TEXT_PAYLOAD = OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . PHP_EOL .
OtpTestData::HOTP_FULL_CUSTOM_URI_NO_IMG . PHP_EOL .
PHP_EOL .
OtpTestData::STEAM_TOTP_URI . PHP_EOL .
PHP_EOL;
const VALID_PLAIN_TEXT_PAYLOAD_WITH_INTRUDER = OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . PHP_EOL .
OtpTestData::HOTP_FULL_CUSTOM_URI_NO_IMG . PHP_EOL .
'This is an intruder line' . PHP_EOL .
OtpTestData::STEAM_TOTP_URI;
const INVALID_PLAIN_TEXT_NO_URI = '
XJGTDRUUKZH3X7TQN2QZUGCGXZCC5LXE
';
const INVALID_PLAIN_TEXT_ONLY_EMPTY_LINES = '
';
const PLAIN_TEXT_PAYLOAD_WITH_INVALID_URI = OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG . PHP_EOL .
'otpauth://totp/';
const VALID_AEGIS_JSON_MIGRATION_PAYLOAD = '
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 2,
"entries": [
{
"type": "totp",
"uuid": "fb2ebd05-9d71-4b2e-9d4e-b7f8d2942bfb",
"name": "' . OtpTestData::ACCOUNT . '",
"issuer": "' . OtpTestData::SERVICE . '",
"note": "",
"icon": null,
"info": {
"secret": "' . OtpTestData::SECRET . '",
"algo": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"period": ' . OtpTestData::PERIOD_CUSTOM . '
}
},
{
"type": "hotp",
"uuid": "e1b3f683-d5fe-4126-b616-8c8abd8ad97c",
"name": "' . OtpTestData::ACCOUNT . '",
"issuer": "' . OtpTestData::SERVICE . '",
"note": "",
"icon": null,
"info": {
"secret": "' . OtpTestData::SECRET . '",
"algo": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"counter": ' . OtpTestData::COUNTER_CUSTOM . '
}
},
{
"type": "steamtotp",
"uuid": "9fb06143-421d-46e1-a7e9-4aafe44b0e72",
"name": "' . OtpTestData::ACCOUNT . '",
"issuer": "' . OtpTestData::STEAM . '",
"note": "",
"icon": "null",
"info": {
"secret": "' . OtpTestData::STEAM_SECRET . '",
"algo": "' . OtpTestData::ALGORITHM_DEFAULT . '",
"digits": ' . OtpTestData::DIGITS_STEAM . ',
"period": ' . OtpTestData::PERIOD_DEFAULT . '
}
}
]
}
}';
const VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_SVG_ICON = '
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 2,
"entries": [
{
"type": "totp",
"uuid": "fb2ebd05-9d71-4b2e-9d4e-b7f8d2942bfb",
"name": "' . OtpTestData::ACCOUNT . '",
"issuer": "' . OtpTestData::SERVICE . '",
"note": "",
"icon": "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPg0KICAgPGNpcmNsZSBjeD0iNTEyIiBjeT0iNTEyIiByPSI1MTIiIHN0eWxlPSJmaWxsOiMwMDBlOWMiLz4NCiAgIDxwYXRoIGQ9Im03MDAuMiA0NjYuNSA2MS4yLTEwNi4zYzIzLjYgNDEuNiAzNy4yIDg5LjggMzcuMiAxNDEuMSAwIDY4LjgtMjQuMyAxMzEuOS02NC43IDE4MS40SDU3NS44bDQ4LjctODQuNmgtNjQuNGw3NS44LTEzMS43IDY0LjMuMXptLTU1LjQtMTI1LjJMNDQ4LjMgNjgyLjVsLjEuMkgyOTAuMWMtNDAuNS00OS41LTY0LjctMTEyLjYtNjQuNy0xODEuNCAwLTUxLjQgMTMuNi05OS42IDM3LjMtMTQxLjNsMTAyLjUgMTc4LjIgMTEzLjMtMTk3aDE2Ni4zeiIgc3R5bGU9ImZpbGw6I2ZmZiIvPg0KPC9zdmc+DQo=",
"icon_mime": "image\/svg+xml",
"info": {
"secret": "' . OtpTestData::SECRET . '",
"algo": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"period": ' . OtpTestData::PERIOD_CUSTOM . '
}
}
]
}
}';
const VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_PNG_ICON = '
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 2,
"entries": [
{
"type": "totp",
"uuid": "fb2ebd05-9d71-4b2e-9d4e-b7f8d2942bfb",
"name": "' . OtpTestData::ACCOUNT . '",
"issuer": "' . OtpTestData::SERVICE . '",
"note": "",
"icon": "iVBORw0KGgoAAAANSUhEUgAAA+gAAAPoAQMAAAB3bUanAAAABlBMVEUAAAD///+l2Z",
"icon_mime": "image\/png",
"info": {
"secret": "' . OtpTestData::SECRET . '",
"algo": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"period": ' . OtpTestData::PERIOD_CUSTOM . '
}
}
]
}
}';
const VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_JPG_ICON = '
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 2,
"entries": [
{
"type": "totp",
"uuid": "fb2ebd05-9d71-4b2e-9d4e-b7f8d2942bfb",
"name": "' . OtpTestData::ACCOUNT . '",
"issuer": "' . OtpTestData::SERVICE . '",
"note": "",
"icon": "/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAFA3PEY8MlBGQUZaVVBfeMiCeG5uePWvuZHI////////////////////////////////////////////////////2wBDAVVaWnhpeOuCguv/////////////////////////////////////////////////////////////////////////wAARCAAyADIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwCIeUkEbNFvLZ/iI6GpYlTzYHRNu7dkZz0oWJ/KVHt923ODvx1p6I/mxfudiJn+LPWgBjWyJEwf5duMSdc/h+lP+0JJL8i7nX7nOM56/wCTUVsGkVU3bo+d64xj0569fSpIY1haENH+8bdznp/TpQA+4RXnhVhkHd/Kq8JgllCeRjPfealUyusEoXeRuzyB7UJGyMGW1wR/00oAoUVP9kn/ALn6j/GigBqOsM4ZTvA/DPFWI5E80yEbtv3penX2/T9aaZIgshaTzi+OMFelSCNZp2cx7426NnGMD069aAG5SL5JYPLR+p356U1IGkswfvH+AdMc8/5NAMQnjNsu485GSO3vU9yGKsm7JfGxcenXn/GgCAW6qse5cyHPyZ+9+PQYHNSFGZpLdRiMY5/u9/qcmorh3jlX99udc/w4xmrbmUsUVdo/v5B/T9KAKP2Sf+5+o/xoo+yT/wBz9R/jRQBbSeV1DLBkH/bFRLEnmqj2+3dnB356UwxPLbQ7Fzjdnn3qWNGRrZWGCN1AEcSRBXPnbo+N42kfT36+lSQuyQQtnEY3b/z49+vpStNmJJ/KzjP8XTt+OaJNjWwjh53/AHR64PPWgBlvAjxRkx7t2dzbsYp1sjSwKrDEYz/wLn8xg/nUUccCZMp3I33G5GfXp/WiO3drYsjZ39Vx1wfWgB/+m/520VN5s/8Az7/+PiigDMqez/4+U/H+RoooAm07/lp+H9aIP+XX/gdFFABB/wAuv/A6mi/4+Z/+A/yoooAzKKKKAP/Z",
"icon_mime": "image\/jpeg",
"info": {
"secret": "' . OtpTestData::SECRET . '",
"algo": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"period": ' . OtpTestData::PERIOD_CUSTOM . '
}
}
]
}
}';
const VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_ICON = '
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 2,
"entries": [
{
"type": "totp",
"uuid": "fb2ebd05-9d71-4b2e-9d4e-b7f8d2942bfb",
"name": "' . OtpTestData::ACCOUNT . '",
"issuer": "' . OtpTestData::SERVICE . '",
"note": "",
"icon": "iVBORw0KGgoAAAANSUhEUgAAA+gAAAPoAQMAAAB3bUanAAAABlBMVEUAAAD///+l2Z",
"icon_mime": "image\/gif",
"info": {
"secret": "' . OtpTestData::SECRET . '",
"algo": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"period": ' . OtpTestData::PERIOD_CUSTOM . '
}
}
]
}
}';
const VALID_2FAS_MIGRATION_PAYLOAD = '{
"groups":
[{
"id": "C2F69014-C4C7-4EEC-9225-D24E750F77FD",
"name": "Test",
"isExpanded": true
}],
"schemaVersion": 2,
"appOrigin": "ios",
"appVersionCode": 32001,
"services":
[{
"secret": "' . OtpTestData::SECRET . '",
"badge": {"color": "Default"},
"order": {"position": 1},
"otp":
{
"account": "' . OtpTestData::ACCOUNT . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"counter": 0,
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"tokenType": "TOTP"
},
"updatedAt": 1657529430000,
"name": "' . OtpTestData::SERVICE . '",
"type": "ManuallyAdded"
},
{
"secret": "' . OtpTestData::SECRET . '",
"badge": {"color": "Default"},
"order": {"position": 2},
"otp":
{
"account": "' . OtpTestData::ACCOUNT . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"counter": ' . OtpTestData::COUNTER_CUSTOM . ',
"period": 30,
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"tokenType": "HOTP"
},
"updatedAt": 1657529430000,
"name": "' . OtpTestData::SERVICE . '",
"type": "ManuallyAdded",
"icon":
{
"selected": "Brand",
"brand":
{
"id": "ManuallyAdded"
},
"label":
{
"text": "OW",
"backgroundColor": "LightBlue"
}
}
}],
"appVersionName": "3.20.1"
}';
const VALID_2FAS_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE = '{
"groups":
[{
"id": "C2F69014-C4C7-4EEC-9225-D24E750F77FD",
"name": "Test",
"isExpanded": true
}],
"schemaVersion": 2,
"appOrigin": "ios",
"appVersionCode": 32001,
"services":
[{
"secret": "' . OtpTestData::SECRET . '",
"badge": {"color": "Default"},
"order": {"position": 1},
"otp":
{
"account": "' . OtpTestData::ACCOUNT . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"counter": 0,
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"tokenType": "TOTP"
},
"updatedAt": 1657529430000,
"name": "' . OtpTestData::SERVICE . '",
"type": "ManuallyAdded"
},
{
"secret": "' . OtpTestData::SECRET . '",
"badge": {"color": "Default"},
"order": {"position": 2},
"otp":
{
"account": "' . OtpTestData::ACCOUNT . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"counter": ' . OtpTestData::COUNTER_CUSTOM . ',
"period": 30,
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"tokenType": "XOTP"
},
"updatedAt": 1657529430000,
"name": "' . OtpTestData::SERVICE . '",
"type": "ManuallyAdded"
}],
"appVersionName": "3.20.1"
}';
const INVALID_2FAS_MIGRATION_PAYLOAD = '{
"groups":
[{
"id": "C2F69014-C4C7-4EEC-9225-D24E750F77FD",
"name": "Test",
"isExpanded": true
}],
"schemaVersion": 2,
"appOrigin": "ios",
"appVersionCode": 32001,
"service__BAD__NAME___s":
[{
"secret": "' . OtpTestData::SECRET . '",
"badge": {"color": "Default"},
"order": {"position": 1},
"otp":
{
"account": "' . OtpTestData::ACCOUNT . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"counter": 0,
"period": ' . OtpTestData::PERIOD_CUSTOM . ',
"algorithm": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"tokenType": "TOTP"
},
"updatedAt": 1657529430000,
"name": "' . OtpTestData::SERVICE . '",
"type": "ManuallyAdded"
}],
"appVersionName": "3.20.1"
}';
const ENCRYPTED_2FAS_MIGRATION_PAYLOAD = '
{
"reference": "RPKUw+hful0yPrvxaDjt2IZWMVgesKPMgJQ/U8CzT6I+eyPo9+yO1xX7jdYYS0aD3DGghxXBN6diIjTe5wKhbWXGvBdCF1a528j1/bgY0cn+MCXf0eG6uQ7oE+idBXeFZsXk5oxqZEA5waYsuwBenILCHqzU7mIlzqUXcNyb3gFF+AhkQGlfUBKeVcIXrbuTuwbtA8IwgJfV8klsUFH96EGsiCMQ2v2MCXVaqFR3TlTMfj4iDg9ULktnKisxfm6O3B/XQtsiDVpnhqQzFSqktbh9vKOrO2NgK40kLmgRkS08SpxiC4Yt0CoGyp9bgLYySdQDvKMnbYxVatx6AJpckVuA7ICiQjbHa5mItGITU78=:yrDmOf1HirJ5N7wlYXyUmL0EtNy2t5oNpvQw1rV22nA=:8MwK8sWyL3iVv+6w",
"schemaVersion": 2,
"appVersionCode": 32001,
"services":
[],
"groups":
[
{
"id": "C2F69014-C4C7-4EEC-9225-D24E750F77FD",
"name": "Test",
"isExpanded": true
}
],
"appOrigin": "ios",
"servicesEncrypted": "xlH6lrz/oW/TJO2WExdt33bI4CvCjE/W2wlcl+8VVbRj/BHrYujY7AexaenaBhoJUB0xeOMDL1q2swvtVYQZLAMFoBcg4XMEVfk5R6R0SjwJd7/cvcv89nKNDX/eHtIHKoaDkgFxk4Po9YN1OtG0M/jXH9DdERu+LAJHboLK8VpnehOJTbs/s0vahrw0q4jZiHGCfV7vXrylsuEjXBVv90/dT0mZE5iiPIteWCJyxl+04cuTLMl6kMUXem0OGTsqOLmmSMVz8sCrf6HRzqH8KFj1sR6h+MGVdsVij8f99O4ZB+tCCy0A3BY8Yn68J8aBAOUU4nzETAl0Zhc489E9X9eibMLkaBt452gfwXU/9muIeA2WM8so3sM6SPQVls8xYu653z7bh7WCCIjrJmZ10DVydK9Krjoe6nd+DQjuNy/vcNFeCe6/CljF3KWfWyNRdcwaUH+yKZE/TFw3P+ZuvudnfHRBE6OsE3c/XmxnI66mSy+SePhmIoe9x0rxC5T5LM5Fw+bWBLR6mLXo1ouxxrljeboqJ4THR6a2x8hhhP8wvK30v/PDPyLTtBdLv+HilQibXF9YqVH562BhlmhLAe5dv67srP6JwK10tAo5TfhKjewMqTouOiCqT+/mvvqpTV/1f676mzF7s1Rh83ikfqzT/u5OJrFqZdu3tC86KqCLIDx0fQYF/Ha88tXPkVGvPXygH3Adgf7c8spyC9s3yeAAtSSc/qtIpm1Ux5XvaZx3M9cD9ku0KLJqE+rJr0HS2AatS54I/ybdHSbMLaPRdV67jeM1OwCkY5F+FXQpFfcQkpmgCMTvFddnkhUqTR8GgqXcOg2aPcwNQ/EqhIJr+sXqfFS85+eSlroErvUuR9gO/awB2XA4NsZI6bHK9GNebcSTmA16FhiuAFpOqfWcdN3iPeKS/kcUJ8KRlsAO0x9UINnkwJtpSGSA8bdXkOFfnd4rPPDcBGrx7dBtPC+Sc5Y8nvbkLcxn8/CCFgenQByPwGLUp4NuMz8H1/1EwNQ4jF5swoqdnhz6ldBAF+T1trOzQEmpFfG54lur3yEoiYipaZ5WTKJnp6X0GHkgT3Wi0N0im4oGEsqyPEEknm17BamooqRo1JMPA6+cp90udr91DKeLxzRBi8rYaolQjaATbc6rvufcoCuEdOYpGqWfX6mGKD/jmHko4RaoM+7cx6LaH5zRdpBLDzr1aW0TJSAHGgTuTiMJwW73MBaayUf+X+alzlXpcyDlWW+aGpSsYI/TkKTPaIgQFcNWa/+1ayUyg1Tmvuf1J6YgyQJNBVf2LV06I+TziUHPGkwwN7nj/0chqw4sv29I4mpI41C6a/NnoSCa3Vz448DPlk1e80KyMba722gBMTCyU+S1K/UtVE7cPchOETL7X5tl6yoyeh95jbpYX4kJhXHNFZ5XA7d0tFxmYF0mSN5+D63jP2tTqPW7lsz3No9y+VFhQLWTEShrBck1blgNXRFdUCDX5zN4vSYmt44vYGWl3+iDNhYysVh2x2bAGOxCR5EPNPNuUjrxCHRtt8X7Xof7/R0fZF4Qi/F1o6D/ToUpp4XZ/3sjJ1BTouw2Jpx/f2gGDwzFq8uql46eeBgqiQwCSYZTbSSYliVaXh01jJltfqFaMMQF9UGehNdcsAHaLr53I8snMJZLl4IIGwtNgb9cHZYXYM68RtP0UPBntCFA74fWMBVcLLfbpUcGe5fuj4CoBo5gCakYygnsvlqcsmXNR/zaf5xIOC009qVUU0BO+qtgt+TGxAn4K9jgxVOCzR7TPGPvwaOajURwcutj9QXUKuqHz9LwIYZLLzDd2OQxkDoPKfLqKSLP4ZXpKiRP8LkgvZE91ZgxJgCy37STqO1py/VyrHjT3OOmbZ6srwaIOJhZ8/YGh6mecA7n4qJsJUepvD3sbAF0FZJE0xu1kkw=:yrDmOf1HirJ5N7wlYXyUmL0EtNy2t5oNpvQw1rV22nA=:ZhgkRGr/vRf6K1ri",
"appVersionName": "3.20.1"
}';
const VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE = '
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 2,
"entries": [
{
"type": "totp",
"uuid": "fb2ebd05-9d71-4b2e-9d4e-b7f8d2942bfb",
"name": "' . OtpTestData::ACCOUNT . '",
"issuer": "' . OtpTestData::SERVICE . '",
"note": "",
"icon": null,
"info": {
"secret": "' . OtpTestData::SECRET . '",
"algo": "' . OtpTestData::ALGORITHM_CUSTOM . '",
"digits": ' . OtpTestData::DIGITS_CUSTOM . ',
"period": ' . OtpTestData::PERIOD_CUSTOM . '
}
},
{
"type": "xotp",
"uuid": "e1b3f683-d5fe-4126-b616-8c8abd8ad97c",
"name": "",
"issuer": "",
"note": "",
"icon": null,
"info": {
"secret": "",
"algo": "",
"digits": 0,
"counter": 0
}
}
]
}
}';
const INVALID_AEGIS_JSON_MIGRATION_PAYLOAD = '
{
"version": 1,
"header": {
"slots": null,
"params": null
},
"db": {
"version": 2,
"thisIsNotTheCorrectKeyName": [
{
"type": "totp",
"uuid": "5be1b189-260d-4fe1-930a-a78cb669dd86",
"name": "' . OtpTestData::ACCOUNT . '",
"issuer": "' . OtpTestData::SERVICE . '",
"note": "",
"icon": null,
"info": {
"secret": "' . OtpTestData::SECRET . '",
"algo": "' . OtpTestData::ALGORITHM_DEFAULT . '",
"digits": ' . OtpTestData::DIGITS_DEFAULT . ',
"period": ' . OtpTestData::PERIOD_DEFAULT . '
}
}
]
}
}';
const ENCRYPTED_AEGIS_JSON_MIGRATION_PAYLOAD = '
{
"version": 1,
"header": {
"slots": [
{
"type": 1,
"uuid": "1f447956-c71c-4be4-8192-97197dc67df7",
"key": "d742967686cae462c5732023a72d59245d8q7c5c93a66aeb2q2a350bb8b6a7ae",
"key_params": {
"nonce": "77a8ff6d84265efd2a3ed9b7",
"tag": "cc13fb4a5baz3fd27bc97f5eacaa00d0"
},
"n": 32768,
"r": 8,
"p": 1,
"salt": "1c245b2696b948dt040c30c538aeb6f9620b054d9ff182f33dd4b285b67bed51",
"repaired": true
}
],
"params": {
"nonce": "f31675d9966d2z588bd07788",
"tag": "ad5729fa135dc6d6sw87e0c955932661"
}
},
"db": "1rX0ajzsxNbhN2hvnNCMBNooLlzqwz\/LMT3bNEIJjPH+zIvIbA6GVVPHLpna+yvjxLPKVkt1OQig=="
}';
}

116
tests/Data/OtpTestData.php Normal file
View File

@ -0,0 +1,116 @@
<?php
namespace Tests\Data;
class OtpTestData
{
const ACCOUNT = 'account';
const SERVICE = 'service';
const STEAM = 'Steam';
const SECRET = 'A4GRFHVVRBGY7UIW';
const STEAM_SECRET = 'XJGTDRUUKZH3X7TQN2QZUGCGXZCC5LXE';
const ALGORITHM_DEFAULT = 'sha1';
const ALGORITHM_CUSTOM = 'sha256';
const DIGITS_DEFAULT = 6;
const DIGITS_CUSTOM = 7;
const DIGITS_STEAM = 5;
const PERIOD_DEFAULT = 30;
const PERIOD_CUSTOM = 40;
const COUNTER_DEFAULT = 0;
const COUNTER_CUSTOM = 5;
const IMAGE = 'https%3A%2F%2Fen.opensuse.org%2Fimages%2F4%2F44%2FButton-filled-colour.png';
const ICON = 'test.png';
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 HOTP_FULL_CUSTOM_URI_NO_IMG = 'otpauth://hotp/' . self::SERVICE . ':' . self::ACCOUNT . '?secret=' . self::SECRET . '&issuer=' . self::SERVICE . '&digits=' . self::DIGITS_CUSTOM . '&counter=' . self::COUNTER_CUSTOM . '&algorithm=' . self::ALGORITHM_CUSTOM;
const HOTP_FULL_CUSTOM_URI = self::HOTP_FULL_CUSTOM_URI_NO_IMG . '&image=' . self::IMAGE;
const TOTP_SHORT_URI = 'otpauth://totp/' . self::ACCOUNT . '?secret=' . self::SECRET;
const HOTP_SHORT_URI = 'otpauth://hotp/' . self::ACCOUNT . '?secret=' . self::SECRET;
const TOTP_URI_WITH_UNREACHABLE_IMAGE = 'otpauth://totp/service:account?secret=A4GRFHVVRBGY7UIW&image=https%3A%2F%2Fen.opensuse.org%2Fimage.png';
const INVALID_OTPAUTH_URI = 'otpauth://Xotp/' . self::ACCOUNT . '?secret=' . self::SECRET;
const STEAM_TOTP_URI = 'otpauth://totp/' . self::STEAM . ':' . self::ACCOUNT . '?secret=' . self::STEAM_SECRET . '&issuer=' . self::STEAM . '&digits=' . self::DIGITS_STEAM . '&period=30&algorithm=' . self::ALGORITHM_DEFAULT;
const ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP = [
'service' => self::SERVICE,
'account' => self::ACCOUNT,
'icon' => self::ICON,
'otp_type' => 'totp',
'secret' => self::SECRET,
'digits' => self::DIGITS_CUSTOM,
'algorithm' => self::ALGORITHM_CUSTOM,
'period' => self::PERIOD_CUSTOM,
'counter' => null,
];
const ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_TOTP = [
'account' => self::ACCOUNT,
'otp_type' => 'totp',
'secret' => self::SECRET,
];
const ARRAY_OF_PARAMETERS_FOR_UNSUPPORTED_OTP_TYPE = [
'account' => self::ACCOUNT,
'otp_type' => 'Xotp',
'secret' => self::SECRET,
];
const ARRAY_OF_INVALID_PARAMETERS_FOR_TOTP = [
'account' => self::ACCOUNT,
'otp_type' => 'totp',
'secret' => 0,
];
const ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP = [
'service' => self::SERVICE,
'account' => self::ACCOUNT,
'icon' => self::ICON,
'otp_type' => 'hotp',
'secret' => self::SECRET,
'digits' => self::DIGITS_CUSTOM,
'algorithm' => self::ALGORITHM_CUSTOM,
'period' => null,
'counter' => self::COUNTER_CUSTOM,
];
const ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_HOTP = [
'account' => self::ACCOUNT,
'otp_type' => 'hotp',
'secret' => self::SECRET,
];
const ARRAY_OF_FULL_VALID_PARAMETERS_FOR_STEAM_TOTP = [
'service' => self::STEAM,
'account' => self::ACCOUNT,
'otp_type' => 'steamtotp',
'secret' => self::STEAM_SECRET,
'digits' => self::DIGITS_STEAM,
'algorithm' => self::ALGORITHM_DEFAULT,
'period' => self::PERIOD_DEFAULT,
'counter' => null,
];
}

View File

@ -9,6 +9,12 @@ use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
use Tests\FeatureTestCase;
/**
* @covers \App\Http\Controllers\Auth\ForgotPasswordController
* @covers \App\Models\User
* @covers \App\Http\Middleware\RejectIfDemoMode
* @covers \App\Http\Middleware\RejectIfAuthenticated
*/
class ForgotPasswordControllerTest extends FeatureTestCase
{
/**
@ -26,7 +32,7 @@ class ForgotPasswordControllerTest extends FeatureTestCase
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['email']);
->assertJsonValidationErrors(['email']);
}
/**
@ -39,7 +45,7 @@ class ForgotPasswordControllerTest extends FeatureTestCase
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['email']);
->assertJsonValidationErrors(['email']);
}
/**
@ -52,7 +58,7 @@ class ForgotPasswordControllerTest extends FeatureTestCase
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['email']);
->assertJsonValidationErrors(['email']);
}
/**
@ -91,4 +97,21 @@ class ForgotPasswordControllerTest extends FeatureTestCase
$response->assertStatus(401);
}
/**
* @test
*/
public function test_submit_email_password_request_when_authenticated_returns_bad_request()
{
$user = User::factory()->create();
$this->actingAs($user, 'web-guard')
->json('POST', '/user/password/lost', [
'email' => $user->email,
])
->assertStatus(400)
->assertJsonStructure([
'message',
]);
}
}

View File

@ -5,7 +5,15 @@ namespace Tests\Feature\Http\Auth;
use App\Facades\Settings;
use App\Models\User;
use Tests\FeatureTestCase;
use Illuminate\Support\Carbon;
/**
* @covers \App\Http\Controllers\Auth\LoginController
* @covers \App\Http\Middleware\RejectIfAuthenticated
* @covers \App\Http\Middleware\RejectIfReverseProxy
* @covers \App\Http\Middleware\RejectIfDemoMode
* @covers \App\Http\Middleware\SkipIfAuthenticated
*/
class LoginTest extends FeatureTestCase
{
/**
@ -20,7 +28,7 @@ class LoginTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
@ -36,15 +44,35 @@ class LoginTest extends FeatureTestCase
'email' => $this->user->email,
'password' => self::PASSWORD,
])
->assertOk()
->assertExactJson([
'message' => 'authenticated',
'name' => $this->user->name,
]);
->assertOk()
->assertExactJson([
'message' => 'authenticated',
'name' => $this->user->name,
]);
}
/**
* @test
*
* @covers \App\Rules\CaseInsensitiveEmailExists
*/
public function test_user_login_with_uppercased_email_returns_success()
{
$response = $this->json('POST', '/user/login', [
'email' => strtoupper($this->user->email),
'password' => self::PASSWORD,
])
->assertOk()
->assertExactJson([
'message' => 'authenticated',
'name' => $this->user->name,
]);
}
/**
* @test
*
* @covers \App\Http\Middleware\SkipIfAuthenticated
*/
public function test_user_login_already_authenticated_returns_bad_request()
{
@ -74,26 +102,28 @@ class LoginTest extends FeatureTestCase
'email' => '',
'password' => '',
])
->assertStatus(422)
->assertJsonValidationErrors([
'email',
'password',
]);
->assertStatus(422)
->assertJsonValidationErrors([
'email',
'password',
]);
}
/**
* @test
*
* @covers \App\Exceptions\Handler
*/
public function test_user_login_with_invalid_credentials_returns_validation_error()
public function test_user_login_with_invalid_credentials_returns_authentication_error()
{
$response = $this->json('POST', '/user/login', [
'email' => $this->user->email,
'password' => self::WRONG_PASSWORD,
])
->assertStatus(401)
->assertJson([
'message' => 'unauthorised',
]);
->assertStatus(401)
->assertJson([
'message' => 'unauthorised',
]);
}
/**
@ -154,6 +184,9 @@ class LoginTest extends FeatureTestCase
/**
* @test
*
* @covers \App\Http\Middleware\KickOutInactiveUser
* @covers \App\Http\Middleware\LogUserLastSeen
*/
public function test_user_logout_after_inactivity_returns_teapot()
{
@ -169,7 +202,7 @@ class LoginTest extends FeatureTestCase
$response = $this->actingAs($this->user, 'api-guard')
->json('GET', '/api/v1/twofaccounts');
sleep(61);
$this->travelTo(Carbon::now()->addMinutes(2));
$response = $this->actingAs($this->user, 'api-guard')
->json('GET', '/api/v1/twofaccounts')

View File

@ -5,6 +5,9 @@ namespace Tests\Feature\Http\Auth;
use App\Models\User;
use Tests\FeatureTestCase;
/**
* @covers \App\Http\Controllers\Auth\PasswordController
*/
class PasswordControllerTest extends FeatureTestCase
{
/**
@ -19,7 +22,7 @@ class PasswordControllerTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();

View File

@ -6,6 +6,9 @@ use App\Models\User;
use Illuminate\Support\Facades\DB;
use Tests\FeatureTestCase;
/**
* @covers \App\Http\Controllers\Auth\RegisterController
*/
class RegisterControllerTest extends FeatureTestCase
{
private const USERNAME = 'john doe';
@ -17,7 +20,7 @@ class RegisterControllerTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
}
@ -35,18 +38,20 @@ class RegisterControllerTest extends FeatureTestCase
'password' => self::PASSWORD,
'password_confirmation' => self::PASSWORD,
])
->assertCreated()
->assertJsonStructure([
'message',
'name',
])
->assertJsonFragment([
'name' => self::USERNAME,
]);
->assertCreated()
->assertJsonStructure([
'message',
'name',
])
->assertJsonFragment([
'name' => self::USERNAME,
]);
}
/**
* @test
*
* @covers \App\Rules\FirstUser
*/
public function test_register_returns_already_an_existing_user()
{
@ -59,7 +64,7 @@ class RegisterControllerTest extends FeatureTestCase
'password' => self::PASSWORD,
'password_confirmation' => self::PASSWORD,
])
->assertJsonValidationErrorFor('name');
->assertJsonValidationErrorFor('name');
}
/**

View File

@ -8,6 +8,10 @@ use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Password;
use Tests\FeatureTestCase;
/**
* @covers \App\Http\Controllers\Auth\ResetPasswordController
* @covers \App\Models\User
*/
class ResetPasswordControllerTest extends FeatureTestCase
{
/**
@ -28,7 +32,7 @@ class ResetPasswordControllerTest extends FeatureTestCase
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['email', 'password', 'token']);
->assertJsonValidationErrors(['email', 'password', 'token']);
}
/**
@ -44,7 +48,7 @@ class ResetPasswordControllerTest extends FeatureTestCase
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['email', 'password']);
->assertJsonValidationErrors(['email', 'password']);
}
/**
@ -60,7 +64,7 @@ class ResetPasswordControllerTest extends FeatureTestCase
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['password']);
->assertJsonValidationErrors(['password']);
}
/**

View File

@ -7,6 +7,10 @@ use App\Models\User;
use Illuminate\Support\Facades\Config;
use Tests\FeatureTestCase;
/**
* @covers \App\Http\Controllers\Auth\UserController
* @covers \App\Http\Middleware\RejectIfDemoMode
*/
class UserControllerTest extends FeatureTestCase
{
/**
@ -23,7 +27,7 @@ class UserControllerTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();

View File

@ -5,7 +5,16 @@ namespace Tests\Feature\Http\Auth;
use App\Models\User;
use Illuminate\Support\Facades\Notification;
use Tests\FeatureTestCase;
use App\Notifications\WebauthnRecoveryNotification;
use Illuminate\Support\Facades\Lang;
/**
* @covers \App\Http\Controllers\Auth\WebAuthnDeviceLostController
* @covers \App\Notifications\WebauthnRecoveryNotification
* @covers \App\Extensions\WebauthnCredentialBroker
* @covers \App\Http\Requests\WebauthnDeviceLostRequest
* @covers \App\Providers\AuthServiceProvider
*/
class WebAuthnDeviceLostControllerTest extends FeatureTestCase
{
/**
@ -16,7 +25,7 @@ class WebAuthnDeviceLostControllerTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
@ -25,6 +34,7 @@ class WebAuthnDeviceLostControllerTest extends FeatureTestCase
/**
* @test
* @covers \App\Models\Traits\WebAuthnManageCredentials
*/
public function test_sendRecoveryEmail_sends_notification_on_success()
{
@ -34,18 +44,60 @@ class WebAuthnDeviceLostControllerTest extends FeatureTestCase
'email' => $this->user->email,
]);
Notification::assertSentTo($this->user, \App\Notifications\WebauthnRecoveryNotification::class);
Notification::assertSentTo($this->user, WebauthnRecoveryNotification::class);
$response->assertStatus(200)
->assertJsonStructure([
'message',
->assertJsonStructure([
'message',
]);
$this->assertDatabaseHas('webauthn_recoveries', [
'email' => $this->user->email
]);
}
/**
* @test
*/
public function test_sendRecoveryEmail_does_not_send_anything_on_error()
public function test_WebauthnRecoveryNotification_renders_to_email()
{
$mail = (new WebauthnRecoveryNotification('test_token'))->toMail($this->user)->render();
$this->assertStringContainsString(
'http://localhost/webauthn/recover?token=test_token&amp;email=' . urlencode($this->user->email),
$mail
);
$this->assertStringContainsString(
Lang::get('Recover Account'),
$mail
);
$this->assertStringContainsString(
Lang::get(
'You are receiving this email because we received an account recovery request for your account.'
),
$mail
);
$this->assertStringContainsString(
Lang::get(
'This recovery link will expire in :count minutes.',
['count' => config('auth.passwords.webauthn.expire')]
),
$mail
);
$this->assertStringContainsString(
Lang::get('If you did not request an account recovery, no further action is required.'),
$mail
);
}
/**
* @test
*/
public function test_sendRecoveryEmail_does_not_send_anything_to_unknown_email()
{
Notification::fake();
@ -56,8 +108,103 @@ class WebAuthnDeviceLostControllerTest extends FeatureTestCase
Notification::assertNothingSent();
$response->assertStatus(422)
->assertJsonValidationErrors([
'email',
->assertJsonValidationErrors([
'email',
]);
$this->assertDatabaseMissing('webauthn_recoveries', [
'email' => 'bad@email.com'
]);
}
/**
* @test
*/
public function test_sendRecoveryEmail_does_not_send_anything_to_invalid_email()
{
Notification::fake();
$response = $this->json('POST', '/webauthn/lost', [
'email' => 'bad@email.com',
]);
Notification::assertNothingSent();
$response->assertStatus(422)
->assertJsonValidationErrors([
'email',
]);
$this->assertDatabaseMissing('webauthn_recoveries', [
'email' => 'bad@email.com'
]);
}
/**
* @test
*/
public function test_sendRecoveryEmail_does_not_send_anything_to_not_WebAuthnAuthenticatable()
{
$mock = $this->mock(\App\Extensions\WebauthnCredentialBroker::class)->makePartial();
$mock->shouldReceive('getUser')
->andReturn(new \Illuminate\Foundation\Auth\User());
Notification::fake();
$response = $this->json('POST', '/webauthn/lost', [
'email' => $this->user->email,
]);
Notification::assertNothingSent();
$response->assertStatus(422)
->assertJsonValidationErrors([
'email',
]);
}
/**
* @test
*/
public function test_sendRecoveryEmail_is_throttled()
{
Notification::fake();
$response = $this->json('POST', '/webauthn/lost', [
'email' => $this->user->email,
]);
Notification::assertSentTo($this->user, WebauthnRecoveryNotification::class);
$response->assertStatus(200)
->assertJsonStructure([
'message',
]);
$this->assertDatabaseHas('webauthn_recoveries', [
'email' => $this->user->email
]);
$this->json('POST', '/webauthn/lost', [
'email' => $this->user->email,
])
->assertStatus(422)
->assertJsonValidationErrorfor('email')
->assertJsonFragment([
'message' => __('passwords.throttled')
]);
}
/**
* @test
*/
public function test_error_if_no_broker_is_set()
{
$this->app['config']->set('auth.passwords.webauthn', null);
$this->json('POST', '/webauthn/lost', [
'email' => $this->user->email
])
->assertStatus(500);
}
}

View File

@ -6,7 +6,14 @@ use App\Models\User;
use Illuminate\Support\Facades\DB;
use Laragear\WebAuthn\Http\Requests\AssertedRequest;
use Tests\FeatureTestCase;
use Laragear\WebAuthn\WebAuthn;
use Illuminate\Support\Facades\Config;
use Laragear\WebAuthn\Assertion\Validator\AssertionValidator;
/**
* @covers \App\Http\Controllers\Auth\WebAuthnLoginController
* @covers \App\Models\User
*/
class WebAuthnLoginControllerTest extends FeatureTestCase
{
/**
@ -15,15 +22,44 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
protected $user;
const CREDENTIAL_ID = 's06aG41wsIYh5X1YUhB-SlH8y3F2RzdJZVse8iXRXOCd3oqQdEyCOsBawzxrYBtJRQA2azAMEN_q19TUp6iMgg';
const CREDENTIAL_ID_ALT = '-VOLFKPY-_FuMI_sJ7gMllK76L3VoRUINj6lL_Z3qDg';
const CREDENTIAL_ID_ALT_RAW = '+VOLFKPY+/FuMI/sJ7gMllK76L3VoRUINj6lL/Z3qDg=';
const PUBLIC_KEY = 'eyJpdiI6ImYyUHlJOEJML0pwTXJ2UDkveTQwZFE9PSIsInZhbHVlIjoiQWFSYi9LVEszazlBRUZsWHp0cGNRNktGeEQ3aTBsbU9zZ1g5MEgrWFJJNmgraElsNU9hV0VsRVlWc3NoUVVHUjRRdlcxTS9pVklnOWtVYWY5TFJQTTFhR1Rxb1ZzTFkxTWE4VUVvK1lyU3pYQ1M3VlBMWWxZcDVaYWFnK25iaXVyWGR6ZFRmMFVoSmdPZ3UvSnptbVZER0FYdEEyYmNYcW43RkV5aTVqSjNwZEFsUjhUYSs0YjU2Z2V2bUJXa0E0aVB1VC8xSjdJZ2llRGlHY2RwOGk3MmNPTyt6eDFDWUs1dVBOSWp1ZUFSeUlkclgwRW16RE9sUUpDSWV6Sk50TSIsIm1hYyI6IjI3ODQ5NzcxZGY1MzMwYTNiZjAwZmEwMDJkZjYzMGU4N2UzZjZlOGM0ZWE3NDkyYWMxMThhNmE5NWZiMTVjNGEiLCJ0YWciOiIifQ==';
const USER_ID = '3b758ac868b74307a7e96e69ae187339';
const USER_ID_ALT = 'e8af6f703f8042aa91c30cf72289aa07';
const ASSERTION_RESPONSE = [
'id' => self::CREDENTIAL_ID_ALT,
'rawId' => self::CREDENTIAL_ID_ALT_RAW,
'type' => 'public-key',
'response' => [
'clientDataJSON' => 'eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiaVhvem15bktpLVlEMmlSdktOYlNQQSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3QiLCJjcm9zc09yaWdpbiI6ZmFsc2V9',
'authenticatorData' => 'SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAQ==',
'signature' => 'ca4IJ9h8bZnjMbEFuHX1zfX5LcbiPyDVz6sD1/ppR4t8++1DxKa5EdBIrfNlo8FSOv/JSzMrGGUCQvc/Ngj1KnZpO3s9OdTb54/gMDewH/K8EG4wSvxzHdL6sMbP7UUc5Wq1pcdu9MgXY8V+1gftXpzcoaae0X+mLEETgU7eB8jG0mZhVWvE4yQKuDnZA1i9r8oQhqsvG4nUw1BxvR8wAGiRR+R287LaL41k+xum5mS8zEojUmuLSH50miyVxZ4Y+/oyfxG7i+wSYGNSXlW5iNPB+2WupGS7ce4TuOgaFeMmP2a9rzP4m2IBSQoJ2FyrdzR7HwBEewqqrUVbGQw3Aw==',
'userHandle' => self::USER_ID_ALT,
]
];
const ASSERTION_RESPONSE_NO_HANDLE = [
'id' => self::CREDENTIAL_ID_ALT,
'rawId' => self::CREDENTIAL_ID_ALT_RAW,
'type' => 'public-key',
'response' => [
'clientDataJSON' => 'eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiaVhvem15bktpLVlEMmlSdktOYlNQQSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3QiLCJjcm9zc09yaWdpbiI6ZmFsc2V9',
'authenticatorData' => 'SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAQ==',
'signature' => 'ca4IJ9h8bZnjMbEFuHX1zfX5LcbiPyDVz6sD1/ppR4t8++1DxKa5EdBIrfNlo8FSOv/JSzMrGGUCQvc/Ngj1KnZpO3s9OdTb54/gMDewH/K8EG4wSvxzHdL6sMbP7UUc5Wq1pcdu9MgXY8V+1gftXpzcoaae0X+mLEETgU7eB8jG0mZhVWvE4yQKuDnZA1i9r8oQhqsvG4nUw1BxvR8wAGiRR+R287LaL41k+xum5mS8zEojUmuLSH50miyVxZ4Y+/oyfxG7i+wSYGNSXlW5iNPB+2WupGS7ce4TuOgaFeMmP2a9rzP4m2IBSQoJ2FyrdzR7HwBEewqqrUVbGQw3Aw==',
'userHandle' => null,
]
];
const ASSERTION_CHALLENGE = 'iXozmynKi+YD2iRvKNbSPA==';
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
@ -47,6 +83,42 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
->assertNoContent();
}
/**
* @test
*/
public function test_webauthn_login_merge_handle_if_missing()
{
$this->user = User::factory()->create();
DB::table('webauthn_credentials')->insert([
'id' => self::CREDENTIAL_ID_ALT,
'authenticatable_type' => \App\Models\User::class,
'authenticatable_id' => $this->user->id,
'user_id' => self::USER_ID_ALT,
'counter' => 0,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost',
'aaguid' => '00000000-0000-0000-0000-000000000000',
'attestation_format' => 'none',
'public_key' => self::PUBLIC_KEY,
'updated_at' => now(),
'created_at' => now(),
]);
$this->session(['_webauthn' => new \Laragear\WebAuthn\Challenge(
new \Laragear\WebAuthn\ByteBuffer(base64_decode(self::ASSERTION_CHALLENGE)),
60,
false,
)]);
$this->mock(AssertionValidator::class)
->expects('send->thenReturn')
->andReturn();
$this->json('POST', '/webauthn/login', self::ASSERTION_RESPONSE_NO_HANDLE)
->assertNoContent();
}
/**
* @test
*/
@ -84,22 +156,24 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
];
$response = $this->json('POST', '/webauthn/login', $data)
->assertStatus(422)
->assertJsonValidationErrors([
'id',
'rawId',
'type',
'response.authenticatorData',
'response.clientDataJSON',
'response.signature',
]);
->assertStatus(422)
->assertJsonValidationErrors([
'id',
'rawId',
'type',
'response.authenticatorData',
'response.clientDataJSON',
'response.signature',
]);
}
/**
* @test
*/
public function test_get_options_returns_success()
public function test_get_options_for_securelogin_returns_success()
{
Config::set('webauthn.user_verification', WebAuthn::USER_VERIFICATION_REQUIRED);
$this->user = User::factory()->create();
DB::table('webauthn_credentials')->insert([
@ -118,18 +192,59 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
]);
$response = $this->json('POST', '/webauthn/login/options')
->assertOk()
->assertJsonStructure([
'challenge',
'userVerification',
'timeout',
])
->assertJsonFragment([
'allowCredentials' => [[
'id' => self::CREDENTIAL_ID,
'type' => 'public-key',
]],
->assertOk()
->assertJsonStructure([
'challenge',
'userVerification',
'timeout',
])
->assertJsonFragment([
'userVerification' => 'required',
'allowCredentials' => [[
'id' => self::CREDENTIAL_ID,
'type' => 'public-key',
]],
]);
}
/**
* @test
*/
public function test_get_options_for_fastlogin_returns_success()
{
Config::set('webauthn.user_verification', WebAuthn::USER_VERIFICATION_DISCOURAGED);
$this->user = User::factory()->create();
DB::table('webauthn_credentials')->insert([
'id' => self::CREDENTIAL_ID,
'authenticatable_type' => \App\Models\User::class,
'authenticatable_id' => $this->user->id,
'user_id' => self::USER_ID,
'counter' => 0,
'rp_id' => 'http://localhost',
'origin' => 'http://localhost',
'aaguid' => '00000000-0000-0000-0000-000000000000',
'attestation_format' => 'none',
'public_key' => self::PUBLIC_KEY,
'updated_at' => now(),
'created_at' => now(),
]);
$response = $this->json('POST', '/webauthn/login/options')
->assertOk()
->assertJsonStructure([
'challenge',
'userVerification',
'timeout',
])
->assertJsonFragment([
'userVerification' => 'discouraged',
'allowCredentials' => [[
'id' => self::CREDENTIAL_ID,
'type' => 'public-key',
]],
]);
}
/**
@ -138,9 +253,9 @@ class WebAuthnLoginControllerTest extends FeatureTestCase
public function test_get_options_with_no_registred_user_returns_error()
{
$this->json('POST', '/webauthn/login/options')
->assertStatus(400)
->assertJsonStructure([
'message',
]);
->assertStatus(400)
->assertJsonStructure([
'message',
]);
}
}

View File

@ -7,6 +7,11 @@ use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\DB;
use Tests\FeatureTestCase;
/**
* @covers \App\Http\Controllers\Auth\WebAuthnManageController
* @covers \App\Http\Middleware\RejectIfReverseProxy
* @covers \App\Models\Traits\WebAuthnManageCredentials
*/
class WebAuthnManageControllerTest extends FeatureTestCase
{
// use WithoutMiddleware;
@ -23,7 +28,7 @@ class WebAuthnManageControllerTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();

View File

@ -8,6 +8,12 @@ use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Tests\FeatureTestCase;
/**
* @covers \App\Http\Controllers\Auth\WebAuthnRecoveryController
* @covers \App\Extensions\WebauthnCredentialBroker
* @covers \App\Http\Requests\WebauthnRecoveryRequest
* @covers \App\Providers\AuthServiceProvider
*/
class WebAuthnRecoveryControllerTest extends FeatureTestCase
{
/**
@ -29,7 +35,7 @@ class WebAuthnRecoveryControllerTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
@ -47,16 +53,55 @@ class WebAuthnRecoveryControllerTest extends FeatureTestCase
/**
* @test
*/
public function test_recover_with_invalid_token_returns_validation_error()
public function test_recover_fails_if_no_recovery_is_set()
{
$response = $this->json('POST', '/webauthn/recover', [
'token' => 'bad_token',
DB::table('webauthn_recoveries')->delete();
$this->json('POST', '/webauthn/recover', [
'token' => self::ACTUAL_TOKEN_VALUE,
'email' => $this->user->email,
'password' => UserFactory::USER_PASSWORD,
])
->assertStatus(422)
->assertJsonMissingValidationErrors('email')
->assertJsonValidationErrors('token');
->assertStatus(422)
->assertJsonValidationErrors('token');
}
/**
* @test
*/
public function test_recover_with_wrong_token_returns_validation_error()
{
$response = $this->json('POST', '/webauthn/recover', [
'token' => 'wrong_token',
'email' => $this->user->email,
'password' => UserFactory::USER_PASSWORD,
])
->assertStatus(422)
->assertJsonMissingValidationErrors('email')
->assertJsonValidationErrors('token');
}
/**
* @test
*/
public function test_recover_with_expired_token_returns_validation_error()
{
Date::setTestNow($now = Date::create(2020, 01, 01, 16, 30));
DB::table('webauthn_recoveries')->delete();
DB::table('webauthn_recoveries')->insert([
'token' => self::STORED_TOKEN_VALUE,
'email' => $this->user->email,
'created_at' => $now->clone()->subHour()->subSecond()->toDateTimeString(),
]);
$this->json('POST', '/webauthn/recover', [
'token' => self::ACTUAL_TOKEN_VALUE,
'email' => $this->user->email,
'password' => UserFactory::USER_PASSWORD,
])
->assertStatus(422)
->assertJsonValidationErrors('token');
}
/**
@ -64,12 +109,28 @@ class WebAuthnRecoveryControllerTest extends FeatureTestCase
*/
public function test_recover_with_invalid_password_returns_authentication_error()
{
$response = $this->json('POST', '/webauthn/recover', [
$this->json('POST', '/webauthn/recover', [
'token' => self::ACTUAL_TOKEN_VALUE,
'email' => $this->user->email,
'password' => 'bad_password',
])
->assertStatus(401);
->assertStatus(401);
}
/**
* @test
*/
public function test_recover_returns_validation_error_when_no_user_exists()
{
$this->json('POST', '/webauthn/recover', [
'token' => self::ACTUAL_TOKEN_VALUE,
'email' => 'no@user.com',
'password' => UserFactory::USER_PASSWORD,
])
->assertStatus(422)
->assertJsonMissingValidationErrors('password')
->assertJsonMissingValidationErrors('token')
->assertJsonValidationErrors('email');
}
/**
@ -82,7 +143,7 @@ class WebAuthnRecoveryControllerTest extends FeatureTestCase
'email' => $this->user->email,
'password' => UserFactory::USER_PASSWORD,
])
->assertStatus(200);
->assertStatus(200);
$this->assertDatabaseMissing('webauthn_recoveries', [
'token' => self::STORED_TOKEN_VALUE,
@ -119,7 +180,7 @@ class WebAuthnRecoveryControllerTest extends FeatureTestCase
'password' => UserFactory::USER_PASSWORD,
'revokeAll' => true,
])
->assertStatus(200);
->assertStatus(200);
$this->assertDatabaseMissing('webauthn_credentials', [
'authenticatable_id' => $this->user->id,

View File

@ -0,0 +1,79 @@
<?php
namespace Tests\Feature\Http\Auth;
use App\Models\User;
use Tests\FeatureTestCase;
use Laragear\WebAuthn\Http\Requests\AttestedRequest;
use Laragear\WebAuthn\Http\Requests\AttestationRequest;
use Illuminate\Support\Facades\Config;
use Laragear\WebAuthn\WebAuthn;
use Laragear\WebAuthn\JsonTransport;
/**
* @covers \App\Http\Controllers\Auth\WebAuthnRegisterController
*/
class WebAuthnRegisterControllerTest extends FeatureTestCase
{
/**
* @var \App\Models\User
*/
protected $user;
/**
* @test
*/
public function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
}
/**
* @test
*/
public function test_uses_attestation_with_fastRegistration_request(): void
{
Config::set('webauthn.user_verification', WebAuthn::USER_VERIFICATION_DISCOURAGED);
$request = $this->mock(AttestationRequest::class);
$request->expects('fastRegistration')->andReturnSelf();
$request->expects('toCreate')->andReturn(new JsonTransport());
$this->actingAs($this->user, 'web-guard')
->json('POST', '/webauthn/register/options')
->assertOk();
}
/**
* @test
*/
public function test_uses_attestation_with_secureRegistration_request(): void
{
Config::set('webauthn.user_verification', WebAuthn::USER_VERIFICATION_REQUIRED);
$request = $this->mock(AttestationRequest::class);
$request->expects('secureRegistration')->andReturnSelf();
$request->expects('toCreate')->andReturn(new JsonTransport());
$this->actingAs($this->user, 'web-guard')
->json('POST', '/webauthn/register/options')
->assertOk();
}
/**
* @test
*/
public function test_register_uses_attested_request(): void
{
$this->mock(AttestedRequest::class)->expects('save')->andReturn();
$this->actingAs($this->user, 'web-guard')
->json('POST', '/webauthn/register')
->assertNoContent();
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace Tests\Api\v1\Controllers;
use App\Models\User;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use App\Services\ReleaseRadarService;
use Tests\FeatureTestCase;
/**
* @covers \App\Http\Controllers\SystemController
*/
class SystemControllerTest extends FeatureTestCase
{
use WithoutMiddleware;
/**
* @var \App\Models\User
*/
protected $user;
/**
* @test
*/
public function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
}
/**
* @test
*/
public function test_infos_returns_only_base_collection()
{
$response = $this->json('GET', '/infos')
->assertOk()
->assertJsonStructure([
'Date',
'userAgent',
'Version',
'Environment',
'Debug',
'Cache driver',
'Log channel',
'Log level',
'DB driver',
'PHP version',
'Operating system',
'interface',
]);
}
/**
* @test
*/
public function test_infos_returns_full_collection_when_signed_in()
{
$response = $this->actingAs($this->user, 'api-guard')
->json('GET', '/infos')
->assertOk()
->assertJsonStructure([
'Auth guard',
'webauthn user verification',
'Trusted proxies',
'options' => [
'showTokenAsDot',
'closeOtpOnCopy',
'copyOtpOnDisplay',
'useBasicQrcodeReader',
'displayMode',
'showAccountsIcons',
'kickUserAfter',
'activeGroup',
'rememberActiveGroup',
'defaultGroup',
'useEncryption',
'defaultCaptureMode',
'useDirectCapture',
'useWebauthnAsDefault',
'useWebauthnOnly',
'getOfficialIcons',
'checkForUpdate',
'lastRadarScan',
'latestRelease',
'lang',
],
]);
}
/**
* @test
*/
public function test_infos_returns_full_collection_when_signed_in_behind_proxy()
{
$response = $this->actingAs($this->user, 'reverse-proxy-guard')
->json('GET', '/infos')
->assertOk()
->assertJsonStructure([
'Auth proxy header for user',
'Auth proxy header for email',
]);
}
/**
* @test
*/
public function test_latestrelease_runs_manual_scan()
{
$releaseRadarService = $this->mock(ReleaseRadarService::class)->makePartial();
$releaseRadarService->shouldReceive('manualScan')
->once()
->andReturn('new_release');
$response = $this->json('GET', '/latestRelease')
->assertOk()
->assertJson([
'newRelease' => 'new_release',
]);
}
}

View File

@ -3,8 +3,14 @@
namespace Tests\Feature\Models;
use App\Models\TwoFAccount;
use Tests\Classes\OtpTestData;
use Tests\Data\OtpTestData;
use Tests\FeatureTestCase;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Testing\FileFactory;
use Illuminate\Support\Facades\Http;
use App\Helpers\Helpers;
use Mockery\MockInterface;
use Tests\Data\HttpRequestTestData;
/**
* @covers \App\Models\TwoFAccount
@ -21,10 +27,15 @@ class TwoFAccountModelTest extends FeatureTestCase
*/
protected $customHotpTwofaccount;
/**
*
*/
const ICON_NAME = 'oDBngpjQaQAgLtHqGuYiPRqftCXv6Sj4hSAXARpA.png';
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
@ -69,12 +80,36 @@ class TwoFAccountModelTest extends FeatureTestCase
/**
* @test
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function test_fill_with_custom_totp_uri_returns_correct_value()
{
$this->mock('alias:' . Helpers::class, function (MockInterface $helper) {
$helper->shouldReceive('getUniqueFilename')
->andReturn(self::ICON_NAME);
$helper->shouldReceive('isValidImage')
->andReturn(true);
});
$file = (new FileFactory)->image(self::ICON_NAME, 10, 10);
Http::preventStrayRequests();
Http::fake([
'https://en.opensuse.org/images/4/44/Button-filled-colour.png' => Http::response($file->tempFile, 200),
]);
Storage::fake('imagesLink');
Storage::fake('icons');
$twofaccount = new TwoFAccount;
$twofaccount->fillWithURI(OtpTestData::TOTP_FULL_CUSTOM_URI);
Storage::disk('icons')->assertExists(self::ICON_NAME);
Storage::disk('imagesLink')->assertMissing(self::ICON_NAME);
$this->assertEquals('totp', $twofaccount->otp_type);
$this->assertEquals(OtpTestData::TOTP_FULL_CUSTOM_URI, $twofaccount->legacy_uri);
$this->assertEquals(OtpTestData::SERVICE, $twofaccount->service);
@ -84,7 +119,7 @@ class TwoFAccountModelTest extends FeatureTestCase
$this->assertEquals(OtpTestData::PERIOD_CUSTOM, $twofaccount->period);
$this->assertEquals(null, $twofaccount->counter);
$this->assertEquals(OtpTestData::ALGORITHM_CUSTOM, $twofaccount->algorithm);
$this->assertStringEndsWith('.png', $twofaccount->icon);
$this->assertEquals(self::ICON_NAME, $twofaccount->icon);
}
/**
@ -109,12 +144,36 @@ class TwoFAccountModelTest extends FeatureTestCase
/**
* @test
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function test_fill_with_custom_hotp_uri_returns_correct_value()
{
$this->mock('alias:' . Helpers::class, function (MockInterface $helper) {
$helper->shouldReceive('getUniqueFilename')
->andReturn(self::ICON_NAME);
$helper->shouldReceive('isValidImage')
->andReturn(true);
});
$file = (new FileFactory)->image(self::ICON_NAME, 10, 10);
Http::preventStrayRequests();
Http::fake([
'https://en.opensuse.org/images/4/44/Button-filled-colour.png' => Http::response($file->tempFile, 200),
]);
Storage::fake('imagesLink');
Storage::fake('icons');
$twofaccount = new TwoFAccount;
$twofaccount->fillWithURI(OtpTestData::HOTP_FULL_CUSTOM_URI);
Storage::disk('icons')->assertExists(self::ICON_NAME);
Storage::disk('imagesLink')->assertMissing(self::ICON_NAME);
$this->assertEquals('hotp', $twofaccount->otp_type);
$this->assertEquals(OtpTestData::HOTP_FULL_CUSTOM_URI, $twofaccount->legacy_uri);
$this->assertEquals(OtpTestData::SERVICE, $twofaccount->service);
@ -124,7 +183,7 @@ class TwoFAccountModelTest extends FeatureTestCase
$this->assertEquals(null, $twofaccount->period);
$this->assertEquals(OtpTestData::COUNTER_CUSTOM, $twofaccount->counter);
$this->assertEquals(OtpTestData::ALGORITHM_CUSTOM, $twofaccount->algorithm);
$this->assertStringEndsWith('.png', $twofaccount->icon);
$this->assertEquals(self::ICON_NAME, $twofaccount->icon);
}
/**
@ -391,9 +450,28 @@ class TwoFAccountModelTest extends FeatureTestCase
/**
* @test
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function test_getOTP_for_totp_returns_the_same_password()
{
$this->mock('alias:' . Helpers::class, function (MockInterface $helper) {
$helper->shouldReceive('getUniqueFilename')
->andReturn(self::ICON_NAME);
$helper->shouldReceive('isValidImage')
->andReturn(true);
});
Http::preventStrayRequests();
Http::fake([
'https://en.opensuse.org/images/4/44/Button-filled-colour.png' => Http::response(HttpRequestTestData::ICON_PNG, 200),
]);
Storage::fake('imagesLink');
Storage::fake('icons');
$twofaccount = new TwoFAccount;
$otp_from_model = $this->customTotpTwofaccount->getOTP();
@ -413,9 +491,28 @@ class TwoFAccountModelTest extends FeatureTestCase
/**
* @test
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function test_getOTP_for_hotp_returns_the_same_password()
{
$this->mock('alias:' . Helpers::class, function (MockInterface $helper) {
$helper->shouldReceive('getUniqueFilename')
->andReturn(self::ICON_NAME);
$helper->shouldReceive('isValidImage')
->andReturn(true);
});
Http::preventStrayRequests();
Http::fake([
'https://en.opensuse.org/images/4/44/Button-filled-colour.png' => Http::response(HttpRequestTestData::ICON_PNG, 200),
]);
Storage::fake('imagesLink');
Storage::fake('icons');
$twofaccount = new TwoFAccount;
$otp_from_model = $this->customHotpTwofaccount->getOTP();
@ -507,4 +604,107 @@ class TwoFAccountModelTest extends FeatureTestCase
$this->assertStringContainsString('counter=' . OtpTestData::COUNTER_CUSTOM, $uri);
$this->assertStringContainsString('algorithm=' . OtpTestData::ALGORITHM_CUSTOM, $uri);
}
/**
* @test
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function test_fill_succeed_when_image_fetching_fails()
{
$this->mock('alias:' . Helpers::class, function (MockInterface $helper) {
$helper->shouldReceive('getUniqueFilename')
->andReturn(self::ICON_NAME);
});
Http::preventStrayRequests();
Storage::fake('imagesLink');
Storage::fake('icons');
$twofaccount = new TwoFAccount;
$twofaccount->fillWithURI(OtpTestData::TOTP_FULL_CUSTOM_URI);
Storage::disk('icons')->assertMissing(self::ICON_NAME);
Storage::disk('imagesLink')->assertMissing(self::ICON_NAME);
}
/**
* @test
*/
public function test_saving_totp_without_period_set_default_one()
{
$twofaccount = new TwoFAccount;
$twofaccount->service = OtpTestData::SERVICE;
$twofaccount->account = OtpTestData::ACCOUNT;
$twofaccount->otp_type = TwoFAccount::TOTP;
$twofaccount->secret = OtpTestData::SECRET;
$twofaccount->save();
$account = TwoFAccount::find($twofaccount->id);
$this->assertEquals(TwoFAccount::DEFAULT_PERIOD, $account->period);
}
/**
* @test
*/
public function test_saving_hotp_without_counter_set_default_one()
{
$twofaccount = new TwoFAccount;
$twofaccount->service = OtpTestData::SERVICE;
$twofaccount->account = OtpTestData::ACCOUNT;
$twofaccount->otp_type = TwoFAccount::HOTP;
$twofaccount->secret = OtpTestData::SECRET;
$twofaccount->save();
$account = TwoFAccount::find($twofaccount->id);
$this->assertEquals(TwoFAccount::DEFAULT_COUNTER, $account->counter);
}
/**
* @test
*/
public function test_equals_returns_true()
{
$twofaccount = new TwoFAccount;
$twofaccount->legacy_uri = OtpTestData::TOTP_FULL_CUSTOM_URI;
$twofaccount->service = OtpTestData::SERVICE;
$twofaccount->account = OtpTestData::ACCOUNT;
$twofaccount->icon = OtpTestData::ICON;
$twofaccount->otp_type = 'totp';
$twofaccount->secret = OtpTestData::SECRET;
$twofaccount->digits = OtpTestData::DIGITS_CUSTOM;
$twofaccount->algorithm = OtpTestData::ALGORITHM_CUSTOM;
$twofaccount->period = OtpTestData::PERIOD_CUSTOM;
$twofaccount->counter = null;
$twofaccount->save();
$this->assertTrue($twofaccount->equals($this->customTotpTwofaccount));
}
/**
* @test
*/
public function test_equals_returns_false()
{
$twofaccount = new TwoFAccount;
$twofaccount->legacy_uri = OtpTestData::TOTP_FULL_CUSTOM_URI;
$twofaccount->service = OtpTestData::SERVICE;
$twofaccount->account = OtpTestData::ACCOUNT;
$twofaccount->icon = OtpTestData::ICON;
$twofaccount->otp_type = 'totp';
$twofaccount->secret = OtpTestData::SECRET;
$twofaccount->digits = OtpTestData::DIGITS_CUSTOM;
$twofaccount->algorithm = OtpTestData::ALGORITHM_CUSTOM;
$twofaccount->period = OtpTestData::PERIOD_CUSTOM;
$twofaccount->counter = null;
$twofaccount->save();
$this->assertFalse($twofaccount->equals($this->customHotpTwofaccount));
}
}

View File

@ -10,6 +10,7 @@ use Tests\FeatureTestCase;
/**
* @covers \App\Services\GroupService
* @covers \App\Facades\Groups
*/
class GroupServiceTest extends FeatureTestCase
{
@ -52,7 +53,7 @@ class GroupServiceTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();

View File

@ -6,6 +6,9 @@ use App\Services\LogoService;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Mockery\MockInterface;
use Tests\TestCase;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Http;
use Tests\Data\HttpRequestTestData;
/**
* @covers \App\Services\LogoService
@ -17,7 +20,7 @@ class LogoServiceTest extends TestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
}
@ -25,18 +28,65 @@ class LogoServiceTest extends TestCase
/**
* @test
*/
public function test_getIcon_returns_iconFilename_when_logo_exists()
public function test_getIcon_returns_stored_icon_file_when_logo_exists()
{
$logoServiceMock = $this->partialMock(LogoService::class, function (MockInterface $mock) {
$mock->shouldAllowMockingProtectedMethods();
$mock->shouldReceive('getLogo', 'copyToIcons')
->once()
->andReturn('service.svg', true);
});
$svgLogo = HttpRequestTestData::SVG_LOGO_BODY;
$tfaJsonBody = HttpRequestTestData::TFA_JSON_BODY;
$icon = $logoServiceMock->getIcon('service');
Http::preventStrayRequests();
Http::fake([
'https://raw.githubusercontent.com/2factorauth/twofactorauth/master/img/*' => Http::response($svgLogo, 200),
'https://2fa.directory/api/v3/tfa.json' => Http::response($tfaJsonBody, 200),
]);
Storage::fake('icons');
Storage::fake('logos');
$logoService = new LogoService();
$icon = $logoService->getIcon('twitter');
$this->assertNotNull($icon);
Storage::disk('icons')->assertExists($icon);
}
/**
* @test
*/
public function test_getIcon_returns_null_when_github_request_fails()
{
Http::preventStrayRequests();
Http::fake([
'https://raw.githubusercontent.com/2factorauth/twofactorauth/master/img/*' => Http::response('not found', 404),
]);
Storage::fake('icons');
Storage::fake('logos');
$logoService = new LogoService();
$icon = $logoService->getIcon('twitter');
$this->assertEquals(null, $icon);
}
/**
* @test
*/
public function test_getIcon_returns_null_when_logo_fetching_fails()
{
$tfaJsonBody = HttpRequestTestData::TFA_JSON_BODY;
Http::preventStrayRequests();
Http::fake([
'https://2fa.directory/api/v3/tfa.json' => Http::response($tfaJsonBody, 200),
]);
Storage::fake('icons');
Storage::fake('logos');
$logoService = new LogoService();
$icon = $logoService->getIcon('twitter');
$this->assertEquals(null, $icon);
}
/**
@ -44,15 +94,32 @@ class LogoServiceTest extends TestCase
*/
public function test_getIcon_returns_null_when_no_logo_exists()
{
$logoServiceMock = $this->partialMock(LogoService::class, function (MockInterface $mock) {
$mock->shouldAllowMockingProtectedMethods()
->shouldReceive('getLogo')
->once()
->andReturn(null);
});
$logoService = new LogoService();
$icon = $logoServiceMock->getIcon('no_logo_should_exists_with_this_name');
$icon = $logoService->getIcon('no_logo_should_exists_with_this_name');
$this->assertEquals(null, $icon);
}
/**
* @test
*/
public function test_logoService_loads_empty_collection_when_tfajson_fetching_fails()
{
$svgLogo = HttpRequestTestData::SVG_LOGO_BODY;
Http::preventStrayRequests();
Http::fake([
'https://raw.githubusercontent.com/2factorauth/twofactorauth/master/img/*' => Http::response($svgLogo, 200),
]);
Storage::fake('icons');
Storage::fake('logos');
$logoService = new LogoService();
$icon = $logoService->getIcon('twitter');
$this->assertNull($icon);
Storage::disk('logos')->assertMissing(LogoService::TFA_JSON);
}
}

View File

@ -8,6 +8,7 @@ use Tests\FeatureTestCase;
/**
* @covers \App\Services\QrCodeService
* @covers \App\Facades\QrCode
*/
class QrCodeServiceTest extends FeatureTestCase
{
@ -20,7 +21,7 @@ class QrCodeServiceTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
}

View File

@ -0,0 +1,138 @@
<?php
namespace Tests\Feature\Services;
use App\Facades\Settings;
use App\Services\ReleaseRadarService;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\FeatureTestCase;
use Illuminate\Support\Facades\Http;
use Tests\Data\HttpRequestTestData;
/**
* @covers \App\Services\ReleaseRadarService
*/
class ReleaseRadarServiceTest extends FeatureTestCase
{
use WithoutMiddleware;
/**
* @test
*/
public function test_manualScan_returns_no_new_release()
{
$url = config('2fauth.latestReleaseUrl');
Http::preventStrayRequests();
Http::fake([
$url => Http::response(HttpRequestTestData::LATEST_RELEASE_BODY_NO_NEW_RELEASE, 200),
]);
$releaseRadarService = new ReleaseRadarService();
$release = $releaseRadarService->manualScan();
$this->assertFalse($release);
$this->assertDatabaseHas('options', [
'key' => 'lastRadarScan',
]);
$this->assertDatabaseMissing('options', [
'key' => 'latestRelease',
'value' => HttpRequestTestData::TAG_NAME
]);
}
/**
* @test
*/
public function test_manualScan_returns_new_release()
{
$url = config('2fauth.latestReleaseUrl');
Http::preventStrayRequests();
Http::fake([
$url => Http::response(HttpRequestTestData::LATEST_RELEASE_BODY_NEW_RELEASE, 200),
]);
$releaseRadarService = new ReleaseRadarService();
$release = $releaseRadarService->manualScan();
$this->assertEquals(HttpRequestTestData::NEW_TAG_NAME, $release);
$this->assertDatabaseHas('options', [
'key' => 'latestRelease',
'value' => HttpRequestTestData::NEW_TAG_NAME
]);
$this->assertDatabaseHas('options', [
'key' => 'lastRadarScan',
]);
}
/**
* @test
*/
public function test_manualScan_succeed_when_something_fails()
{
$url = config('2fauth.latestReleaseUrl');
// We do not fake the http request so an exception will be thrown
Http::preventStrayRequests();
$releaseRadarService = new ReleaseRadarService();
$release = $releaseRadarService->manualScan();
$this->assertFalse($release);
}
/**
* @test
*/
public function test_manualScan_succeed_when_github_is_unreachable()
{
$url = config('2fauth.latestReleaseUrl');
Http::preventStrayRequests();
Http::fake([
$url => Http::response(null, 400),
]);
$releaseRadarService = new ReleaseRadarService();
$release = $releaseRadarService->manualScan();
$this->assertFalse($release);
}
/**
* @test
*/
public function test_scheduleScan_runs_after_one_week()
{
$url = config('2fauth.latestReleaseUrl');
Http::preventStrayRequests();
Http::fake([
$url => Http::response(HttpRequestTestData::LATEST_RELEASE_BODY_NEW_RELEASE, 200),
]);
Settings::set('lastRadarScan', time() - (60 * 60 * 24 * 7) - 1);
$releaseRadarService = $this->mock(ReleaseRadarService::class)->makePartial();
$releaseRadarService->shouldAllowMockingProtectedMethods()
->shouldReceive('newRelease')
->once();
$releaseRadarService->scheduledScan();
}
/**
* @test
*/
public function test_scheduleScan_does_not_run_before_one_week()
{
Settings::set('lastRadarScan', time() - (60 * 60 * 24 * 7) + 2);
$releaseRadarService = $this->mock(ReleaseRadarService::class)->makePartial();
$releaseRadarService->shouldAllowMockingProtectedMethods()
->shouldNotReceive('newRelease');
$releaseRadarService->scheduledScan();
}
}

View File

@ -10,6 +10,7 @@ use Tests\FeatureTestCase;
/**
* @covers \App\Services\SettingService
* @covers \App\Facades\Settings
*/
class SettingServiceTest extends FeatureTestCase
{
@ -57,7 +58,7 @@ class SettingServiceTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
@ -238,7 +239,7 @@ class SettingServiceTest extends FeatureTestCase
/**
* Provide invalid data for validation test
*/
public function provideUndecipherableData() : array
public function provideUndecipherableData(): array
{
return [
[[
@ -316,4 +317,26 @@ class SettingServiceTest extends FeatureTestCase
self::VALUE => self::SETTING_VALUE_STRING,
]);
}
/**
* @test
*/
public function test_isUserDefined_returns_true()
{
DB::table('options')->insert(
[self::KEY => 'showTokenAsDot', self::VALUE => strval(self::SETTING_VALUE_TRUE_TRANSFORMED)]
);
$this->assertTrue(Settings::isUserDefined('showTokenAsDot'));
}
/**
* @test
*/
public function test_isUserDefined_returns_false()
{
DB::table('options')->where(self::KEY, 'showTokenAsDot')->delete();
$this->assertFalse(Settings::isUserDefined('showTokenAsDot'));
}
}

View File

@ -5,11 +5,13 @@ namespace Tests\Feature\Services;
use App\Facades\TwoFAccounts;
use App\Models\Group;
use App\Models\TwoFAccount;
use Tests\Classes\OtpTestData;
use Tests\Data\OtpTestData;
use Tests\FeatureTestCase;
use Tests\Data\MigrationTestData;
/**
* @covers \App\Services\TwoFAccountService
* @covers \App\Facades\TwoFAccounts
*/
class TwoFAccountServiceTest extends FeatureTestCase
{
@ -31,7 +33,7 @@ class TwoFAccountServiceTest extends FeatureTestCase
/**
* @test
*/
public function setUp() : void
public function setUp(): void
{
parent::setUp();
@ -179,7 +181,7 @@ class TwoFAccountServiceTest extends FeatureTestCase
*/
public function test_convert_migration_from_gauth_returns_correct_accounts()
{
$twofaccounts = TwoFAccounts::migrate(OtpTestData::GOOGLE_AUTH_MIGRATION_URI);
$twofaccounts = TwoFAccounts::migrate(MigrationTestData::GOOGLE_AUTH_MIGRATION_URI);
$this->assertCount(2, $twofaccounts);
@ -226,7 +228,7 @@ class TwoFAccountServiceTest extends FeatureTestCase
$twofaccount = new TwoFAccount;
$twofaccount->fillWithOtpParameters($parameters)->save();
$twofaccounts = TwoFAccounts::migrate(OtpTestData::GOOGLE_AUTH_MIGRATION_URI);
$twofaccounts = TwoFAccounts::migrate(MigrationTestData::GOOGLE_AUTH_MIGRATION_URI);
$this->assertEquals(-1, $twofaccounts->first()->id);
$this->assertEquals(-1, $twofaccounts->last()->id);
@ -238,6 +240,6 @@ class TwoFAccountServiceTest extends FeatureTestCase
public function test_convert_invalid_migration_from_gauth_returns_InvalidMigrationData_exception()
{
$this->expectException(\App\Exceptions\InvalidMigrationDataException::class);
$twofaccounts = TwoFAccounts::migrate(OtpTestData::GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA);
$twofaccounts = TwoFAccounts::migrate(MigrationTestData::GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA);
}
}

View File

@ -7,6 +7,15 @@ use Illuminate\Contracts\Container\Container;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Tests\TestCase;
use App\Exceptions\InvalidOtpParameterException;
use \App\Exceptions\InvalidQrCodeException;
use App\Exceptions\InvalidSecretException;
use App\Exceptions\DbEncryptionException;
use App\Exceptions\InvalidMigrationDataException;
use App\Exceptions\UndecipherableException;
use App\Exceptions\UnsupportedMigrationException;
use App\Exceptions\UnsupportedOtpTypeException;
use App\Exceptions\EncryptedMigrationException;
/**
* @covers \App\Exceptions\Handler
@ -41,32 +50,35 @@ class HandlerTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideExceptionsforBadRequest() : array
public function provideExceptionsforBadRequest(): array
{
return [
[
'\App\Exceptions\InvalidOtpParameterException',
InvalidOtpParameterException::class,
],
[
'\App\Exceptions\InvalidQrCodeException',
InvalidQrCodeException::class,
],
[
'\App\Exceptions\InvalidSecretException',
InvalidSecretException::class,
],
[
'\App\Exceptions\DbEncryptionException',
DbEncryptionException::class,
],
[
'\App\Exceptions\InvalidMigrationDataException',
InvalidMigrationDataException::class,
],
[
'\App\Exceptions\UndecipherableException',
UndecipherableException::class,
],
[
'\App\Exceptions\UnsupportedMigrationException',
UnsupportedMigrationException::class,
],
[
'\App\Exceptions\UnsupportedOtpTypeException',
UnsupportedOtpTypeException::class,
],
[
EncryptedMigrationException::class,
],
];
}
@ -99,7 +111,7 @@ class HandlerTest extends TestCase
/**
* Provide Valid data for validation test
*/
public function provideExceptionsforNotFound() : array
public function provideExceptionsforNotFound(): array
{
return [
[
@ -111,6 +123,32 @@ class HandlerTest extends TestCase
];
}
/**
* @test
*/
public function test_authenticationException_returns_unauthorized_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(\Illuminate\Auth\AuthenticationException::class);
$mockException->method('guards')->willReturn(['web-guard']);
$response = $method->invokeArgs($instance, [$request, $mockException]);
$this->assertInstanceOf(JsonResponse::class, $response);
$response = \Illuminate\Testing\TestResponse::fromBaseResponse($response);
$response->assertStatus(401)
->assertJsonStructure([
'message',
]);
}
/**
* @test
*/

View File

@ -0,0 +1,98 @@
<?php
namespace Tests\Unit;
use App\Helpers\Helpers;
use Tests\TestCase;
/**
* @covers \App\Helpers\Helpers
*/
class HelpersTest extends TestCase
{
/**
* @test
*/
public function test_getUniqueFilename_returns_filename()
{
$ext = 'jpg';
$filename = Helpers::getUniqueFilename($ext);
$this->assertIsString($filename);
$this->assertStringEndsWith('.' . $ext, $filename);
$this->assertEquals(41 + strlen($ext), strlen($filename));
}
/**
* @test
*
* @dataProvider versionNumberProvider
*/
public function test_cleanVersionNumber_returns_cleaned_version($dirtyVersion, $expected)
{
$cleanedVersion = Helpers::cleanVersionNumber($dirtyVersion);
$this->assertEquals($expected, $cleanedVersion);
}
/**
* Provide data for cleanVersionNumber() tests
*/
public function versionNumberProvider()
{
return [
[
'v3.2.1',
'3.2.1',
],
[
'v3.2.1-beta',
'3.2.1-beta',
],
[
'v3.0.1-alpha+001',
'3.0.1-alpha+001',
],
[
'version03.0.1 alpha+001',
'3.0.1',
],
];
}
/**
* @test
*
* @dataProvider invalidVersionNumberProvider
*/
public function test_cleanVersionNumber_returns_false_with_invalid_semver($dirtyVersion)
{
$cleanedVersion = Helpers::cleanVersionNumber($dirtyVersion);
$this->assertEquals(false, $cleanedVersion);
}
/**
* Provide data for cleanVersionNumber() tests
*/
public function invalidVersionNumberProvider()
{
return [
[
'v3.2.',
],
[
'v3..1-beta',
],
[
'v.0.1-alpha+001',
],
[
'3.00.1 alpha+001',
],
[
'3.00.1 alpha+001',
],
];
}
}

View File

@ -16,6 +16,9 @@ use Tests\TestCase;
*/
class CleanIconStorageTest extends TestCase
{
/**
* @test
*/
public function test_it_deletes_icon_file_on_twofaccount_deletion()
{
$settingService = $this->mock(SettingService::class, function (MockInterface $settingService) {
@ -34,6 +37,9 @@ class CleanIconStorageTest extends TestCase
$this->assertNull($listener->handle($event));
}
/**
* @test
*/
public function test_CleanIconStorage_listen_to_TwoFAccountDeleted_event()
{
Event::fake();

View File

@ -5,23 +5,41 @@ namespace Tests\Unit\Listeners;
use App\Events\GroupDeleting;
use App\Listeners\DissociateTwofaccountFromGroup;
use App\Models\Group;
use App\Models\TwoFAccount;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
use Mockery\MockInterface;
/**
* @covers \App\Listeners\DissociateTwofaccountFromGroup
*/
class DissociateTwofaccountFromGroupTest extends TestCase
{
// public function test_twofaccount_is_released_on_group_deletion()
// {
// $group = Group::factory()->make();
// $event = new GroupDeleting($group);
// $listener = new DissociateTwofaccountFromGroup();
/**
* @test
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function test_twofaccount_is_released_on_group_deletion()
{
// $this->assertNull($listener->handle($event));
// }
$this->mock('alias:' . TwoFAccount::class, function (MockInterface $twoFAccount) {
$twoFAccount->shouldReceive('where->update')
->once()
->andReturn(1);
});
$group = Group::factory()->make();
$event = new GroupDeleting($group);
$listener = new DissociateTwofaccountFromGroup();
$this->assertNull($listener->handle($event));
}
/**
* @test
*/
public function test_DissociateTwofaccountFromGroup_listen_to_groupDeleting_event()
{
Event::fake();

484
tests/Unit/MigratorTest.php Normal file
View File

@ -0,0 +1,484 @@
<?php
namespace Tests\Unit;
use App\Exceptions\EncryptedMigrationException;
use App\Factories\MigratorFactory;
use App\Exceptions\InvalidMigrationDataException;
use App\Models\TwoFAccount;
use App\Services\Migrators\AegisMigrator;
use App\Services\Migrators\TwoFASMigrator;
use App\Services\Migrators\Migrator;
use App\Services\Migrators\PlainTextMigrator;
use App\Services\Migrators\GoogleAuthMigrator;
use App\Services\SettingService;
use Illuminate\Support\Facades\Storage;
use Mockery;
use Mockery\Mock;
use Mockery\MockInterface;
use Tests\Data\MigrationTestData;
use Tests\Data\OtpTestData;
use Tests\TestCase;
use ParagonIE\ConstantTime\Base32;
use App\Protobuf\GoogleAuth\Payload\Algorithm;
use App\Exceptions\UnsupportedMigrationException;
/**
* @covers \App\Providers\MigrationServiceProvider
* @covers \App\Factories\MigratorFactory
* @covers \App\Services\Migrators\Migrator
* @covers \App\Services\Migrators\AegisMigrator
* @covers \App\Services\Migrators\TwoFASMigrator
* @covers \App\Services\Migrators\PlainTextMigrator
* @covers \App\Services\Migrators\GoogleAuthMigrator
* @uses \App\Models\TwoFAccount
*/
class MigratorTest extends TestCase
{
/**
* App\Models\TwoFAccount $totpTwofaccount
*/
protected $totpTwofaccount;
/**
* App\Models\TwoFAccount $totpTwofaccount
*/
protected $hotpTwofaccount;
/**
* App\Models\TwoFAccount $steamTwofaccount
*/
protected $steamTwofaccount;
/**
* App\Models\TwoFAccount $GAuthTotpTwofaccount
*/
protected $GAuthTotpTwofaccount;
/**
* App\Models\TwoFAccount $GAuthTotpBisTwofaccount
*/
protected $GAuthTotpBisTwofaccount;
public function setUp(): void
{
parent::setUp();
$this->mock(SettingService::class, function (MockInterface $settingService) {
$settingService->allows()
->get('useEncryption')
->andReturn(false);
$settingService->allows()
->get('getOfficialIcons')
->andReturn(false);
});
$this->totpTwofaccount = new TwoFAccount;
$this->totpTwofaccount->legacy_uri = OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG;
$this->totpTwofaccount->service = OtpTestData::SERVICE;
$this->totpTwofaccount->account = OtpTestData::ACCOUNT;
$this->totpTwofaccount->icon = null;
$this->totpTwofaccount->otp_type = 'totp';
$this->totpTwofaccount->secret = OtpTestData::SECRET;
$this->totpTwofaccount->digits = OtpTestData::DIGITS_CUSTOM;
$this->totpTwofaccount->algorithm = OtpTestData::ALGORITHM_CUSTOM;
$this->totpTwofaccount->period = OtpTestData::PERIOD_CUSTOM;
$this->totpTwofaccount->counter = null;
$this->hotpTwofaccount = new TwoFAccount;
$this->hotpTwofaccount->legacy_uri = OtpTestData::HOTP_FULL_CUSTOM_URI_NO_IMG;
$this->hotpTwofaccount->service = OtpTestData::SERVICE;
$this->hotpTwofaccount->account = OtpTestData::ACCOUNT;
$this->hotpTwofaccount->icon = null;
$this->hotpTwofaccount->otp_type = 'hotp';
$this->hotpTwofaccount->secret = OtpTestData::SECRET;
$this->hotpTwofaccount->digits = OtpTestData::DIGITS_CUSTOM;
$this->hotpTwofaccount->algorithm = OtpTestData::ALGORITHM_CUSTOM;
$this->hotpTwofaccount->period = null;
$this->hotpTwofaccount->counter = OtpTestData::COUNTER_CUSTOM;
$this->steamTwofaccount = new TwoFAccount;
$this->steamTwofaccount->legacy_uri = OtpTestData::STEAM_TOTP_URI;
$this->steamTwofaccount->service = OtpTestData::STEAM;
$this->steamTwofaccount->account = OtpTestData::ACCOUNT;
$this->steamTwofaccount->icon = null;
$this->steamTwofaccount->otp_type = 'steamtotp';
$this->steamTwofaccount->secret = OtpTestData::STEAM_SECRET;
$this->steamTwofaccount->digits = OtpTestData::DIGITS_STEAM;
$this->steamTwofaccount->algorithm = OtpTestData::ALGORITHM_DEFAULT;
$this->steamTwofaccount->period = OtpTestData::PERIOD_DEFAULT;
$this->steamTwofaccount->counter = null;
$this->GAuthTotpTwofaccount = new TwoFAccount;
$this->GAuthTotpTwofaccount->service = OtpTestData::SERVICE;
$this->GAuthTotpTwofaccount->account = OtpTestData::ACCOUNT;
$this->GAuthTotpTwofaccount->icon = null;
$this->GAuthTotpTwofaccount->otp_type = 'totp';
$this->GAuthTotpTwofaccount->secret = OtpTestData::SECRET;
$this->GAuthTotpTwofaccount->digits = OtpTestData::DIGITS_DEFAULT;
$this->GAuthTotpTwofaccount->algorithm = OtpTestData::ALGORITHM_DEFAULT;
$this->GAuthTotpTwofaccount->period = OtpTestData::PERIOD_DEFAULT;
$this->GAuthTotpTwofaccount->counter = null;
$this->GAuthTotpBisTwofaccount = new TwoFAccount;
$this->GAuthTotpBisTwofaccount->service = OtpTestData::SERVICE . '_bis';
$this->GAuthTotpBisTwofaccount->account = OtpTestData::ACCOUNT . '_bis';
$this->GAuthTotpBisTwofaccount->icon = null;
$this->GAuthTotpBisTwofaccount->otp_type = 'totp';
$this->GAuthTotpBisTwofaccount->secret = OtpTestData::SECRET;
$this->GAuthTotpBisTwofaccount->digits = OtpTestData::DIGITS_DEFAULT;
$this->GAuthTotpBisTwofaccount->algorithm = OtpTestData::ALGORITHM_DEFAULT;
$this->GAuthTotpBisTwofaccount->period = OtpTestData::PERIOD_DEFAULT;
$this->GAuthTotpBisTwofaccount->counter = null;
$this->fakeTwofaccount = new TwoFAccount;
$this->fakeTwofaccount->id = TwoFAccount::FAKE_ID;
}
/**
* @test
*
* @dataProvider validMigrationsProvider
*/
public function test_migrate_returns_consistent_accounts(Migrator $migrator, mixed $payload, string $expected, bool $hasSteam)
{
$accounts = $migrator->migrate($payload);
if ($expected === 'gauth') {
$totp = $this->GAuthTotpTwofaccount;
$hotp = $this->GAuthTotpBisTwofaccount;
} else {
$totp = $this->totpTwofaccount;
$hotp = $this->hotpTwofaccount;
if ($hasSteam) {
$steam = $this->steamTwofaccount;
}
}
$this->assertContainsOnlyInstancesOf(TwoFAccount::class, $accounts);
$this->assertCount($hasSteam ? 3 : 2, $accounts);
// The returned collection could have non-linear index (because of possible blank lines
// in the migration payload) so we do not use get() to retrieve items
$this->assertObjectEquals($totp, $accounts->first());
$this->assertObjectEquals($hotp, $accounts->slice(1, 1)->first());
if ($hasSteam) {
$this->assertObjectEquals($steam, $accounts->last());
}
}
/**
* Provide data for TwoFAccount store tests
*/
public function validMigrationsProvider()
{
return [
'PLAIN_TEXT_PAYLOAD' => [
new PlainTextMigrator(),
MigrationTestData::VALID_PLAIN_TEXT_PAYLOAD,
'custom',
$hasSteam = true
],
'PLAIN_TEXT_PAYLOAD_WITH_INTRUDER' => [
new PlainTextMigrator(),
MigrationTestData::VALID_PLAIN_TEXT_PAYLOAD_WITH_INTRUDER,
'custom',
$hasSteam = true
],
'AEGIS_JSON_MIGRATION_PAYLOAD' => [
new AegisMigrator(),
MigrationTestData::VALID_AEGIS_JSON_MIGRATION_PAYLOAD,
'custom',
$hasSteam = true
],
'2FAS_MIGRATION_PAYLOAD' => [
new TwoFASMigrator(),
MigrationTestData::VALID_2FAS_MIGRATION_PAYLOAD,
'custom',
$hasSteam = false
],
'GOOGLE_AUTH_MIGRATION_PAYLOAD' => [
new GoogleAuthMigrator(),
MigrationTestData::GOOGLE_AUTH_MIGRATION_URI,
'gauth',
$hasSteam = false,
],
];
}
/**
* @test
*
* @dataProvider invalidMigrationsProvider
*/
public function test_migrate_with_invalid_payload_returns_InvalidMigrationDataException(Migrator $migrator, mixed $payload)
{
$this->expectException(InvalidMigrationDataException::class);
$accounts = $migrator->migrate($payload);
}
/**
* Provide data for TwoFAccount store tests
*/
public function invalidMigrationsProvider()
{
return [
'INVALID_PLAIN_TEXT_NO_URI' => [
new PlainTextMigrator(),
MigrationTestData::INVALID_PLAIN_TEXT_NO_URI,
],
'INVALID_PLAIN_TEXT_ONLY_EMPTY_LINES' => [
new PlainTextMigrator(),
MigrationTestData::INVALID_PLAIN_TEXT_ONLY_EMPTY_LINES,
],
'INVALID_PLAIN_TEXT_NULL' => [
new PlainTextMigrator(),
null,
],
'INVALID_PLAIN_TEXT_EMPTY_STRING' => [
new PlainTextMigrator(),
'',
],
'INVALID_PLAIN_TEXT_INT' => [
new PlainTextMigrator(),
10,
],
'INVALID_PLAIN_TEXT_BOOL' => [
new PlainTextMigrator(),
true,
],
'INVALID_AEGIS_JSON_MIGRATION_PAYLOAD' => [
new AegisMigrator(),
MigrationTestData::INVALID_AEGIS_JSON_MIGRATION_PAYLOAD,
],
'ENCRYPTED_AEGIS_JSON_MIGRATION_PAYLOAD' => [
new AegisMigrator(),
MigrationTestData::ENCRYPTED_AEGIS_JSON_MIGRATION_PAYLOAD,
],
'INVALID_2FAS_MIGRATION_PAYLOAD' => [
new TwoFASMigrator(),
MigrationTestData::INVALID_2FAS_MIGRATION_PAYLOAD,
],
'INVALID_GOOGLE_AUTH_MIGRATION_URI' => [
new GoogleAuthMigrator(),
MigrationTestData::INVALID_GOOGLE_AUTH_MIGRATION_URI,
],
'GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA' => [
new GoogleAuthMigrator(),
MigrationTestData::GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA,
],
];
}
/**
* @test
*
* @dataProvider migrationWithInvalidAccountsProvider
*/
public function test_migrate_returns_fake_accounts(Migrator $migrator, mixed $payload)
{
$accounts = $migrator->migrate($payload);
$this->assertContainsOnlyInstancesOf(TwoFAccount::class, $accounts);
$this->assertCount(2, $accounts);
// The returned collection could have non-linear index (because of possible blank lines
// in the migration payload) so we do not use get() to retrieve items
$this->assertObjectEquals($this->totpTwofaccount, $accounts->first());
$this->assertEquals($this->fakeTwofaccount->id, $accounts->last()->id);
}
/**
* Provide data for TwoFAccount store tests
*/
public function migrationWithInvalidAccountsProvider()
{
return [
'PLAIN_TEXT_PAYLOAD_WITH_INVALID_URI' => [
new PlainTextMigrator(),
MigrationTestData::PLAIN_TEXT_PAYLOAD_WITH_INVALID_URI,
],
'VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE' => [
new AegisMigrator(),
MigrationTestData::VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE,
],
'VALID_2FAS_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE' => [
new TwoFASMigrator(),
MigrationTestData::VALID_2FAS_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_OTP_TYPE,
],
];
}
/**
* @test
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function test_migrate_gauth_returns_fake_accounts()
{
$this->mock('alias:' . Base32::class, function (MockInterface $baseEncoder) {
$baseEncoder->shouldReceive('encodeUpper')
->andThrow(new \Exception());
});
$migrator = new GoogleAuthMigrator();
$accounts = $migrator->migrate(MigrationTestData::GOOGLE_AUTH_MIGRATION_URI);
$this->assertContainsOnlyInstancesOf(TwoFAccount::class, $accounts);
$this->assertCount(2, $accounts);
// The returned collection could have non-linear index (because of possible blank lines
// in the migration payload) so we do not use get() to retrieve items
$this->assertEquals($this->fakeTwofaccount->id, $accounts->first()->id);
$this->assertEquals($this->fakeTwofaccount->id, $accounts->last()->id);
}
/**
* @test
*
* @dataProvider AegisWithIconMigrationProvider
*/
public function test_migrate_aegis_payload_with_icon_sets_and_stores_the_icon($migration)
{
Storage::fake('icons');
$migrator = new AegisMigrator();
$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 AegisWithIconMigrationProvider()
{
return [
'SVG' => [
MigrationTestData::VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_SVG_ICON,
],
'PNG' => [
MigrationTestData::VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_PNG_ICON,
],
'JPG' => [
MigrationTestData::VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_JPG_ICON,
],
];
}
/**
* @test
*/
public function test_migrate_aegis_payload_with_unsupported_icon_does_not_fail()
{
Storage::fake('icons');
$migrator = new AegisMigrator();
$accounts = $migrator->migrate(MigrationTestData::VALID_AEGIS_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
*
* @dataProvider factoryProvider
*/
public function test_factory_returns_plain_text_migrator($payload, $migratorClass)
{
$factory = new MigratorFactory();
$migrator = $factory->create($payload);
$this->assertInstanceOf($migratorClass, $migrator);
}
/**
* Provide data for TwoFAccount store tests
*/
public function factoryProvider()
{
return [
'VALID_PLAIN_TEXT_PAYLOAD' => [
MigrationTestData::VALID_PLAIN_TEXT_PAYLOAD,
PlainTextMigrator::class,
],
'VALID_AEGIS_JSON_MIGRATION_PAYLOAD' => [
MigrationTestData::VALID_AEGIS_JSON_MIGRATION_PAYLOAD,
AegisMigrator::class,
],
'VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_ICON' => [
MigrationTestData::VALID_AEGIS_JSON_MIGRATION_PAYLOAD_WITH_UNSUPPORTED_ICON,
AegisMigrator::class,
],
'VALID_2FAS_MIGRATION_PAYLOAD' => [
MigrationTestData::VALID_2FAS_MIGRATION_PAYLOAD,
TwoFASMigrator::class,
],
'GOOGLE_AUTH_MIGRATION_URI' => [
MigrationTestData::GOOGLE_AUTH_MIGRATION_URI,
GoogleAuthMigrator::class,
],
];
}
/**
* @test
*/
public function test_factory_throw_UnsupportedMigrationException()
{
$this->expectException(UnsupportedMigrationException::class);
$factory = new MigratorFactory();
$migrator = $factory->create('not_a_valid_payload');
}
/**
* @test
*
* @dataProvider encryptedMigrationDataProvider
*/
public function test_factory_throw_EncryptedMigrationException($payload)
{
$this->expectException(EncryptedMigrationException::class);
$factory = new MigratorFactory();
$migrator = $factory->create($payload);
}
/**
* Provide data for TwoFAccount store tests
*/
public function encryptedMigrationDataProvider()
{
return [
'ENCRYPTED_AEGIS_JSON_MIGRATION_PAYLOAD' => [
MigrationTestData::ENCRYPTED_AEGIS_JSON_MIGRATION_PAYLOAD
],
'ENCRYPTED_2FAS_MIGRATION_PAYLOAD' => [
MigrationTestData::ENCRYPTED_2FAS_MIGRATION_PAYLOAD
],
];
}
/**
*
*/
protected function tearDown(): void
{
Mockery::close();
}
}