TwoFAccountControllerTest.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. <?php
  2. namespace Tests\Api\v1\Controllers;
  3. use App\Models\User;
  4. use App\Models\Group;
  5. use App\Facades\Settings;
  6. use Tests\FeatureTestCase;
  7. use Tests\Classes\OtpTestData;
  8. use App\Models\TwoFAccount;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Facades\Storage;
  11. /**
  12. * @covers \App\Api\v1\Controllers\TwoFAccountController
  13. * @covers \App\Api\v1\Resources\TwoFAccountReadResource
  14. * @covers \App\Api\v1\Resources\TwoFAccountStoreResource
  15. */
  16. class TwoFAccountControllerTest extends FeatureTestCase
  17. {
  18. /**
  19. * @var \App\Models\User
  20. */
  21. protected $user;
  22. /**
  23. * @var \App\Models\Group
  24. */
  25. protected $group;
  26. private const VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET = [
  27. 'id',
  28. 'group_id',
  29. 'service',
  30. 'account',
  31. 'icon',
  32. 'otp_type',
  33. 'digits',
  34. 'algorithm',
  35. 'period',
  36. 'counter'
  37. ];
  38. private const VALID_RESOURCE_STRUCTURE_WITH_SECRET = [
  39. 'id',
  40. 'group_id',
  41. 'service',
  42. 'account',
  43. 'icon',
  44. 'otp_type',
  45. 'secret',
  46. 'digits',
  47. 'algorithm',
  48. 'period',
  49. 'counter'
  50. ];
  51. private const VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP = [
  52. 'generated_at',
  53. 'otp_type',
  54. 'password',
  55. 'period',
  56. ];
  57. private const VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP = [
  58. 'otp_type',
  59. 'password',
  60. 'counter',
  61. ];
  62. private const JSON_FRAGMENTS_FOR_CUSTOM_TOTP = [
  63. 'service' => OtpTestData::SERVICE,
  64. 'account' => OtpTestData::ACCOUNT,
  65. 'otp_type' => 'totp',
  66. 'secret' => OtpTestData::SECRET,
  67. 'digits' => OtpTestData::DIGITS_CUSTOM,
  68. 'algorithm' => OtpTestData::ALGORITHM_CUSTOM,
  69. 'period' => OtpTestData::PERIOD_CUSTOM,
  70. 'counter' => null,
  71. ];
  72. private const JSON_FRAGMENTS_FOR_DEFAULT_TOTP = [
  73. 'service' => null,
  74. 'account' => OtpTestData::ACCOUNT,
  75. 'otp_type' => 'totp',
  76. 'secret' => OtpTestData::SECRET,
  77. 'digits' => OtpTestData::DIGITS_DEFAULT,
  78. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  79. 'period' => OtpTestData::PERIOD_DEFAULT,
  80. 'counter' => null,
  81. ];
  82. private const JSON_FRAGMENTS_FOR_CUSTOM_HOTP = [
  83. 'service' => OtpTestData::SERVICE,
  84. 'account' => OtpTestData::ACCOUNT,
  85. 'otp_type' => 'hotp',
  86. 'secret' => OtpTestData::SECRET,
  87. 'digits' => OtpTestData::DIGITS_CUSTOM,
  88. 'algorithm' => OtpTestData::ALGORITHM_CUSTOM,
  89. 'period' => null,
  90. 'counter' => OtpTestData::COUNTER_CUSTOM,
  91. ];
  92. private const JSON_FRAGMENTS_FOR_DEFAULT_HOTP = [
  93. 'service' => null,
  94. 'account' => OtpTestData::ACCOUNT,
  95. 'otp_type' => 'hotp',
  96. 'secret' => OtpTestData::SECRET,
  97. 'digits' => OtpTestData::DIGITS_DEFAULT,
  98. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  99. 'period' => null,
  100. 'counter' => OtpTestData::COUNTER_DEFAULT,
  101. ];
  102. private const ARRAY_OF_INVALID_PARAMETERS = [
  103. 'account' => null,
  104. 'otp_type' => 'totp',
  105. 'secret' => OtpTestData::SECRET,
  106. ];
  107. /**
  108. * @test
  109. */
  110. public function setUp(): void
  111. {
  112. parent::setUp();
  113. $this->user = User::factory()->create();
  114. $this->group = Group::factory()->create();
  115. }
  116. /**
  117. * @test
  118. */
  119. public function test_index_returns_twofaccount_collection()
  120. {
  121. TwoFAccount::factory()->count(3)->create();
  122. $response = $this->actingAs($this->user, 'api-guard')
  123. ->json('GET', '/api/v1/twofaccounts')
  124. ->assertOk()
  125. ->assertJsonCount(3, $key = null)
  126. ->assertJsonStructure([
  127. '*' => self::VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET
  128. ]);
  129. }
  130. /**
  131. * @test
  132. */
  133. public function test_index_returns_twofaccount_collection_with_secret()
  134. {
  135. TwoFAccount::factory()->count(3)->create();
  136. $response = $this->actingAs($this->user, 'api-guard')
  137. ->json('GET', '/api/v1/twofaccounts?withSecret=1')
  138. ->assertOk()
  139. ->assertJsonCount(3, $key = null)
  140. ->assertJsonStructure([
  141. '*' => self::VALID_RESOURCE_STRUCTURE_WITH_SECRET
  142. ]);
  143. }
  144. /**
  145. * @test
  146. */
  147. public function test_show_twofaccount_returns_twofaccount_resource_with_secret()
  148. {
  149. $twofaccount = TwoFAccount::factory()->create();
  150. $response = $this->actingAs($this->user, 'api-guard')
  151. ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id)
  152. ->assertOk()
  153. ->assertJsonStructure(self::VALID_RESOURCE_STRUCTURE_WITH_SECRET);
  154. }
  155. /**
  156. * @test
  157. */
  158. public function test_show_twofaccount_returns_twofaccount_resource_without_secret()
  159. {
  160. $twofaccount = TwoFAccount::factory()->create();
  161. $response = $this->actingAs($this->user, 'api-guard')
  162. ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '?withSecret=0')
  163. ->assertOk()
  164. ->assertJsonStructure(self::VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET);
  165. }
  166. /**
  167. * @test
  168. */
  169. // public function test_show_twofaccount_with_indeciphered_data_returns_replaced_data()
  170. // {
  171. // $dbEncryptionService = resolve('App\Services\DbEncryptionService');
  172. // $dbEncryptionService->setTo(true);
  173. // $twofaccount = TwoFAccount::factory()->create();
  174. // DB::table('twofaccounts')
  175. // ->where('id', $twofaccount->id)
  176. // ->update([
  177. // 'secret' => '**encrypted**',
  178. // 'account' => '**encrypted**',
  179. // ]);
  180. // $response = $this->actingAs($this->user, 'api-guard')
  181. // ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id)
  182. // ->assertJsonFragment([
  183. // 'secret' => '*indecipherable*',
  184. // 'account' => '*indecipherable*',
  185. // ]);
  186. // }
  187. /**
  188. * @test
  189. */
  190. public function test_show_missing_twofaccount_returns_not_found()
  191. {
  192. $response = $this->actingAs($this->user, 'api-guard')
  193. ->json('GET', '/api/v1/twofaccounts/1000')
  194. ->assertNotFound()
  195. ->assertJsonStructure([
  196. 'message'
  197. ]);
  198. }
  199. /**
  200. * @dataProvider provideDataForTestStoreStructure
  201. * @test
  202. */
  203. public function test_store_returns_success_with_consistent_resource_structure(array $data)
  204. {
  205. Storage::put('test.png', 'emptied to prevent missing resource replaced by null by the model getter');
  206. $response = $this->actingAs($this->user, 'api-guard')
  207. ->json('POST', '/api/v1/twofaccounts', $data)
  208. ->assertCreated()
  209. ->assertJsonStructure(self::VALID_RESOURCE_STRUCTURE_WITH_SECRET);
  210. }
  211. /**
  212. * Provide data for TwoFAccount store test
  213. */
  214. public function provideDataForTestStoreStructure() : array
  215. {
  216. return [
  217. [[
  218. 'uri' => OtpTestData::TOTP_FULL_CUSTOM_URI,
  219. ]],
  220. [[
  221. 'uri' => OtpTestData::TOTP_SHORT_URI,
  222. ]],
  223. [
  224. OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP
  225. ],
  226. [
  227. OtpTestData::ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_TOTP
  228. ],
  229. [[
  230. 'uri' => OtpTestData::HOTP_FULL_CUSTOM_URI,
  231. ]],
  232. [[
  233. 'uri' => OtpTestData::HOTP_SHORT_URI,
  234. ]],
  235. [
  236. OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP
  237. ],
  238. [
  239. OtpTestData::ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_HOTP
  240. ],
  241. ];
  242. }
  243. /**
  244. * @test
  245. */
  246. public function test_store_totp_using_fully_custom_uri_returns_consistent_resource()
  247. {
  248. $response = $this->actingAs($this->user, 'api-guard')
  249. ->json('POST', '/api/v1/twofaccounts', [
  250. 'uri' => OtpTestData::TOTP_FULL_CUSTOM_URI,
  251. ])
  252. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
  253. }
  254. /**
  255. * @test
  256. */
  257. public function test_store_totp_using_short_uri_returns_resource_with_default_otp_parameter()
  258. {
  259. $response = $this->actingAs($this->user, 'api-guard')
  260. ->json('POST', '/api/v1/twofaccounts', [
  261. 'uri' => OtpTestData::TOTP_SHORT_URI,
  262. ])
  263. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP);
  264. }
  265. /**
  266. * @test
  267. */
  268. public function test_store_totp_using_fully_custom_parameters_returns_consistent_resource()
  269. {
  270. $response = $this->actingAs($this->user, 'api-guard')
  271. ->json('POST', '/api/v1/twofaccounts', OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP)
  272. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
  273. }
  274. /**
  275. * @test
  276. */
  277. public function test_store_totp_using_minimum_parameters_returns_consistent_resource()
  278. {
  279. $response = $this->actingAs($this->user, 'api-guard')
  280. ->json('POST', '/api/v1/twofaccounts', OtpTestData::ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_TOTP)
  281. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP);
  282. }
  283. /**
  284. * @test
  285. */
  286. public function test_store_hotp_using_fully_custom_uri_returns_consistent_resource()
  287. {
  288. $response = $this->actingAs($this->user, 'api-guard')
  289. ->json('POST', '/api/v1/twofaccounts', [
  290. 'uri' => OtpTestData::HOTP_FULL_CUSTOM_URI,
  291. ])
  292. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_HOTP);
  293. }
  294. /**
  295. * @test
  296. */
  297. public function test_store_hotp_using_short_uri_returns_resource_with_default_otp_parameter()
  298. {
  299. $response = $this->actingAs($this->user, 'api-guard')
  300. ->json('POST', '/api/v1/twofaccounts', [
  301. 'uri' => OtpTestData::HOTP_SHORT_URI,
  302. ])
  303. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP);
  304. }
  305. /**
  306. * @test
  307. */
  308. public function test_store_hotp_using_fully_custom_parameters_returns_consistent_resource()
  309. {
  310. $response = $this->actingAs($this->user, 'api-guard')
  311. ->json('POST', '/api/v1/twofaccounts', OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP)
  312. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_HOTP);
  313. }
  314. /**
  315. * @test
  316. */
  317. public function test_store_hotp_using_minimum_parameters_returns_consistent_resource()
  318. {
  319. $response = $this->actingAs($this->user, 'api-guard')
  320. ->json('POST', '/api/v1/twofaccounts', OtpTestData::ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_HOTP)
  321. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP);
  322. }
  323. /**
  324. * @test
  325. */
  326. public function test_store_with_invalid_uri_returns_validation_error()
  327. {
  328. $response = $this->actingAs($this->user, 'api-guard')
  329. ->json('POST', '/api/v1/twofaccounts', [
  330. 'uri' => OtpTestData::INVALID_OTPAUTH_URI,
  331. ])
  332. ->assertStatus(422);
  333. }
  334. /**
  335. * @test
  336. */
  337. public function test_store_assigns_created_account_when_default_group_is_a_specific_one()
  338. {
  339. // Set the default group to a specific one
  340. Settings::set('defaultGroup', $this->group->id);
  341. $response = $this->actingAs($this->user, 'api-guard')
  342. ->json('POST', '/api/v1/twofaccounts', [
  343. 'uri' => OtpTestData::TOTP_SHORT_URI,
  344. ])
  345. ->assertJsonFragment([
  346. 'group_id' => $this->group->id
  347. ]);
  348. }
  349. /**
  350. * @test
  351. */
  352. public function test_store_assigns_created_account_when_default_group_is_the_active_one()
  353. {
  354. // Set the default group to be the active one
  355. Settings::set('defaultGroup', -1);
  356. // Set the active group
  357. Settings::set('activeGroup', $this->group->id);
  358. $response = $this->actingAs($this->user, 'api-guard')
  359. ->json('POST', '/api/v1/twofaccounts', [
  360. 'uri' => OtpTestData::TOTP_SHORT_URI,
  361. ])
  362. ->assertJsonFragment([
  363. 'group_id' => $this->group->id
  364. ]);
  365. }
  366. /**
  367. * @test
  368. */
  369. public function test_store_assigns_created_account_when_default_group_is_no_group()
  370. {
  371. // Set the default group to No group
  372. Settings::set('defaultGroup', 0);
  373. $response = $this->actingAs($this->user, 'api-guard')
  374. ->json('POST', '/api/v1/twofaccounts', [
  375. 'uri' => OtpTestData::TOTP_SHORT_URI,
  376. ])
  377. ->assertJsonFragment([
  378. 'group_id' => null
  379. ]);
  380. }
  381. /**
  382. * @test
  383. */
  384. public function test_store_assigns_created_account_when_default_group_does_not_exist()
  385. {
  386. // Set the default group to a non-existing one
  387. Settings::set('defaultGroup', 1000);
  388. $response = $this->actingAs($this->user, 'api-guard')
  389. ->json('POST', '/api/v1/twofaccounts', [
  390. 'uri' => OtpTestData::TOTP_SHORT_URI,
  391. ])
  392. ->assertJsonFragment([
  393. 'group_id' => null
  394. ]);
  395. }
  396. /**
  397. * @test
  398. */
  399. public function test_update_totp_returns_success_with_updated_resource()
  400. {
  401. $twofaccount = TwoFAccount::factory()->create();
  402. $response = $this->actingAs($this->user, 'api-guard')
  403. ->json('PUT', '/api/v1/twofaccounts/' . $twofaccount->id, OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP)
  404. ->assertOk()
  405. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
  406. }
  407. /**
  408. * @test
  409. */
  410. public function test_update_hotp_returns_success_with_updated_resource()
  411. {
  412. $twofaccount = TwoFAccount::factory()->create();
  413. $response = $this->actingAs($this->user, 'api-guard')
  414. ->json('PUT', '/api/v1/twofaccounts/' . $twofaccount->id, OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP)
  415. ->assertOk()
  416. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_HOTP);
  417. }
  418. /**
  419. * @test
  420. */
  421. public function test_update_missing_twofaccount_returns_not_found()
  422. {
  423. $response = $this->actingAs($this->user, 'api-guard')
  424. ->json('PUT', '/api/v1/twofaccounts/1000', OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP)
  425. ->assertNotFound();
  426. }
  427. /**
  428. * @test
  429. */
  430. public function test_update_twofaccount_with_invalid_data_returns_validation_error()
  431. {
  432. $twofaccount = TwoFAccount::factory()->create();
  433. $response = $this->actingAs($this->user, 'api-guard')
  434. ->json('PUT', '/api/v1/twofaccounts/' . $twofaccount->id, self::ARRAY_OF_INVALID_PARAMETERS)
  435. ->assertStatus(422);
  436. }
  437. /**
  438. * @test
  439. */
  440. public function test_import_valid_gauth_data_returns_success_with_consistent_resources()
  441. {
  442. $response = $this->actingAs($this->user, 'api-guard')
  443. ->json('POST', '/api/v1/twofaccounts/import', [
  444. 'uri' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI,
  445. ])
  446. ->assertOk()
  447. ->assertJsonCount(2, $key = null)
  448. ->assertJsonFragment([
  449. 'id' => 0,
  450. 'service' => OtpTestData::SERVICE,
  451. 'account' => OtpTestData::ACCOUNT,
  452. 'otp_type' => 'totp',
  453. 'secret' => OtpTestData::SECRET,
  454. 'digits' => OtpTestData::DIGITS_DEFAULT,
  455. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  456. 'period' => OtpTestData::PERIOD_DEFAULT,
  457. 'counter' => null
  458. ])
  459. ->assertJsonFragment([
  460. 'id' => 0,
  461. 'service' => OtpTestData::SERVICE . '_bis',
  462. 'account' => OtpTestData::ACCOUNT . '_bis',
  463. 'otp_type' => 'totp',
  464. 'secret' => OtpTestData::SECRET,
  465. 'digits' => OtpTestData::DIGITS_DEFAULT,
  466. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  467. 'period' => OtpTestData::PERIOD_DEFAULT,
  468. 'counter' => null
  469. ]);
  470. }
  471. /**
  472. * @test
  473. */
  474. public function test_import_with_invalid_uri_returns_validation_error()
  475. {
  476. $response = $this->actingAs($this->user, 'api-guard')
  477. ->json('POST', '/api/v1/twofaccounts', [
  478. 'uri' => OtpTestData::INVALID_GOOGLE_AUTH_MIGRATION_URI,
  479. ])
  480. ->assertStatus(422);
  481. }
  482. /**
  483. * @test
  484. */
  485. public function test_import_gauth_data_with_duplicates_returns_negative_ids()
  486. {
  487. $twofaccount = TwoFAccount::factory()->create([
  488. 'otp_type' => 'totp',
  489. 'account' => OtpTestData::ACCOUNT,
  490. 'service' => OtpTestData::SERVICE,
  491. 'secret' => OtpTestData::SECRET,
  492. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  493. 'digits' => OtpTestData::DIGITS_DEFAULT,
  494. 'period' => OtpTestData::PERIOD_DEFAULT,
  495. 'legacy_uri' => OtpTestData::TOTP_SHORT_URI,
  496. 'icon' => '',
  497. ]);
  498. $response = $this->actingAs($this->user, 'api-guard')
  499. ->json('POST', '/api/v1/twofaccounts/import', [
  500. 'uri' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI,
  501. ])
  502. ->assertOk()
  503. ->assertJsonFragment([
  504. 'id' => -1,
  505. 'service' => OtpTestData::SERVICE,
  506. 'account' => OtpTestData::ACCOUNT,
  507. ]);
  508. }
  509. /**
  510. * @test
  511. */
  512. public function test_import_invalid_gauth_data_returns_bad_request()
  513. {
  514. $response = $this->actingAs($this->user, 'api-guard')
  515. ->json('POST', '/api/v1/twofaccounts/import', [
  516. 'uri' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA,
  517. ])
  518. ->assertStatus(400)
  519. ->assertJsonStructure([
  520. 'message'
  521. ]);
  522. }
  523. /**
  524. * @test
  525. */
  526. public function test_reorder_returns_success()
  527. {
  528. TwoFAccount::factory()->count(3)->create();
  529. $response = $this->actingAs($this->user, 'api-guard')
  530. ->json('POST', '/api/v1/twofaccounts/reorder', [
  531. 'orderedIds' => [3,2,1]])
  532. ->assertStatus(200)
  533. ->assertJsonStructure([
  534. 'message'
  535. ]);
  536. }
  537. /**
  538. * @test
  539. */
  540. public function test_reorder_with_invalid_data_returns_validation_error()
  541. {
  542. TwoFAccount::factory()->count(3)->create();
  543. $response = $this->actingAs($this->user, 'api-guard')
  544. ->json('POST', '/api/v1/twofaccounts/reorder', [
  545. 'orderedIds' => '3,2,1'])
  546. ->assertStatus(422);
  547. }
  548. /**
  549. * @test
  550. */
  551. public function test_preview_returns_success_with_resource()
  552. {
  553. $response = $this->actingAs($this->user, 'api-guard')
  554. ->json('POST', '/api/v1/twofaccounts/preview', [
  555. 'uri' => OtpTestData::TOTP_FULL_CUSTOM_URI,
  556. ])
  557. ->assertOk()
  558. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
  559. }
  560. /**
  561. * @test
  562. */
  563. public function test_preview_with_invalid_data_returns_validation_error()
  564. {
  565. $response = $this->actingAs($this->user, 'api-guard')
  566. ->json('POST', '/api/v1/twofaccounts/preview', [
  567. 'uri' => OtpTestData::INVALID_OTPAUTH_URI,
  568. ])
  569. ->assertStatus(422);
  570. }
  571. /**
  572. * @test
  573. */
  574. public function test_preview_with_unreachable_image_returns_success()
  575. {
  576. $response = $this->actingAs($this->user, 'api-guard')
  577. ->json('POST', '/api/v1/twofaccounts/preview', [
  578. 'uri' => OtpTestData::TOTP_URI_WITH_UNREACHABLE_IMAGE,
  579. ])
  580. ->assertOk()
  581. ->assertJsonFragment([
  582. 'icon' => null
  583. ]);
  584. }
  585. /**
  586. * @test
  587. */
  588. public function test_get_otp_using_totp_twofaccount_id_returns_consistent_resource()
  589. {
  590. $twofaccount = TwoFAccount::factory()->create([
  591. 'otp_type' => 'totp',
  592. 'account' => OtpTestData::ACCOUNT,
  593. 'service' => OtpTestData::SERVICE,
  594. 'secret' => OtpTestData::SECRET,
  595. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  596. 'digits' => OtpTestData::DIGITS_DEFAULT,
  597. 'period' => OtpTestData::PERIOD_DEFAULT,
  598. 'legacy_uri' => OtpTestData::TOTP_SHORT_URI,
  599. 'icon' => '',
  600. ]);
  601. $response = $this->actingAs($this->user, 'api-guard')
  602. ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '/otp')
  603. ->assertOk()
  604. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP)
  605. ->assertJsonFragment([
  606. 'otp_type' => 'totp',
  607. 'period' => OtpTestData::PERIOD_DEFAULT,
  608. ]);
  609. }
  610. /**
  611. * @test
  612. */
  613. public function test_get_otp_by_posting_totp_uri_returns_consistent_resource()
  614. {
  615. $response = $this->actingAs($this->user, 'api-guard')
  616. ->json('POST', '/api/v1/twofaccounts/otp', [
  617. 'uri' => OtpTestData::TOTP_FULL_CUSTOM_URI,
  618. ])
  619. ->assertOk()
  620. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP)
  621. ->assertJsonFragment([
  622. 'otp_type' => 'totp',
  623. 'period' => OtpTestData::PERIOD_CUSTOM,
  624. ]);
  625. }
  626. /**
  627. * @test
  628. */
  629. public function test_get_otp_by_posting_totp_parameters_returns_consistent_resource()
  630. {
  631. $response = $this->actingAs($this->user, 'api-guard')
  632. ->json('POST', '/api/v1/twofaccounts/otp', OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP)
  633. ->assertOk()
  634. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP)
  635. ->assertJsonFragment([
  636. 'otp_type' => 'totp',
  637. 'period' => OtpTestData::PERIOD_CUSTOM,
  638. ]);
  639. }
  640. /**
  641. * @test
  642. */
  643. public function test_get_otp_using_hotp_twofaccount_id_returns_consistent_resource()
  644. {
  645. $twofaccount = TwoFAccount::factory()->create([
  646. 'otp_type' => 'hotp',
  647. 'account' => OtpTestData::ACCOUNT,
  648. 'service' => OtpTestData::SERVICE,
  649. 'secret' => OtpTestData::SECRET,
  650. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  651. 'digits' => OtpTestData::DIGITS_DEFAULT,
  652. 'period' => null,
  653. 'legacy_uri' => OtpTestData::HOTP_SHORT_URI,
  654. 'icon' => '',
  655. ]);
  656. $response = $this->actingAs($this->user, 'api-guard')
  657. ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '/otp')
  658. ->assertOk()
  659. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP)
  660. ->assertJsonFragment([
  661. 'otp_type' => 'hotp',
  662. 'counter' => OtpTestData::COUNTER_DEFAULT + 1,
  663. ]);
  664. }
  665. /**
  666. * @test
  667. */
  668. public function test_get_otp_by_posting_hotp_uri_returns_consistent_resource()
  669. {
  670. $response = $this->actingAs($this->user, 'api-guard')
  671. ->json('POST', '/api/v1/twofaccounts/otp', [
  672. 'uri' => OtpTestData::HOTP_FULL_CUSTOM_URI,
  673. ])
  674. ->assertOk()
  675. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP)
  676. ->assertJsonFragment([
  677. 'otp_type' => 'hotp',
  678. 'counter' => OtpTestData::COUNTER_CUSTOM + 1,
  679. ]);
  680. }
  681. /**
  682. * @test
  683. */
  684. public function test_get_otp_by_posting_hotp_parameters_returns_consistent_resource()
  685. {
  686. $response = $this->actingAs($this->user, 'api-guard')
  687. ->json('POST', '/api/v1/twofaccounts/otp', OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP)
  688. ->assertOk()
  689. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP)
  690. ->assertJsonFragment([
  691. 'otp_type' => 'hotp',
  692. 'counter' => OtpTestData::COUNTER_CUSTOM + 1,
  693. ]);
  694. }
  695. /**
  696. * @test
  697. */
  698. public function test_get_otp_by_posting_multiple_inputs_returns_bad_request()
  699. {
  700. $response = $this->actingAs($this->user, 'api-guard')
  701. ->json('POST', '/api/v1/twofaccounts/otp', [
  702. 'uri' => OtpTestData::HOTP_FULL_CUSTOM_URI,
  703. 'key' => 'value',
  704. ])
  705. ->assertStatus(400)
  706. ->assertJsonStructure([
  707. 'message',
  708. 'reason',
  709. ]);
  710. }
  711. /**
  712. * @test
  713. */
  714. public function test_get_otp_using_indecipherable_twofaccount_id_returns_bad_request()
  715. {
  716. Settings::set('useEncryption', true);
  717. $twofaccount = TwoFAccount::factory()->create();
  718. DB::table('twofaccounts')
  719. ->where('id', $twofaccount->id)
  720. ->update([
  721. 'secret' => '**encrypted**',
  722. ]);
  723. $response = $this->actingAs($this->user, 'api-guard')
  724. ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '/otp')
  725. ->assertStatus(400)
  726. ->assertJsonStructure([
  727. 'message',
  728. ]);
  729. }
  730. /**
  731. * @test
  732. */
  733. public function test_get_otp_using_missing_twofaccount_id_returns_not_found()
  734. {
  735. $response = $this->actingAs($this->user, 'api-guard')
  736. ->json('GET', '/api/v1/twofaccounts/1000/otp')
  737. ->assertNotFound();
  738. }
  739. /**
  740. * @test
  741. */
  742. public function test_get_otp_by_posting_invalid_uri_returns_validation_error()
  743. {
  744. $response = $this->actingAs($this->user, 'api-guard')
  745. ->json('POST', '/api/v1/twofaccounts/otp', [
  746. 'uri' => OtpTestData::INVALID_OTPAUTH_URI,
  747. ])
  748. ->assertStatus(422);
  749. }
  750. /**
  751. * @test
  752. */
  753. public function test_get_otp_by_posting_invalid_parameters_returns_validation_error()
  754. {
  755. $response = $this->actingAs($this->user, 'api-guard')
  756. ->json('POST', '/api/v1/twofaccounts/otp', self::ARRAY_OF_INVALID_PARAMETERS)
  757. ->assertStatus(422);
  758. }
  759. /**
  760. * @test
  761. */
  762. public function test_count_returns_right_number_of_twofaccount()
  763. {
  764. TwoFAccount::factory()->count(3)->create();
  765. $response = $this->actingAs($this->user, 'api-guard')
  766. ->json('GET', '/api/v1/twofaccounts/count')
  767. ->assertStatus(200)
  768. ->assertExactJson([
  769. 'count' => 3
  770. ]);
  771. }
  772. /**
  773. * @test
  774. */
  775. public function test_withdraw_returns_success()
  776. {
  777. TwoFAccount::factory()->count(3)->create();
  778. $ids = DB::table('twofaccounts')->pluck('id')->implode(',');
  779. $response = $this->actingAs($this->user, 'api-guard')
  780. ->json('PATCH', '/api/v1/twofaccounts/withdraw?ids=1,2,3' . $ids)
  781. ->assertOk()
  782. ->assertJsonStructure([
  783. 'message',
  784. ]);
  785. }
  786. /**
  787. * @test
  788. */
  789. public function test_withdraw_too_many_ids_returns_bad_request()
  790. {
  791. TwoFAccount::factory()->count(102)->create();
  792. $ids = DB::table('twofaccounts')->pluck('id')->implode(',');
  793. $response = $this->actingAs($this->user, 'api-guard')
  794. ->json('PATCH', '/api/v1/twofaccounts/withdraw?ids=' . $ids)
  795. ->assertStatus(400)
  796. ->assertJsonStructure([
  797. 'message',
  798. 'reason',
  799. ]);
  800. }
  801. /**
  802. * @test
  803. */
  804. public function test_destroy_twofaccount_returns_success()
  805. {
  806. $twofaccount = TwoFAccount::factory()->create();
  807. $response = $this->actingAs($this->user, 'api-guard')
  808. ->json('DELETE', '/api/v1/twofaccounts/' . $twofaccount->id)
  809. ->assertNoContent();
  810. }
  811. /**
  812. * @test
  813. */
  814. public function test_destroy_missing_twofaccount_returns_not_found()
  815. {
  816. $twofaccount = TwoFAccount::factory()->create();
  817. $response = $this->actingAs($this->user, 'api-guard')
  818. ->json('DELETE', '/api/v1/twofaccounts/1000')
  819. ->assertNotFound();
  820. }
  821. /**
  822. * @test
  823. */
  824. public function test_batch_destroy_twofaccount_returns_success()
  825. {
  826. TwoFAccount::factory()->count(3)->create();
  827. $ids = DB::table('twofaccounts')->pluck('id')->implode(',');
  828. $response = $this->actingAs($this->user, 'api-guard')
  829. ->json('DELETE', '/api/v1/twofaccounts?ids=' . $ids)
  830. ->assertNoContent();
  831. }
  832. /**
  833. * @test
  834. */
  835. public function test_batch_destroy_too_many_twofaccounts_returns_bad_request()
  836. {
  837. TwoFAccount::factory()->count(102)->create();
  838. $ids = DB::table('twofaccounts')->pluck('id')->implode(',');
  839. $response = $this->actingAs($this->user, 'api-guard')
  840. ->json('DELETE', '/api/v1/twofaccounts?ids=' . $ids)
  841. ->assertStatus(400)
  842. ->assertJsonStructure([
  843. 'message',
  844. 'reason',
  845. ]);
  846. }
  847. }