TwoFAccountControllerTest.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  1. <?php
  2. namespace Tests\Api\v1\Controllers;
  3. use App\Models\User;
  4. use App\Models\Group;
  5. use Tests\FeatureTestCase;
  6. use Tests\Classes\OtpTestData;
  7. use App\Models\TwoFAccount;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Support\Facades\Storage;
  10. use Illuminate\Http\UploadedFile;
  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. $settingService = resolve('App\Services\SettingService');
  341. $settingService->set('defaultGroup', $this->group->id);
  342. $response = $this->actingAs($this->user, 'api-guard')
  343. ->json('POST', '/api/v1/twofaccounts', [
  344. 'uri' => OtpTestData::TOTP_SHORT_URI,
  345. ])
  346. ->assertJsonFragment([
  347. 'group_id' => $this->group->id
  348. ]);
  349. }
  350. /**
  351. * @test
  352. */
  353. public function test_store_assigns_created_account_when_default_group_is_the_active_one()
  354. {
  355. $settingService = resolve('App\Services\SettingService');
  356. // Set the default group to be the active one
  357. $settingService->set('defaultGroup', -1);
  358. // Set the active group
  359. $settingService->set('activeGroup', $this->group->id);
  360. $response = $this->actingAs($this->user, 'api-guard')
  361. ->json('POST', '/api/v1/twofaccounts', [
  362. 'uri' => OtpTestData::TOTP_SHORT_URI,
  363. ])
  364. ->assertJsonFragment([
  365. 'group_id' => $this->group->id
  366. ]);
  367. }
  368. /**
  369. * @test
  370. */
  371. public function test_store_assigns_created_account_when_default_group_is_no_group()
  372. {
  373. $settingService = resolve('App\Services\SettingService');
  374. // Set the default group to No group
  375. $settingService->set('defaultGroup', 0);
  376. $response = $this->actingAs($this->user, 'api-guard')
  377. ->json('POST', '/api/v1/twofaccounts', [
  378. 'uri' => OtpTestData::TOTP_SHORT_URI,
  379. ])
  380. ->assertJsonFragment([
  381. 'group_id' => null
  382. ]);
  383. }
  384. /**
  385. * @test
  386. */
  387. public function test_store_assigns_created_account_when_default_group_does_not_exist()
  388. {
  389. $settingService = resolve('App\Services\SettingService');
  390. // Set the default group to a non-existing one
  391. $settingService->set('defaultGroup', 1000);
  392. $response = $this->actingAs($this->user, 'api-guard')
  393. ->json('POST', '/api/v1/twofaccounts', [
  394. 'uri' => OtpTestData::TOTP_SHORT_URI,
  395. ])
  396. ->assertJsonFragment([
  397. 'group_id' => null
  398. ]);
  399. }
  400. /**
  401. * @test
  402. */
  403. public function test_update_totp_returns_success_with_updated_resource()
  404. {
  405. $twofaccount = TwoFAccount::factory()->create();
  406. $response = $this->actingAs($this->user, 'api-guard')
  407. ->json('PUT', '/api/v1/twofaccounts/' . $twofaccount->id, OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP)
  408. ->assertOk()
  409. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
  410. }
  411. /**
  412. * @test
  413. */
  414. public function test_update_hotp_returns_success_with_updated_resource()
  415. {
  416. $twofaccount = TwoFAccount::factory()->create();
  417. $response = $this->actingAs($this->user, 'api-guard')
  418. ->json('PUT', '/api/v1/twofaccounts/' . $twofaccount->id, OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP)
  419. ->assertOk()
  420. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_HOTP);
  421. }
  422. /**
  423. * @test
  424. */
  425. public function test_update_missing_twofaccount_returns_not_found()
  426. {
  427. $response = $this->actingAs($this->user, 'api-guard')
  428. ->json('PUT', '/api/v1/twofaccounts/1000', OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP)
  429. ->assertNotFound();
  430. }
  431. /**
  432. * @test
  433. */
  434. public function test_update_twofaccount_with_invalid_data_returns_validation_error()
  435. {
  436. $twofaccount = TwoFAccount::factory()->create();
  437. $response = $this->actingAs($this->user, 'api-guard')
  438. ->json('PUT', '/api/v1/twofaccounts/' . $twofaccount->id, self::ARRAY_OF_INVALID_PARAMETERS)
  439. ->assertStatus(422);
  440. }
  441. /**
  442. * @test
  443. */
  444. public function test_import_valid_gauth_data_returns_success_with_consistent_resources()
  445. {
  446. $response = $this->actingAs($this->user, 'api-guard')
  447. ->json('POST', '/api/v1/twofaccounts/import', [
  448. 'uri' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI,
  449. ])
  450. ->assertOk()
  451. ->assertJsonCount(2, $key = null)
  452. ->assertJsonFragment([
  453. 'id' => 0,
  454. 'service' => OtpTestData::SERVICE,
  455. 'account' => OtpTestData::ACCOUNT,
  456. 'otp_type' => 'totp',
  457. 'secret' => OtpTestData::SECRET,
  458. 'digits' => OtpTestData::DIGITS_DEFAULT,
  459. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  460. 'period' => OtpTestData::PERIOD_DEFAULT,
  461. 'counter' => null
  462. ])
  463. ->assertJsonFragment([
  464. 'id' => 0,
  465. 'service' => OtpTestData::SERVICE . '_bis',
  466. 'account' => OtpTestData::ACCOUNT . '_bis',
  467. 'otp_type' => 'totp',
  468. 'secret' => OtpTestData::SECRET,
  469. 'digits' => OtpTestData::DIGITS_DEFAULT,
  470. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  471. 'period' => OtpTestData::PERIOD_DEFAULT,
  472. 'counter' => null
  473. ]);
  474. }
  475. /**
  476. * @test
  477. */
  478. public function test_import_with_invalid_uri_returns_validation_error()
  479. {
  480. $response = $this->actingAs($this->user, 'api-guard')
  481. ->json('POST', '/api/v1/twofaccounts', [
  482. 'uri' => OtpTestData::INVALID_GOOGLE_AUTH_MIGRATION_URI,
  483. ])
  484. ->assertStatus(422);
  485. }
  486. /**
  487. * @test
  488. */
  489. public function test_import_gauth_data_with_duplicates_returns_negative_ids()
  490. {
  491. $twofaccount = TwoFAccount::factory()->create([
  492. 'otp_type' => 'totp',
  493. 'account' => OtpTestData::ACCOUNT,
  494. 'service' => OtpTestData::SERVICE,
  495. 'secret' => OtpTestData::SECRET,
  496. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  497. 'digits' => OtpTestData::DIGITS_DEFAULT,
  498. 'period' => OtpTestData::PERIOD_DEFAULT,
  499. 'legacy_uri' => OtpTestData::TOTP_SHORT_URI,
  500. 'icon' => '',
  501. ]);
  502. $response = $this->actingAs($this->user, 'api-guard')
  503. ->json('POST', '/api/v1/twofaccounts/import', [
  504. 'uri' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI,
  505. ])
  506. ->assertOk()
  507. ->assertJsonFragment([
  508. 'id' => -1,
  509. 'service' => OtpTestData::SERVICE,
  510. 'account' => OtpTestData::ACCOUNT,
  511. ]);
  512. }
  513. /**
  514. * @test
  515. */
  516. public function test_import_invalid_gauth_data_returns_bad_request()
  517. {
  518. $response = $this->actingAs($this->user, 'api-guard')
  519. ->json('POST', '/api/v1/twofaccounts/import', [
  520. 'uri' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI_WITH_INVALID_DATA,
  521. ])
  522. ->assertStatus(400)
  523. ->assertJsonStructure([
  524. 'message'
  525. ]);
  526. }
  527. /**
  528. * @test
  529. */
  530. public function test_reorder_returns_success()
  531. {
  532. TwoFAccount::factory()->count(3)->create();
  533. $response = $this->actingAs($this->user, 'api-guard')
  534. ->json('POST', '/api/v1/twofaccounts/reorder', [
  535. 'orderedIds' => [3,2,1]])
  536. ->assertStatus(200)
  537. ->assertJsonStructure([
  538. 'message'
  539. ]);
  540. }
  541. /**
  542. * @test
  543. */
  544. public function test_reorder_with_invalid_data_returns_validation_error()
  545. {
  546. TwoFAccount::factory()->count(3)->create();
  547. $response = $this->actingAs($this->user, 'api-guard')
  548. ->json('POST', '/api/v1/twofaccounts/reorder', [
  549. 'orderedIds' => '3,2,1'])
  550. ->assertStatus(422);
  551. }
  552. /**
  553. * @test
  554. */
  555. public function test_preview_returns_success_with_resource()
  556. {
  557. $response = $this->actingAs($this->user, 'api-guard')
  558. ->json('POST', '/api/v1/twofaccounts/preview', [
  559. 'uri' => OtpTestData::TOTP_FULL_CUSTOM_URI,
  560. ])
  561. ->assertOk()
  562. ->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
  563. }
  564. /**
  565. * @test
  566. */
  567. public function test_preview_with_invalid_data_returns_validation_error()
  568. {
  569. $response = $this->actingAs($this->user, 'api-guard')
  570. ->json('POST', '/api/v1/twofaccounts/preview', [
  571. 'uri' => OtpTestData::INVALID_OTPAUTH_URI,
  572. ])
  573. ->assertStatus(422);
  574. }
  575. /**
  576. * @test
  577. */
  578. public function test_preview_with_unreachable_image_returns_success()
  579. {
  580. $response = $this->actingAs($this->user, 'api-guard')
  581. ->json('POST', '/api/v1/twofaccounts/preview', [
  582. 'uri' => OtpTestData::TOTP_URI_WITH_UNREACHABLE_IMAGE,
  583. ])
  584. ->assertOk()
  585. ->assertJsonFragment([
  586. 'icon' => null
  587. ]);
  588. }
  589. /**
  590. * @test
  591. */
  592. public function test_get_otp_using_totp_twofaccount_id_returns_consistent_resource()
  593. {
  594. $twofaccount = TwoFAccount::factory()->create([
  595. 'otp_type' => 'totp',
  596. 'account' => OtpTestData::ACCOUNT,
  597. 'service' => OtpTestData::SERVICE,
  598. 'secret' => OtpTestData::SECRET,
  599. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  600. 'digits' => OtpTestData::DIGITS_DEFAULT,
  601. 'period' => OtpTestData::PERIOD_DEFAULT,
  602. 'legacy_uri' => OtpTestData::TOTP_SHORT_URI,
  603. 'icon' => '',
  604. ]);
  605. $response = $this->actingAs($this->user, 'api-guard')
  606. ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '/otp')
  607. ->assertOk()
  608. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP)
  609. ->assertJsonFragment([
  610. 'otp_type' => 'totp',
  611. 'period' => OtpTestData::PERIOD_DEFAULT,
  612. ]);
  613. }
  614. /**
  615. * @test
  616. */
  617. public function test_get_otp_by_posting_totp_uri_returns_consistent_resource()
  618. {
  619. $response = $this->actingAs($this->user, 'api-guard')
  620. ->json('POST', '/api/v1/twofaccounts/otp', [
  621. 'uri' => OtpTestData::TOTP_FULL_CUSTOM_URI,
  622. ])
  623. ->assertOk()
  624. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP)
  625. ->assertJsonFragment([
  626. 'otp_type' => 'totp',
  627. 'period' => OtpTestData::PERIOD_CUSTOM,
  628. ]);
  629. }
  630. /**
  631. * @test
  632. */
  633. public function test_get_otp_by_posting_totp_parameters_returns_consistent_resource()
  634. {
  635. $response = $this->actingAs($this->user, 'api-guard')
  636. ->json('POST', '/api/v1/twofaccounts/otp', OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP)
  637. ->assertOk()
  638. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP)
  639. ->assertJsonFragment([
  640. 'otp_type' => 'totp',
  641. 'period' => OtpTestData::PERIOD_CUSTOM,
  642. ]);
  643. }
  644. /**
  645. * @test
  646. */
  647. public function test_get_otp_using_hotp_twofaccount_id_returns_consistent_resource()
  648. {
  649. $twofaccount = TwoFAccount::factory()->create([
  650. 'otp_type' => 'hotp',
  651. 'account' => OtpTestData::ACCOUNT,
  652. 'service' => OtpTestData::SERVICE,
  653. 'secret' => OtpTestData::SECRET,
  654. 'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
  655. 'digits' => OtpTestData::DIGITS_DEFAULT,
  656. 'period' => null,
  657. 'legacy_uri' => OtpTestData::HOTP_SHORT_URI,
  658. 'icon' => '',
  659. ]);
  660. $response = $this->actingAs($this->user, 'api-guard')
  661. ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '/otp')
  662. ->assertOk()
  663. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP)
  664. ->assertJsonFragment([
  665. 'otp_type' => 'hotp',
  666. 'counter' => OtpTestData::COUNTER_DEFAULT + 1,
  667. ]);
  668. }
  669. /**
  670. * @test
  671. */
  672. public function test_get_otp_by_posting_hotp_uri_returns_consistent_resource()
  673. {
  674. $response = $this->actingAs($this->user, 'api-guard')
  675. ->json('POST', '/api/v1/twofaccounts/otp', [
  676. 'uri' => OtpTestData::HOTP_FULL_CUSTOM_URI,
  677. ])
  678. ->assertOk()
  679. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP)
  680. ->assertJsonFragment([
  681. 'otp_type' => 'hotp',
  682. 'counter' => OtpTestData::COUNTER_CUSTOM + 1,
  683. ]);
  684. }
  685. /**
  686. * @test
  687. */
  688. public function test_get_otp_by_posting_hotp_parameters_returns_consistent_resource()
  689. {
  690. $response = $this->actingAs($this->user, 'api-guard')
  691. ->json('POST', '/api/v1/twofaccounts/otp', OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP)
  692. ->assertOk()
  693. ->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP)
  694. ->assertJsonFragment([
  695. 'otp_type' => 'hotp',
  696. 'counter' => OtpTestData::COUNTER_CUSTOM + 1,
  697. ]);
  698. }
  699. /**
  700. * @test
  701. */
  702. public function test_get_otp_by_posting_multiple_inputs_returns_bad_request()
  703. {
  704. $response = $this->actingAs($this->user, 'api-guard')
  705. ->json('POST', '/api/v1/twofaccounts/otp', [
  706. 'uri' => OtpTestData::HOTP_FULL_CUSTOM_URI,
  707. 'key' => 'value',
  708. ])
  709. ->assertStatus(400)
  710. ->assertJsonStructure([
  711. 'message',
  712. 'reason',
  713. ]);
  714. }
  715. /**
  716. * @test
  717. */
  718. public function test_get_otp_using_indecipherable_twofaccount_id_returns_bad_request()
  719. {
  720. $settingService = resolve('App\Services\SettingService');
  721. $settingService->set('useEncryption', true);
  722. $twofaccount = TwoFAccount::factory()->create();
  723. DB::table('twofaccounts')
  724. ->where('id', $twofaccount->id)
  725. ->update([
  726. 'secret' => '**encrypted**',
  727. ]);
  728. $response = $this->actingAs($this->user, 'api-guard')
  729. ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '/otp')
  730. ->assertStatus(400)
  731. ->assertJsonStructure([
  732. 'message',
  733. ]);
  734. }
  735. /**
  736. * @test
  737. */
  738. public function test_get_otp_using_missing_twofaccount_id_returns_not_found()
  739. {
  740. $response = $this->actingAs($this->user, 'api-guard')
  741. ->json('GET', '/api/v1/twofaccounts/1000/otp')
  742. ->assertNotFound();
  743. }
  744. /**
  745. * @test
  746. */
  747. public function test_get_otp_by_posting_invalid_uri_returns_validation_error()
  748. {
  749. $response = $this->actingAs($this->user, 'api-guard')
  750. ->json('POST', '/api/v1/twofaccounts/otp', [
  751. 'uri' => OtpTestData::INVALID_OTPAUTH_URI,
  752. ])
  753. ->assertStatus(422);
  754. }
  755. /**
  756. * @test
  757. */
  758. public function test_get_otp_by_posting_invalid_parameters_returns_validation_error()
  759. {
  760. $response = $this->actingAs($this->user, 'api-guard')
  761. ->json('POST', '/api/v1/twofaccounts/otp', self::ARRAY_OF_INVALID_PARAMETERS)
  762. ->assertStatus(422);
  763. }
  764. /**
  765. * @test
  766. */
  767. public function test_count_returns_right_number_of_twofaccount()
  768. {
  769. TwoFAccount::factory()->count(3)->create();
  770. $response = $this->actingAs($this->user, 'api-guard')
  771. ->json('GET', '/api/v1/twofaccounts/count')
  772. ->assertStatus(200)
  773. ->assertExactJson([
  774. 'count' => 3
  775. ]);
  776. }
  777. /**
  778. * @test
  779. */
  780. public function test_withdraw_returns_success()
  781. {
  782. TwoFAccount::factory()->count(3)->create();
  783. $ids = DB::table('twofaccounts')->pluck('id')->implode(',');
  784. $response = $this->actingAs($this->user, 'api-guard')
  785. ->json('PATCH', '/api/v1/twofaccounts/withdraw?ids=1,2,3' . $ids)
  786. ->assertOk()
  787. ->assertJsonStructure([
  788. 'message',
  789. ]);
  790. }
  791. /**
  792. * @test
  793. */
  794. public function test_withdraw_too_many_ids_returns_bad_request()
  795. {
  796. TwoFAccount::factory()->count(102)->create();
  797. $ids = DB::table('twofaccounts')->pluck('id')->implode(',');
  798. $response = $this->actingAs($this->user, 'api-guard')
  799. ->json('PATCH', '/api/v1/twofaccounts/withdraw?ids=' . $ids)
  800. ->assertStatus(400)
  801. ->assertJsonStructure([
  802. 'message',
  803. 'reason',
  804. ]);
  805. }
  806. /**
  807. * @test
  808. */
  809. public function test_destroy_twofaccount_returns_success()
  810. {
  811. $twofaccount = TwoFAccount::factory()->create();
  812. $response = $this->actingAs($this->user, 'api-guard')
  813. ->json('DELETE', '/api/v1/twofaccounts/' . $twofaccount->id)
  814. ->assertNoContent();
  815. }
  816. /**
  817. * @test
  818. */
  819. public function test_destroy_missing_twofaccount_returns_not_found()
  820. {
  821. $twofaccount = TwoFAccount::factory()->create();
  822. $response = $this->actingAs($this->user, 'api-guard')
  823. ->json('DELETE', '/api/v1/twofaccounts/1000')
  824. ->assertNotFound();
  825. }
  826. /**
  827. * @test
  828. */
  829. public function test_batch_destroy_twofaccount_returns_success()
  830. {
  831. TwoFAccount::factory()->count(3)->create();
  832. $ids = DB::table('twofaccounts')->pluck('id')->implode(',');
  833. $response = $this->actingAs($this->user, 'api-guard')
  834. ->json('DELETE', '/api/v1/twofaccounts?ids=' . $ids)
  835. ->assertNoContent();
  836. }
  837. /**
  838. * @test
  839. */
  840. public function test_batch_destroy_too_many_twofaccounts_returns_bad_request()
  841. {
  842. TwoFAccount::factory()->count(102)->create();
  843. $ids = DB::table('twofaccounts')->pluck('id')->implode(',');
  844. $response = $this->actingAs($this->user, 'api-guard')
  845. ->json('DELETE', '/api/v1/twofaccounts?ids=' . $ids)
  846. ->assertStatus(400)
  847. ->assertJsonStructure([
  848. 'message',
  849. 'reason',
  850. ]);
  851. }
  852. }