Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

357 lines
14 KiB
Java
Raw Normal View History

2024-01-03 17:59:04 +00:00
package stirling.software.SPDF.config.security;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
2024-01-03 17:59:04 +00:00
import org.springframework.beans.factory.annotation.Autowired;
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.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation;
2024-01-03 17:59:04 +00:00
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User;
2024-01-03 17:59:04 +00:00
import org.springframework.stereotype.Service;
import stirling.software.SPDF.config.DatabaseBackupInterface;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
2024-01-03 17:59:04 +00:00
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
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;
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;
@Autowired private AuthorityRepository authorityRepository;
2024-01-03 17:59:04 +00:00
@Autowired private PasswordEncoder passwordEncoder;
@Autowired private MessageSource messageSource;
@Autowired private SessionPersistentRegistry sessionRegistry;
@Autowired DatabaseBackupInterface databaseBackupHelper;
// Handle OAUTH2 login and user auto creation.
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
return false;
}
Optional<User> existingUser = findByUsernameIgnoreCase(username);
if (existingUser.isPresent()) {
return true;
}
if (autoCreateUser) {
saveUser(username, AuthenticationType.OAUTH2);
return true;
}
return false;
}
2024-01-03 17:59:04 +00:00
public Authentication getAuthentication(String apiKey) {
Optional<User> user = getUserByApiKey(apiKey);
if (!user.isPresent()) {
2024-01-03 17:59:04 +00:00
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.get()) // user's authorities (roles/permissions)
2024-01-03 17:59:04 +00:00
);
}
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).isPresent()); // Ensure uniqueness
2024-01-03 17:59:04 +00:00
return apiKey;
}
public User addApiKeyToUser(String username) {
Optional<User> user = findByUsernameIgnoreCase(username);
if (user.isPresent()) {
user.get().setApiKey(generateApiKey());
return userRepository.save(user.get());
}
throw new UsernameNotFoundException("User not found");
2024-01-03 17:59:04 +00:00
}
public User refreshApiKeyForUser(String username) {
return addApiKeyToUser(username); // reuse the add API key method for refreshing
}
public String getApiKeyForUser(String username) {
User user =
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).isPresent();
2024-01-03 17:59:04 +00:00
}
public Optional<User> getUserByApiKey(String apiKey) {
2024-01-03 17:59:04 +00:00
return userRepository.findByApiKey(apiKey);
}
public Optional<User> loadUserByApiKey(String apiKey) {
Optional<User> user = userRepository.findByApiKey(apiKey);
if (user.isPresent()) {
return user;
2024-01-03 17:59:04 +00:00
}
return null; // or throw an exception
}
public boolean validateApiKeyForUser(String username, String apiKey) {
Optional<User> userOpt = findByUsernameIgnoreCase(username);
return userOpt.isPresent() && apiKey.equals(userOpt.get().getApiKey());
2024-01-03 17:59:04 +00:00
}
public void saveUser(String username, AuthenticationType authenticationType)
throws IllegalArgumentException, IOException {
saveUser(username, authenticationType, Role.USER.getRoleId());
}
public void saveUser(String username, AuthenticationType authenticationType, String role)
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
throw new IllegalArgumentException(getInvalidUsernameMessage());
}
2024-01-03 17:59:04 +00:00
User user = new User();
user.setUsername(username);
user.setEnabled(true);
user.setFirstLogin(false);
user.addAuthority(new Authority(role, user));
user.setAuthenticationType(authenticationType);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
}
public void saveUser(String username, String password)
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
throw new IllegalArgumentException(getInvalidUsernameMessage());
}
User user = new User();
user.setUsername(username);
2024-01-03 17:59:04 +00:00
user.setPassword(passwordEncoder.encode(password));
user.setEnabled(true);
user.setAuthenticationType(AuthenticationType.WEB);
2024-01-03 17:59:04 +00:00
userRepository.save(user);
databaseBackupHelper.exportDatabase();
2024-01-03 17:59:04 +00:00
}
public void saveUser(String username, String password, String role, boolean firstLogin)
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
throw new IllegalArgumentException(getInvalidUsernameMessage());
}
2024-01-03 17:59:04 +00:00
User user = new User();
user.setUsername(username);
2024-01-03 17:59:04 +00:00
user.setPassword(passwordEncoder.encode(password));
user.addAuthority(new Authority(role, user));
user.setEnabled(true);
user.setAuthenticationType(AuthenticationType.WEB);
2024-01-03 17:59:04 +00:00
user.setFirstLogin(firstLogin);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
2024-01-03 17:59:04 +00:00
}
public void saveUser(String username, String password, String role)
throws IllegalArgumentException, IOException {
saveUser(username, password, role, false);
2024-01-03 17:59:04 +00:00
}
public void deleteUser(String username) {
Optional<User> userOpt = 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());
}
invalidateUserSessions(username);
2024-01-03 17:59:04 +00:00
}
public boolean usernameExists(String username) {
return findByUsername(username).isPresent();
2024-01-03 17:59:04 +00:00
}
public boolean usernameExistsIgnoreCase(String username) {
return findByUsernameIgnoreCase(username).isPresent();
}
2024-01-03 17:59:04 +00:00
public boolean hasUsers() {
long userCount = userRepository.count();
if (findByUsernameIgnoreCase(Role.INTERNAL_API_USER.getRoleId()).isPresent()) {
userCount -= 1;
}
return userCount > 0;
2024-01-03 17:59:04 +00:00
}
public void updateUserSettings(String username, Map<String, String> updates)
throws IOException {
Optional<User> userOpt = findByUsernameIgnoreCaseWithSettings(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<>();
2024-01-03 17:59:04 +00:00
}
settingsMap.clear();
settingsMap.putAll(updates);
user.setSettings(settingsMap);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
2024-01-03 17:59:04 +00:00
}
}
public Optional<User> findByUsername(String username) {
return userRepository.findByUsername(username);
}
public Optional<User> findByUsernameIgnoreCase(String username) {
return userRepository.findByUsernameIgnoreCase(username);
}
public Optional<User> findByUsernameIgnoreCaseWithSettings(String username) {
return userRepository.findByUsernameIgnoreCaseWithSettings(username);
}
public Authority findRole(User user) {
return authorityRepository.findByUserId(user.getId());
}
public void changeUsername(User user, String newUsername)
throws IllegalArgumentException, IOException {
if (!isUsernameValid(newUsername)) {
throw new IllegalArgumentException(getInvalidUsernameMessage());
}
user.setUsername(newUsername);
2024-01-03 17:59:04 +00:00
userRepository.save(user);
databaseBackupHelper.exportDatabase();
2024-01-03 17:59:04 +00:00
}
public void changePassword(User user, String newPassword) throws IOException {
2024-01-03 17:59:04 +00:00
user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user);
databaseBackupHelper.exportDatabase();
2024-01-03 17:59:04 +00:00
}
public void changeFirstUse(User user, boolean firstUse) throws IOException {
2024-01-03 17:59:04 +00:00
user.setFirstLogin(firstUse);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
2024-01-03 17:59:04 +00:00
}
public void changeRole(User user, String newRole) throws IOException {
Authority userAuthority = this.findRole(user);
userAuthority.setAuthority(newRole);
authorityRepository.save(userAuthority);
databaseBackupHelper.exportDatabase();
}
public void changeUserEnabled(User user, Boolean enbeled) throws IOException {
user.setEnabled(enbeled);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
}
2024-01-03 17:59:04 +00:00
public boolean isPasswordCorrect(User user, String currentPassword) {
return passwordEncoder.matches(currentPassword, user.getPassword());
}
public boolean isUsernameValid(String username) {
// 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;
}
private String getInvalidUsernameMessage() {
return messageSource.getMessage(
"invalidUsernameMessage", null, LocaleContextHolder.getLocale());
}
public boolean hasPassword(String username) {
Optional<User> user = findByUsernameIgnoreCase(username);
return user.isPresent() && user.get().hasPassword();
}
public boolean isAuthenticationTypeByUsername(
String username, AuthenticationType authenticationType) {
Optional<User> user = findByUsernameIgnoreCase(username);
return user.isPresent()
&& authenticationType.name().equalsIgnoreCase(user.get().getAuthenticationType());
}
public boolean isUserDisabled(String username) {
Optional<User> userOpt = findByUsernameIgnoreCase(username);
return userOpt.map(user -> !user.isEnabled()).orElse(false);
}
public void invalidateUserSessions(String username) {
String usernameP = "";
for (Object principal : sessionRegistry.getAllPrincipals()) {
for (SessionInformation sessionsInformation :
sessionRegistry.getAllSessions(principal, false)) {
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
usernameP = userDetails.getUsername();
} else if (principal instanceof OAuth2User) {
OAuth2User oAuth2User = (OAuth2User) principal;
usernameP = oAuth2User.getName();
} else if (principal instanceof String) {
usernameP = (String) principal;
}
if (usernameP.equalsIgnoreCase(username)) {
sessionRegistry.expireSession(sessionsInformation.getSessionId());
}
}
}
}
public String getCurrentUsername() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
} else {
return principal.toString();
}
}
2024-01-03 17:59:04 +00:00
}