1
0
mirror of https://github.com/Frooodle/Stirling-PDF.git synced 2025-03-30 00:17:49 +01:00

introduces custom settings file ()

* Introducing a custom settings file

* formats

* chnages

* Update README.md
This commit is contained in:
Anthony Stirling 2024-05-03 20:43:48 +01:00 committed by GitHub
parent fbbc71d7e6
commit 890163053b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 272 additions and 253 deletions

View File

@ -244,6 +244,8 @@ metrics:
enabled: true # 'true' to enable Info APIs endpoints (view http://localhost:8080/swagger-ui/index.html#/API to learn more), 'false' to disable enabled: true # 'true' to enable Info APIs endpoints (view http://localhost:8080/swagger-ui/index.html#/API to learn more), 'false' to disable
``` ```
There is an additional config file ``/configs/custom_settings.yml`` were users familiar with java and spring application.properties can input their own settings on-top of Stirling-PDFs existing ones
### Extra notes ### Extra notes
- Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md) - Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)

View File

@ -5,6 +5,8 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -65,14 +67,36 @@ public class SPdfApplication {
SpringApplication app = new SpringApplication(SPdfApplication.class); SpringApplication app = new SpringApplication(SPdfApplication.class);
app.addInitializers(new ConfigInitializer()); app.addInitializers(new ConfigInitializer());
Map<String, String> propertyFiles = new HashMap<>();
// stirling pdf settings file
if (Files.exists(Paths.get("configs/settings.yml"))) { if (Files.exists(Paths.get("configs/settings.yml"))) {
app.setDefaultProperties( propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
Collections.singletonMap(
"spring.config.additional-location", "file:configs/settings.yml"));
} else { } else {
logger.warn( logger.warn(
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); "External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
} }
// custom javs settings file
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
String existing = propertyFiles.getOrDefault("spring.config.additional-location", "");
if (!existing.isEmpty()) {
existing += ",";
}
propertyFiles.put(
"spring.config.additional-location",
existing + "file:configs/custom_settings.yml");
} else {
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
}
if (!propertyFiles.isEmpty()) {
app.setDefaultProperties(
Collections.singletonMap(
"spring.config.additional-location",
propertyFiles.get("spring.config.additional-location")));
}
app.run(args); app.run(args);
try { try {

View File

@ -1,20 +1,20 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.BufferedReader;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.regex.Matcher;
import java.util.stream.Collectors; import java.util.regex.Pattern;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@ -26,12 +26,12 @@ public class ConfigInitializer
public void initialize(ConfigurableApplicationContext applicationContext) { public void initialize(ConfigurableApplicationContext applicationContext) {
try { try {
ensureConfigExists(); ensureConfigExists();
} catch (IOException e) { } catch (Exception e) {
throw new RuntimeException("Failed to initialize application configuration", e); throw new RuntimeException("Failed to initialize application configuration", e);
} }
} }
public void ensureConfigExists() throws IOException { public void ensureConfigExists() throws IOException, URISyntaxException {
// Define the path to the external config directory // Define the path to the external config directory
Path destPath = Paths.get("configs", "settings.yml"); Path destPath = Paths.get("configs", "settings.yml");
@ -51,170 +51,132 @@ public class ConfigInitializer
} }
} }
} else { } else {
// If user file exists, we need to merge it with the template from the classpath Path templatePath =
List<String> templateLines; Paths.get(
try (InputStream in = getClass()
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { .getClassLoader()
templateLines = .getResource("settings.yml.template")
new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)) .toURI());
.lines() Path userPath = Paths.get("configs", "settings.yml");
.collect(Collectors.toList());
List<String> templateLines = Files.readAllLines(templatePath);
List<String> userLines =
Files.exists(userPath) ? Files.readAllLines(userPath) : new ArrayList<>();
Map<String, String> templateEntries = extractEntries(templateLines);
Map<String, String> userEntries = extractEntries(userLines);
List<String> mergedLines = mergeConfigs(templateLines, templateEntries, userEntries);
mergedLines = cleanInvalidYamlEntries(mergedLines);
Files.write(userPath, mergedLines);
} }
mergeYamlFiles(templateLines, destPath, destPath); Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
if (!Files.exists(customSettingsPath)) {
Files.createFile(customSettingsPath);
} }
} }
public void mergeYamlFiles(List<String> templateLines, Path userFilePath, Path outputPath) private static Map<String, String> extractEntries(List<String> lines) {
throws IOException { Map<String, String> entries = new HashMap<>();
List<String> userLines = Files.readAllLines(userFilePath); String keyRegex = "^\\s*(\\w+)\\s*:\\s*(.*)"; // Capture key and value
Pattern pattern = Pattern.compile(keyRegex);
for (String line : lines) {
Matcher matcher = pattern.matcher(line);
if (matcher.find() && !line.trim().startsWith("#")) {
String key = matcher.group(1).trim();
String value = matcher.group(2).trim(); // Capture the value directly
entries.put(key, line);
}
}
return entries;
}
private static List<String> mergeConfigs(
List<String> templateLines,
Map<String, String> templateEntries,
Map<String, String> userEntries) {
List<String> mergedLines = new ArrayList<>(); List<String> mergedLines = new ArrayList<>();
boolean insideAutoGenerated = false; Set<String> handledKeys = new HashSet<>();
boolean beforeFirstKey = true;
Function<String, Boolean> isCommented = line -> line.trim().startsWith("#"); for (String line : templateLines) {
Function<String, String> extractKey = String cleanLine = line.split("#")[0].trim();
line -> { if (!cleanLine.isEmpty() && cleanLine.contains(":")) {
String[] parts = line.split(":"); String key = cleanLine.split(":")[0].trim();
return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : ""; if (userEntries.containsKey(key)) {
}; // Always use user's entry if exists
mergedLines.add(userEntries.get(key));
handledKeys.add(key);
} else {
// Use template's entry if no user entry
mergedLines.add(line);
}
} else {
// Add comments and other lines directly
mergedLines.add(line);
}
}
Function<String, Integer> getIndentationLevel = // Add user entries not present in the template at the end
line -> { for (String key : userEntries.keySet()) {
if (!handledKeys.contains(key)) {
mergedLines.add(userEntries.get(key));
}
}
return mergedLines;
}
private static List<String> cleanInvalidYamlEntries(List<String> lines) {
List<String> cleanedLines = new ArrayList<>();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
String trimmedLine = line.trim();
// Ignore commented lines and lines that don't look like key-only entries
if (trimmedLine.startsWith("#")
|| !trimmedLine.endsWith(":")
|| trimmedLine.contains(" ")) {
cleanedLines.add(line);
continue;
}
// For potential key-only lines, check the next line to determine context
if (isKeyWithoutChildrenOrValue(i, lines)) {
// Skip adding the current line since it's a key without any following value or
// children
continue;
}
cleanedLines.add(line);
}
return cleanedLines;
}
private static boolean isKeyWithoutChildrenOrValue(int currentIndex, List<String> lines) {
if (currentIndex + 1 < lines.size()) {
String currentLine = lines.get(currentIndex);
String nextLine = lines.get(currentIndex + 1);
int currentIndentation = getIndentationLevel(currentLine);
int nextIndentation = getIndentationLevel(nextLine);
// If the next line is less or equally indented, it's not a child or value
return nextIndentation <= currentIndentation;
}
// If it's the last line, then it definitely has no children or value
return true;
}
private static int getIndentationLevel(String line) {
int count = 0; int count = 0;
for (char ch : line.toCharArray()) { for (char ch : line.toCharArray()) {
if (ch == ' ') count++; if (ch == ' ') count++;
else break; else break;
} }
return count; return count;
};
Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
for (String line : templateLines) {
String key = extractKey.apply(line);
if ("AutomaticallyGenerated:".equalsIgnoreCase(line.trim())) {
insideAutoGenerated = true;
mergedLines.add(line);
continue;
} else if (insideAutoGenerated && line.trim().isEmpty()) {
insideAutoGenerated = false;
mergedLines.add(line);
continue;
}
if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) {
// Handle top comments and empty lines before the first key.
mergedLines.add(line);
continue;
}
if (!key.isEmpty()) beforeFirstKey = false;
if (userKeys.contains(key)) {
// If user has any version (commented or uncommented) of this key, skip the
// template line
Optional<String> userValue =
userLines.stream()
.filter(
l ->
extractKey.apply(l).equalsIgnoreCase(key)
&& !isCommented.apply(l))
.findFirst();
if (userValue.isPresent()) mergedLines.add(userValue.get());
continue;
}
if (isCommented.apply(line) || line.trim().isEmpty() || !userKeys.contains(key)) {
mergedLines.add(
line); // If line is commented, empty or key not present in user's file,
// retain the
// template line
continue;
}
}
// Add any additional uncommented user lines that are not present in the
// template
for (String userLine : userLines) {
String userKey = extractKey.apply(userLine);
boolean isPresentInTemplate =
templateLines.stream()
.map(extractKey)
.anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey));
if (!isPresentInTemplate && !isCommented.apply(userLine)) {
if (!childOfTemplateEntry(
isCommented,
extractKey,
getIndentationLevel,
userLines,
userLine,
templateLines)) {
// check if userLine is a child of a entry within templateLines or not, if child
// of parent in templateLines then dont add to mergedLines, if anything else
// then add
mergedLines.add(userLine);
}
}
}
Files.write(outputPath, mergedLines, StandardCharsets.UTF_8);
}
// New method to check if a userLine is a child of an entry in templateLines
boolean childOfTemplateEntry(
Function<String, Boolean> isCommented,
Function<String, String> extractKey,
Function<String, Integer> getIndentationLevel,
List<String> userLines,
String userLine,
List<String> templateLines) {
String userKey = extractKey.apply(userLine).trim();
int userIndentation = getIndentationLevel.apply(userLine);
// Start by assuming the line is not a child of an entry in templateLines
boolean isChild = false;
// Iterate backwards through userLines from the current line to find any parent
for (int i = userLines.indexOf(userLine) - 1; i >= 0; i--) {
String potentialParentLine = userLines.get(i);
int parentIndentation = getIndentationLevel.apply(potentialParentLine);
// Check if we've reached a potential parent based on indentation
if (parentIndentation < userIndentation) {
String parentKey = extractKey.apply(potentialParentLine).trim();
// Now, check if this potential parent or any of its parents exist in templateLines
boolean parentExistsInTemplate =
templateLines.stream()
.filter(line -> !isCommented.apply(line)) // Skip commented lines
.anyMatch(
templateLine -> {
String templateKey =
extractKey.apply(templateLine).trim();
return parentKey.equalsIgnoreCase(templateKey);
});
if (!parentExistsInTemplate) {
// If the parent does not exist in template, check the next level parent
userIndentation =
parentIndentation; // Update userIndentation to the parent's indentation
// for next iteration
if (parentIndentation == 0) {
// If we've reached the top-level parent and it's not in template, the
// original line is considered not a child
isChild = false;
break;
}
} else {
// If any parent exists in template, the original line is considered a child
isChild = true;
break;
}
}
}
return isChild; // Return true if the line is not a child of any entry in templateLines
} }
} }

