Skip to content

Commit 28541f5

Browse files
chore: Add PR's requested changes
Add tests changes with suggestion
1 parent 70ba198 commit 28541f5

2 files changed

Lines changed: 101 additions & 40 deletions

File tree

app/libs/Auth/AuthService.php

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<?php namespace Auth;
1+
<?php
2+
namespace Auth;
23
/**
34
* Copyright 2016 OpenStack Foundation
45
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +18,7 @@
1718
use App\Services\AbstractService;
1819
use Auth\Exceptions\AuthenticationException;
1920
use Auth\Exceptions\AuthenticationLockedUserLoginAttempt;
21+
use Auth\Exceptions\UnverifiedEmailMemberException;
2022
use Auth\Repositories\IUserRepository;
2123
use Illuminate\Support\Facades\Auth;
2224
use Illuminate\Support\Facades\Config;
@@ -100,17 +102,16 @@ final class AuthService extends AbstractService implements IAuthService
100102
*/
101103
public function __construct
102104
(
103-
IUserRepository $user_repository,
104-
IOAuth2OTPRepository $otp_repository,
105-
IPrincipalService $principal_service,
106-
IUserService $user_service,
107-
IUserActionService $user_action_service,
108-
ICacheService $cache_service,
109-
IAuthUserService $auth_user_service,
105+
IUserRepository $user_repository,
106+
IOAuth2OTPRepository $otp_repository,
107+
IPrincipalService $principal_service,
108+
IUserService $user_service,
109+
IUserActionService $user_action_service,
110+
ICacheService $cache_service,
111+
IAuthUserService $auth_user_service,
110112
ISecurityContextService $security_context_service,
111-
ITransactionService $tx_service
112-
)
113-
{
113+
ITransactionService $tx_service
114+
) {
114115
parent::__construct($tx_service);
115116
$this->user_repository = $user_repository;
116117
$this->principal_service = $principal_service;
@@ -159,7 +160,7 @@ public function login(string $username, string $password, bool $remember_me): bo
159160
Log::debug("AuthService::login: clearing principal");
160161
$this->principal_service->clear();
161162
$current_user = $this->getCurrentUser();
162-
if(is_null($current_user) || !$current_user->canLogin())
163+
if (is_null($current_user) || !$current_user->canLogin())
163164
throw new AuthenticationException
164165
(
165166
"We are sorry, your username or password does not match an existing record."
@@ -183,12 +184,16 @@ public function validateCredentials(string $username, string $password): User
183184
{
184185
Log::debug("AuthService::validateCredentials");
185186

186-
/**
187-
* @var User|null $user
188-
*/
189-
$user = Auth::getProvider()->retrieveByCredentials(['username' => $username, 'password' => $password]);
190-
if (!$user) {
191-
throw new AuthenticationException();
187+
try {
188+
/**
189+
* @var User|null $user
190+
*/
191+
$user = Auth::getProvider()->retrieveByCredentials(['username' => $username, 'password' => $password]);
192+
if (!$user instanceof User || !$user->canLogin()) {
193+
throw new AuthenticationException("We are sorry, your username or password does not match an existing record.");
194+
}
195+
} catch (UnverifiedEmailMemberException $ex) {
196+
throw new AuthenticationException($ex->getMessage());
192197
}
193198

194199
return $user;
@@ -202,7 +207,8 @@ public function validateCredentials(string $username, string $password): User
202207
public function loginUser(User $user, bool $remember): void
203208
{
204209
Log::debug("AuthService::loginUser");
205-
if (!$user->canLogin()) throw new AuthenticationException("User is not active or cannot login.");
210+
if (!$user->canLogin())
211+
throw new AuthenticationException("User is not active or cannot login.");
206212
Auth::login($user, $remember);
207213
}
208214

@@ -261,11 +267,13 @@ public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null, bool $
261267
throw new AuthenticationException("Single-use code mismatch.");
262268
}
263269

264-
if(!empty($otpClaim->getScope()) && !$otp->allowScope($otpClaim->getScope()))
270+
if (!empty($otpClaim->getScope()) && !$otp->allowScope($otpClaim->getScope()))
265271
throw new InvalidOTPException("Single-use code requested scopes escalates former scopes.");
266272

267-
if (($otp->hasClient() && is_null($client)) ||
268-
($otp->hasClient() && !is_null($client) && $client->getClientId() != $otp->getClient()->getClientId())) {
273+
if (
274+
($otp->hasClient() && is_null($client)) ||
275+
($otp->hasClient() && !is_null($client) && $client->getClientId() != $otp->getClient()->getClientId())
276+
) {
269277
throw new AuthenticationException("Single-use code audience mismatch.");
270278
}
271279

@@ -287,15 +295,14 @@ public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null, bool $
287295
],
288296
$otp
289297
);
290-
}
291-
else{
292-
if($user->isActive()) {
298+
} else {
299+
if ($user->isActive()) {
293300
// verify email
294301
$user->verifyEmail(false);
295302
}
296303
}
297304

298-
if(!$user->canLogin()){
305+
if (!$user->canLogin()) {
299306
Log::warning(sprintf("AuthService::loginWithOTP user %s cannot login ( is not active ).", $user->getId()));
300307
throw new AuthenticationException("We are sorry, your username or password does not match an existing record.");
301308
}
@@ -311,7 +318,7 @@ public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null, bool $
311318
$client
312319
);
313320

314-
foreach ($grants2Revoke as $otp2Revoke){
321+
foreach ($grants2Revoke as $otp2Revoke) {
315322
try {
316323
Log::debug(sprintf("AuthService::loginWithOTP revoking otp %s ", $otp2Revoke->getValue()));
317324
if ($otp2Revoke->getValue() !== $otpClaim->getValue())
@@ -332,12 +339,12 @@ public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null, bool $
332339
* @param bool $clear_security_ctx
333340
* @return void
334341
*/
335-
public function logout(bool $clear_security_ctx = true):void
342+
public function logout(bool $clear_security_ctx = true): void
336343
{
337344
Log::debug("AuthService::logout");
338345
$current_user = $this->getCurrentUser();
339346
// check if we have user on session
340-
if(!is_null($current_user)) {
347+
if (!is_null($current_user)) {
341348
$ip = IPHelper::getUserIp();
342349
Log::debug(sprintf("AuthService::logout we have user %s from ip %s", $current_user->getId(), $ip));
343350
$this->user_action_service->addUserAction
@@ -351,7 +358,7 @@ public function logout(bool $clear_security_ctx = true):void
351358
// regular flow
352359
$this->invalidateSession();
353360
$this->principal_service->clear();
354-
if($clear_security_ctx)
361+
if ($clear_security_ctx)
355362
$this->security_context_service->clear();
356363
Auth::logout();
357364
// put in past
@@ -471,8 +478,7 @@ public function unwrapUserId(string $user_id): string
471478
$unwrapped_name = $this->decrypt($user_id);
472479
$parts = explode(':', $unwrapped_name);
473480
return intval($parts[1]);
474-
}
475-
catch (Exception $ex){
481+
} catch (Exception $ex) {
476482
Log::warning($ex);
477483
}
478484
return $user_id;
@@ -535,7 +541,8 @@ public function registerRPLogin(string $client_id): void
535541
$rps = $zlib->uncompress($rps);
536542
$rps .= '|';
537543
}
538-
if (is_null($rps)) $rps = "";
544+
if (is_null($rps))
545+
$rps = "";
539546

540547
if (!str_contains($rps, $client_id))
541548
$rps .= $client_id;
@@ -574,8 +581,7 @@ public function getLoggedRPs(): array
574581
$rps = $zlib->uncompress($rps);
575582
return explode('|', $rps);
576583
}
577-
}
578-
catch (Exception $ex){
584+
} catch (Exception $ex) {
579585
Log::warning($ex);
580586
}
581587
return [];
@@ -642,14 +648,17 @@ public function invalidateSession(): void
642648
public function postLoginUserActions(int $user_id): void
643649
{
644650
Log::debug(sprintf("AuthService::postLoginUserActions user %s", $user_id));
645-
$this->tx_service->transaction(function () use($user_id){
651+
$this->tx_service->transaction(function () use ($user_id) {
646652
$user = $this->user_repository->getById($user_id);
647-
if(!$user instanceof User) return;
653+
if (!$user instanceof User)
654+
return;
648655

649656
if (!$user->isActive()) {
650-
Log::warning(sprintf("AuthService::postLoginUserActions user %s is not active.", $user_id));
651-
throw new AuthenticationLockedUserLoginAttempt($user->getEmail(),
652-
sprintf("User %s is locked.", $user->getEmail()));
657+
Log::warning(sprintf("AuthService::postLoginUserActions user %s is not active.", $user_id));
658+
throw new AuthenticationLockedUserLoginAttempt(
659+
$user->getEmail(),
660+
sprintf("User %s is locked.", $user->getEmail())
661+
);
653662
}
654663

655664
//update user fields

tests/unit/AuthServiceValidateCredentialsTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Auth\AuthService;
1717
use Auth\CustomAuthProvider;
1818
use Auth\Exceptions\AuthenticationException;
19+
use Auth\Exceptions\UnverifiedEmailMemberException;
1920
use Auth\Repositories\IUserRepository;
2021
use Mockery;
2122
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
@@ -91,6 +92,7 @@ public function testValidCredentials_returnsUser_withoutEstablishingSession(): v
9192
$password = 'Str0ng!Pass';
9293

9394
$resolved_user = Mockery::mock('Auth\User');
95+
$resolved_user->shouldReceive('canLogin')->once()->andReturn(true);
9496

9597
$provider_mock = Mockery::mock(CustomAuthProvider::class);
9698
$provider_mock->shouldReceive('retrieveByCredentials')
@@ -179,4 +181,54 @@ public function testLoginUser_throwsException_whenIsNotActive(): void
179181
$this->service->loginUser($user, true);
180182
}
181183

184+
/**
185+
* UnverifiedEmailMemberException from the provider must be caught and
186+
* re-thrown as AuthenticationException (contract: @throws AuthenticationException only).
187+
*/
188+
public function testUnverifiedUser_throwsAuthenticationException(): void
189+
{
190+
$username = 'unverified@example.com';
191+
$password = 'any';
192+
193+
$provider_mock = Mockery::mock(CustomAuthProvider::class);
194+
$provider_mock->shouldReceive('retrieveByCredentials')
195+
->once()
196+
->with(['username' => $username, 'password' => $password])
197+
->andThrow(new UnverifiedEmailMemberException('Email not verified.'));
198+
199+
$this->auth_mock->shouldReceive('getProvider')->once()->andReturn($provider_mock);
200+
$this->auth_mock->shouldNotReceive('login');
201+
202+
$this->expectException(AuthenticationException::class);
203+
$this->expectExceptionMessage('Email not verified.');
204+
205+
$this->service->validateCredentials($username, $password);
206+
}
207+
208+
/**
209+
* Provider returns a valid User but canLogin() is false (locked/inactive):
210+
* must throw AuthenticationException — not silently return the user.
211+
*/
212+
public function testUserCannotLogin_throwsAuthenticationException(): void
213+
{
214+
$username = 'locked@example.com';
215+
$password = 'any';
216+
217+
$locked_user = Mockery::mock('Auth\User');
218+
$locked_user->shouldReceive('canLogin')->once()->andReturn(false);
219+
220+
$provider_mock = Mockery::mock(CustomAuthProvider::class);
221+
$provider_mock->shouldReceive('retrieveByCredentials')
222+
->once()
223+
->with(['username' => $username, 'password' => $password])
224+
->andReturn($locked_user);
225+
226+
$this->auth_mock->shouldReceive('getProvider')->once()->andReturn($provider_mock);
227+
$this->auth_mock->shouldNotReceive('login');
228+
229+
$this->expectException(AuthenticationException::class);
230+
231+
$this->service->validateCredentials($username, $password);
232+
}
233+
182234
}

0 commit comments

Comments
 (0)