mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-02-08 22:49:25 +01:00
Add ability to export data as otpauth URIs - Closes #386
This commit is contained in:
parent
63a700da44
commit
3e2a80b816
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Api\v1\Requests\TwoFAccountBatchRequest;
|
use App\Api\v1\Requests\TwoFAccountBatchRequest;
|
||||||
use App\Api\v1\Requests\TwoFAccountDynamicRequest;
|
use App\Api\v1\Requests\TwoFAccountDynamicRequest;
|
||||||
|
use App\Api\v1\Requests\TwoFAccountExportRequest;
|
||||||
use App\Api\v1\Requests\TwoFAccountImportRequest;
|
use App\Api\v1\Requests\TwoFAccountImportRequest;
|
||||||
use App\Api\v1\Requests\TwoFAccountIndexRequest;
|
use App\Api\v1\Requests\TwoFAccountIndexRequest;
|
||||||
use App\Api\v1\Requests\TwoFAccountReorderRequest;
|
use App\Api\v1\Requests\TwoFAccountReorderRequest;
|
||||||
@ -184,7 +185,7 @@ public function preview(TwoFAccountUriRequest $request)
|
|||||||
*
|
*
|
||||||
* @return TwoFAccountExportCollection|\Illuminate\Http\JsonResponse
|
* @return TwoFAccountExportCollection|\Illuminate\Http\JsonResponse
|
||||||
*/
|
*/
|
||||||
public function export(TwoFAccountBatchRequest $request)
|
public function export(TwoFAccountExportRequest $request)
|
||||||
{
|
{
|
||||||
$validated = $request->validated();
|
$validated = $request->validated();
|
||||||
|
|
||||||
|
33
app/Api/v1/Requests/TwoFAccountExportRequest.php
Normal file
33
app/Api/v1/Requests/TwoFAccountExportRequest.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Api\v1\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class TwoFAccountExportRequest extends TwoFAccountBatchRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return array_merge(
|
||||||
|
parent::rules(),
|
||||||
|
[
|
||||||
|
'otpauth' => 'sometimes|required|boolean',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ public function toArray($request)
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'app' => '2fauth_v' . config('2fauth.version'),
|
'app' => '2fauth_v' . config('2fauth.version'),
|
||||||
'schema' => 1,
|
'schema' => $this->when($request->missing('otpauth') || ! $request->boolean('otpauth'), 1),
|
||||||
'datetime' => now(),
|
'datetime' => now(),
|
||||||
'data' => $this->collection,
|
'data' => $this->collection,
|
||||||
];
|
];
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
* @property int|null $period
|
* @property int|null $period
|
||||||
* @property int|null $counter
|
* @property int|null $counter
|
||||||
* @property string $legacy_uri
|
* @property string $legacy_uri
|
||||||
|
* @method string getURI()
|
||||||
*/
|
*/
|
||||||
class TwoFAccountExportResource extends JsonResource
|
class TwoFAccountExportResource extends JsonResource
|
||||||
{
|
{
|
||||||
@ -28,7 +29,11 @@ class TwoFAccountExportResource extends JsonResource
|
|||||||
*/
|
*/
|
||||||
public function toArray($request)
|
public function toArray($request)
|
||||||
{
|
{
|
||||||
return [
|
return $request->has('otpauth') && $request->boolean('otpauth')
|
||||||
|
? [
|
||||||
|
'uri' => urldecode($this->getURI()),
|
||||||
|
]
|
||||||
|
: [
|
||||||
'otp_type' => $this->otp_type,
|
'otp_type' => $this->otp_type,
|
||||||
'account' => $this->account,
|
'account' => $this->account,
|
||||||
'service' => $this->service,
|
'service' => $this->service,
|
||||||
|
@ -31,6 +31,7 @@ public function index()
|
|||||||
$githubAuth = config('services.github.client_secret') ? true : false;
|
$githubAuth = config('services.github.client_secret') ? true : false;
|
||||||
$installDocUrl = config('2fauth.installDocUrl');
|
$installDocUrl = config('2fauth.installDocUrl');
|
||||||
$ssoDocUrl = config('2fauth.ssoDocUrl');
|
$ssoDocUrl = config('2fauth.ssoDocUrl');
|
||||||
|
$exportSchemaUrl = config('2fauth.exportSchemaUrl');
|
||||||
|
|
||||||
// if (Auth::user()->preferences)
|
// if (Auth::user()->preferences)
|
||||||
|
|
||||||
@ -48,6 +49,7 @@ public function index()
|
|||||||
'urls' => collect([
|
'urls' => collect([
|
||||||
'installDocUrl' => $installDocUrl,
|
'installDocUrl' => $installDocUrl,
|
||||||
'ssoDocUrl' => $ssoDocUrl,
|
'ssoDocUrl' => $ssoDocUrl,
|
||||||
|
'exportSchemaUrl' => $exportSchemaUrl,
|
||||||
]),
|
]),
|
||||||
'defaultPreferences' => $defaultPreferences,
|
'defaultPreferences' => $defaultPreferences,
|
||||||
'subdirectory' => $subdir,
|
'subdirectory' => $subdir,
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
'latestReleaseUrl' => 'https://api.github.com/repos/Bubka/2FAuth/releases/latest',
|
'latestReleaseUrl' => 'https://api.github.com/repos/Bubka/2FAuth/releases/latest',
|
||||||
'installDocUrl' => 'https://docs.2fauth.app/getting-started/installation/self-hosted-server/',
|
'installDocUrl' => 'https://docs.2fauth.app/getting-started/installation/self-hosted-server/',
|
||||||
'ssoDocUrl' => 'https://docs.2fauth.app/security/authentication/sso/',
|
'ssoDocUrl' => 'https://docs.2fauth.app/security/authentication/sso/',
|
||||||
|
'exportSchemaUrl' => 'https://docs.2fauth.app/usage/migration/#export-schema',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
:disabled='areDisabled' class="button is-rounded"
|
:disabled='areDisabled' class="button is-rounded"
|
||||||
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
:class="[{ 'is-outlined': mode == 'dark' || areDisabled }, areDisabled ? 'is-dark': 'is-link']"
|
||||||
@click="$emit('export-button-clicked')"
|
@click="$emit('export-button-clicked')"
|
||||||
:title="$t('twofaccounts.export_selected_to_json')" >
|
:title="$t('twofaccounts.export_selected_accounts')" >
|
||||||
{{ $t('commons.export') }}
|
{{ $t('commons.export') }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
39
resources/js/components/ExportButtons.vue
Normal file
39
resources/js/components/ExportButtons.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<script setup>
|
||||||
|
import { UseColorMode } from '@vueuse/components'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const emit = defineEmits(['export-twofauth-format', 'export-otpauth-format'])
|
||||||
|
const $2fauth = inject('2fauth')
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="block">
|
||||||
|
<UseColorMode v-slot="{ mode }">
|
||||||
|
<p class="has-text-weight-bold has-text-grey">
|
||||||
|
{{ $t('twofaccounts.twofauth_export_format_sub') }}
|
||||||
|
</p>
|
||||||
|
</UseColorMode>
|
||||||
|
<p class="is-size-7-mobile">
|
||||||
|
{{ $t('twofaccounts.twofauth_export_format_desc') }}
|
||||||
|
{{ $t('twofaccounts.twofauth_export_format_url') }}
|
||||||
|
<a id="lnkExportSchemaUrl" class="is-link" tabindex="0" :href="$2fauth.urls.exportSchemaUrl" target="_blank">
|
||||||
|
{{ $t('twofaccounts.twofauth_export_schema') }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<button id="btnExport2FAuth" class="button is-link is-rounded is-focus my-3" @click="$emit('export-twofauth-format')" :title="$t('twofaccounts.export_selected_to_json')">
|
||||||
|
{{ $t('twofaccounts.twofauth_export_format') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<p class="has-text-weight-bold has-text-grey">
|
||||||
|
{{ $t('twofaccounts.otpauth_export_format_sub') }}
|
||||||
|
</p>
|
||||||
|
<p class="is-size-7-mobile">
|
||||||
|
{{ $t('twofaccounts.otpauth_export_format_desc') }}
|
||||||
|
</p>
|
||||||
|
<button id="btnExportOtpauth" class="button is-link is-rounded is-focus my-3" @click="$emit('export-otpauth-format')" :title="$t('twofaccounts.export_selected_to_otpauth_uri')">
|
||||||
|
{{ $t('twofaccounts.otpauth_export_format') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
4
resources/js/services/twofaccountService.js
vendored
4
resources/js/services/twofaccountService.js
vendored
@ -55,8 +55,8 @@ export default {
|
|||||||
return apiClient.delete('/twofaccounts?ids=' + ids, { ...config })
|
return apiClient.delete('/twofaccounts?ids=' + ids, { ...config })
|
||||||
},
|
},
|
||||||
|
|
||||||
export(ids, config = {}) {
|
export(ids, otpauthFormat, config = {}) {
|
||||||
return apiClient.get('/twofaccounts/export?ids=' + ids, { ...config })
|
return apiClient.get('/twofaccounts/export?ids=' + ids + (otpauthFormat ? '&otpauth=1' : ''), { ...config })
|
||||||
},
|
},
|
||||||
|
|
||||||
getQrcode(id, config = {}) {
|
getQrcode(id, config = {}) {
|
||||||
|
17
resources/js/stores/twofaccounts.js
vendored
17
resources/js/stores/twofaccounts.js
vendored
@ -163,12 +163,25 @@ export const useTwofaccounts = defineStore({
|
|||||||
/**
|
/**
|
||||||
* Exports selected accounts to a json file
|
* Exports selected accounts to a json file
|
||||||
*/
|
*/
|
||||||
export() {
|
export(format = '2fauth') {
|
||||||
twofaccountService.export(this.selectedIds.join(), {responseType: 'blob'})
|
if (format == 'otpauth') {
|
||||||
|
twofaccountService.export(this.selectedIds.join(), true)
|
||||||
|
.then((response) => {
|
||||||
|
let uris = []
|
||||||
|
response.data.data.forEach(account => {
|
||||||
|
uris.push(account.uri)
|
||||||
|
});
|
||||||
|
var blob = new Blob([uris.join('\n')], {type: "text/plain;charset=utf-8"});
|
||||||
|
saveAs.saveAs(blob, "2fauth_export_otpauth.txt");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
twofaccountService.export(this.selectedIds.join(), false, {responseType: 'blob'})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
var blob = new Blob([response.data], {type: "application/json;charset=utf-8"});
|
var blob = new Blob([response.data], {type: "application/json;charset=utf-8"});
|
||||||
saveAs.saveAs(blob, "2fauth_export.json");
|
saveAs.saveAs(blob, "2fauth_export.json");
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
import Toolbar from '@/components/Toolbar.vue'
|
import Toolbar from '@/components/Toolbar.vue'
|
||||||
import OtpDisplay from '@/components/OtpDisplay.vue'
|
import OtpDisplay from '@/components/OtpDisplay.vue'
|
||||||
import ActionButtons from '@/components/ActionButtons.vue'
|
import ActionButtons from '@/components/ActionButtons.vue'
|
||||||
|
import ExportButtons from '@/components/ExportButtons.vue'
|
||||||
import Dots from '@/components/Dots.vue'
|
import Dots from '@/components/Dots.vue'
|
||||||
import { UseColorMode } from '@vueuse/components'
|
import { UseColorMode } from '@vueuse/components'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
@ -29,6 +30,7 @@
|
|||||||
const groups = useGroups()
|
const groups = useGroups()
|
||||||
|
|
||||||
const showOtpInModal = ref(false)
|
const showOtpInModal = ref(false)
|
||||||
|
const showExportFormatSelector = ref(false)
|
||||||
const showGroupSwitch = ref(false)
|
const showGroupSwitch = ref(false)
|
||||||
const showDestinationGroupSelector = ref(false)
|
const showDestinationGroupSelector = ref(false)
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
@ -357,7 +359,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- modal -->
|
<!-- export modal -->
|
||||||
|
<Modal v-model="showExportFormatSelector" :isFullHeight="true">
|
||||||
|
<ExportButtons
|
||||||
|
@export-twofauth-format="twofaccounts.export()"
|
||||||
|
@export-otpauth-format="twofaccounts.export('otpauth')">
|
||||||
|
</ExportButtons>
|
||||||
|
</Modal>
|
||||||
|
<!-- otp modal -->
|
||||||
<Modal v-model="showOtpInModal">
|
<Modal v-model="showOtpInModal">
|
||||||
<OtpDisplay
|
<OtpDisplay
|
||||||
ref="otpDisplay"
|
ref="otpDisplay"
|
||||||
@ -467,7 +476,7 @@
|
|||||||
:areDisabled="twofaccounts.hasNoneSelected"
|
:areDisabled="twofaccounts.hasNoneSelected"
|
||||||
@move-button-clicked="showDestinationGroupSelector = true"
|
@move-button-clicked="showDestinationGroupSelector = true"
|
||||||
@delete-button-clicked="deleteAccounts"
|
@delete-button-clicked="deleteAccounts"
|
||||||
@export-button-clicked="twofaccounts.export()">
|
@export-button-clicked="showExportFormatSelector = true">
|
||||||
</ActionButtons>
|
</ActionButtons>
|
||||||
</VueFooter>
|
</VueFooter>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,7 +28,17 @@
|
|||||||
'account_updated' => 'Account successfully updated',
|
'account_updated' => 'Account successfully updated',
|
||||||
'accounts_deleted' => 'Account(s) successfully deleted',
|
'accounts_deleted' => 'Account(s) successfully deleted',
|
||||||
'accounts_moved' => 'Account(s) successfully moved',
|
'accounts_moved' => 'Account(s) successfully moved',
|
||||||
'export_selected_to_json' => 'Download a json export of selected accounts',
|
'export_selected_accounts' => 'Export selected accounts',
|
||||||
|
'export_selected_to_json' => 'Export accounts using the 2FAuth json format',
|
||||||
|
'export_selected_to_otpauth_uri' => 'Export accounts to plain text otpauth URIs',
|
||||||
|
'twofauth_export_format' => '2FAuth format',
|
||||||
|
'twofauth_export_format_sub' => 'Export data using the 2FAuth json schema',
|
||||||
|
'twofauth_export_format_desc' => 'You should prefer this option if you need to create a backup that can be restored. This format takes care of the icons.',
|
||||||
|
'twofauth_export_format_url' => 'The schema definition is described here:',
|
||||||
|
'twofauth_export_schema' => '2FAuth export schema',
|
||||||
|
'otpauth_export_format' => 'otpauth URIs',
|
||||||
|
'otpauth_export_format_sub' => 'Export data as a list of otpauth URIs',
|
||||||
|
'otpauth_export_format_desc' => 'otpauth URI is the most common format used to exchange 2FA data, for example in the form of a QR code when you enable 2FA on a web site. Select this if you want to switch from 2FAuth.',
|
||||||
'reveal' => 'reveal',
|
'reveal' => 'reveal',
|
||||||
'forms' => [
|
'forms' => [
|
||||||
'service' => [
|
'service' => [
|
||||||
|
@ -166,7 +166,18 @@ class TwoFAccountControllerTest extends FeatureTestCase
|
|||||||
'period',
|
'period',
|
||||||
'counter',
|
'counter',
|
||||||
'legacy_uri',
|
'legacy_uri',
|
||||||
], ],
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
private const VALID_EXPORT_AS_URIS_STRUTURE = [
|
||||||
|
'app',
|
||||||
|
'datetime',
|
||||||
|
'data' => [
|
||||||
|
'*' => [
|
||||||
|
'uri',
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
private const JSON_FRAGMENTS_FOR_CUSTOM_TOTP = [
|
private const JSON_FRAGMENTS_FOR_CUSTOM_TOTP = [
|
||||||
@ -1221,6 +1232,34 @@ public function test_export_returns_json_migration_resource()
|
|||||||
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP);
|
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function test_export_returns_plain_text_with_otpauth_uris()
|
||||||
|
{
|
||||||
|
$this->twofaccountA = TwoFAccount::factory()->for($this->user)->create(self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP);
|
||||||
|
$this->twofaccountB = TwoFAccount::factory()->for($this->user)->create(self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP);
|
||||||
|
|
||||||
|
$this->actingAs($this->user, 'api-guard')
|
||||||
|
->json('GET', '/api/v1/twofaccounts/export?ids=' . $this->twofaccountA->id . ',' . $this->twofaccountB->id . '&otpauth=1')
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonStructure(self::VALID_EXPORT_AS_URIS_STRUTURE)
|
||||||
|
->assertJsonFragment(['uri' => $this->twofaccountA->getURI()])
|
||||||
|
->assertJsonFragment(['uri' => $this->twofaccountB->getURI()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function test_export_returns_json_migration_resource_when_otpauth_param_is_off()
|
||||||
|
{
|
||||||
|
$this->twofaccountA = TwoFAccount::factory()->for($this->user)->create(self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP);
|
||||||
|
$this->twofaccountB = TwoFAccount::factory()->for($this->user)->create(self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP);
|
||||||
|
|
||||||
|
$this->actingAs($this->user, 'api-guard')
|
||||||
|
->json('GET', '/api/v1/twofaccounts/export?ids=' . $this->twofaccountA->id . ',' . $this->twofaccountB->id . '&otpauth=0')
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonStructure(self::VALID_EXPORT_STRUTURE)
|
||||||
|
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP)
|
||||||
|
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP);
|
||||||
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
public function test_export_too_many_ids_returns_bad_request()
|
public function test_export_too_many_ids_returns_bad_request()
|
||||||
{
|
{
|
||||||
|
126
tests/Api/v1/Requests/TwoFAccountExportRequestTest.php
Normal file
126
tests/Api/v1/Requests/TwoFAccountExportRequestTest.php
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Api\v1\Requests;
|
||||||
|
|
||||||
|
use App\Api\v1\Requests\TwoFAccountExportRequest;
|
||||||
|
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TwoFAccountExportRequestTest test class
|
||||||
|
*/
|
||||||
|
#[CoversClass(TwoFAccountExportRequest::class)]
|
||||||
|
class TwoFAccountExportRequestTest extends TestCase
|
||||||
|
{
|
||||||
|
use WithoutMiddleware;
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function test_user_is_authorized()
|
||||||
|
{
|
||||||
|
Auth::shouldReceive('check')
|
||||||
|
->once()
|
||||||
|
->andReturn(true);
|
||||||
|
|
||||||
|
$request = new TwoFAccountExportRequest;
|
||||||
|
|
||||||
|
$this->assertTrue($request->authorize());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[DataProvider('provideValidData')]
|
||||||
|
public function test_valid_data(array $data) : void
|
||||||
|
{
|
||||||
|
$request = new TwoFAccountExportRequest;
|
||||||
|
$validator = Validator::make($data, $request->rules());
|
||||||
|
|
||||||
|
$this->assertFalse($validator->fails());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide Valid data for validation test
|
||||||
|
*/
|
||||||
|
public static function provideValidData() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => '1',
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => 1,
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => true,
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => '0',
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => 0,
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => false,
|
||||||
|
]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[DataProvider('provideInvalidData')]
|
||||||
|
public function test_invalid_data(array $data) : void
|
||||||
|
{
|
||||||
|
$request = new TwoFAccountExportRequest;
|
||||||
|
$validator = Validator::make($data, $request->rules());
|
||||||
|
|
||||||
|
$this->assertTrue($validator->fails());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide invalid data for validation test
|
||||||
|
*/
|
||||||
|
public static function provideInvalidData() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => null,
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => '',
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => 2,
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => 'string',
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => 0.1,
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => '01/01/2020',
|
||||||
|
]],
|
||||||
|
[[
|
||||||
|
'ids' => '1',
|
||||||
|
'otpauth' => '01',
|
||||||
|
]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user