Skip to content

Commit 7f97be9

Browse files
committed
Improve test cases and code coverage
1 parent 48c993f commit 7f97be9

File tree

5 files changed

+178
-4
lines changed

5 files changed

+178
-4
lines changed

iam-login-service/src/main/java/it/infn/mw/iam/api/account/lockout/AccountLockoutController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public AccountLockoutController(IamAccountLoginLockoutRepository lockoutRepo) {
3939

4040
@GetMapping("/iam/account/{uuid}/lockout")
4141
@PreAuthorize("#iam.hasScope('iam:admin.read') or #iam.hasDashboardRole('ROLE_ADMIN')")
42-
public ResponseEntity<?> getLockoutStatus(@PathVariable String uuid) {
42+
public ResponseEntity<Map<String, Object>> getLockoutStatus(@PathVariable String uuid) {
4343
Optional<IamAccountLoginLockout> lockout = lockoutRepo.findByAccountUuid(uuid);
4444

4545
if (lockout.isPresent() && lockout.get().getSuspendedUntil() != null
@@ -54,7 +54,7 @@ public ResponseEntity<?> getLockoutStatus(@PathVariable String uuid) {
5454

5555
@GetMapping("/iam/account/lockout/suspended")
5656
@PreAuthorize("#iam.hasScope('iam:admin.read') or #iam.hasDashboardRole('ROLE_ADMIN')")
57-
public ResponseEntity<?> getAllSuspendedUsers() {
57+
public ResponseEntity<List<String>> getAllSuspendedUsers() {
5858
List<String> suspendedUsers = lockoutRepo.findAllSuspendedUsers();
5959
return ResponseEntity.ok(suspendedUsers);
6060
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package it.infn.mw.iam.test.api.account.lockout;
17+
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
import static org.mockito.Mockito.when;
20+
21+
import java.util.Date;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Optional;
25+
26+
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.api.extension.ExtendWith;
29+
import org.mockito.Mock;
30+
import org.mockito.junit.jupiter.MockitoExtension;
31+
import org.springframework.http.ResponseEntity;
32+
33+
import it.infn.mw.iam.api.account.lockout.AccountLockoutController;
34+
import it.infn.mw.iam.persistence.model.IamAccount;
35+
import it.infn.mw.iam.persistence.model.IamAccountLoginLockout;
36+
import it.infn.mw.iam.persistence.repository.IamAccountLoginLockoutRepository;
37+
38+
@ExtendWith(MockitoExtension.class)
39+
class AccountLockoutControllerTests {
40+
41+
@Mock
42+
private IamAccountLoginLockoutRepository lockoutRepo;
43+
44+
private AccountLockoutController controller;
45+
private IamAccount account;
46+
47+
@BeforeEach
48+
void setup() {
49+
controller = new AccountLockoutController(lockoutRepo);
50+
account = new IamAccount();
51+
account.setUuid("uuid-1");
52+
account.setUsername("testuser");
53+
}
54+
55+
@Test
56+
void getLockoutStatusReturnsSuspended() {
57+
IamAccountLoginLockout lockout = new IamAccountLoginLockout(account);
58+
long future = System.currentTimeMillis() + 60_000;
59+
lockout.setSuspendedUntil(new Date(future));
60+
when(lockoutRepo.findByAccountUuid("uuid-1")).thenReturn(Optional.of(lockout));
61+
62+
ResponseEntity<Map<String, Object>> r = controller.getLockoutStatus("uuid-1");
63+
assertEquals(true, r.getBody().get("suspended"));
64+
assertEquals(future, r.getBody().get("suspendedUntil"));
65+
}
66+
67+
@Test
68+
void getLockoutStatusReturnsNotSuspendedWhenEmpty() {
69+
when(lockoutRepo.findByAccountUuid("uuid-1")).thenReturn(Optional.empty());
70+
71+
ResponseEntity<Map<String, Object>> r = controller.getLockoutStatus("uuid-1");
72+
assertEquals(false, r.getBody().get("suspended"));
73+
}
74+
75+
@Test
76+
void getLockoutStatusReturnsNotSuspendedWhenExpired() {
77+
IamAccountLoginLockout lockout = new IamAccountLoginLockout(account);
78+
lockout.setSuspendedUntil(new Date(System.currentTimeMillis() - 1_000));
79+
when(lockoutRepo.findByAccountUuid("uuid-1")).thenReturn(Optional.of(lockout));
80+
81+
ResponseEntity<Map<String, Object>> r = controller.getLockoutStatus("uuid-1");
82+
assertEquals(false, r.getBody().get("suspended"));
83+
}
84+
85+
@Test
86+
void getLockoutStatusReturnsNotSuspendedWhenNullSuspendedUntil() {
87+
IamAccountLoginLockout lockout = new IamAccountLoginLockout(account);
88+
when(lockoutRepo.findByAccountUuid("uuid-1")).thenReturn(Optional.of(lockout));
89+
90+
ResponseEntity<Map<String, Object>> r = controller.getLockoutStatus("uuid-1");
91+
assertEquals(false, r.getBody().get("suspended"));
92+
}
93+
94+
@Test
95+
void getAllSuspendedUsersReturnsUuids() {
96+
when(lockoutRepo.findAllSuspendedUsers()).thenReturn(List.of("uuid-a", "uuid-b"));
97+
98+
ResponseEntity<List<String>> r = controller.getAllSuspendedUsers();
99+
assertEquals(2, r.getBody().size());
100+
}
101+
}

iam-login-service/src/test/java/it/infn/mw/iam/test/authn/lockout/DefaultLoginLockoutServiceTests.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ void fullLifecycleSuspendSuspendDisable() {
145145
// Suspension expires again
146146
lockout.setSuspendedUntil(new Date(System.currentTimeMillis() - 1000));
147147
service.checkIamAccountLockout(USERNAME);
148-
149148
// ROUND 3: 2 failures -> account disabled, row deleted
150149
service.recordFailedAttempt(USERNAME);
151150
service.recordFailedAttempt(USERNAME);
@@ -154,4 +153,44 @@ void fullLifecycleSuspendSuspendDisable() {
154153
verify(accountRepo).save(account);
155154
verify(lockoutRepo).delete(lockout);
156155
}
156+
157+
@Test
158+
void checkLockoutNoRecordIsNoop() {
159+
when(lockoutRepo.findByAccountUsername(USERNAME)).thenReturn(Optional.empty());
160+
assertDoesNotThrow(() -> service.checkIamAccountLockout(USERNAME));
161+
}
162+
163+
@Test
164+
void recordFailedAttemptUnknownUserIsNoop() {
165+
when(accountRepo.findByUsername(USERNAME)).thenReturn(Optional.empty());
166+
service.recordFailedAttempt(USERNAME);
167+
verify(lockoutRepo, never()).save(any());
168+
}
169+
170+
@Test
171+
void recordFailedAttemptInactiveAccountIsNoop() {
172+
account.setActive(false);
173+
when(accountRepo.findByUsername(USERNAME)).thenReturn(Optional.of(account));
174+
service.recordFailedAttempt(USERNAME);
175+
verify(lockoutRepo, never()).save(any());
176+
}
177+
178+
@Test
179+
void recordFailedAttemptWhileSuspendedIsNoop() {
180+
IamAccountLoginLockout lockout = new IamAccountLoginLockout(account);
181+
lockout.setSuspendedUntil(new Date(System.currentTimeMillis() + 60_000));
182+
when(accountRepo.findByUsername(USERNAME)).thenReturn(Optional.of(account));
183+
when(lockoutRepo.findByAccountUsername(USERNAME)).thenReturn(Optional.of(lockout));
184+
185+
service.recordFailedAttempt(USERNAME);
186+
verify(lockoutRepo, never()).save(any());
187+
}
188+
189+
@Test
190+
void resetNoRowIsNoop() {
191+
when(lockoutRepo.findByAccountUsername(USERNAME)).thenReturn(Optional.empty());
192+
service.resetFailedAttempts(USERNAME);
193+
verify(lockoutRepo, never()).delete(any());
194+
}
195+
157196
}

iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamLocalAuthenticationProviderTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,17 @@ void testWhenPreAuthenticatedThenAuthenticateSetFalseToAuthenticated() {
9999
// Verify that super.authenticate was not called
100100
verify(iamLocalAuthenticationProvider, never()).authenticate(any(UsernamePasswordAuthenticationToken.class));
101101
}
102+
103+
@Test
104+
void resetCalledOnNonMfaSuccess() {
105+
ExtendedAuthenticationToken token = new ExtendedAuthenticationToken("user", "cred");
106+
token.setPreAuthenticated(true);
107+
IamAccount account = newAccount("user");
108+
when(accountRepo.findByUsername("user")).thenReturn(Optional.of(account));
109+
when(iamTotpMfaService.isAuthenticatorAppActive(account)).thenReturn(false);
110+
111+
iamLocalAuthenticationProvider.authenticate(token);
112+
113+
verify(lockoutService).resetFailedAttempts("user");
114+
}
102115
}

iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MultiFactorTotpCheckProviderTests.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import static org.junit.jupiter.api.Assertions.assertNull;
2020
import static org.junit.jupiter.api.Assertions.assertThrows;
2121
import static org.mockito.ArgumentMatchers.anyString;
22+
import static org.mockito.Mockito.doThrow;
23+
import static org.mockito.Mockito.never;
24+
import static org.mockito.Mockito.verify;
2225
import static org.mockito.Mockito.when;
2326

2427
import java.util.Optional;
@@ -28,6 +31,7 @@
2831
import org.mockito.Mock;
2932
import org.mockito.MockitoAnnotations;
3033
import org.springframework.security.authentication.BadCredentialsException;
34+
import org.springframework.security.authentication.LockedException;
3135

3236
import it.infn.mw.iam.api.account.multi_factor_authentication.IamTotpMfaService;
3337
import it.infn.mw.iam.authn.lockout.LoginLockoutService;
@@ -106,17 +110,34 @@ void authenticateThrowsBadCredentialsExceptionWhenTotpIsInvalid() {
106110

107111
assertThrows(BadCredentialsException.class,
108112
() -> multiFactorTotpCheckProvider.authenticate(token));
113+
114+
verify(lockoutService).recordFailedAttempt("totp");
115+
verify(lockoutService, never()).resetFailedAttempts(anyString());
109116
}
110117

111118
@Test
112-
void authenticateReturnsSuccessfulAuthenticationWhenTotpIsValid() {
119+
void authenticateResetsLockoutWhenTotpIsValid() {
113120
IamAccount account = cloneAccount(TOTP_MFA_ACCOUNT);
114121
when(token.getName()).thenReturn("totp");
115122
when(token.getTotp()).thenReturn("123456");
116123
when(accountRepo.findByUsername("totp")).thenReturn(Optional.of(account));
117124
when(totpMfaService.verifyTotp(account, "123456")).thenReturn(true);
118125

119126
assertNotNull(multiFactorTotpCheckProvider.authenticate(token));
127+
128+
verify(lockoutService).resetFailedAttempts("totp");
129+
verify(lockoutService, never()).recordFailedAttempt(anyString());
130+
}
131+
132+
@Test
133+
void authenticateThrowsLockedExceptionWhenSuspended() {
134+
when(token.getTotp()).thenReturn("123456");
135+
when(token.getName()).thenReturn("locked");
136+
doThrow(new LockedException("Suspended")).when(lockoutService).checkIamAccountLockout("locked");
137+
138+
assertThrows(LockedException.class,
139+
() -> multiFactorTotpCheckProvider.authenticate(token));
140+
verify(accountRepo, never()).findByUsername(anyString());
120141
}
121142

122143
@Test

0 commit comments

Comments
 (0)