2024-01-03 17:59:04 +00:00
|
|
|
package stirling.software.SPDF.config.security;
|
|
|
|
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Optional;
|
|
|
|
import java.util.UUID;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
2024-05-12 19:58:34 +02:00
|
|
|
import org.springframework.context.MessageSource;
|
|
|
|
import org.springframework.context.i18n.LocaleContextHolder;
|
2024-01-03 17:59:04 +00:00
|
|
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
|
|
import org.springframework.security.core.Authentication;
|
|
|
|
import org.springframework.security.core.GrantedAuthority;
|
|
|
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
|
|
import org.springframework.security.core.userdetails.UserDetails;
|
|
|
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
|
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
2024-05-12 19:58:34 +02:00
|
|
|
import stirling.software.SPDF.model.AuthenticationType;
|
2024-01-03 17:59:04 +00:00
|
|
|
import stirling.software.SPDF.model.Authority;
|
|
|
|
import stirling.software.SPDF.model.Role;
|
|
|
|
import stirling.software.SPDF.model.User;
|
2024-05-02 14:52:50 -06:00
|
|
|
import stirling.software.SPDF.repository.AuthorityRepository;
|
2024-01-03 17:59:04 +00:00
|
|
|
import stirling.software.SPDF.repository.UserRepository;
|
|
|
|
|
|
|
|
@Service
|
|
|
|
public class UserService implements UserServiceInterface {
|
|
|
|
|
|
|
|
@Autowired private UserRepository userRepository;
|
|
|
|
|
2024-05-02 14:52:50 -06:00
|
|
|
@Autowired private AuthorityRepository authorityRepository;
|
|
|
|
|
2024-01-03 17:59:04 +00:00
|
|
|
@Autowired private PasswordEncoder passwordEncoder;
|
|
|
|
|
2024-05-12 19:58:34 +02:00
|
|
|
@Autowired private MessageSource messageSource;
|
|
|
|
|
2024-04-29 15:01:22 -06:00
|
|
|
// Handle OAUTH2 login and user auto creation.
|
|
|
|
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser) {
|
2024-05-12 19:58:34 +02:00
|
|
|
if (!isUsernameValidWithReturn(username).equals(username)) {
|
|
|
|
return false;
|
|
|
|
}
|
2024-04-29 15:01:22 -06:00
|
|
|
Optional<User> existUser = userRepository.findByUsernameIgnoreCase(username);
|
|
|
|
if (existUser.isPresent()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (autoCreateUser) {
|
2024-05-12 19:58:34 +02:00
|
|
|
saveUser(username, AuthenticationType.OAUTH2);
|
2024-04-29 15:01:22 -06:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-01-03 17:59:04 +00:00
|
|
|
public Authentication getAuthentication(String apiKey) {
|
|
|
|
User user = getUserByApiKey(apiKey);
|
|
|
|
if (user == null) {
|
|
|
|
throw new UsernameNotFoundException("API key is not valid");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the user into an Authentication object
|
|
|
|
return new UsernamePasswordAuthenticationToken(
|
|
|
|
user, // principal (typically the user)
|
|
|
|
null, // credentials (we don't expose the password or API key here)
|
|
|
|
getAuthorities(user) // user's authorities (roles/permissions)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
|
|
|
|
// Convert each Authority object into a SimpleGrantedAuthority object.
|
|
|
|
return user.getAuthorities().stream()
|
|
|
|
.map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority()))
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
}
|
|
|
|
|
|
|
|
private String generateApiKey() {
|
|
|
|
String apiKey;
|
|
|
|
do {
|
|
|
|
apiKey = UUID.randomUUID().toString();
|
|
|
|
} while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness
|
|
|
|
return apiKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
public User addApiKeyToUser(String username) {
|
|
|
|
User user =
|
|
|
|
userRepository
|
2024-04-14 23:07:03 +02:00
|
|
|
.findByUsernameIgnoreCase(username)
|
2024-01-03 17:59:04 +00:00
|
|
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
|
|
|
|
|
|
|
user.setApiKey(generateApiKey());
|
|
|
|
return userRepository.save(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
public User refreshApiKeyForUser(String username) {
|
|
|
|
return addApiKeyToUser(username); // reuse the add API key method for refreshing
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getApiKeyForUser(String username) {
|
|
|
|
User user =
|
|
|
|
userRepository
|
2024-04-14 23:07:03 +02:00
|
|
|
.findByUsernameIgnoreCase(username)
|
2024-01-03 17:59:04 +00:00
|
|
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
|
|
|
return user.getApiKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isValidApiKey(String apiKey) {
|
|
|
|
return userRepository.findByApiKey(apiKey) != null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public User getUserByApiKey(String apiKey) {
|
|
|
|
return userRepository.findByApiKey(apiKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
public UserDetails loadUserByApiKey(String apiKey) {
|
|
|
|
User userOptional = userRepository.findByApiKey(apiKey);
|
|
|
|
if (userOptional != null) {
|
|
|
|
User user = userOptional;
|
|
|
|
// Convert your User entity to a UserDetails object with authorities
|
|
|
|
return new org.springframework.security.core.userdetails.User(
|
|
|
|
user.getUsername(),
|
|
|
|
user.getPassword(), // you might not need this for API key auth
|
|
|
|
getAuthorities(user));
|
|
|
|
}
|
|
|
|
return null; // or throw an exception
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean validateApiKeyForUser(String username, String apiKey) {
|
2024-04-14 23:07:03 +02:00
|
|
|
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
|
2024-01-03 17:59:04 +00:00
|
|
|
return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey);
|
|
|
|
}
|
|
|
|
|
2024-05-12 19:58:34 +02:00
|
|
|
public void saveUser(String username, AuthenticationType authenticationType)
|
|
|
|
throws IllegalArgumentException {
|
2024-01-03 17:59:04 +00:00
|
|
|
User user = new User();
|
2024-05-12 19:58:34 +02:00
|
|
|
user.setUsername(isUsernameValidWithReturn(username));
|
|
|
|
user.setEnabled(true);
|
|
|
|
user.setFirstLogin(false);
|
|
|
|
user.addAuthority(new Authority(Role.USER.getRoleId(), user));
|
|
|
|
user.setAuthenticationType(authenticationType);
|
|
|
|
userRepository.save(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void saveUser(String username, String password) throws IllegalArgumentException {
|
|
|
|
User user = new User();
|
|
|
|
user.setUsername(isUsernameValidWithReturn(username));
|
2024-01-03 17:59:04 +00:00
|
|
|
user.setPassword(passwordEncoder.encode(password));
|
|
|
|
user.setEnabled(true);
|
2024-05-12 19:58:34 +02:00
|
|
|
user.setAuthenticationType(AuthenticationType.WEB);
|
2024-01-03 17:59:04 +00:00
|
|
|
userRepository.save(user);
|
|
|
|
}
|
|
|
|
|
2024-05-12 19:58:34 +02:00
|
|
|
public void saveUser(String username, String password, String role, boolean firstLogin)
|
|
|
|
throws IllegalArgumentException {
|
2024-01-03 17:59:04 +00:00
|
|
|
User user = new User();
|
2024-05-12 19:58:34 +02:00
|
|
|
user.setUsername(isUsernameValidWithReturn(username));
|
2024-01-03 17:59:04 +00:00
|
|
|
user.setPassword(passwordEncoder.encode(password));
|
|
|
|
user.addAuthority(new Authority(role, user));
|
|
|
|
user.setEnabled(true);
|
2024-05-12 19:58:34 +02:00
|
|
|
user.setAuthenticationType(AuthenticationType.WEB);
|
2024-01-03 17:59:04 +00:00
|
|
|
user.setFirstLogin(firstLogin);
|
|
|
|
userRepository.save(user);
|
|
|
|
}
|
|
|
|
|
2024-05-12 19:58:34 +02:00
|
|
|
public void saveUser(String username, String password, String role)
|
|
|
|
throws IllegalArgumentException {
|
2024-01-03 17:59:04 +00:00
|
|
|
User user = new User();
|
2024-05-12 19:58:34 +02:00
|
|
|
user.setUsername(isUsernameValidWithReturn(username));
|
2024-01-03 17:59:04 +00:00
|
|
|
user.setPassword(passwordEncoder.encode(password));
|
|
|
|
user.addAuthority(new Authority(role, user));
|
|
|
|
user.setEnabled(true);
|
2024-05-12 19:58:34 +02:00
|
|
|
user.setAuthenticationType(AuthenticationType.WEB);
|
2024-01-03 17:59:04 +00:00
|
|
|
user.setFirstLogin(false);
|
|
|
|
userRepository.save(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void deleteUser(String username) {
|
2024-04-14 23:07:03 +02:00
|
|
|
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
|
2024-01-03 17:59:04 +00:00
|
|
|
if (userOpt.isPresent()) {
|
|
|
|
for (Authority authority : userOpt.get().getAuthorities()) {
|
|
|
|
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
userRepository.delete(userOpt.get());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean usernameExists(String username) {
|
|
|
|
return userRepository.findByUsername(username).isPresent();
|
|
|
|
}
|
|
|
|
|
2024-04-14 23:07:03 +02:00
|
|
|
public boolean usernameExistsIgnoreCase(String username) {
|
|
|
|
return userRepository.findByUsernameIgnoreCase(username).isPresent();
|
|
|
|
}
|
|
|
|
|
2024-01-03 17:59:04 +00:00
|
|
|
public boolean hasUsers() {
|
|
|
|
return userRepository.count() > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void updateUserSettings(String username, Map<String, String> updates) {
|
2024-04-14 23:07:03 +02:00
|
|
|
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
|
2024-01-03 17:59:04 +00:00
|
|
|
if (userOpt.isPresent()) {
|
|
|
|
User user = userOpt.get();
|
|
|
|
Map<String, String> settingsMap = user.getSettings();
|
|
|
|
|
|
|
|
if (settingsMap == null) {
|
|
|
|
settingsMap = new HashMap<String, String>();
|
|
|
|
}
|
|
|
|
settingsMap.clear();
|
|
|
|
settingsMap.putAll(updates);
|
|
|
|
user.setSettings(settingsMap);
|
|
|
|
|
|
|
|
userRepository.save(user);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Optional<User> findByUsername(String username) {
|
|
|
|
return userRepository.findByUsername(username);
|
|
|
|
}
|
|
|
|
|
2024-03-13 23:09:16 +01:00
|
|
|
public Optional<User> findByUsernameIgnoreCase(String username) {
|
|
|
|
return userRepository.findByUsernameIgnoreCase(username);
|
|
|
|
}
|
|
|
|
|
2024-05-02 14:52:50 -06:00
|
|
|
public Authority findRole(User user) {
|
|
|
|
return authorityRepository.findByUserId(user.getId());
|
|
|
|
}
|
|
|
|
|
2024-05-12 19:58:34 +02:00
|
|
|
public void changeUsername(User user, String newUsername) throws IllegalArgumentException {
|
|
|
|
user.setUsername(isUsernameValidWithReturn(newUsername));
|
2024-01-03 17:59:04 +00:00
|
|
|
userRepository.save(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void changePassword(User user, String newPassword) {
|
|
|
|
user.setPassword(passwordEncoder.encode(newPassword));
|
|
|
|
userRepository.save(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void changeFirstUse(User user, boolean firstUse) {
|
|
|
|
user.setFirstLogin(firstUse);
|
|
|
|
userRepository.save(user);
|
|
|
|
}
|
|
|
|
|
2024-05-02 14:52:50 -06:00
|
|
|
public void changeRole(User user, String newRole) {
|
|
|
|
Authority userAuthority = this.findRole(user);
|
|
|
|
userAuthority.setAuthority(newRole);
|
|
|
|
authorityRepository.save(userAuthority);
|
|
|
|
}
|
|
|
|
|
2024-01-03 17:59:04 +00:00
|
|
|
public boolean isPasswordCorrect(User user, String currentPassword) {
|
|
|
|
return passwordEncoder.matches(currentPassword, user.getPassword());
|
|
|
|
}
|
2024-03-13 23:09:16 +01:00
|
|
|
|
|
|
|
public boolean isUsernameValid(String username) {
|
2024-05-12 19:58:34 +02:00
|
|
|
// Checks whether the simple username is formatted correctly
|
|
|
|
boolean isValidSimpleUsername =
|
|
|
|
username.matches("^[a-zA-Z0-9][a-zA-Z0-9@._+-]*[a-zA-Z0-9]$");
|
|
|
|
// Checks whether the email address is formatted correctly
|
|
|
|
boolean isValidEmail =
|
|
|
|
username.matches(
|
|
|
|
"^(?=.{1,64}@)[A-Za-z0-9]+(\\.[A-Za-z0-9_+.-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$");
|
|
|
|
return isValidSimpleUsername || isValidEmail;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String isUsernameValidWithReturn(String username) throws IllegalArgumentException {
|
|
|
|
if (!isUsernameValid(username)) {
|
|
|
|
String message =
|
|
|
|
messageSource.getMessage(
|
|
|
|
"invalidUsernameMessage", null, LocaleContextHolder.getLocale());
|
|
|
|
throw new IllegalArgumentException(message);
|
|
|
|
}
|
|
|
|
return username;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean hasPassword(String username) {
|
|
|
|
Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
|
|
|
|
if (user.isPresent() && user.get().hasPassword()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isAuthenticationTypeByUsername(
|
|
|
|
String username, AuthenticationType authenticationType) {
|
|
|
|
Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
|
|
|
|
if (user.isPresent() && user.get().getAuthenticationType() != null) {
|
|
|
|
return user.get().getAuthenticationType().equalsIgnoreCase(authenticationType.name());
|
|
|
|
}
|
|
|
|
return false;
|
2024-03-13 23:09:16 +01:00
|
|
|
}
|
2024-01-03 17:59:04 +00:00
|
|
|
}
|