Enhance auth user lifecycle and registration checks
This commit is contained in:
parent
7d7b131f55
commit
ee14e5d7d1
|
|
@ -58,7 +58,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||
String username = jwtUtil.extractUsername(jwt);
|
||||
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
LoginEntity userDetails = authRepository.findById(username).orElse(null);
|
||||
LoginEntity userDetails = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
|
||||
if (userDetails == null) {
|
||||
logger.warn("JWT username '{}' not found in database", username);
|
||||
} else if (jwtUtil.validateToken(jwt, userDetails)) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ public class AuthRequest {
|
|||
private String username;
|
||||
private String password;
|
||||
private String email;
|
||||
private String turnstileToken;
|
||||
|
||||
public AuthRequest(String username, String password, String email) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
// Getters e Setters
|
||||
public String getUsername() { return username; }
|
||||
|
|
@ -20,6 +27,9 @@ public class AuthRequest {
|
|||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
|
||||
public String getTurnstileToken() { return turnstileToken; }
|
||||
public void setTurnstileToken(String turnstileToken) { this.turnstileToken = turnstileToken; }
|
||||
|
||||
boolean isValid() {
|
||||
return username.contains(" ") && username != null && !username.isEmpty() && username.length()>5 &&
|
||||
password != null && username.contains(" ") && password.length()>5 && !password.isEmpty();
|
||||
|
|
|
|||
|
|
@ -2,13 +2,30 @@ package it.cnr.isti.workflow.manager.auth.repo;
|
|||
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import it.cnr.isti.workflow.manager.auth.model.UserRole;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface AuthRepository extends JpaRepository<LoginEntity, String> {
|
||||
|
||||
long countByRole(UserRole role);
|
||||
|
||||
@Query("select count(u) from LoginEntity u where u.role = :role and (u.active is null or u.active = true)")
|
||||
long countByRoleAndActiveTrue(@Param("role") UserRole role);
|
||||
|
||||
@Query("select case when count(u) > 0 then true else false end from LoginEntity u where u.username = :username and (u.active is null or u.active = true)")
|
||||
boolean existsByUsernameAndActiveTrue(@Param("username") String username);
|
||||
|
||||
@Query("select u from LoginEntity u where u.username = :username and (u.active is null or u.active = true)")
|
||||
Optional<LoginEntity> findByUsernameAndActiveTrue(@Param("username") String username);
|
||||
|
||||
@Query("select u from LoginEntity u where u.active is null or u.active = true")
|
||||
List<LoginEntity> findByActiveTrue();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,18 +18,27 @@ public class LoginEntity {
|
|||
private String username;
|
||||
private String password;
|
||||
private String email;
|
||||
private Boolean active;
|
||||
@Enumerated(EnumType.STRING)
|
||||
private UserRole role;
|
||||
|
||||
public LoginEntity(String username, String password) {
|
||||
this(username, password, null, UserRole.USER);
|
||||
this(username, password, null, true, UserRole.USER);
|
||||
}
|
||||
|
||||
public LoginEntity(String username, String password, UserRole role) {
|
||||
this(username, password, null, role);
|
||||
this(username, password, null, true, role);
|
||||
}
|
||||
|
||||
public LoginEntity(String username, String password, String email, UserRole role) {
|
||||
this(username, password, email, true, role);
|
||||
}
|
||||
|
||||
public UserRole effectiveRole() {
|
||||
return role == null ? UserRole.USER : role;
|
||||
}
|
||||
|
||||
public boolean isActiveUser() {
|
||||
return active == null || active;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package it.cnr.isti.workflow.manager.auth.services;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import it.cnr.isti.workflow.manager.auth.model.AdminChangePasswordResult;
|
||||
import it.cnr.isti.workflow.manager.auth.model.ChangePasswordResult;
|
||||
|
|
@ -13,6 +14,8 @@ import it.cnr.isti.workflow.manager.auth.model.UserRole;
|
|||
import it.cnr.isti.workflow.manager.auth.model.UserView;
|
||||
import it.cnr.isti.workflow.manager.auth.repo.AuthRepository;
|
||||
import it.cnr.isti.workflow.manager.auth.repo.LoginEntity;
|
||||
import it.cnr.isti.workflow.manager.executions.repo.ExecutionRepository;
|
||||
import it.cnr.isti.workflow.manager.flows.repo.FlowRepository;
|
||||
|
||||
import static it.cnr.isti.workflow.manager.auth.services.PasswordHasher.*;
|
||||
|
||||
|
|
@ -23,8 +26,14 @@ public class AuthService {
|
|||
@Autowired
|
||||
AuthRepository authRepository;
|
||||
|
||||
@Autowired
|
||||
FlowRepository flowRepository;
|
||||
|
||||
@Autowired
|
||||
ExecutionRepository executionRepository;
|
||||
|
||||
public boolean validateUser(String username, String password) throws UsernameNotFoundException {
|
||||
LoginEntity le = authRepository.findById(username).orElse(null);
|
||||
LoginEntity le = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
|
||||
if (le == null) {
|
||||
throw new UsernameNotFoundException("User not found");
|
||||
}
|
||||
|
|
@ -52,7 +61,7 @@ public class AuthService {
|
|||
}
|
||||
|
||||
public ChangePasswordResult changePassword(String username, String oldPassword, String newPassword) {
|
||||
LoginEntity user = authRepository.findById(username).orElse(null);
|
||||
LoginEntity user = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
|
||||
if (user == null) {
|
||||
return ChangePasswordResult.USER_NOT_FOUND;
|
||||
}
|
||||
|
|
@ -68,7 +77,7 @@ public class AuthService {
|
|||
}
|
||||
|
||||
public AdminChangePasswordResult adminChangePassword(String username, String newPassword) {
|
||||
LoginEntity user = authRepository.findById(username).orElse(null);
|
||||
LoginEntity user = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
|
||||
if (user == null) {
|
||||
return AdminChangePasswordResult.USER_NOT_FOUND;
|
||||
}
|
||||
|
|
@ -80,20 +89,25 @@ public class AuthService {
|
|||
return AdminChangePasswordResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeleteUserResult deleteUser(String username) {
|
||||
LoginEntity user = authRepository.findById(username).orElse(null);
|
||||
LoginEntity user = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
|
||||
if (user == null) {
|
||||
return DeleteUserResult.USER_NOT_FOUND;
|
||||
}
|
||||
if (user.effectiveRole() == UserRole.ADMIN && authRepository.countByRole(UserRole.ADMIN) <= 1) {
|
||||
if (user.effectiveRole() == UserRole.ADMIN && authRepository.countByRoleAndActiveTrue(UserRole.ADMIN) <= 1) {
|
||||
return DeleteUserResult.LAST_ADMIN;
|
||||
}
|
||||
authRepository.deleteById(username);
|
||||
|
||||
executionRepository.deleteByOwner(username);
|
||||
flowRepository.deleteAll(flowRepository.findByOwnerAndFinalizedFalse(username));
|
||||
user.setActive(false);
|
||||
authRepository.save(user);
|
||||
return DeleteUserResult.SUCCESS;
|
||||
}
|
||||
|
||||
public ChangeUserRoleResult changeUserRole(String username, UserRole role) {
|
||||
LoginEntity user = authRepository.findById(username).orElse(null);
|
||||
LoginEntity user = authRepository.findByUsernameAndActiveTrue(username).orElse(null);
|
||||
if (user == null) {
|
||||
return ChangeUserRoleResult.USER_NOT_FOUND;
|
||||
}
|
||||
|
|
@ -102,7 +116,7 @@ public class AuthService {
|
|||
}
|
||||
if (user.effectiveRole() == UserRole.ADMIN
|
||||
&& role != UserRole.ADMIN
|
||||
&& authRepository.countByRole(UserRole.ADMIN) <= 1) {
|
||||
&& authRepository.countByRoleAndActiveTrue(UserRole.ADMIN) <= 1) {
|
||||
return ChangeUserRoleResult.LAST_ADMIN;
|
||||
}
|
||||
user.setRole(role);
|
||||
|
|
@ -111,14 +125,14 @@ public class AuthService {
|
|||
}
|
||||
|
||||
public List<UserView> listUsers() {
|
||||
return authRepository.findAll().stream()
|
||||
return authRepository.findByActiveTrue().stream()
|
||||
.map(user -> new UserView(user.getUsername(), user.getEmail(), user.effectiveRole()))
|
||||
.sorted(java.util.Comparator.comparing(UserView::username))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public LoginEntity getUser(String username) {
|
||||
return authRepository.findById(username).orElse(null);
|
||||
return authRepository.findByUsernameAndActiveTrue(username).orElse(null);
|
||||
}
|
||||
|
||||
private boolean isValidPassword(String password) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package it.cnr.isti.workflow.manager.auth.services;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@Service
|
||||
public class TurnstileService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TurnstileService.class);
|
||||
|
||||
private final WebClient.Builder webClientBuilder;
|
||||
private final boolean enabled;
|
||||
private final String secret;
|
||||
private final String verifyUrl;
|
||||
|
||||
public TurnstileService(WebClient.Builder webClientBuilder,
|
||||
@Value("${app.turnstile.enabled:false}") boolean enabled,
|
||||
@Value("${app.turnstile.secret:}") String secret,
|
||||
@Value("${app.turnstile.verify-url:https://challenges.cloudflare.com/turnstile/v0/siteverify}") String verifyUrl) {
|
||||
this.webClientBuilder = Objects.requireNonNull(webClientBuilder, "webClientBuilder cannot be null");
|
||||
this.enabled = enabled;
|
||||
this.secret = secret;
|
||||
this.verifyUrl = verifyUrl;
|
||||
}
|
||||
|
||||
public boolean verifyRegistrationToken(String token) {
|
||||
if (!enabled) {
|
||||
return true;
|
||||
}
|
||||
if (!StringUtils.hasText(secret)) {
|
||||
logger.error("Turnstile validation is enabled but no secret is configured");
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
TurnstileVerificationResponse response = webClientBuilder.build()
|
||||
.post()
|
||||
.uri(verifyUrl)
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body(BodyInserters.fromFormData("secret", secret)
|
||||
.with("response", token))
|
||||
.retrieve()
|
||||
.bodyToMono(TurnstileVerificationResponse.class)
|
||||
.block();
|
||||
return response != null && Boolean.TRUE.equals(response.success());
|
||||
} catch (RuntimeException exception) {
|
||||
logger.warn("Turnstile verification failed", exception);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private record TurnstileVerificationResponse(Boolean success) {
|
||||
}
|
||||
}
|
||||
|
|
@ -52,6 +52,9 @@ public class UserImportComponent {
|
|||
user.getUsername()),
|
||||
() -> {
|
||||
logger.debug("User {} not found, saving new user", user.getUsername());
|
||||
if (user.getActive() == null) {
|
||||
user.setActive(true);
|
||||
}
|
||||
user.setPassword(PasswordHasher.hashPassword(user.getPassword()));
|
||||
authRepository.save(user);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import it.cnr.isti.workflow.manager.auth.model.ChangePasswordRequest;
|
|||
import it.cnr.isti.workflow.manager.auth.model.CreateUserResult;
|
||||
import it.cnr.isti.workflow.manager.auth.model.DeleteUserResult;
|
||||
import it.cnr.isti.workflow.manager.auth.services.AuthService;
|
||||
import it.cnr.isti.workflow.manager.auth.services.TurnstileService;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
|
@ -45,6 +46,9 @@ public class AuthController {
|
|||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
@Autowired
|
||||
private TurnstileService turnstileService;
|
||||
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "Login", description = "Authenticates a user and returns a JWT token on success.")
|
||||
public ResponseEntity<?> login(@RequestBody AuthRequest request) {
|
||||
|
|
@ -76,6 +80,9 @@ public class AuthController {
|
|||
if (request.getUsername() == null || request.getPassword() == null || request.getEmail() == null) {
|
||||
return ResponseEntity.badRequest().body("Username, password and email are required");
|
||||
}
|
||||
if (!turnstileService.verifyRegistrationToken(request.getTurnstileToken())) {
|
||||
return ResponseEntity.badRequest().body("INVALID_TURNSTILE");
|
||||
}
|
||||
if (request.getUsername().length() < 3) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body("Username must be at least 3 characters");
|
||||
|
|
|
|||
|
|
@ -2,5 +2,9 @@ package it.cnr.isti.workflow.manager.executions.repo;
|
|||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ExecutionRepository extends JpaRepository<ExecutionEntity, String> {
|
||||
List<ExecutionEntity> findByOwner(String owner);
|
||||
void deleteByOwner(String owner);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,4 +15,8 @@ public interface FlowRepository extends JpaRepository<FlowEntity, String> {
|
|||
List<FlowEntity> findFlowsByOwnerOrPublic(@Param("owner") String owner);
|
||||
|
||||
java.util.Optional<FlowEntity> findByOwnerAndName(String owner, String name);
|
||||
|
||||
List<FlowEntity> findByOwner(String owner);
|
||||
|
||||
List<FlowEntity> findByOwnerAndFinalizedFalse(String owner);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,5 +37,8 @@ app.assistant.default-model=${ASSISTANT_DEFAULT_MODEL:gemma3:12b}
|
|||
cors.allowed-origins=${CORS_ALLOWED_ORIGINS:http://localhost:4200}
|
||||
app.import.path=${IMPORT_PATH:/workflow-editor-init}
|
||||
app.import.enabled=true
|
||||
app.turnstile.enabled=${TURNSTILE_ENABLED:false}
|
||||
app.turnstile.secret=${TURNSTILE_SECRET:}
|
||||
app.turnstile.verify-url=${TURNSTILE_VERIFY_URL:https://challenges.cloudflare.com/turnstile/v0/siteverify}
|
||||
logging.level.it.cnr.isti.workflow.manager=DEBUG
|
||||
logging.level.root=ERROR
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
[
|
||||
{
|
||||
"username": "testuser",
|
||||
"password": "testpassword",
|
||||
"email": "testuser@example.com"
|
||||
}
|
||||
{
|
||||
"username": "testuser",
|
||||
"password": "testpassword",
|
||||
"email": "testuser@example.com"
|
||||
},
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "Adminpass1!",
|
||||
"email": "admin@example.com",
|
||||
"role": "ADMIN"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package it.cnr.isti.workflow.manager.controllers;
|
|||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
|
@ -12,6 +14,7 @@ import org.springframework.http.ResponseEntity;
|
|||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
|
|
@ -24,24 +27,50 @@ import it.cnr.isti.workflow.manager.auth.model.ChangePasswordRequest;
|
|||
import it.cnr.isti.workflow.manager.auth.model.UserRole;
|
||||
import it.cnr.isti.workflow.manager.auth.repo.AuthRepository;
|
||||
import it.cnr.isti.workflow.manager.auth.repo.LoginEntity;
|
||||
import it.cnr.isti.workflow.manager.auth.services.TurnstileService;
|
||||
import it.cnr.isti.workflow.manager.executions.persistence.ExecutionSnapshot;
|
||||
import it.cnr.isti.workflow.manager.executions.repo.ExecutionEntity;
|
||||
import it.cnr.isti.workflow.manager.executions.repo.ExecutionRepository;
|
||||
import it.cnr.isti.workflow.manager.executions.ExecutionStatus;
|
||||
import it.cnr.isti.workflow.manager.flows.model.FlowData;
|
||||
import it.cnr.isti.workflow.manager.flows.repo.FlowEntity;
|
||||
import it.cnr.isti.workflow.manager.flows.repo.FlowRepository;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@TestPropertySource(locations = "classpath:test.properties")
|
||||
public class AuthControllerTest {
|
||||
|
||||
private static String uniqueSuffix() {
|
||||
return java.util.UUID.randomUUID().toString().substring(0, 8);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private AuthController authController;
|
||||
|
||||
@Autowired
|
||||
private AuthRepository authRepository;
|
||||
|
||||
@Autowired
|
||||
private FlowRepository flowRepository;
|
||||
|
||||
@Autowired
|
||||
private ExecutionRepository executionRepository;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@MockitoBean
|
||||
private TurnstileService turnstileService;
|
||||
|
||||
@BeforeEach
|
||||
public void configureTurnstile() {
|
||||
Mockito.when(turnstileService.verifyRegistrationToken(Mockito.any())).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogin() {
|
||||
ResponseEntity<?> response = authController.login(new AuthRequest("testuser", "testpassword", "testuser@example.com"));
|
||||
|
|
@ -53,22 +82,32 @@ public class AuthControllerTest {
|
|||
|
||||
@Test
|
||||
public void testChangePassword() {
|
||||
String suffix = uniqueSuffix();
|
||||
String username = "changepwd-" + suffix;
|
||||
String oldPassword = "Startpass1!";
|
||||
String newPassword = "Newpassword1!";
|
||||
authController.register(new AuthRequest(username, oldPassword, username + "@example.com"));
|
||||
|
||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||
request.setUsername("testuser");
|
||||
request.setOldPassword("testpassword");
|
||||
request.setNewPassword("Newpassword1!");
|
||||
request.setUsername(username);
|
||||
request.setOldPassword(oldPassword);
|
||||
request.setNewPassword(newPassword);
|
||||
|
||||
ResponseEntity<?> response = authController.changePassword(request);
|
||||
assert response.getStatusCode().is2xxSuccessful();
|
||||
|
||||
ResponseEntity<?> loginResponse = authController.login(new AuthRequest("testuser", "Newpassword1!", "testuser@example.com"));
|
||||
ResponseEntity<?> loginResponse = authController.login(new AuthRequest(username, newPassword, username + "@example.com"));
|
||||
assert loginResponse.getStatusCode().is2xxSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangePasswordReturnsUnauthorizedForInvalidCurrentPassword() {
|
||||
String suffix = uniqueSuffix();
|
||||
String username = "wrongold-" + suffix;
|
||||
authController.register(new AuthRequest(username, "Startpass1!", username + "@example.com"));
|
||||
|
||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||
request.setUsername("testuser");
|
||||
request.setUsername(username);
|
||||
request.setOldPassword("wrong-password");
|
||||
request.setNewPassword("Anotherpass1!");
|
||||
|
||||
|
|
@ -78,10 +117,12 @@ public class AuthControllerTest {
|
|||
|
||||
@Test
|
||||
public void testChangePasswordReturnsInvalidNewPasswordForWeakPassword() {
|
||||
authController.register(new AuthRequest("weakpwduser", "Startpass1!", "weakpwduser@example.com"));
|
||||
String suffix = uniqueSuffix();
|
||||
String username = "weakpwduser-" + suffix;
|
||||
authController.register(new AuthRequest(username, "Startpass1!", username + "@example.com"));
|
||||
|
||||
ChangePasswordRequest request = new ChangePasswordRequest();
|
||||
request.setUsername("weakpwduser");
|
||||
request.setUsername(username);
|
||||
request.setOldPassword("Startpass1!");
|
||||
request.setNewPassword("weakpass");
|
||||
|
||||
|
|
@ -90,11 +131,28 @@ public class AuthControllerTest {
|
|||
assert "INVALID_NEW_PASSWORD".equals(response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerRejectsInvalidTurnstile() {
|
||||
Mockito.when(turnstileService.verifyRegistrationToken(Mockito.any())).thenReturn(false);
|
||||
|
||||
ResponseEntity<?> response = authController.register(
|
||||
new AuthRequest("turnstile-user-" + uniqueSuffix(), "Validpass1!", "turnstile-user@example.com"));
|
||||
|
||||
assert response.getStatusCode() == HttpStatus.BAD_REQUEST;
|
||||
assert "INVALID_TURNSTILE".equals(response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adminCanCreateListChangePasswordAndDeleteUsers() {
|
||||
LoginEntity admin = new LoginEntity("adminuser",
|
||||
String suffix = uniqueSuffix();
|
||||
String adminUsername = "adminuser-" + suffix;
|
||||
String managedUsername = "manageduser-" + suffix;
|
||||
String managedEmail = managedUsername + "@example.com";
|
||||
String executionId = "managed-exec-" + suffix;
|
||||
|
||||
LoginEntity admin = new LoginEntity(adminUsername,
|
||||
it.cnr.isti.workflow.manager.auth.services.PasswordHasher.hashPassword("Adminpass1!"),
|
||||
"adminuser@example.com",
|
||||
adminUsername + "@example.com",
|
||||
UserRole.ADMIN);
|
||||
authRepository.save(admin);
|
||||
|
||||
|
|
@ -104,34 +162,81 @@ public class AuthControllerTest {
|
|||
java.util.List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))));
|
||||
try {
|
||||
ResponseEntity<?> createResponse = authController.createUser(
|
||||
new AdminCreateUserRequest("manageduser", "Managedpass1!", "manageduser@example.com", UserRole.USER));
|
||||
new AdminCreateUserRequest(managedUsername, "Managedpass1!", managedEmail, UserRole.USER));
|
||||
assert createResponse.getStatusCode().is2xxSuccessful();
|
||||
|
||||
ResponseEntity<?> listResponse = authController.listUsers();
|
||||
assert listResponse.getStatusCode().is2xxSuccessful();
|
||||
assert listResponse.getBody() instanceof java.util.List<?>;
|
||||
assert ((java.util.List<?>) listResponse.getBody()).stream()
|
||||
.anyMatch(item -> item.toString().contains("manageduser") && item.toString().contains("manageduser@example.com"));
|
||||
.anyMatch(item -> item.toString().contains(managedUsername) && item.toString().contains(managedEmail));
|
||||
|
||||
ResponseEntity<?> passwordResponse = authController.adminChangePassword(
|
||||
"manageduser",
|
||||
managedUsername,
|
||||
new AdminChangeUserPasswordRequest("Changedpass1!"));
|
||||
assert passwordResponse.getStatusCode().is2xxSuccessful();
|
||||
|
||||
ResponseEntity<?> roleResponse = authController.adminChangeUserRole(
|
||||
"manageduser",
|
||||
managedUsername,
|
||||
new AdminChangeUserRoleRequest(UserRole.ADMIN));
|
||||
assert roleResponse.getStatusCode().is2xxSuccessful();
|
||||
assert authRepository.findById("manageduser").orElseThrow().effectiveRole() == UserRole.ADMIN;
|
||||
assert authRepository.findById(managedUsername).orElseThrow().effectiveRole() == UserRole.ADMIN;
|
||||
|
||||
ResponseEntity<?> loginResponse = authController.login(new AuthRequest("manageduser", "Changedpass1!", "manageduser@example.com"));
|
||||
ResponseEntity<?> loginResponse = authController.login(new AuthRequest(managedUsername, "Changedpass1!", managedEmail));
|
||||
assert loginResponse.getStatusCode().is2xxSuccessful();
|
||||
assert loginResponse.getBody() instanceof java.util.Map<?, ?>;
|
||||
assert "ADMIN".equals(((java.util.Map<?, ?>) loginResponse.getBody()).get("role"));
|
||||
assert "manageduser@example.com".equals(((java.util.Map<?, ?>) loginResponse.getBody()).get("email"));
|
||||
assert managedEmail.equals(((java.util.Map<?, ?>) loginResponse.getBody()).get("email"));
|
||||
|
||||
ResponseEntity<?> deleteResponse = authController.deleteUser("manageduser");
|
||||
FlowEntity draftFlow = flowRepository.save(FlowEntity.builder()
|
||||
.name("Managed Draft")
|
||||
.owner(managedUsername)
|
||||
.description("draft")
|
||||
.published(true)
|
||||
.finalized(false)
|
||||
.createdAt(java.time.LocalDateTime.now().minusDays(1))
|
||||
.lastUpdateAt(java.time.LocalDateTime.now().minusHours(1))
|
||||
.flow(FlowData.builder().build())
|
||||
.build());
|
||||
FlowEntity finalizedFlow = flowRepository.save(FlowEntity.builder()
|
||||
.name("Managed Final")
|
||||
.owner(managedUsername)
|
||||
.description("final")
|
||||
.published(true)
|
||||
.finalized(true)
|
||||
.createdAt(java.time.LocalDateTime.now().minusDays(2))
|
||||
.lastUpdateAt(java.time.LocalDateTime.now().minusHours(2))
|
||||
.flow(FlowData.builder().build())
|
||||
.build());
|
||||
executionRepository.save(ExecutionEntity.builder()
|
||||
.id(executionId)
|
||||
.name("Managed Execution")
|
||||
.owner(managedUsername)
|
||||
.creationTime(100L)
|
||||
.lastUpdateTime(200L)
|
||||
.flow(FlowData.builder().build())
|
||||
.snapshot(ExecutionSnapshot.builder()
|
||||
.status(ExecutionStatus.SUCCESS)
|
||||
.providedAuthorizations(java.util.Map.of())
|
||||
.build())
|
||||
.build());
|
||||
|
||||
ResponseEntity<?> deleteResponse = authController.deleteUser(managedUsername);
|
||||
assert deleteResponse.getStatusCode().is2xxSuccessful();
|
||||
|
||||
ResponseEntity<?> deletedLoginResponse = authController.login(
|
||||
new AuthRequest(managedUsername, "Changedpass1!", managedEmail));
|
||||
assert deletedLoginResponse.getStatusCode() == HttpStatus.NOT_FOUND;
|
||||
|
||||
ResponseEntity<?> listAfterDelete = authController.listUsers();
|
||||
assert listAfterDelete.getStatusCode().is2xxSuccessful();
|
||||
assert listAfterDelete.getBody() instanceof java.util.List<?>;
|
||||
assert ((java.util.List<?>) listAfterDelete.getBody()).stream()
|
||||
.noneMatch(item -> item.toString().contains(managedUsername));
|
||||
|
||||
assert executionRepository.findByOwner(managedUsername).isEmpty();
|
||||
assert flowRepository.findById(draftFlow.getId()).isEmpty();
|
||||
assert flowRepository.findById(finalizedFlow.getId()).isPresent();
|
||||
} finally {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
|
@ -143,9 +248,10 @@ public class AuthControllerTest {
|
|||
.filter(user -> user.effectiveRole() == UserRole.ADMIN)
|
||||
.map(LoginEntity::getUsername)
|
||||
.forEach(authRepository::deleteById);
|
||||
LoginEntity admin = new LoginEntity("soloadmin",
|
||||
String username = "soloadmin-" + uniqueSuffix();
|
||||
LoginEntity admin = new LoginEntity(username,
|
||||
it.cnr.isti.workflow.manager.auth.services.PasswordHasher.hashPassword("Adminpass1!"),
|
||||
"soloadmin@example.com",
|
||||
username + "@example.com",
|
||||
UserRole.ADMIN);
|
||||
authRepository.save(admin);
|
||||
|
||||
|
|
@ -155,7 +261,7 @@ public class AuthControllerTest {
|
|||
java.util.List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))));
|
||||
try {
|
||||
ResponseEntity<?> roleResponse = authController.adminChangeUserRole(
|
||||
"soloadmin",
|
||||
username,
|
||||
new AdminChangeUserRoleRequest(UserRole.USER));
|
||||
assert roleResponse.getStatusCode() == HttpStatus.CONFLICT;
|
||||
assert "LAST_ADMIN".equals(roleResponse.getBody());
|
||||
|
|
@ -170,9 +276,10 @@ public class AuthControllerTest {
|
|||
.filter(user -> user.effectiveRole() == UserRole.ADMIN)
|
||||
.map(LoginEntity::getUsername)
|
||||
.forEach(authRepository::deleteById);
|
||||
LoginEntity admin = new LoginEntity("onlyadmin",
|
||||
String username = "onlyadmin-" + uniqueSuffix();
|
||||
LoginEntity admin = new LoginEntity(username,
|
||||
it.cnr.isti.workflow.manager.auth.services.PasswordHasher.hashPassword("Adminpass1!"),
|
||||
"onlyadmin@example.com",
|
||||
username + "@example.com",
|
||||
UserRole.ADMIN);
|
||||
authRepository.save(admin);
|
||||
|
||||
|
|
@ -181,7 +288,7 @@ public class AuthControllerTest {
|
|||
null,
|
||||
java.util.List.of(new SimpleGrantedAuthority("ROLE_ADMIN"))));
|
||||
try {
|
||||
ResponseEntity<?> deleteResponse = authController.deleteUser("onlyadmin");
|
||||
ResponseEntity<?> deleteResponse = authController.deleteUser(username);
|
||||
assert deleteResponse.getStatusCode() == HttpStatus.CONFLICT;
|
||||
assert "LAST_ADMIN".equals(deleteResponse.getBody());
|
||||
} finally {
|
||||
|
|
@ -191,16 +298,17 @@ public class AuthControllerTest {
|
|||
|
||||
@Test
|
||||
public void registerRejectsInvalidEmail() {
|
||||
ResponseEntity<?> response = authController.register(new AuthRequest("mailuser", "Validpass1!", "not-an-email"));
|
||||
ResponseEntity<?> response = authController.register(new AuthRequest("mailuser-" + uniqueSuffix(), "Validpass1!", "not-an-email"));
|
||||
assert response.getStatusCode() == HttpStatus.BAD_REQUEST;
|
||||
assert "INVALID_EMAIL".equals(response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adminEndpointsAcceptJwtAdminRole() throws Exception {
|
||||
LoginEntity admin = new LoginEntity("jwtadmin",
|
||||
String username = "jwtadmin-" + uniqueSuffix();
|
||||
LoginEntity admin = new LoginEntity(username,
|
||||
it.cnr.isti.workflow.manager.auth.services.PasswordHasher.hashPassword("Adminpass1!"),
|
||||
"jwtadmin@example.com",
|
||||
username + "@example.com",
|
||||
UserRole.ADMIN);
|
||||
authRepository.save(admin);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ spring.jpa.properties.jakarta.persistence.validation.mode=none
|
|||
app.db.init.enabled=true
|
||||
app.assistant.default-model=assistant-test-model
|
||||
app.mcp.bridge.url=http://localhost:18080
|
||||
app.turnstile.enabled=false
|
||||
|
||||
app.import.path=src/test/resources/workflow-editor-init
|
||||
app.import.enabled=true
|
||||
|
|
|
|||
Loading…
Reference in New Issue