TwoFAccountControllerTest.php 25 KB

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