View File

@ -2,41 +2,37 @@ package stirling.software.SPDF.config.security;
import java.io.IOException; import java.io.IOException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.ServletException;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler import jakarta.servlet.ServletException;
{ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
@Bean @Bean
public SessionRegistry sessionRegistry() { public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl(); return new SessionRegistryImpl();
} }
@Override @Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException public void onLogoutSuccess(
{ HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
HttpSession session = request.getSession(false); HttpSession session = request.getSession(false);
if (session != null) { if (session != null) {
String sessionId = session.getId(); String sessionId = session.getId();
sessionRegistry() sessionRegistry().removeSessionInformation(sessionId);
.removeSessionInformation(
sessionId);
} }
if(request.getParameter("oauth2AutoCreateDisabled") != null) if (request.getParameter("oauth2AutoCreateDisabled") != null) {
{ response.sendRedirect(
response.sendRedirect(request.getContextPath()+"/login?error=oauth2AutoCreateDisabled"); request.getContextPath() + "/login?error=oauth2AutoCreateDisabled");
} } else {
else
{
response.sendRedirect(request.getContextPath() + "/login?logout=true"); response.sendRedirect(request.getContextPath() + "/login?logout=true");
} }
} }

View File

@ -50,7 +50,7 @@ public class InitialSecuritySetup {
@PostConstruct @PostConstruct
public void initSecretKey() throws IOException { public void initSecretKey() throws IOException {
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
if (secretKey == null || secretKey.isEmpty()) { if (!isValidUUID(secretKey)) {
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
saveKeyToConfig(secretKey); saveKeyToConfig(secretKey);
} }
@ -85,4 +85,16 @@ public class InitialSecuritySetup {
// Write back to the file // Write back to the file
Files.write(path, lines); Files.write(path, lines);
} }
private boolean isValidUUID(String uuid) {
if (uuid == null) {
return false;
}
try {
UUID.fromString(uuid);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
} }

View File

@ -1,8 +1,8 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import jakarta.servlet.ServletException; import java.io.IOException;
import jakarta.servlet.http.HttpServletRequest; import java.util.*;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -24,6 +24,8 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
@ -33,17 +35,15 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
import stirling.software.SPDF.repository.JPATokenRepositoryImpl; import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
import java.io.IOException;
import java.util.*;
@Configuration @Configuration
@EnableWebSecurity() @EnableWebSecurity()
@EnableMethodSecurity @EnableMethodSecurity
@ -109,7 +109,7 @@ public class SecurityConfiguration {
logout -> logout ->
logout.logoutRequestMatcher( logout.logoutRequestMatcher(
new AntPathRequestMatcher("/logout")) new AntPathRequestMatcher("/logout"))
.logoutSuccessHandler(new CustomLogoutSuccessHandler()) // Use a Custom Logout Handler to handle custom error message if OAUTH2 Auto Create is disabled .logoutSuccessHandler(new CustomLogoutSuccessHandler())
.invalidateHttpSession(true) // Invalidate session .invalidateHttpSession(true) // Invalidate session
.deleteCookies("JSESSIONID", "remember-me") .deleteCookies("JSESSIONID", "remember-me")
.addLogoutHandler( .addLogoutHandler(
@ -167,32 +167,44 @@ public class SecurityConfiguration {
// Handle OAUTH2 Logins // Handle OAUTH2 Logins
if (applicationProperties.getSecurity().getOAUTH2().getEnabled()) { if (applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
http.oauth2Login( oauth2 -> oauth2 http.oauth2Login(
.loginPage("/oauth2") oauth2 ->
oauth2.loginPage("/oauth2")
/* /*
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database. This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser' If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
is set as true, else login fails with an error message advising the same. is set as true, else login fails with an error message advising the same.
*/ */
.successHandler(new AuthenticationSuccessHandler() { .successHandler(
new AuthenticationSuccessHandler() {
@Override @Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, public void onAuthenticationSuccess(
Authentication authentication) throws ServletException , IOException{ HttpServletRequest request,
OAuth2User oauthUser = (OAuth2User) authentication.getPrincipal(); HttpServletResponse response,
if (userService.processOAuth2PostLogin(oauthUser.getAttribute("email"), applicationProperties.getSecurity().getOAUTH2().getAutoCreateUser())) { Authentication authentication)
throws ServletException, IOException {
OAuth2User oauthUser =
(OAuth2User)
authentication
.getPrincipal();
if (userService.processOAuth2PostLogin(
oauthUser.getAttribute("email"),
applicationProperties
.getSecurity()
.getOAUTH2()
.getAutoCreateUser())) {
response.sendRedirect("/"); response.sendRedirect("/");
} } else {
else{ response.sendRedirect(
response.sendRedirect("/logout?oauth2AutoCreateDisabled=true"); "/logout?oauth2AutoCreateDisabled=true");
} }
} }
} })
)
// Add existing Authorities from the database // Add existing Authorities from the database
.userInfoEndpoint( userInfoEndpoint -> .userInfoEndpoint(
userInfoEndpoint.userAuthoritiesMapper(userAuthoritiesMapper()) userInfoEndpoint ->
) userInfoEndpoint.userAuthoritiesMapper(
); userAuthoritiesMapper())));
} }
} else { } else {
http.csrf(csrf -> csrf.disable()) http.csrf(csrf -> csrf.disable())
@ -204,13 +216,17 @@ public class SecurityConfiguration {
// Client Registration Repository for OAUTH2 OIDC Login // Client Registration Repository for OAUTH2 OIDC Login
@Bean @Bean
@ConditionalOnProperty(value = "security.oauth2.enabled" , havingValue = "true", matchIfMissing = false) @ConditionalOnProperty(
value = "security.oauth2.enabled",
havingValue = "true",
matchIfMissing = false)
public ClientRegistrationRepository clientRegistrationRepository() { public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(this.oidcClientRegistration()); return new InMemoryClientRegistrationRepository(this.oidcClientRegistration());
} }
private ClientRegistration oidcClientRegistration() { private ClientRegistration oidcClientRegistration() {
return ClientRegistrations.fromOidcIssuerLocation(applicationProperties.getSecurity().getOAUTH2().getIssuer()) return ClientRegistrations.fromOidcIssuerLocation(
applicationProperties.getSecurity().getOAUTH2().getIssuer())
.registrationId("oidc") .registrationId("oidc")
.clientId(applicationProperties.getSecurity().getOAUTH2().getClientId()) .clientId(applicationProperties.getSecurity().getOAUTH2().getClientId())
.clientSecret(applicationProperties.getSecurity().getOAUTH2().getClientSecret()) .clientSecret(applicationProperties.getSecurity().getOAUTH2().getClientSecret())
@ -225,25 +241,30 @@ public class SecurityConfiguration {
This is required for the internal; 'hasRole()' function to give out the correct role. This is required for the internal; 'hasRole()' function to give out the correct role.
*/ */
@Bean @Bean
@ConditionalOnProperty(value = "security.oauth2.enabled" , havingValue = "true", matchIfMissing = false) @ConditionalOnProperty(
value = "security.oauth2.enabled",
havingValue = "true",
matchIfMissing = false)
GrantedAuthoritiesMapper userAuthoritiesMapper() { GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> { return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>(); Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> { authorities.forEach(
authority -> {
// Add existing OAUTH2 Authorities // Add existing OAUTH2 Authorities
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority())); mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
// Add Authorities from database for existing user, if user is present. // Add Authorities from database for existing user, if user is present.
if (authority instanceof OAuth2UserAuthority oauth2Auth) { if (authority instanceof OAuth2UserAuthority oauth2Auth) {
Optional<User> userOpt = userService.findByUsernameIgnoreCase((String)oauth2Auth.getAttributes().get("email")); Optional<User> userOpt =
userService.findByUsernameIgnoreCase(
(String) oauth2Auth.getAttributes().get("email"));
if (userOpt.isPresent()) { if (userOpt.isPresent()) {
User user = userOpt.get(); User user = userOpt.get();
if (user != null){ if (user != null) {
mappedAuthorities.add(new SimpleGrantedAuthority( mappedAuthorities.add(
userService new SimpleGrantedAuthority(
.findRole(user) userService.findRole(user).getAuthority()));
.getAuthority()));
} }
} }
} }

View File

@ -44,7 +44,7 @@ public class UserService implements UserServiceInterface {
user.setUsername(username); user.setUsername(username);
user.setEnabled(true); user.setEnabled(true);
user.setFirstLogin(false); user.setFirstLogin(false);
user.addAuthority(new Authority( Role.USER.getRoleId(), user)); user.addAuthority(new Authority(Role.USER.getRoleId(), user));
userRepository.save(user); userRepository.save(user);
return true; return true;
} }

View File

@ -6,7 +6,6 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
@ -39,7 +38,8 @@ public class AccountWebController {
return "redirect:/"; return "redirect:/";
} }
model.addAttribute("oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled()); model.addAttribute(
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
model.addAttribute("currentPage", "login"); model.addAttribute("currentPage", "login");

View File

@ -271,7 +271,7 @@ public class ApplicationProperties {
+ ", clientId=" + ", clientId="
+ clientId + clientId
+ ", clientSecret=" + ", clientSecret="
+ (clientSecret!= null && !clientSecret.isEmpty() ? "MASKED" : "NULL") + (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
+ ", autoCreateUser=" + ", autoCreateUser="
+ autoCreateUser + autoCreateUser
+ "]"; + "]";

View File

@ -11,5 +11,4 @@ public interface AuthorityRepository extends JpaRepository<Authority, Long> {
Set<Authority> findByUser_Username(String username); Set<Authority> findByUser_Username(String username);
Authority findByUserId(long user_id); Authority findByUserId(long user_id);
} }

View File

@ -20,7 +20,6 @@ system:
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes) enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
showUpdate: true # see when a new update is available showUpdate: true # see when a new update is available
showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true' showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
customHTMLFiles: false # Enable to have files placed in /customFiles/templates override the existing template html files
#ui: #ui:
# appName: exampleAppName # Application's visible name # appName: exampleAppName # Application's visible name
@ -33,3 +32,7 @@ endpoints:
metrics: metrics:
enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable
# Automatically Generated Settings (Do Not Edit Directly)
AutomaticallyGenerated:
key: example