Google2FASpec.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <?php
  2. namespace spec\PragmaRX\Google2FA;
  3. use PhpSpec\ObjectBehavior;
  4. use PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException;
  5. use PragmaRX\Google2FA\Google2FA;
  6. class Google2FASpec extends ObjectBehavior
  7. {
  8. public $secret = 'ADUMJO5634NPDEKW';
  9. public $wrongSecret = 'ADUMJO5634NPDEKX';
  10. public $url = 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2FPragmaRX%3Aacr%252Bpragmarx%2540antoniocarlosribeiro.com%3Fsecret%3DADUMJO5634NPDEKW%26issuer%3DPragmaRX';
  11. public function it_is_initializable()
  12. {
  13. $this->shouldHaveType('PragmaRX\Google2FA\Google2FA');
  14. }
  15. public function it_generates_a_valid_secret_key()
  16. {
  17. $this->generateSecretKey()->shouldHaveLength(16);
  18. $this->generateSecretKey(32)->shouldHaveLength(32);
  19. $this->generateSecretKey(59, 'ant')->shouldStartWith('MFXHI');
  20. $this->generateSecretKey()->shouldBeAmongst(Google2FA::VALID_FOR_B32);
  21. }
  22. public function it_generates_a_secret_keys_compatible_with_google_authenticator_or_not()
  23. {
  24. $this->shouldThrow(new IncompatibleWithGoogleAuthenticatorException())->during('generateSecretKey', [17]);
  25. $this->setEnforceGoogleAuthenticatorCompatibility(false)->generateSecretKey(17)->shouldHaveLength(17);
  26. }
  27. public function it_gets_valid_timestamps()
  28. {
  29. $this->getTimestamp()->shouldBeValidTimestamp();
  30. }
  31. public function it_decodes_base32_strings()
  32. {
  33. $this->base32Decode($this->secret)->shouldBe(
  34. chr(0)
  35. .chr(232)
  36. .chr(196)
  37. .chr(187)
  38. .chr(190)
  39. .chr(223)
  40. .chr(26)
  41. .chr(241)
  42. .chr(145)
  43. .chr(86)
  44. );
  45. }
  46. public function it_creates_a_one_time_password()
  47. {
  48. $this->getCurrentOtp($this->secret)->shouldHaveLength(6);
  49. }
  50. public function it_verifies_keys()
  51. {
  52. // $ts 26213400 with KEY_REGENERATION 30 seconds is
  53. // timestamp 786402000, which is 1994-12-02 21:00:00 UTC
  54. $this->verifyKey($this->secret, '093183', 2, 26213400)->shouldBe(false); // 26213397
  55. $this->verifyKey($this->secret, '558854', 2, 26213400)->shouldBe(true); // 26213398
  56. $this->verifyKey($this->secret, '981084', 2, 26213400)->shouldBe(true); // 26213399
  57. $this->verifyKey($this->secret, '512396', 2, 26213400)->shouldBe(true); // 26213400
  58. $this->verifyKey($this->secret, '410272', 2, 26213400)->shouldBe(true); // 26213401
  59. $this->verifyKey($this->secret, '239815', 2, 26213400)->shouldBe(true); // 26213402
  60. $this->verifyKey($this->secret, '313366', 2, 26213400)->shouldBe(false); // 26213403
  61. }
  62. public function it_verifies_keys_newer()
  63. {
  64. $this->verifyKeyNewer($this->secret, '512396', 26213401, 2, 26213400)->shouldBe(false); // 26213400
  65. $this->verifyKeyNewer($this->secret, '410272', 26213401, 2, 26213400)->shouldBe(26213401); // 26213401
  66. $this->verifyKeyNewer($this->secret, '239815', 26213401, 2, 26213400)->shouldBe(26213402); // 26213402
  67. $this->verifyKeyNewer($this->secret, '313366', 26213401, 2, 26213400)->shouldBe(false); // 26213403
  68. }
  69. public function it_removes_invalid_chars_from_secret()
  70. {
  71. $this->removeInvalidChars($this->secret.'!1-@@@')->shouldBe($this->secret);
  72. }
  73. public function it_creates_a_qr_code()
  74. {
  75. $this->getQRCodeGoogleUrl('PragmaRX', 'acr+pragmarx@antoniocarlosribeiro.com', $this->secret)->shouldBe($this->url);
  76. }
  77. public function it_converts_to_base32()
  78. {
  79. $this->toBase32('PragmaRX')->shouldBe('KBZGCZ3NMFJFQ');
  80. }
  81. public function getMatchers()
  82. {
  83. return [
  84. 'haveLength' => function ($subject, $key) {
  85. return strlen($subject) == $key;
  86. },
  87. 'shouldStartWith' => function ($subject, $key) {
  88. return substr($key, 0, strlen($subject)) == $subject;
  89. },
  90. 'beAmongst' => function ($subject, $key) {
  91. return preg_replace('/[^'.$key.']/', '', $subject) === $subject;
  92. },
  93. 'beValidTimestamp' => function ($timestamp) {
  94. return is_float($timestamp)
  95. && ($timestamp <= PHP_INT_MAX)
  96. && ($timestamp >= ~PHP_INT_MAX);
  97. },
  98. ];
  99. }
  100. public function it_sets_the_window()
  101. {
  102. $this->setWindow(6);
  103. $this->getWindow()->shouldBe(6);
  104. $this->getWindow(1)->shouldBe(1);
  105. $this->setWindow(0);
  106. $this->verifyKey($this->secret, '558854', null, 26213400)->shouldBe(false);
  107. $this->setWindow(2);
  108. $this->verifyKey($this->secret, '558854', null, 26213400)->shouldBe(true);
  109. $this->verifyKey($this->secret, '558854', null, 26213399)->shouldBe(true);
  110. $this->verifyKey($this->secret, '558854', null, 26213398)->shouldBe(true);
  111. $this->verifyKey($this->secret, '558854', null, 26213396)->shouldBe(true);
  112. $this->verifyKey($this->secret, '558854', null, 26213395)->shouldBe(false);
  113. }
  114. public function it_sets_the_secret()
  115. {
  116. $this->verify('558854', $this->wrongSecret)->shouldBe(false);
  117. $this->setWindow(2);
  118. $this->verify('558854', $this->secret, null, 26213400)->shouldBe(true);
  119. $this->setSecret($this->secret);
  120. $this->verify('558854', null, null, 26213400)->shouldBe(true);
  121. }
  122. public function it_gets_key_regeneration()
  123. {
  124. $this->setKeyRegeneration(11);
  125. $this->getKeyRegeneration()->shouldBe(11);
  126. }
  127. public function it_gets_otp_length()
  128. {
  129. $this->setOneTimePasswordLength(7);
  130. $this->getOneTimePasswordLength()->shouldBe(7);
  131. }
  132. public function it_generates_passwords_in_many_different_sizes()
  133. {
  134. $this->setWindow(2);
  135. $this->setOneTimePasswordLength(6);
  136. $this->verifyKey($this->secret, '558854', null, 26213400)->shouldBe(true);
  137. $this->setOneTimePasswordLength(7);
  138. $this->verifyKey($this->secret, '8981084', null, 26213400)->shouldBe(true);
  139. }
  140. }