44
55
66use CirclicalUser \Exception \PersistedUserRequiredException ;
7+ use CirclicalUser \Exception \WeakPasswordException ;
78use CirclicalUser \Provider \AuthenticationProviderInterface ;
89use CirclicalUser \Provider \AuthenticationRecordInterface ;
10+ use CirclicalUser \Provider \PasswordCheckerInterface ;
911use CirclicalUser \Provider \UserInterface as User ;
1012use CirclicalUser \Exception \BadPasswordException ;
1113use CirclicalUser \Exception \EmailUsernameTakenException ;
@@ -82,6 +84,11 @@ class AuthenticationService
8284 private $ secure ;
8385
8486
87+ /**
88+ * @var PasswordCheckerInterface
89+ */
90+ private $ passwordChecker ;
91+
8592 /**
8693 * AuthenticationService constructor.
8794 *
@@ -90,15 +97,16 @@ class AuthenticationService
9097 * @param string $systemEncryptionKey The raw material of a Halite-generated encryption key, stored in config.
9198 * @param bool $transient True if cookies should expire at the end of the session (zero value, for expiry)
9299 * @param bool $secure True if cookies should be marked as 'Secure', enforced as 'true' in production by this service's Factory
100+ * @param PasswordCheckerInterface $passwordChecker Optional, a password checker implementation
93101 */
94- public function __construct (AuthenticationProviderInterface $ authenticationProvider , UserProviderInterface $ userProvider , $ systemEncryptionKey , $ transient , $ secure )
102+ public function __construct (AuthenticationProviderInterface $ authenticationProvider , UserProviderInterface $ userProvider , string $ systemEncryptionKey , bool $ transient , bool $ secure, $ passwordChecker = null )
95103 {
96104 $ this ->authenticationProvider = $ authenticationProvider ;
97105 $ this ->userProvider = $ userProvider ;
98106 $ this ->systemEncryptionKey = $ systemEncryptionKey ;
99107 $ this ->transient = $ transient ;
100108 $ this ->secure = $ secure ;
101- $ this ->identity = null ;
109+ $ this ->passwordChecker = $ passwordChecker ;
102110 }
103111
104112 /**
@@ -107,7 +115,7 @@ public function __construct(AuthenticationProviderInterface $authenticationProvi
107115 */
108116 public function hasIdentity (): bool
109117 {
110- return $ this ->getIdentity () != null ;
118+ return $ this ->getIdentity () !== null ;
111119 }
112120
113121 /**
@@ -133,7 +141,7 @@ private function setIdentity(User $user)
133141 * @throws BadPasswordException Thrown when the password doesn't work
134142 * @throws NoSuchUserException Thrown when the user can't be identified
135143 */
136- public function authenticate ($ username , $ password ): User
144+ public function authenticate (string $ username , string $ password ): User
137145 {
138146 $ auth = $ this ->authenticationProvider ->findByUsername ($ username );
139147 $ user = null ;
@@ -185,7 +193,7 @@ public function authenticate($username, $password): User
185193 * @throws NoSuchUserException Thrown when the user's authentication records couldn't be found
186194 * @throws UsernameTakenException
187195 */
188- public function changeUsername (User $ user , $ newUsername ): AuthenticationRecordInterface
196+ public function changeUsername (User $ user , string $ newUsername ): AuthenticationRecordInterface
189197 {
190198 /** @var AuthenticationRecordInterface $auth */
191199 $ auth = $ this ->authenticationProvider ->findByUserId ($ user ->getId ());
@@ -220,7 +228,7 @@ private function setSessionCookies(AuthenticationRecordInterface $authentication
220228 $ systemKey = new EncryptionKey ($ this ->systemEncryptionKey );
221229 $ userKey = new EncryptionKey ($ authentication ->getSessionKey ());
222230 $ hashCookieName = hash_hmac ('sha256 ' , $ authentication ->getSessionKey () . $ authentication ->getUsername (), $ systemKey );
223- $ userTuple = base64_encode (Crypto::encrypt ($ authentication ->getUserId () . " : " . $ hashCookieName , $ systemKey ));
231+ $ userTuple = base64_encode (Crypto::encrypt ($ authentication ->getUserId () . ' : ' . $ hashCookieName , $ systemKey ));
224232 $ hashCookieContents = base64_encode (Crypto::encrypt (time () . ': ' . $ authentication ->getUserId () . ': ' . $ authentication ->getUsername (), $ userKey ));
225233
226234 //
@@ -262,7 +270,7 @@ private function setSessionCookies(AuthenticationRecordInterface $authentication
262270 * @param $name
263271 * @param $value
264272 */
265- private function setCookie ($ name , $ value )
273+ private function setCookie (string $ name , $ value )
266274 {
267275 $ expiry = $ this ->transient ? 0 : (time () + 2629743 );
268276 $ sessionParameters = session_get_cookie_params ();
@@ -332,7 +340,7 @@ public function getIdentity()
332340
333341 // paranoid, make sure we have everything we need
334342 @list ($ cookieUserId , $ hashCookieSuffix ) = @explode (": " , $ userTuple , 2 );
335- if (!isset ($ cookieUserId) || ! isset ( $ hashCookieSuffix ) || !is_numeric ($ cookieUserId ) || !trim ($ hashCookieSuffix )) {
343+ if (!isset ($ cookieUserId, $ hashCookieSuffix ) || !is_numeric ($ cookieUserId ) || !trim ($ hashCookieSuffix )) {
336344 throw new \Exception ();
337345 }
338346
@@ -364,7 +372,7 @@ public function getIdentity()
364372 // 3. Decrypt the hash cookie with the user key
365373 //
366374 $ hashedCookieContents = Crypto::decrypt (base64_decode ($ _COOKIE [$ hashCookieName ]), $ userKey );
367- if (!substr_count ($ hashedCookieContents , ': ' ) == 2 ) {
375+ if (!substr_count ($ hashedCookieContents , ': ' ) === 2 ) {
368376 throw new \Exception ();
369377 }
370378
@@ -405,12 +413,19 @@ private function purgeHashCookies(string $skipCookie = null)
405413 {
406414 $ sp = session_get_cookie_params ();
407415 foreach ($ _COOKIE as $ cookieName => $ value ) {
408- if ($ cookieName != $ skipCookie && strpos ($ cookieName , self ::COOKIE_HASH_PREFIX ) !== false ) {
416+ if ($ cookieName !== $ skipCookie && strpos ($ cookieName , self ::COOKIE_HASH_PREFIX ) !== false ) {
409417 setcookie ($ cookieName , null , null , '/ ' , $ sp ['domain ' ], false , true );
410418 }
411419 }
412420 }
413421
422+ private function enforcePasswordStrength (string $ password )
423+ {
424+ if ($ this ->passwordChecker && !$ this ->passwordChecker ->isStrongPassword ($ password )) {
425+ throw new WeakPasswordException ();
426+ }
427+ }
428+
414429
415430 /**
416431 * Reset this user's password
@@ -419,9 +434,12 @@ private function purgeHashCookies(string $skipCookie = null)
419434 * @param string $newPassword Cleartext password that's being hashed
420435 *
421436 * @throws NoSuchUserException
437+ * @throws WeakPasswordException
422438 */
423- public function resetPassword (User $ user , $ newPassword )
439+ public function resetPassword (User $ user , string $ newPassword )
424440 {
441+ $ this ->enforcePasswordStrength ($ newPassword );
442+
425443 $ auth = $ this ->authenticationProvider ->findByUserId ($ user ->getId ());
426444 if (!$ auth ) {
427445 throw new NoSuchUserException ();
@@ -442,9 +460,12 @@ public function resetPassword(User $user, $newPassword)
442460 * @return bool
443461 *
444462 * @throws NoSuchUserException
463+ * @throws WeakPasswordException
445464 */
446465 public function verifyPassword (User $ user , string $ password ): bool
447466 {
467+ $ this ->enforcePasswordStrength ($ password );
468+
448469 $ auth = $ this ->authenticationProvider ->findByUserId ($ user ->getId ());
449470 if (!$ auth ) {
450471 throw new NoSuchUserException ();
@@ -467,6 +488,8 @@ public function verifyPassword(User $user, string $password): bool
467488 */
468489 public function create (User $ user , string $ username , string $ password ): AuthenticationRecordInterface
469490 {
491+ $ this ->enforcePasswordStrength ($ password );
492+
470493 $ auth = $ this ->registerAuthenticationRecord ($ user , $ username , $ password );
471494 $ this ->setSessionCookies ($ auth );
472495 $ this ->setIdentity ($ user );
@@ -500,12 +523,12 @@ public function registerAuthenticationRecord(User $user, string $username, strin
500523 }
501524
502525 if (filter_var ($ username , FILTER_VALIDATE_EMAIL )) {
503- if ($ user ->getEmail () != $ username ) {
526+ if ($ user ->getEmail () !== $ username ) {
504527 throw new MismatchedEmailsException ();
505528 }
506529
507530 if ($ emailUser = $ this ->userProvider ->findByEmail ($ username )) {
508- if ($ emailUser != $ user ) {
531+ if ($ emailUser !== $ user ) {
509532 throw new EmailUsernameTakenException ();
510533 }
511534 }
0 commit comments