2 changed files with 209 additions and 5 deletions
@ -0,0 +1,204 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.security.system; |
||||
|
|
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.junit.runner.RunWith; |
||||
|
import org.mockito.Mock; |
||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
import org.springframework.security.authentication.BadCredentialsException; |
||||
|
import org.springframework.security.authentication.DisabledException; |
||||
|
import org.springframework.security.authentication.LockedException; |
||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
||||
|
import org.thingsboard.rule.engine.api.MailService; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UserId; |
||||
|
import org.thingsboard.server.common.data.security.UserCredentials; |
||||
|
import org.thingsboard.server.common.data.security.model.SecuritySettings; |
||||
|
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; |
||||
|
import org.thingsboard.server.dao.audit.AuditLogService; |
||||
|
import org.thingsboard.server.dao.settings.AdminSettingsService; |
||||
|
import org.thingsboard.server.dao.settings.SecuritySettingsService; |
||||
|
import org.thingsboard.server.dao.user.UserService; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
|
import static org.mockito.ArgumentMatchers.any; |
||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||
|
import static org.mockito.Mockito.never; |
||||
|
import static org.mockito.Mockito.verify; |
||||
|
import static org.mockito.Mockito.when; |
||||
|
|
||||
|
@RunWith(MockitoJUnitRunner.class) |
||||
|
public class DefaultSystemSecurityServiceTest { |
||||
|
|
||||
|
@Mock |
||||
|
private AdminSettingsService adminSettingsService; |
||||
|
@Mock |
||||
|
private BCryptPasswordEncoder encoder; |
||||
|
@Mock |
||||
|
private UserService userService; |
||||
|
@Mock |
||||
|
private MailService mailService; |
||||
|
@Mock |
||||
|
private AuditLogService auditLogService; |
||||
|
@Mock |
||||
|
private SecuritySettingsService securitySettingsService; |
||||
|
|
||||
|
private DefaultSystemSecurityService systemSecurityService; |
||||
|
|
||||
|
private TenantId tenantId; |
||||
|
private UserId userId; |
||||
|
private UserCredentials userCredentials; |
||||
|
private SecuritySettings securitySettings; |
||||
|
private String username; |
||||
|
private String password; |
||||
|
private String encodedPassword; |
||||
|
|
||||
|
@Before |
||||
|
public void setUp() { |
||||
|
systemSecurityService = new DefaultSystemSecurityService(adminSettingsService, encoder, userService, mailService, auditLogService, securitySettingsService); |
||||
|
|
||||
|
tenantId = TenantId.fromUUID(UUID.randomUUID()); |
||||
|
userId = new UserId(UUID.randomUUID()); |
||||
|
username = "tenant@example.com"; |
||||
|
password = "correctPassword"; |
||||
|
encodedPassword = "$2a$10$encodedPasswordHash"; |
||||
|
|
||||
|
userCredentials = new UserCredentials(); |
||||
|
userCredentials.setUserId(userId); |
||||
|
userCredentials.setEnabled(true); |
||||
|
userCredentials.setPassword(encodedPassword); |
||||
|
userCredentials.setCreatedTime(System.currentTimeMillis()); |
||||
|
|
||||
|
securitySettings = new SecuritySettings(); |
||||
|
securitySettings.setMaxFailedLoginAttempts(5); |
||||
|
securitySettings.setPasswordPolicy(new UserPasswordPolicy()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testValidateUserCredentials_successfulLogin() { |
||||
|
when(encoder.matches(password, encodedPassword)).thenReturn(true); |
||||
|
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings); |
||||
|
|
||||
|
systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password); |
||||
|
|
||||
|
verify(encoder).matches(password, encodedPassword); |
||||
|
verify(userService).resetFailedLoginAttempts(tenantId, userId); |
||||
|
verify(userService, never()).increaseFailedLoginAttempts(any(), any()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testValidateUserCredentials_wrongPassword_incrementsFailedAttempts() { |
||||
|
when(encoder.matches(password, encodedPassword)).thenReturn(false); |
||||
|
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(3); |
||||
|
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings); |
||||
|
|
||||
|
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password)) |
||||
|
.isInstanceOf(BadCredentialsException.class) |
||||
|
.hasMessageContaining("Authentication Failed"); |
||||
|
|
||||
|
verify(userService).increaseFailedLoginAttempts(tenantId, userId); |
||||
|
verify(userService, never()).setUserCredentialsEnabled(any(), any(), eq(false)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testValidateUserCredentials_wrongPassword_accountLocked() { |
||||
|
when(encoder.matches(password, encodedPassword)).thenReturn(false); |
||||
|
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(6); |
||||
|
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings); |
||||
|
|
||||
|
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password)) |
||||
|
.isInstanceOf(LockedException.class) |
||||
|
.hasMessageContaining("locked due to security policy"); |
||||
|
|
||||
|
verify(userService).increaseFailedLoginAttempts(tenantId, userId); |
||||
|
verify(userService).setUserCredentialsEnabled(TenantId.SYS_TENANT_ID, userId, false); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testValidateUserCredentials_wrongPassword_exactlyAtThreshold_noLock() { |
||||
|
when(encoder.matches(password, encodedPassword)).thenReturn(false); |
||||
|
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(5); |
||||
|
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings); |
||||
|
|
||||
|
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password)) |
||||
|
.isInstanceOf(BadCredentialsException.class) |
||||
|
.hasMessageContaining("Authentication Failed"); |
||||
|
|
||||
|
verify(userService).increaseFailedLoginAttempts(tenantId, userId); |
||||
|
verify(userService, never()).setUserCredentialsEnabled(any(), any(), eq(false)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testValidateUserCredentials_correctPassword_disabledUser_throwsDisabledException() { |
||||
|
userCredentials.setEnabled(false); |
||||
|
|
||||
|
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password)) |
||||
|
.isInstanceOf(DisabledException.class) |
||||
|
.hasMessage("User is not active"); |
||||
|
|
||||
|
verify(encoder, never()).matches(any(), any()); |
||||
|
verify(userService, never()).increaseFailedLoginAttempts(any(), any()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testValidateUserCredentials_wrongPassword_maxAttemptsDisabled() { |
||||
|
securitySettings.setMaxFailedLoginAttempts(null); |
||||
|
when(encoder.matches(password, encodedPassword)).thenReturn(false); |
||||
|
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(100); |
||||
|
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings); |
||||
|
|
||||
|
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password)) |
||||
|
.isInstanceOf(BadCredentialsException.class); |
||||
|
|
||||
|
verify(userService).increaseFailedLoginAttempts(tenantId, userId); |
||||
|
verify(userService, never()).setUserCredentialsEnabled(any(), any(), eq(false)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testValidateUserCredentials_wrongPassword_maxAttemptsSetToZero() { |
||||
|
securitySettings.setMaxFailedLoginAttempts(0); |
||||
|
when(encoder.matches(password, encodedPassword)).thenReturn(false); |
||||
|
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(100); |
||||
|
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings); |
||||
|
|
||||
|
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password)) |
||||
|
.isInstanceOf(BadCredentialsException.class); |
||||
|
|
||||
|
verify(userService).increaseFailedLoginAttempts(tenantId, userId); |
||||
|
verify(userService, never()).setUserCredentialsEnabled(any(), any(), eq(false)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testValidateUserCredentials_wrongPassword_withNotificationEmail() throws ThingsboardException { |
||||
|
String notificationEmail = "admin@example.com"; |
||||
|
securitySettings.setUserLockoutNotificationEmail(notificationEmail); |
||||
|
when(encoder.matches(password, encodedPassword)).thenReturn(false); |
||||
|
when(userService.increaseFailedLoginAttempts(tenantId, userId)).thenReturn(6); |
||||
|
when(securitySettingsService.getSecuritySettings()).thenReturn(securitySettings); |
||||
|
|
||||
|
assertThatThrownBy(() -> systemSecurityService.validateUserCredentials(tenantId, userCredentials, username, password)) |
||||
|
.isInstanceOf(LockedException.class); |
||||
|
|
||||
|
verify(userService).setUserCredentialsEnabled(TenantId.SYS_TENANT_ID, userId, false); |
||||
|
verify(mailService).sendAccountLockoutEmail(eq(username), eq(notificationEmail), eq(5)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue