Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change: method write and read settings.yml #1441 #1463

Merged
merged 2 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ sourceCompatibility = '17'

repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}

licenseReport {
Expand Down Expand Up @@ -74,7 +75,7 @@ spotless {
java {
target project.fileTree('src/main/java')

googleJavaFormat('1.19.1').aosp().reorderImports(false)
googleJavaFormat('1.22.0').aosp().reorderImports(false)

importOrder('java', 'javax', 'org', 'com', 'net', 'io')
toggleOffOn()
Expand All @@ -93,6 +94,7 @@ dependencies {
implementation("io.github.pixee:java-security-toolkit:1.1.3")

implementation 'org.yaml:snakeyaml:2.2'
implementation 'com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4'

// Exclude Tomcat and include Jetty
implementation('org.springframework.boot:spring-boot-starter-web:3.2.4') {
Expand Down
165 changes: 84 additions & 81 deletions src/main/java/stirling/software/SPDF/config/ConfigInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,26 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;

import org.simpleyaml.configuration.comments.CommentType;
import org.simpleyaml.configuration.file.YamlFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class ConfigInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {

private static final Logger logger = LoggerFactory.getLogger(ConfigInitializer.class);

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Expand Down Expand Up @@ -44,95 +53,89 @@ public void ensureConfigExists() throws IOException, URISyntaxException {
}
}
} else {
// Path templatePath =
// Paths.get(
// getClass()
// .getClassLoader()
// .getResource("settings.yml.template")
// .toURI());
// Path userPath = Paths.get("configs", "settings.yml");
//
// List<String> templateLines = Files.readAllLines(templatePath);
// List<String> userLines =
// Files.exists(userPath) ? Files.readAllLines(userPath) : new
// ArrayList<>();
//
// List<String> resultLines = new ArrayList<>();
// int position = 0;
// for (String templateLine : templateLines) {
// // Check if the line is a comment
// if (templateLine.trim().startsWith("#")) {
// String entry = templateLine.trim().substring(1).trim();
// if (!entry.isEmpty()) {
// // Check if this comment has been uncommented in userLines
// String key = entry.split(":")[0].trim();
// addLine(resultLines, userLines, templateLine, key, position);
// } else {
// resultLines.add(templateLine);
// }
// }
// // Check if the line is a key-value pair
// else if (templateLine.contains(":")) {
// String key = templateLine.split(":")[0].trim();
// addLine(resultLines, userLines, templateLine, key, position);
// }
// // Handle empty lines
// else if (templateLine.trim().length() == 0) {
// resultLines.add("");
// }
// position++;
// }
//
// // Write the result to the user settings file
// Files.write(userPath, resultLines);
}

Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
if (!Files.exists(customSettingsPath)) {
Files.createFile(customSettingsPath);
}
}
// Define the path to the config settings file
Path settingsPath = Paths.get("configs", "settings.yml");
// Load the template resource
URL settingsTemplateResource =
getClass().getClassLoader().getResource("settings.yml.template");
if (settingsTemplateResource == null) {
throw new IOException("Resource not found: settings.yml.template");
}

// Create a temporary file to copy the resource content
Path tempTemplatePath = Files.createTempFile("settings.yml", ".template");

try (InputStream in = settingsTemplateResource.openStream()) {
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
}

final YamlFile settingsTemplateFile = new YamlFile(tempTemplatePath.toFile());
settingsTemplateFile.loadWithComments();

// TODO check parent value instead of just indent lines for duplicate keys (like enabled etc)
private static void addLine(
List<String> resultLines,
List<String> userLines,
String templateLine,
String key,
int position) {
boolean added = false;
int templateIndentationLevel = getIndentationLevel(templateLine);
int pos = 0;
for (String settingsLine : userLines) {
if (settingsLine.trim().startsWith(key + ":") && position == pos) {
int settingsIndentationLevel = getIndentationLevel(settingsLine);
// Check if it is correct settingsLine and has the same parent as templateLine
if (settingsIndentationLevel == templateIndentationLevel) {
resultLines.add(settingsLine);
added = true;
break;
final YamlFile settingsFile = new YamlFile(settingsPath.toFile());
settingsFile.loadWithComments();

// Load headers and comments
String header = settingsTemplateFile.getHeader();

// Create a new file for temporary settings
final YamlFile tempSettingFile = new YamlFile(settingsPath.toFile());
tempSettingFile.createNewFile(true);
tempSettingFile.setHeader(header);

// Get all keys from the template
List<String> keys =
Arrays.asList(settingsTemplateFile.getKeys(true).toArray(new String[0]));

for (String key : keys) {
if (!key.contains(".")) {
// Add blank lines and comments to specific sections
tempSettingFile
.path(key)
.comment(settingsTemplateFile.getComment(key))
.blankLine();
continue;
}
// Copy settings from the template to the settings.yml file
changeConfigItemFromCommentToKeyValue(
settingsTemplateFile, settingsFile, tempSettingFile, key);
}
pos++;

// Save the settings.yml file
tempSettingFile.save();
}
if (!added) {
resultLines.add(templateLine);

// Create custom settings file if it doesn't exist
Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
if (!Files.exists(customSettingsPath)) {
Files.createFile(customSettingsPath);
}
}

private static int getIndentationLevel(String line) {
int indentationLevel = 0;
String trimmedLine = line.trim();
if (trimmedLine.startsWith("#")) {
line = trimmedLine.substring(1);
}
for (char c : line.toCharArray()) {
if (c == ' ') {
indentationLevel++;
} else {
break;
}
private void changeConfigItemFromCommentToKeyValue(
final YamlFile settingsTemplateFile,
final YamlFile settingsFile,
final YamlFile tempSettingFile,
String path) {
if (settingsFile.get(path) == null && settingsTemplateFile.get(path) != null) {
// If the key is only in the template, add it to the temporary settings with comments
tempSettingFile
.path(path)
.set(settingsTemplateFile.get(path))
.comment(settingsTemplateFile.getComment(path, CommentType.BLOCK))
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
} else if (settingsFile.get(path) != null && settingsTemplateFile.get(path) != null) {
// If the key is in both, update the temporary settings with the main settings' value
// and comments
tempSettingFile
.path(path)
.set(settingsFile.get(path))
.comment(settingsTemplateFile.getComment(path, CommentType.BLOCK))
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
} else {
// Log if the key is not found in both YAML files
logger.info("Key not found in both YAML files: " + path);
}
return indentationLevel;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package stirling.software.SPDF.config.security;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;

import org.simpleyaml.configuration.file.YamlFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -87,32 +86,16 @@ private void initializeInternalApiUser() {

private void saveKeyToConfig(String key) throws IOException {
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
List<String> lines = Files.readAllLines(path);
boolean keyFound = false;

// Search for the existing key to replace it or place to add it
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).startsWith("AutomaticallyGenerated:")) {
keyFound = true;
if (i + 1 < lines.size() && lines.get(i + 1).trim().startsWith("key:")) {
lines.set(i + 1, " key: " + key);
break;
} else {
lines.add(i + 1, " key: " + key);
break;
}
}
}

// If the section doesn't exist, append it
if (!keyFound) {
lines.add("# Automatically Generated Settings (Do Not Edit Directly)");
lines.add("AutomaticallyGenerated:");
lines.add(" key: " + key);
}
final YamlFile settingsYml = new YamlFile(path.toFile());

settingsYml.loadWithComments();

// Write back to the file
Files.write(path, lines);
settingsYml
.path("AutomaticallyGenerated.key")
.set(key)
.comment("# Automatically Generated Settings (Do Not Edit Directly)");
settingsYml.save();
}

private boolean isValidUUID(String uuid) {
Expand Down
82 changes: 46 additions & 36 deletions src/main/resources/settings.yml.template
Original file line number Diff line number Diff line change
@@ -1,54 +1,64 @@
# Welcome to settings file
# Remove comment marker # if on start of line to enable the configuration
# If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME
#############################################################################################################
# Welcome to settings file from #
# ____ _____ ___ ____ _ ___ _ _ ____ ____ ____ _____ #
# / ___|_ _|_ _| _ \| | |_ _| \ | |/ ___| | _ \| _ \| ___| #
# \___ \ | | | || |_) | | | || \| | | _ _____| |_) | | | | |_ #
# ___) || | | || _ <| |___ | || |\ | |_| |_____| __/| |_| | _| #
# |____/ |_| |___|_| \_\_____|___|_| \_|\____| |_| |____/|_| #
# #
# Do not comment out any entry, it will be removed on next startup #
# If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME #
#############################################################################################################


security:
enableLogin: false # set to 'true' to enable login
csrfDisabled: true # Set to 'true' to disable CSRF protection (not recommended for production)
loginAttemptCount: 5 # lock user account after 5 tries
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
# initialLogin:
# username: "admin" # Initial username for the first login
# password: "stirling" # Initial password for the first login
# oauth2:
# enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
# issuer: "" # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
# clientId: "" # Client ID from your provider
# clientSecret: "" # Client Secret from your provider
# autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
# client:
# google:
# clientId: "" # Client ID for Google OAuth2
# clientSecret: "" # Client Secret for Google OAuth2
# scopes: "https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile" # Scopes for Google OAuth2
# useAsUsername: "email" # Field to use as the username for Google OAuth2
# github:
# clientId: "" # Client ID for GitHub OAuth2
# clientSecret: "" # Client Secret for GitHub OAuth2
# scopes: "read:user" # Scope for GitHub OAuth2
# useAsUsername: "login" # Field to use as the username for GitHub OAuth2
# keycloak:
# issuer: "http://192.168.0.123:8888/realms/stirling-pdf" # URL of the Keycloak realm's OpenID Connect Discovery endpoint
# clientId: "stirling-pdf" # Client ID for Keycloak OAuth2
# clientSecret: "" # Client Secret for Keycloak OAuth2
# scopes: "openid, profile, email" # Scopes for Keycloak OAuth2
# useAsUsername: "email" # Field to use as the username for Keycloak OAuth2
loginMethod: all # 'all' (Login Username/Password and OAuth2[must be enabled and configured]), 'normal'(only Login with Username/Password) or 'oauth2'(only Login with OAuth2)
initialLogin:
username: '' # Initial username for the first login
password: '' # Initial password for the first login
oauth2:
enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
client:
keycloak:
issuer: '' # URL of the Keycloak realm's OpenID Connect Discovery endpoint
clientId: '' # Client ID for Keycloak OAuth2
clientSecret: '' # Client Secret for Keycloak OAuth2
scopes: openid, profile, email # Scopes for Keycloak OAuth2
useAsUsername: preferred_username # Field to use as the username for Keycloak OAuth2
google:
clientId: '' # Client ID for Google OAuth2
clientSecret: '' # Client Secret for Google OAuth2
scopes: https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile # Scopes for Google OAuth2
useAsUsername: email # Field to use as the username for Google OAuth2
github:
clientId: '' # Client ID for GitHub OAuth2
clientSecret: '' # Client Secret for GitHub OAuth2
scopes: read:user # Scope for GitHub OAuth2
useAsUsername: login # Field to use as the username for GitHub OAuth2
issuer: '' # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
clientId: '' # Client ID from your provider
clientSecret: '' # Client Secret from your provider
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
useAsUsername: email # Default is 'email'; custom fields can be used as the username
scopes: openid, profile, email # Specify the scopes for which the application will request permissions
provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'

system:
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
defaultLocale: en-US # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
showUpdate: false # 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'
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template html files

ui:
appName: null # Application's visible name
homeDescription: null # Short description or tagline shown on homepage.
appNameNavbar: null # Name displayed on the navigation bar
appName: '' # Application's visible name
homeDescription: '' # Short description or tagline shown on homepage.
appNameNavbar: '' # Name displayed on the navigation bar

endpoints:
toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
Expand Down
Loading