Compare commits

..

1 Commits

Author SHA1 Message Date
stirlingbot[bot]
17eb68f5a7
Update 3rd Party Licenses
Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
2025-06-09 11:52:46 +00:00
348 changed files with 2870 additions and 15682 deletions

View File

@ -196,9 +196,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
if len(file_list) == 1: if len(file_list) == 1:
file_arr = file_list[0].split() file_arr = file_list[0].split()
base_dir = os.path.abspath( base_dir = os.path.abspath(os.path.join(os.getcwd(), "src", "main", "resources"))
os.path.join(os.getcwd(), "stirling-pdf", "src", "main", "resources")
)
for file_path in file_arr: for file_path in file_arr:
file_normpath = os.path.normpath(file_path) file_normpath = os.path.normpath(file_path)
@ -218,19 +216,10 @@ def check_for_differences(reference_file, file_list, branch, actor):
or ( or (
# only local windows command # only local windows command
not file_normpath.startswith( not file_normpath.startswith(
os.path.join( os.path.join("", "src", "main", "resources", "messages_")
"", "stirling-pdf", "src", "main", "resources", "messages_"
)
) )
and not file_normpath.startswith( and not file_normpath.startswith(
os.path.join( os.path.join(os.getcwd(), "src", "main", "resources", "messages_")
os.getcwd(),
"stirling-pdf",
"src",
"main",
"resources",
"messages_",
)
) )
) )
or not file_normpath.endswith(".properties") or not file_normpath.endswith(".properties")
@ -388,12 +377,7 @@ if __name__ == "__main__":
else: else:
file_list = glob.glob( file_list = glob.glob(
os.path.join( os.path.join(
os.getcwd(), os.getcwd(), "src", "main", "resources", "messages_*.properties"
"stirling-pdf",
"src",
"main",
"resources",
"messages_*.properties",
) )
) )
update_missing_keys(args.reference_file, file_list) update_missing_keys(args.reference_file, file_list)

View File

@ -47,56 +47,18 @@ jobs:
env: env:
DISABLE_ADDITIONAL_FEATURES: false DISABLE_ADDITIONAL_FEATURES: false
- name: Check Test Reports Exist
id: check-reports
if: always()
run: |
missing_reports=()
# Check for required test report directories
if [ ! -d "stirling-pdf/build/reports/tests/" ]; then
missing_reports+=("stirling-pdf/build/reports/tests/")
fi
if [ ! -d "stirling-pdf/build/test-results/" ]; then
missing_reports+=("stirling-pdf/build/test-results/")
fi
if [ ! -d "common/build/reports/tests/" ]; then
missing_reports+=("common/build/reports/tests/")
fi
if [ ! -d "common/build/test-results/" ]; then
missing_reports+=("common/build/test-results/")
fi
if [ ! -d "proprietary/build/reports/tests/" ]; then
missing_reports+=("proprietary/build/reports/tests/")
fi
if [ ! -d "proprietary/build/test-results/" ]; then
missing_reports+=("proprietary/build/test-results/")
fi
# Fail if any required reports are missing
if [ ${#missing_reports[@]} -gt 0 ]; then
echo "ERROR: The following required test report directories are missing:"
printf '%s\n' "${missing_reports[@]}"
exit 1
fi
echo "All required test report directories are present"
- name: Upload Test Reports - name: Upload Test Reports
if: steps.check-reports.outcome == 'success' if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: test-reports-jdk-${{ matrix.jdk-version }} name: test-reports-jdk-${{ matrix.jdk-version }}
path: | path: |
stirling-pdf/build/reports/tests/ build/reports/tests/
stirling-pdf/build/test-results/ build/test-results/
stirling-pdf/build/reports/problems/ build/reports/problems/
common/build/reports/tests/ /common/build/reports/tests/
common/build/test-results/ /common/build/test-results/
common/build/reports/problems/ /common/build/reports/problems/
proprietary/build/reports/tests/
proprietary/build/test-results/
proprietary/build/reports/problems/
retention-days: 3 retention-days: 3
check-licence: check-licence:

View File

@ -115,11 +115,8 @@ jobs:
// Filter for relevant files based on the PR changes // Filter for relevant files based on the PR changes
const changedFiles = files const changedFiles = files
.filter(file => .map(file => file.filename)
file.status !== "removed" && .filter(file => /^stirling-pdf\src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file));
/^stirling-pdf\/src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file.filename)
)
.map(file => file.filename);
console.log("Changed files:", changedFiles); console.log("Changed files:", changedFiles);

View File

@ -74,6 +74,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -86,9 +86,4 @@
"spring.initializr.defaultLanguage": "Java", "spring.initializr.defaultLanguage": "Java",
"spring.initializr.defaultGroupId": "stirling.software.SPDF", "spring.initializr.defaultGroupId": "stirling.software.SPDF",
"spring.initializr.defaultArtifactId": "SPDF", "spring.initializr.defaultArtifactId": "SPDF",
"java.project.sourcePaths": [
"stirling-pdf/src/main/java",
"common/src/main/java",
"proprietary/src/main/java"
],
} }

View File

@ -5,7 +5,8 @@ FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be02
COPY scripts /scripts COPY scripts /scripts
COPY pipeline /pipeline COPY pipeline /pipeline
COPY stirling-pdf/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ COPY stirling-pdf/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
COPY stirling-pdf/build/libs/*.jar app.jar #COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
COPY build/libs/*.jar app.jar
ARG VERSION_TAG ARG VERSION_TAG

View File

@ -5,7 +5,6 @@ COPY build.gradle .
COPY settings.gradle . COPY settings.gradle .
COPY gradlew . COPY gradlew .
COPY gradle gradle/ COPY gradle gradle/
COPY stirling-pdf/build.gradle stirling-pdf/.
COPY common/build.gradle common/. COPY common/build.gradle common/.
COPY proprietary/build.gradle proprietary/. COPY proprietary/build.gradle proprietary/.
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
@ -28,7 +27,7 @@ FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be02
COPY scripts /scripts COPY scripts /scripts
COPY pipeline /pipeline COPY pipeline /pipeline
COPY stirling-pdf/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ COPY stirling-pdf/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
COPY --from=build /app/stirling-pdf/build/libs/*.jar app.jar COPY --from=build /app/build/libs/*.jar app.jar
ARG VERSION_TAG ARG VERSION_TAG

View File

@ -18,7 +18,7 @@ COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
COPY scripts/installFonts.sh /scripts/installFonts.sh COPY scripts/installFonts.sh /scripts/installFonts.sh
COPY pipeline /pipeline COPY pipeline /pipeline
COPY stirling-pdf/build/libs/*.jar app.jar COPY build/libs/*.jar app.jar
# Set up necessary directories and permissions # Set up necessary directories and permissions
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \

View File

@ -116,47 +116,47 @@ Stirling-PDF currently supports 40 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![68%](https://geps.dev/progress/68) | | Arabic (العربية) (ar_AR) | ![71%](https://geps.dev/progress/71) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![68%](https://geps.dev/progress/68) | | Azerbaijani (Azərbaycan Dili) (az_AZ) | ![72%](https://geps.dev/progress/72) |
| Basque (Euskara) (eu_ES) | ![40%](https://geps.dev/progress/40) | | Basque (Euskara) (eu_ES) | ![42%](https://geps.dev/progress/42) |
| Bulgarian (Български) (bg_BG) | ![75%](https://geps.dev/progress/75) | | Bulgarian (Български) (bg_BG) | ![79%](https://geps.dev/progress/79) |
| Catalan (Català) (ca_CA) | ![75%](https://geps.dev/progress/75) | | Catalan (Català) (ca_CA) | ![78%](https://geps.dev/progress/78) |
| Croatian (Hrvatski) (hr_HR) | ![67%](https://geps.dev/progress/67) | | Croatian (Hrvatski) (hr_HR) | ![70%](https://geps.dev/progress/70) |
| Czech (Česky) (cs_CZ) | ![77%](https://geps.dev/progress/77) | | Czech (Česky) (cs_CZ) | ![81%](https://geps.dev/progress/81) |
| Danish (Dansk) (da_DK) | ![68%](https://geps.dev/progress/68) | | Danish (Dansk) (da_DK) | ![71%](https://geps.dev/progress/71) |
| Dutch (Nederlands) (nl_NL) | ![66%](https://geps.dev/progress/66) | | Dutch (Nederlands) (nl_NL) | ![69%](https://geps.dev/progress/69) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![76%](https://geps.dev/progress/76) | | French (Français) (fr_FR) | ![80%](https://geps.dev/progress/80) |
| German (Deutsch) (de_DE) | ![96%](https://geps.dev/progress/96) | | German (Deutsch) (de_DE) | ![97%](https://geps.dev/progress/97) |
| Greek (Ελληνικά) (el_GR) | ![74%](https://geps.dev/progress/74) | | Greek (Ελληνικά) (el_GR) | ![78%](https://geps.dev/progress/78) |
| Hindi (हिंदी) (hi_IN) | ![74%](https://geps.dev/progress/74) | | Hindi (हिंदी) (hi_IN) | ![78%](https://geps.dev/progress/78) |
| Hungarian (Magyar) (hu_HU) | ![97%](https://geps.dev/progress/97) | | Hungarian (Magyar) (hu_HU) | ![89%](https://geps.dev/progress/89) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![68%](https://geps.dev/progress/68) | | Indonesian (Bahasa Indonesia) (id_ID) | ![72%](https://geps.dev/progress/72) |
| Irish (Gaeilge) (ga_IE) | ![75%](https://geps.dev/progress/75) | | Irish (Gaeilge) (ga_IE) | ![79%](https://geps.dev/progress/79) |
| Italian (Italiano) (it_IT) | ![87%](https://geps.dev/progress/87) | | Italian (Italiano) (it_IT) | ![97%](https://geps.dev/progress/97) |
| Japanese (日本語) (ja_JP) | ![76%](https://geps.dev/progress/76) | | Japanese (日本語) (ja_JP) | ![79%](https://geps.dev/progress/79) |
| Korean (한국어) (ko_KR) | ![75%](https://geps.dev/progress/75) | | Korean (한국어) (ko_KR) | ![78%](https://geps.dev/progress/78) |
| Norwegian (Norsk) (no_NB) | ![73%](https://geps.dev/progress/73) | | Norwegian (Norsk) (no_NB) | ![76%](https://geps.dev/progress/76) |
| Persian (فارسی) (fa_IR) | ![71%](https://geps.dev/progress/71) | | Persian (فارسی) (fa_IR) | ![74%](https://geps.dev/progress/74) |
| Polish (Polski) (pl_PL) | ![79%](https://geps.dev/progress/79) | | Polish (Polski) (pl_PL) | ![83%](https://geps.dev/progress/83) |
| Portuguese (Português) (pt_PT) | ![76%](https://geps.dev/progress/76) | | Portuguese (Português) (pt_PT) | ![79%](https://geps.dev/progress/79) |
| Portuguese Brazilian (Português) (pt_BR) | ![84%](https://geps.dev/progress/84) | | Portuguese Brazilian (Português) (pt_BR) | ![84%](https://geps.dev/progress/84) |
| Romanian (Română) (ro_RO) | ![63%](https://geps.dev/progress/63) | | Romanian (Română) (ro_RO) | ![66%](https://geps.dev/progress/66) |
| Russian (Русский) (ru_RU) | ![76%](https://geps.dev/progress/76) | | Russian (Русский) (ru_RU) | ![84%](https://geps.dev/progress/84) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![48%](https://geps.dev/progress/48) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![51%](https://geps.dev/progress/51) |
| Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) | | Simplified Chinese (简体中文) (zh_CN) | ![84%](https://geps.dev/progress/84) |
| Slovakian (Slovensky) (sk_SK) | ![57%](https://geps.dev/progress/57) | | Slovakian (Slovensky) (sk_SK) | ![60%](https://geps.dev/progress/60) |
| Slovenian (Slovenščina) (sl_SI) | ![79%](https://geps.dev/progress/79) | | Slovenian (Slovenščina) (sl_SI) | ![82%](https://geps.dev/progress/82) |
| Spanish (Español) (es_ES) | ![82%](https://geps.dev/progress/82) | | Spanish (Español) (es_ES) | ![86%](https://geps.dev/progress/86) |
| Swedish (Svenska) (sv_SE) | ![72%](https://geps.dev/progress/72) | | Swedish (Svenska) (sv_SE) | ![76%](https://geps.dev/progress/76) |
| Thai (ไทย) (th_TH) | ![65%](https://geps.dev/progress/65) | | Thai (ไทย) (th_TH) | ![68%](https://geps.dev/progress/68) |
| Tibetan (བོད་ཡིག་) (bo_CN) | ![0%](https://geps.dev/progress/0) | | Tibetan (བོད་ཡིག་) (bo_CN) | ![75%](https://geps.dev/progress/75) |
| Traditional Chinese (繁體中文) (zh_TW) | ![80%](https://geps.dev/progress/80) | | Traditional Chinese (繁體中文) (zh_TW) | ![85%](https://geps.dev/progress/85) |
| Turkish (Türkçe) (tr_TR) | ![81%](https://geps.dev/progress/81) | | Turkish (Türkçe) (tr_TR) | ![85%](https://geps.dev/progress/85) |
| Ukrainian (Українська) (uk_UA) | ![78%](https://geps.dev/progress/78) | | Ukrainian (Українська) (uk_UA) | ![84%](https://geps.dev/progress/84) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![63%](https://geps.dev/progress/63) | | Vietnamese (Tiếng Việt) (vi_VN) | ![66%](https://geps.dev/progress/66) |
| Malayalam (മലയാളം) (ml_IN) | ![81%](https://geps.dev/progress/81) | | Malayalam (മലയാളം) (ml_IN) | ![85%](https://geps.dev/progress/85) |
## Stirling PDF Enterprise ## Stirling PDF Enterprise

View File

@ -1,8 +1,8 @@
plugins { plugins {
id "java" id "java"
id "jacoco" id "jacoco"
id "io.spring.dependency-management" version "1.1.7"
id "org.springframework.boot" version "3.5.0" id "org.springframework.boot" version "3.5.0"
id "io.spring.dependency-management" version "1.1.7"
id "org.springdoc.openapi-gradle-plugin" version "1.9.0" id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
id "io.swagger.swaggerhub" version "1.3.2" id "io.swagger.swaggerhub" version "1.3.2"
id "edu.sc.seis.launch4j" version "3.0.6" id "edu.sc.seis.launch4j" version "3.0.6"
@ -23,11 +23,10 @@ ext {
pdfboxVersion = "3.0.5" pdfboxVersion = "3.0.5"
imageioVersion = "3.12.0" imageioVersion = "3.12.0"
lombokVersion = "1.18.38" lombokVersion = "1.18.38"
bouncycastleVersion = "1.81" bouncycastleVersion = "1.80"
springSecuritySamlVersion = "6.5.0" springSecuritySamlVersion = "6.5.0"
openSamlVersion = "4.3.2" openSamlVersion = "4.3.2"
commonmarkVersion = "0.24.0" commonmarkVersion = "0.24.0"
googleJavaFormatVersion = "1.27.0"
tempJrePath = null tempJrePath = null
} }
@ -51,6 +50,7 @@ sourceSets {
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')) { && System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')) {
exclude 'stirling/software/proprietary/security/**' exclude 'stirling/software/proprietary/security/**'
} }
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') { if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
exclude 'stirling/software/SPDF/UI/impl/**' exclude 'stirling/software/SPDF/UI/impl/**'
} }
@ -75,7 +75,7 @@ sourceSets {
allprojects { allprojects {
group = 'stirling.software' group = 'stirling.software'
version = '1.0.0' version = '0.46.2'
configurations.configureEach { configurations.configureEach {
exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'commons-logging', module: 'commons-logging'
@ -130,7 +130,6 @@ subprojects {
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0' testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2'
} }
tasks.withType(JavaCompile).configureEach { tasks.withType(JavaCompile).configureEach {
@ -147,11 +146,6 @@ subprojects {
} }
} }
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
dependsOn "spotlessApply"
}
licenseReport { licenseReport {
renderers = [new JsonReportRenderer()] renderers = [new JsonReportRenderer()]
allowedLicensesFile = new File("$projectDir/allowed-licenses.json") allowedLicensesFile = new File("$projectDir/allowed-licenses.json")
@ -474,9 +468,8 @@ spotless {
target sourceSets.main.allJava target sourceSets.main.allJava
target project(':common').sourceSets.main.allJava target project(':common').sourceSets.main.allJava
target project(':proprietary').sourceSets.main.allJava target project(':proprietary').sourceSets.main.allJava
target project(':stirling-pdf').sourceSets.main.allJava
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false) googleJavaFormat("1.27.0").aosp().reorderImports(false)
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling") importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
toggleOffOn() toggleOffOn()
@ -507,17 +500,12 @@ swaggerhubUpload {
oas = "3.0.0" // The version of the OpenAPI Specification you"re using oas = "3.0.0" // The version of the OpenAPI Specification you"re using
} }
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2'
}
tasks.named("test") { tasks.named("test") {
useJUnitPlatform() useJUnitPlatform()
} }
tasks.register('writeVersion') { tasks.register('writeVersion') {
def propsFile = file("$projectDir/common/src/main/resources/version.properties") def propsFile = file("$projectDir/stirling-pdf/src/main/resources/version.properties")
def propsDir = propsFile.parentFile def propsDir = propsFile.parentFile
doLast { doLast {
@ -541,7 +529,6 @@ tasks.register('writeVersion') {
} }
processResources.dependsOn(writeVersion) processResources.dependsOn(writeVersion)
project(':stirling-pdf').tasks.bootJar.dependsOn(writeVersion)
tasks.register('printVersion') { tasks.register('printVersion') {
doLast { doLast {
@ -558,22 +545,3 @@ tasks.register('printMacVersion') {
tasks.named('generateOpenApiDocs') { tasks.named('generateOpenApiDocs') {
doNotTrackState("Tracking state is not supported for this task") doNotTrackState("Tracking state is not supported for this task")
} }
tasks.named('bootRun') {
group = 'application'
description = 'Delegates to :stirling-pdf:bootRun'
dependsOn ':stirling-pdf:bootRun'
doFirst {
println "Delegating to :stirling-pdf:bootRun"
}
}
tasks.named('build') {
group = 'build'
description = 'Delegates to :stirling-pdf:bootJar'
dependsOn ':stirling-pdf:bootJar'
doFirst {
println "Delegating to :stirling-pdf:bootJar"
}
}

View File

@ -1,13 +1,3 @@
// Configure bootRun to disable it or point to a main class
bootRun {
enabled = false
}
spotless {
java {
target sourceSets.main.allJava
googleJavaFormat(googleJavaFormatVersion).aosp()
}
}
dependencies { dependencies {
api 'org.springframework.boot:spring-boot-starter-web' api 'org.springframework.boot:spring-boot-starter-web'
api 'org.springframework.boot:spring-boot-starter-thymeleaf' api 'org.springframework.boot:spring-boot-starter-thymeleaf'

View File

@ -4,9 +4,11 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.io.RandomAccessReadBufferedFile; import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
import lombok.extern.slf4j.Slf4j;
/** A custom RandomAccessRead implementation that deletes the file when closed */ /** A custom RandomAccessRead implementation that deletes the file when closed */
@Slf4j @Slf4j
public class DeletingRandomAccessFile extends RandomAccessReadBufferedFile { public class DeletingRandomAccessFile extends RandomAccessReadBufferedFile {

View File

@ -1,5 +1,7 @@
package stirling.software.common.configuration; package stirling.software.common.configuration;
import io.github.pixee.security.SystemCommand;
import jakarta.annotation.PostConstruct;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -8,22 +10,25 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import java.util.function.Predicate; import java.util.function.Predicate;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.spring6.SpringTemplateEngine; import org.thymeleaf.spring6.SpringTemplateEngine;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
@Lazy @Lazy
@ -248,33 +253,9 @@ public class AppConfig {
return applicationProperties.getSystem().getDatasource(); return applicationProperties.getSystem().getDatasource();
} }
@Bean(name = "runningProOrHigher")
@Profile("default")
public boolean runningProOrHigher() {
return false;
}
@Bean(name = "runningEE")
@Profile("default")
public boolean runningEnterprise() {
return false;
}
@Bean(name = "GoogleDriveEnabled")
@Profile("default")
public boolean googleDriveEnabled() {
return false;
}
@Bean(name = "license")
@Profile("default")
public String licenseType() {
return "NORMAL";
}
@Bean(name = "disablePixel") @Bean(name = "disablePixel")
public boolean disablePixel() { public boolean disablePixel() {
return Boolean.parseBoolean(env.getProperty("DISABLE_PIXEL", "false")); return Boolean.getBoolean(env.getProperty("DISABLE_PIXEL"));
} }
@Bean(name = "machineType") @Bean(name = "machineType")

View File

@ -10,7 +10,9 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.List; import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.YamlHelper; import stirling.software.common.util.YamlHelper;
/** /**

View File

@ -3,13 +3,16 @@ package stirling.software.common.configuration;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Map; import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.IEngineConfiguration; import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver; import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver;
import org.thymeleaf.templateresource.FileTemplateResource; import org.thymeleaf.templateresource.FileTemplateResource;
import org.thymeleaf.templateresource.ITemplateResource; import org.thymeleaf.templateresource.ITemplateResource;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.InputStreamTemplateResource; import stirling.software.common.model.InputStreamTemplateResource;
@Slf4j @Slf4j

View File

@ -2,6 +2,7 @@ package stirling.software.common.configuration;
import java.io.File; import java.io.File;
import java.nio.file.Paths; import java.nio.file.Paths;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j

View File

@ -1,12 +1,15 @@
package stirling.software.common.configuration; package stirling.software.common.configuration;
import com.posthog.java.PostHog;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.posthog.java.PostHog;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
@Configuration @Configuration
@Slf4j @Slf4j
public class PostHogConfig { public class PostHogConfig {

View File

@ -1,9 +1,11 @@
package stirling.software.common.configuration; package stirling.software.common.configuration;
import com.posthog.java.PostHogLogger;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.posthog.java.PostHogLogger;
import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@Component @Component
public class PostHogLoggerImpl implements PostHogLogger { public class PostHogLoggerImpl implements PostHogLogger {

View File

@ -2,10 +2,13 @@ package stirling.software.common.configuration;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.CustomPaths.Operations; import stirling.software.common.model.ApplicationProperties.CustomPaths.Operations;
import stirling.software.common.model.ApplicationProperties.CustomPaths.Pipeline; import stirling.software.common.model.ApplicationProperties.CustomPaths.Pipeline;

View File

@ -1,6 +1,7 @@
package stirling.software.common.configuration; package stirling.software.common.configuration;
import java.util.Properties; import java.util.Properties;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;

View File

@ -12,11 +12,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -28,6 +24,13 @@ import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.EncodedResource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.configuration.YamlPropertySourceFactory; import stirling.software.common.configuration.YamlPropertySourceFactory;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;

View File

@ -5,6 +5,7 @@ import java.nio.file.Paths;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Locale; import java.util.Locale;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;

View File

@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import org.thymeleaf.templateresource.ITemplateResource; import org.thymeleaf.templateresource.ITemplateResource;
public class InputStreamTemplateResource implements ITemplateResource { public class InputStreamTemplateResource implements ITemplateResource {

View File

@ -1,6 +1,7 @@
package stirling.software.common.model; package stirling.software.common.model;
import java.util.Calendar; import java.util.Calendar;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;

View File

@ -1,9 +1,11 @@
package stirling.software.common.model.api; package stirling.software.common.model.api;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.springframework.web.multipart.MultipartFile;
@Data @Data
@EqualsAndHashCode @EqualsAndHashCode

View File

@ -1,10 +1,12 @@
package stirling.software.common.model.api; package stirling.software.common.model.api;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
@Data @Data
@NoArgsConstructor @NoArgsConstructor

View File

@ -1,8 +1,10 @@
package stirling.software.common.model.api.converters; package stirling.software.common.model.api.converters;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
@Data @Data

View File

@ -1,6 +1,7 @@
package stirling.software.common.model.api.security; package stirling.software.common.model.api.security;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;

View File

@ -2,6 +2,7 @@ package stirling.software.common.model.enumeration;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -2,7 +2,9 @@ package stirling.software.common.model.oauth2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
@NoArgsConstructor @NoArgsConstructor

View File

@ -2,7 +2,9 @@ package stirling.software.common.model.oauth2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
@NoArgsConstructor @NoArgsConstructor

View File

@ -2,7 +2,9 @@ package stirling.software.common.model.oauth2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
@NoArgsConstructor @NoArgsConstructor

View File

@ -5,8 +5,10 @@ import static stirling.software.common.model.enumeration.UsernameAttribute.EMAIL
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import stirling.software.common.model.enumeration.UsernameAttribute; import stirling.software.common.model.enumeration.UsernameAttribute;
import stirling.software.common.model.exception.UnsupportedClaimException; import stirling.software.common.model.exception.UnsupportedClaimException;

View File

@ -8,8 +8,7 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.examples.util.DeletingRandomAccessFile; import org.apache.pdfbox.examples.util.DeletingRandomAccessFile;
import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.io.IOUtils;
@ -19,6 +18,10 @@ import org.apache.pdfbox.io.ScratchFile;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
/** /**

View File

@ -1,10 +1,12 @@
package stirling.software.common.service; package stirling.software.common.service;
import java.util.Calendar; import java.util.Calendar;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
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.stereotype.Service; import org.springframework.stereotype.Service;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.PdfMetadata; import stirling.software.common.model.PdfMetadata;

View File

@ -1,6 +1,5 @@
package stirling.software.common.service; package stirling.software.common.service;
import com.posthog.java.PostHog;
import java.io.File; import java.io.File;
import java.lang.management.GarbageCollectorMXBean; import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
@ -17,11 +16,15 @@ import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
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.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.posthog.java.PostHog;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
@Service @Service

View File

@ -3,6 +3,7 @@ package stirling.software.common.util;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
public class CheckProgramInstall { public class CheckProgramInstall {

View File

@ -19,10 +19,7 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import lombok.Data;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@ -38,6 +35,11 @@ import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import lombok.Data;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.converters.EmlToPdfRequest; import stirling.software.common.model.api.converters.EmlToPdfRequest;
@Slf4j @Slf4j
@ -47,8 +49,7 @@ public class EmlToPdf {
private static final class StyleConstants { private static final class StyleConstants {
// Font and layout constants // Font and layout constants
static final int DEFAULT_FONT_SIZE = 12; static final int DEFAULT_FONT_SIZE = 12;
static final String DEFAULT_FONT_FAMILY = static final String DEFAULT_FONT_FAMILY = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif";
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif";
static final float DEFAULT_LINE_HEIGHT = 1.4f; static final float DEFAULT_LINE_HEIGHT = 1.4f;
static final String DEFAULT_ZOOM = "1.0"; static final String DEFAULT_ZOOM = "1.0";
@ -75,8 +76,7 @@ public class EmlToPdf {
} }
private static final class MimeConstants { private static final class MimeConstants {
static final Pattern MIME_ENCODED_PATTERN = static final Pattern MIME_ENCODED_PATTERN = Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
static final String PAPERCLIP_EMOJI = "\uD83D\uDCCE"; // 📎 static final String PAPERCLIP_EMOJI = "\uD83D\uDCCE"; // 📎
static final String ATTACHMENT_ICON_PLACEHOLDER = "icon"; static final String ATTACHMENT_ICON_PLACEHOLDER = "icon";
@ -113,8 +113,7 @@ public class EmlToPdf {
return jakartaMailAvailable; return jakartaMailAvailable;
} }
public static String convertEmlToHtml(byte[] emlBytes, EmlToPdfRequest request) public static String convertEmlToHtml(byte[] emlBytes, EmlToPdfRequest request) throws IOException {
throws IOException {
validateEmlInput(emlBytes); validateEmlInput(emlBytes);
if (isJakartaMailAvailable()) { if (isJakartaMailAvailable()) {
@ -148,14 +147,11 @@ public class EmlToPdf {
} }
// Convert HTML to PDF // Convert HTML to PDF
byte[] pdfBytes = byte[] pdfBytes = convertHtmlToPdf(weasyprintPath, request, htmlContent, disableSanitize);
convertHtmlToPdf(weasyprintPath, request, htmlContent, disableSanitize);
// Attach files if available and requested // Attach files if available and requested
if (shouldAttachFiles(emailContent, request)) { if (shouldAttachFiles(emailContent, request)) {
pdfBytes = pdfBytes = attachFilesToPdf(pdfBytes, emailContent.getAttachments(), pdfDocumentFactory);
attachFilesToPdf(
pdfBytes, emailContent.getAttachments(), pdfDocumentFactory);
} }
return pdfBytes; return pdfBytes;
@ -181,20 +177,16 @@ public class EmlToPdf {
private static boolean shouldAttachFiles(EmailContent emailContent, EmlToPdfRequest request) { private static boolean shouldAttachFiles(EmailContent emailContent, EmlToPdfRequest request) {
return emailContent != null return emailContent != null
&& request != null && request != null
&& request.isIncludeAttachments() && request.isIncludeAttachments()
&& !emailContent.getAttachments().isEmpty(); && !emailContent.getAttachments().isEmpty();
} }
private static byte[] convertHtmlToPdf( private static byte[] convertHtmlToPdf(String weasyprintPath, EmlToPdfRequest request,
String weasyprintPath, String htmlContent, boolean disableSanitize)
EmlToPdfRequest request,
String htmlContent,
boolean disableSanitize)
throws IOException, InterruptedException { throws IOException, InterruptedException {
stirling.software.common.model.api.converters.HTMLToPdfRequest htmlRequest = stirling.software.common.model.api.converters.HTMLToPdfRequest htmlRequest = createHtmlRequest(request);
createHtmlRequest(request);
try { try {
return FileToPdf.convertHtmlToPdf( return FileToPdf.convertHtmlToPdf(
@ -226,7 +218,8 @@ public class EmlToPdf {
return "attachment_" + filename.hashCode() + "_" + System.nanoTime(); return "attachment_" + filename.hashCode() + "_" + System.nanoTime();
} }
private static String convertEmlToHtmlBasic(byte[] emlBytes, EmlToPdfRequest request) { private static String convertEmlToHtmlBasic(
byte[] emlBytes, EmlToPdfRequest request) {
if (emlBytes == null || emlBytes.length == 0) { if (emlBytes == null || emlBytes.length == 0) {
throw new IllegalArgumentException("EML file is empty or null"); throw new IllegalArgumentException("EML file is empty or null");
} }
@ -342,6 +335,7 @@ public class EmlToPdf {
Object message = Object message =
mimeMessageConstructor.newInstance(session, new ByteArrayInputStream(emlBytes)); mimeMessageConstructor.newInstance(session, new ByteArrayInputStream(emlBytes));
return extractEmailContentAdvanced(message, request); return extractEmailContentAdvanced(message, request);
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
@ -352,7 +346,8 @@ public class EmlToPdf {
} }
} }
private static String convertEmlToHtmlAdvanced(byte[] emlBytes, EmlToPdfRequest request) { private static String convertEmlToHtmlAdvanced(
byte[] emlBytes, EmlToPdfRequest request) {
EmailContent content = extractEmailContentAdvanced(emlBytes, request); EmailContent content = extractEmailContentAdvanced(emlBytes, request);
return generateEnhancedEmailHtml(content, request); return generateEnhancedEmailHtml(content, request);
} }
@ -484,12 +479,8 @@ public class EmlToPdf {
// Create attachment info with paperclip emoji before filename // Create attachment info with paperclip emoji before filename
attachmentInfo attachmentInfo
.append("<div class=\"attachment-item\">") .append("<div class=\"attachment-item\">")
.append("<span class=\"attachment-icon\">") .append("<span class=\"attachment-icon\">").append(MimeConstants.ATTACHMENT_ICON_PLACEHOLDER).append("</span> ")
.append(MimeConstants.ATTACHMENT_ICON_PLACEHOLDER) .append("<span class=\"attachment-name\">").append(escapeHtml(filename)).append("</span>");
.append("</span> ")
.append("<span class=\"attachment-name\">")
.append(escapeHtml(filename))
.append("</span>");
// Add content type and encoding info // Add content type and encoding info
if (!contentType.isEmpty() || !encoding.isEmpty()) { if (!contentType.isEmpty() || !encoding.isEmpty()) {
@ -512,20 +503,17 @@ public class EmlToPdf {
String content = new String(emlBytes, 0, checkLength, StandardCharsets.UTF_8); String content = new String(emlBytes, 0, checkLength, StandardCharsets.UTF_8);
String lowerContent = content.toLowerCase(); String lowerContent = content.toLowerCase();
boolean hasFrom = boolean hasFrom = lowerContent.contains("from:") || lowerContent.contains("return-path:");
lowerContent.contains("from:") || lowerContent.contains("return-path:");
boolean hasSubject = lowerContent.contains("subject:"); boolean hasSubject = lowerContent.contains("subject:");
boolean hasMessageId = lowerContent.contains("message-id:"); boolean hasMessageId = lowerContent.contains("message-id:");
boolean hasDate = lowerContent.contains("date:"); boolean hasDate = lowerContent.contains("date:");
boolean hasTo = boolean hasTo = lowerContent.contains("to:")
lowerContent.contains("to:") || lowerContent.contains("cc:")
|| lowerContent.contains("cc:") || lowerContent.contains("bcc:");
|| lowerContent.contains("bcc:"); boolean hasMimeStructure = lowerContent.contains("multipart/")
boolean hasMimeStructure = || lowerContent.contains("text/plain")
lowerContent.contains("multipart/") || lowerContent.contains("text/html")
|| lowerContent.contains("text/plain") || lowerContent.contains("boundary=");
|| lowerContent.contains("text/html")
|| lowerContent.contains("boundary=");
int headerCount = 0; int headerCount = 0;
if (hasFrom) headerCount++; if (hasFrom) headerCount++;
@ -696,19 +684,17 @@ public class EmlToPdf {
html.append(" font-size: ").append(fontSize - 1).append("px;\n"); html.append(" font-size: ").append(fontSize - 1).append("px;\n");
html.append("}\n\n"); html.append("}\n\n");
html.append(".email-body {\n"); html.append(".email-body {\n");
html.append(" word-wrap: break-word;\n"); html.append(" word-wrap: break-word;\n");
html.append("}\n\n"); html.append("}\n\n");
html.append(".attachment-section {\n"); html.append(".attachment-section {\n");
html.append(" margin-top: 15px;\n"); html.append(" margin-top: 15px;\n");
html.append(" padding: 10px;\n"); html.append(" padding: 10px;\n");
html.append(" background-color: ") html.append(" background-color: ").append(StyleConstants.ATTACHMENT_BACKGROUND_COLOR).append(";\n");
.append(StyleConstants.ATTACHMENT_BACKGROUND_COLOR) html.append(" border: 1px solid ").append(StyleConstants.ATTACHMENT_BORDER_COLOR).append(";\n");
.append(";\n");
html.append(" border: 1px solid ")
.append(StyleConstants.ATTACHMENT_BORDER_COLOR)
.append(";\n");
html.append(" border-radius: 3px;\n"); html.append(" border-radius: 3px;\n");
html.append("}\n\n"); html.append("}\n\n");
html.append(".attachment-section h3 {\n"); html.append(".attachment-section h3 {\n");
@ -760,6 +746,7 @@ public class EmlToPdf {
html.append(" margin-left: 8px;\n"); html.append(" margin-left: 8px;\n");
html.append("}\n\n"); html.append("}\n\n");
// Basic image styling: ensure images are responsive but not overly constrained. // Basic image styling: ensure images are responsive but not overly constrained.
html.append("img {\n"); html.append("img {\n");
html.append(" max-width: 100%;\n"); // Make images responsive to container width html.append(" max-width: 100%;\n"); // Make images responsive to container width
@ -814,9 +801,7 @@ public class EmlToPdf {
java.lang.reflect.Method getAllRecipients = messageClass.getMethod("getAllRecipients"); java.lang.reflect.Method getAllRecipients = messageClass.getMethod("getAllRecipients");
Object[] recipients = (Object[]) getAllRecipients.invoke(message); Object[] recipients = (Object[]) getAllRecipients.invoke(message);
content.setTo( content.setTo(
recipients != null && recipients.length > 0 recipients != null && recipients.length > 0 ? safeMimeDecode(recipients[0].toString()) : "");
? safeMimeDecode(recipients[0].toString())
: "");
java.lang.reflect.Method getSentDate = messageClass.getMethod("getSentDate"); java.lang.reflect.Method getSentDate = messageClass.getMethod("getSentDate");
content.setDate((Date) getSentDate.invoke(message)); content.setDate((Date) getSentDate.invoke(message));
@ -923,14 +908,13 @@ public class EmlToPdf {
try { try {
attachmentData = inputStream.readAllBytes(); attachmentData = inputStream.readAllBytes();
} catch (IOException e) { } catch (IOException e) {
log.warn( log.warn("Failed to read InputStream attachment: {}", e.getMessage());
"Failed to read InputStream attachment: {}",
e.getMessage());
} }
} else if (attachmentContent instanceof byte[] byteArray) { } else if (attachmentContent instanceof byte[] byteArray) {
attachmentData = byteArray; attachmentData = byteArray;
} else if (attachmentContent instanceof String stringContent) { } else if (attachmentContent instanceof String stringContent) {
attachmentData = stringContent.getBytes(StandardCharsets.UTF_8); attachmentData =
stringContent.getBytes(StandardCharsets.UTF_8);
} }
if (attachmentData != null) { if (attachmentData != null) {
@ -990,9 +974,7 @@ public class EmlToPdf {
html.append("<div><strong>From:</strong> ") html.append("<div><strong>From:</strong> ")
.append(escapeHtml(content.getFrom())) .append(escapeHtml(content.getFrom()))
.append("</div>\n"); .append("</div>\n");
html.append("<div><strong>To:</strong> ") html.append("<div><strong>To:</strong> ").append(escapeHtml(content.getTo())).append("</div>\n");
.append(escapeHtml(content.getTo()))
.append("</div>\n");
if (content.getDate() != null) { if (content.getDate() != null) {
html.append("<div><strong>Date:</strong> ") html.append("<div><strong>Date:</strong> ")
@ -1032,20 +1014,15 @@ public class EmlToPdf {
? attachment.getEmbeddedFilename() ? attachment.getEmbeddedFilename()
: attachment.getFilename()); : attachment.getFilename());
html.append("<div class=\"attachment-item\" id=\"") html.append("<div class=\"attachment-item\" id=\"").append(uniqueId).append("\">")
.append(uniqueId) .append("<span class=\"attachment-icon\">").append(MimeConstants.PAPERCLIP_EMOJI).append("</span> ")
.append("\">")
.append("<span class=\"attachment-icon\">")
.append(MimeConstants.PAPERCLIP_EMOJI)
.append("</span> ")
.append("<span class=\"attachment-name\">") .append("<span class=\"attachment-name\">")
.append(escapeHtml(safeMimeDecode(attachment.getFilename()))) .append(escapeHtml(safeMimeDecode(attachment.getFilename())))
.append("</span>"); .append("</span>");
String sizeStr = formatFileSize(attachment.getSizeBytes()); String sizeStr = formatFileSize(attachment.getSizeBytes());
html.append(" <span class=\"attachment-details\">(").append(sizeStr); html.append(" <span class=\"attachment-details\">(").append(sizeStr);
if (attachment.getContentType() != null if (attachment.getContentType() != null && !attachment.getContentType().isEmpty()) {
&& !attachment.getContentType().isEmpty()) {
html.append(", ").append(escapeHtml(attachment.getContentType())); html.append(", ").append(escapeHtml(attachment.getContentType()));
} }
html.append(")</span></div>\n"); html.append(")</span></div>\n");
@ -1054,7 +1031,8 @@ public class EmlToPdf {
if (request.isIncludeAttachments()) { if (request.isIncludeAttachments()) {
html.append("<div class=\"attachment-info-note\">\n"); html.append("<div class=\"attachment-info-note\">\n");
html.append("<p><em>Attachments are embedded in the file.</em></p>\n"); html.append(
"<p><em>Attachments are embedded in the file.</em></p>\n");
html.append("</div>\n"); html.append("</div>\n");
} else { } else {
html.append("<div class=\"attachment-info-note\">\n"); html.append("<div class=\"attachment-info-note\">\n");
@ -1072,10 +1050,7 @@ public class EmlToPdf {
return html.toString(); return html.toString();
} }
private static byte[] attachFilesToPdf( private static byte[] attachFilesToPdf(byte[] pdfBytes, List<EmailAttachment> attachments, stirling.software.common.service.CustomPDFDocumentFactory pdfDocumentFactory)
byte[] pdfBytes,
List<EmailAttachment> attachments,
stirling.software.common.service.CustomPDFDocumentFactory pdfDocumentFactory)
throws IOException { throws IOException {
try (PDDocument document = pdfDocumentFactory.load(pdfBytes); try (PDDocument document = pdfDocumentFactory.load(pdfBytes);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
@ -1129,8 +1104,7 @@ public class EmlToPdf {
// Create embedded file // Create embedded file
PDEmbeddedFile embeddedFile = PDEmbeddedFile embeddedFile =
new PDEmbeddedFile( new PDEmbeddedFile(document, new ByteArrayInputStream(attachment.getData()));
document, new ByteArrayInputStream(attachment.getData()));
embeddedFile.setSize(attachment.getData().length); embeddedFile.setSize(attachment.getData().length);
embeddedFile.setCreationDate(new GregorianCalendar()); embeddedFile.setCreationDate(new GregorianCalendar());
if (attachment.getContentType() != null) { if (attachment.getContentType() != null) {
@ -1176,13 +1150,11 @@ public class EmlToPdf {
} }
} }
private static String getUniqueFilename( private static String getUniqueFilename(String filename, List<String> embeddedFiles, Map<String, PDComplexFileSpecification> efMap) {
String filename,
List<String> embeddedFiles,
Map<String, PDComplexFileSpecification> efMap) {
String uniqueFilename = filename; String uniqueFilename = filename;
int counter = 1; int counter = 1;
while (embeddedFiles.contains(uniqueFilename) || efMap.containsKey(uniqueFilename)) { while (embeddedFiles.contains(uniqueFilename)
|| efMap.containsKey(uniqueFilename)) {
String extension = ""; String extension = "";
String baseName = filename; String baseName = filename;
int lastDot = filename.lastIndexOf('.'); int lastDot = filename.lastIndexOf('.');
@ -1231,8 +1203,8 @@ public class EmlToPdf {
} }
private static void addAttachmentAnnotationToPage( private static void addAttachmentAnnotationToPage(
PDDocument document, PDPage page, EmailAttachment attachment, float x, float y) PDDocument document, PDPage page, EmailAttachment attachment, float x, float y)
throws IOException { throws IOException {
PDAnnotationFileAttachment fileAnnotation = new PDAnnotationFileAttachment(); PDAnnotationFileAttachment fileAnnotation = new PDAnnotationFileAttachment();
@ -1254,12 +1226,11 @@ public class EmlToPdf {
// Set invisibility flags but keep it functional // Set invisibility flags but keep it functional
fileAnnotation.setInvisible(true); fileAnnotation.setInvisible(true);
fileAnnotation.setHidden(false); // Must be false to remain clickable fileAnnotation.setHidden(false); // Must be false to remain clickable
fileAnnotation.setNoView(false); // Must be false to remain clickable fileAnnotation.setNoView(false); // Must be false to remain clickable
fileAnnotation.setPrinted(false); fileAnnotation.setPrinted(false);
PDEmbeddedFilesNameTreeNode efTree = PDEmbeddedFilesNameTreeNode efTree = document.getDocumentCatalog().getNames().getEmbeddedFiles();
document.getDocumentCatalog().getNames().getEmbeddedFiles();
if (efTree != null) { if (efTree != null) {
Map<String, PDComplexFileSpecification> efMap = efTree.getNames(); Map<String, PDComplexFileSpecification> efMap = efTree.getNames();
if (efMap != null) { if (efMap != null) {
@ -1275,27 +1246,24 @@ public class EmlToPdf {
page.getAnnotations().add(fileAnnotation); page.getAnnotations().add(fileAnnotation);
log.info( log.info("Added attachment annotation for '{}' on page {}",
"Added attachment annotation for '{}' on page {}", attachment.getFilename(), document.getPages().indexOf(page) + 1);
attachment.getFilename(),
document.getPages().indexOf(page) + 1);
} }
private static @NotNull PDRectangle getPdRectangle(PDPage page, float x, float y) { private static @NotNull PDRectangle getPdRectangle(PDPage page, float x, float y) {
PDRectangle mediaBox = page.getMediaBox(); PDRectangle mediaBox = page.getMediaBox();
float pdfY = mediaBox.getHeight() - y; float pdfY = mediaBox.getHeight() - y;
float iconWidth = float iconWidth = StyleConstants.ATTACHMENT_ICON_WIDTH; // Keep original size for clickability
StyleConstants.ATTACHMENT_ICON_WIDTH; // Keep original size for clickability float iconHeight = StyleConstants.ATTACHMENT_ICON_HEIGHT; // Keep original size for clickability
float iconHeight =
StyleConstants.ATTACHMENT_ICON_HEIGHT; // Keep original size for clickability
// Keep the full-size rectangle so it remains clickable // Keep the full-size rectangle so it remains clickable
return new PDRectangle( return new PDRectangle(
x + StyleConstants.ANNOTATION_X_OFFSET, x + StyleConstants.ANNOTATION_X_OFFSET,
pdfY - iconHeight + StyleConstants.ANNOTATION_Y_OFFSET, pdfY - iconHeight + StyleConstants.ANNOTATION_Y_OFFSET,
iconWidth, iconWidth,
iconHeight); iconHeight
);
} }
private static String formatEmailDate(Date date) { private static String formatEmailDate(Date date) {
@ -1325,27 +1293,23 @@ public class EmlToPdf {
COSDictionary catalogDict = catalog.getCOSObject(); COSDictionary catalogDict = catalog.getCOSObject();
// Set PageMode to UseAttachments - this is the standard PDF specification approach // Set PageMode to UseAttachments - this is the standard PDF specification approach
// PageMode values: UseNone, UseOutlines, UseThumbs, FullScreen, UseOC, // PageMode values: UseNone, UseOutlines, UseThumbs, FullScreen, UseOC, UseAttachments
// UseAttachments
catalogDict.setName(COSName.PAGE_MODE, "UseAttachments"); catalogDict.setName(COSName.PAGE_MODE, "UseAttachments");
// Also set viewer preferences for better attachment viewing experience // Also set viewer preferences for better attachment viewing experience
COSDictionary viewerPrefs = COSDictionary viewerPrefs = (COSDictionary) catalogDict.getDictionaryObject(COSName.VIEWER_PREFERENCES);
(COSDictionary) catalogDict.getDictionaryObject(COSName.VIEWER_PREFERENCES);
if (viewerPrefs == null) { if (viewerPrefs == null) {
viewerPrefs = new COSDictionary(); viewerPrefs = new COSDictionary();
catalogDict.setItem(COSName.VIEWER_PREFERENCES, viewerPrefs); catalogDict.setItem(COSName.VIEWER_PREFERENCES, viewerPrefs);
} }
// Set NonFullScreenPageMode to UseAttachments as fallback for viewers that support // Set NonFullScreenPageMode to UseAttachments as fallback for viewers that support it
// it
viewerPrefs.setName(COSName.getPDFName("NonFullScreenPageMode"), "UseAttachments"); viewerPrefs.setName(COSName.getPDFName("NonFullScreenPageMode"), "UseAttachments");
// Additional viewer preferences that may help with attachment display // Additional viewer preferences that may help with attachment display
viewerPrefs.setBoolean(COSName.getPDFName("DisplayDocTitle"), true); viewerPrefs.setBoolean(COSName.getPDFName("DisplayDocTitle"), true);
log.info( log.info("Set PDF PageMode to UseAttachments to automatically show attachments pane");
"Set PDF PageMode to UseAttachments to automatically show attachments pane");
} }
} catch (Exception e) { } catch (Exception e) {
// Log warning but don't fail the entire operation for viewer preferences // Log warning but don't fail the entire operation for viewer preferences
@ -1427,7 +1391,7 @@ public class EmlToPdf {
} }
} }
case '_' -> // In RFC 2047, underscore represents space case '_' -> // In RFC 2047, underscore represents space
result.append(' '); result.append(' ');
default -> result.append(c); default -> result.append(c);
} }
} }
@ -1500,7 +1464,8 @@ public class EmlToPdf {
private float y; private float y;
private String character; private String character;
public EmojiPosition() {} public EmojiPosition() {
}
public EmojiPosition(int pageIndex, float x, float y, String character) { public EmojiPosition(int pageIndex, float x, float y, String character) {
this.pageIndex = pageIndex; this.pageIndex = pageIndex;
@ -1510,8 +1475,9 @@ public class EmlToPdf {
} }
} }
public static class EmojiPositionFinder extends org.apache.pdfbox.text.PDFTextStripper { public static class EmojiPositionFinder extends org.apache.pdfbox.text.PDFTextStripper {
@Getter private final List<EmojiPosition> positions = new ArrayList<>(); @Getter
private final List<EmojiPosition> positions = new ArrayList<>();
private int currentPageIndex; private int currentPageIndex;
private boolean sortByPosition; private boolean sortByPosition;
private boolean isInAttachmentSection; private boolean isInAttachmentSection;
@ -1537,9 +1503,7 @@ public class EmlToPdf {
} }
@Override @Override
protected void writeString( protected void writeString(String string, List<org.apache.pdfbox.text.TextPosition> textPositions) throws IOException {
String string, List<org.apache.pdfbox.text.TextPosition> textPositions)
throws IOException {
// Check if we are entering or exiting the attachment section // Check if we are entering or exiting the attachment section
String lowerString = string.toLowerCase(); String lowerString = string.toLowerCase();
@ -1549,14 +1513,10 @@ public class EmlToPdf {
attachmentSectionFound = true; attachmentSectionFound = true;
} }
// Look for attachment section end markers (common patterns that indicate end of // Look for attachment section end markers (common patterns that indicate end of attachments)
// attachments) if (isInAttachmentSection && (lowerString.contains("</body>") ||
if (isInAttachmentSection lowerString.contains("</html>") ||
&& (lowerString.contains("</body>") (attachmentSectionFound && lowerString.trim().isEmpty() && string.length() > 50))) {
|| lowerString.contains("</html>")
|| (attachmentSectionFound
&& lowerString.trim().isEmpty()
&& string.length() > 50))) {
isInAttachmentSection = false; isInAttachmentSection = false;
} }
@ -1567,17 +1527,17 @@ public class EmlToPdf {
for (int i = 0; i < string.length(); i++) { for (int i = 0; i < string.length(); i++) {
// Check if we have a complete paperclip emoji at this position // Check if we have a complete paperclip emoji at this position
if (i < string.length() - 1 if (i < string.length() - 1 &&
&& string.substring(i, i + 2).equals(paperclipEmoji) string.substring(i, i + 2).equals(paperclipEmoji) &&
&& i < textPositions.size()) { i < textPositions.size()) {
org.apache.pdfbox.text.TextPosition textPosition = textPositions.get(i); org.apache.pdfbox.text.TextPosition textPosition = textPositions.get(i);
EmojiPosition position = EmojiPosition position = new EmojiPosition(
new EmojiPosition( currentPageIndex,
currentPageIndex, textPosition.getXDirAdj(),
textPosition.getXDirAdj(), textPosition.getYDirAdj(),
textPosition.getYDirAdj(), paperclipEmoji
paperclipEmoji); );
positions.add(position); positions.add(position);
} }
} }
@ -1594,6 +1554,7 @@ public class EmlToPdf {
return sortByPosition; return sortByPosition;
} }
public void reset() { public void reset() {
positions.clear(); positions.clear();
currentPageIndex = 0; currentPageIndex = 0;

View File

@ -2,6 +2,7 @@ package stirling.software.common.util;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;

View File

@ -11,10 +11,13 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.configuration.RuntimePathConfig;
@Component @Component

View File

@ -1,6 +1,5 @@
package stirling.software.common.util; package stirling.software.common.util;
import io.github.pixee.security.ZipSecurity;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
@ -14,6 +13,9 @@ import java.util.stream.Stream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import io.github.pixee.security.ZipSecurity;
import stirling.software.common.model.api.converters.HTMLToPdfRequest; import stirling.software.common.model.api.converters.HTMLToPdfRequest;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;

View File

@ -1,8 +1,5 @@
package stirling.software.common.util; package stirling.software.common.util;
import com.fathzer.soft.javaluator.DoubleEvaluator;
import io.github.pixee.security.HostValidator;
import io.github.pixee.security.Urls;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@ -17,11 +14,19 @@ import java.util.Arrays;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.fathzer.soft.javaluator.DoubleEvaluator;
import io.github.pixee.security.HostValidator;
import io.github.pixee.security.Urls;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.common.configuration.InstallationPathConfig;
@Slf4j @Slf4j

View File

@ -1,18 +1,22 @@
package stirling.software.common.util; package stirling.software.common.util;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import javax.imageio.ImageIO;
import org.springframework.web.multipart.MultipartFile;
import com.drew.imaging.ImageMetadataReader; import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException; import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata; import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException; import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifSubIFDDirectory; import com.drew.metadata.exif.ExifSubIFDDirectory;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import javax.imageio.ImageIO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
@Slf4j @Slf4j
public class ImageProcessingUtils { public class ImageProcessingUtils {

View File

@ -1,8 +1,5 @@
package stirling.software.common.util; package stirling.software.common.util;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import com.vladsch.flexmark.util.data.MutableDataSet;
import io.github.pixee.security.Filenames;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -15,14 +12,22 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import com.vladsch.flexmark.util.data.MutableDataSet;
import io.github.pixee.security.Filenames;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
@Slf4j @Slf4j

View File

@ -1,6 +1,5 @@
package stirling.software.common.util; package stirling.software.common.util;
import io.github.pixee.security.Filenames;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage; import java.awt.image.RenderedImage;
@ -11,9 +10,10 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import javax.imageio.*; import javax.imageio.*;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@ -30,6 +30,11 @@ import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
@Slf4j @Slf4j

View File

@ -1,6 +1,5 @@
package stirling.software.common.util; package stirling.software.common.util;
import io.github.pixee.security.BoundedLineReader;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -13,7 +12,11 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import io.github.pixee.security.BoundedLineReader;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
@Slf4j @Slf4j

View File

@ -1,9 +1,10 @@
package stirling.software.common.util; package stirling.software.common.util;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
import jakarta.servlet.http.HttpServletRequest;
public class UrlUtils { public class UrlUtils {
public static String getOrigin(HttpServletRequest request) { public static String getOrigin(HttpServletRequest request) {

View File

@ -1,10 +1,10 @@
package stirling.software.common.util; package stirling.software.common.util;
import io.github.pixee.security.Filenames;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -12,6 +12,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
public class WebResponseUtils { public class WebResponseUtils {
public static ResponseEntity<byte[]> boasToWebResponse( public static ResponseEntity<byte[]> boasToWebResponse(

View File

@ -13,7 +13,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
import org.snakeyaml.engine.v2.api.Dump; import org.snakeyaml.engine.v2.api.Dump;
import org.snakeyaml.engine.v2.api.DumpSettings; import org.snakeyaml.engine.v2.api.DumpSettings;
import org.snakeyaml.engine.v2.api.LoadSettings; import org.snakeyaml.engine.v2.api.LoadSettings;
@ -30,6 +30,8 @@ import org.snakeyaml.engine.v2.nodes.Tag;
import org.snakeyaml.engine.v2.parser.ParserImpl; import org.snakeyaml.engine.v2.parser.ParserImpl;
import org.snakeyaml.engine.v2.scanner.StreamReader; import org.snakeyaml.engine.v2.scanner.StreamReader;
import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class YamlHelper { public class YamlHelper {

View File

@ -8,7 +8,7 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@ -21,6 +21,9 @@ import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.text.TextPosition; import org.apache.pdfbox.text.TextPosition;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.misc.HighContrastColorCombination; import stirling.software.common.model.api.misc.HighContrastColorCombination;
import stirling.software.common.model.api.misc.ReplaceAndInvert; import stirling.software.common.model.api.misc.ReplaceAndInvert;

View File

@ -7,7 +7,9 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@ -16,6 +18,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.api.misc.ReplaceAndInvert; import stirling.software.common.model.api.misc.ReplaceAndInvert;
public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy { public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy {

View File

@ -3,6 +3,7 @@ package stirling.software.common.util.misc;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.text.PDFTextStripperByArea; import org.apache.pdfbox.text.PDFTextStripperByArea;
import org.apache.pdfbox.text.TextPosition; import org.apache.pdfbox.text.TextPosition;

View File

@ -1,10 +1,13 @@
package stirling.software.common.util.misc; package stirling.software.common.util.misc;
import java.io.IOException; import java.io.IOException;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import lombok.Data;
import lombok.EqualsAndHashCode;
import stirling.software.common.model.api.PDFFile; import stirling.software.common.model.api.PDFFile;
import stirling.software.common.model.api.misc.ReplaceAndInvert; import stirling.software.common.model.api.misc.ReplaceAndInvert;

View File

@ -1,12 +1,15 @@
package stirling.software.common.util.propertyeditor; package stirling.software.common.util.propertyeditor;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.api.security.RedactionArea; import stirling.software.common.model.api.security.RedactionArea;
@Slf4j @Slf4j

View File

@ -1,11 +1,12 @@
package stirling.software.common.util.propertyeditor; package stirling.software.common.util.propertyeditor;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.beans.PropertyEditorSupport; import java.beans.PropertyEditorSupport;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
public class StringToMapPropertyEditor extends PropertyEditorSupport { public class StringToMapPropertyEditor extends PropertyEditorSupport {
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();

View File

@ -1,15 +1,7 @@
repositories { repositories {
maven { url = "https://build.shibboleth.net/maven/releases" } maven { url = "https://build.shibboleth.net/maven/releases" }
} }
bootRun {
enabled = false
}
spotless {
java {
target sourceSets.main.allJava
googleJavaFormat(googleJavaFormatVersion).aosp()
}
}
dependencies { dependencies {
implementation project(':common') implementation project(':common')
@ -23,17 +15,17 @@ dependencies {
api 'org.springframework.boot:spring-boot-starter-data-jpa' api 'org.springframework.boot:spring-boot-starter-data-jpa'
api 'org.springframework.boot:spring-boot-starter-oauth2-client' api 'org.springframework.boot:spring-boot-starter-oauth2-client'
api 'org.springframework.boot:spring-boot-starter-mail' api 'org.springframework.boot:spring-boot-starter-mail'
api 'io.swagger.core.v3:swagger-core-jakarta:2.2.32' api 'io.swagger.core.v3:swagger-core-jakarta:2.2.30'
implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0' implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0'
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17 // https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
implementation 'org.bouncycastle:bcprov-jdk18on:1.81' implementation 'org.bouncycastle:bcprov-jdk18on:1.80'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE'
api 'io.micrometer:micrometer-registry-prometheus' api 'io.micrometer:micrometer-registry-prometheus'
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database
runtimeOnly 'org.postgresql:postgresql:42.7.7' runtimeOnly 'org.postgresql:postgresql:42.7.5'
constraints { constraints {
implementation "org.opensaml:opensaml-core:$openSamlVersion" implementation "org.opensaml:opensaml-core:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion" implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"

View File

@ -1,41 +0,0 @@
package stirling.software.proprietary.model;
import jakarta.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import lombok.*;
import stirling.software.proprietary.security.model.User;
@Entity
@Table(name = "teams")
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString(onlyExplicitlyIncluded = true)
public class Team implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "team_id")
private Long id;
@Column(name = "name", unique = true, nullable = false)
private String name;
@OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<User> users = new HashSet<>();
public void addUser(User user) {
users.add(user);
user.setTeam(this);
}
public void removeUser(User user) {
users.remove(user);
user.setTeam(null);
}
}

View File

@ -1,19 +0,0 @@
package stirling.software.proprietary.model.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class TeamWithUserCountDTO {
private Long id;
private String name;
private Long userCount;
// Constructor for JPQL projection
public TeamWithUserCountDTO(Long id, String name, Long userCount) {
this.id = id;
this.name = name;
this.userCount = userCount;
}
}

View File

@ -1,11 +1,8 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.InternalAuthenticationServiceException;
@ -13,6 +10,13 @@ import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.service.LoginAttemptService; import stirling.software.proprietary.security.service.LoginAttemptService;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;

View File

@ -1,14 +1,18 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import java.io.IOException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
import stirling.software.common.util.RequestUriUtils; import stirling.software.common.util.RequestUriUtils;
import stirling.software.proprietary.security.service.LoginAttemptService; import stirling.software.proprietary.security.service.LoginAttemptService;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;

View File

@ -1,22 +1,27 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import com.coveo.saml.SamlClient;
import com.coveo.saml.SamlException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import com.coveo.saml.SamlClient;
import com.coveo.saml.SamlException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.AppConfig; import stirling.software.common.configuration.AppConfig;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;

View File

@ -1,20 +1,19 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import jakarta.annotation.PostConstruct;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.enumeration.Role; import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.service.DatabaseServiceInterface; import stirling.software.proprietary.security.service.DatabaseServiceInterface;
import stirling.software.proprietary.security.service.TeamService;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;
@Slf4j @Slf4j
@ -23,8 +22,9 @@ import stirling.software.proprietary.security.service.UserService;
public class InitialSecuritySetup { public class InitialSecuritySetup {
private final UserService userService; private final UserService userService;
private final TeamService teamService;
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final DatabaseServiceInterface databaseService; private final DatabaseServiceInterface databaseService;
@PostConstruct @PostConstruct
@ -40,7 +40,6 @@ public class InitialSecuritySetup {
} }
userService.migrateOauth2ToSSO(); userService.migrateOauth2ToSSO();
assignUsersToDefaultTeamIfMissing();
initializeInternalApiUser(); initializeInternalApiUser();
} catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) { } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {
log.error("Failed to initialize security setup.", e); log.error("Failed to initialize security setup.", e);
@ -48,24 +47,6 @@ public class InitialSecuritySetup {
} }
} }
private void assignUsersToDefaultTeamIfMissing() {
Team defaultTeam = teamService.getOrCreateDefaultTeam();
Team internalTeam = teamService.getOrCreateInternalTeam();
List<User> usersWithoutTeam = userService.getUsersWithoutTeam();
for (User user : usersWithoutTeam) {
if (user.getUsername().equalsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
user.setTeam(internalTeam);
} else {
user.setTeam(defaultTeam);
}
}
userService.saveAll(usersWithoutTeam); // batch save
log.info(
"Assigned {} user(s) without a team to the default team.", usersWithoutTeam.size());
}
private void initializeAdminUser() throws SQLException, UnsupportedProviderException { private void initializeAdminUser() throws SQLException, UnsupportedProviderException {
String initialUsername = String initialUsername =
applicationProperties.getSecurity().getInitialLogin().getUsername(); applicationProperties.getSecurity().getInitialLogin().getUsername();
@ -77,9 +58,7 @@ public class InitialSecuritySetup {
&& !initialPassword.isEmpty() && !initialPassword.isEmpty()
&& userService.findByUsernameIgnoreCase(initialUsername).isEmpty()) { && userService.findByUsernameIgnoreCase(initialUsername).isEmpty()) {
Team team = teamService.getOrCreateDefaultTeam(); userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
userService.saveUser(
initialUsername, initialPassword, team, Role.ADMIN.getRoleId(), false);
log.info("Admin user created: {}", initialUsername); log.info("Admin user created: {}", initialUsername);
} else { } else {
createDefaultAdminUser(); createDefaultAdminUser();
@ -91,9 +70,7 @@ public class InitialSecuritySetup {
String defaultPassword = "stirling"; String defaultPassword = "stirling";
if (userService.findByUsernameIgnoreCase(defaultUsername).isEmpty()) { if (userService.findByUsernameIgnoreCase(defaultUsername).isEmpty()) {
Team team = teamService.getOrCreateDefaultTeam(); userService.saveUser(defaultUsername, defaultPassword, Role.ADMIN.getRoleId(), true);
userService.saveUser(
defaultUsername, defaultPassword, team, Role.ADMIN.getRoleId(), true);
log.info("Default admin user created: {}", defaultUsername); log.info("Default admin user created: {}", defaultUsername);
} }
} }
@ -101,29 +78,12 @@ public class InitialSecuritySetup {
private void initializeInternalApiUser() private void initializeInternalApiUser()
throws IllegalArgumentException, SQLException, UnsupportedProviderException { throws IllegalArgumentException, SQLException, UnsupportedProviderException {
if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) { if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
Team team = teamService.getOrCreateInternalTeam();
userService.saveUser( userService.saveUser(
Role.INTERNAL_API_USER.getRoleId(), Role.INTERNAL_API_USER.getRoleId(),
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
team, Role.INTERNAL_API_USER.getRoleId());
Role.INTERNAL_API_USER.getRoleId(),
false);
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId()); userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
log.info("Internal API user created: {}", Role.INTERNAL_API_USER.getRoleId()); log.info("Internal API user created: {}", Role.INTERNAL_API_USER.getRoleId());
} else {
Optional<User> internalApiUserOpt =
userService.findByUsernameIgnoreCase(Role.INTERNAL_API_USER.getRoleId());
if (internalApiUserOpt.isPresent()) {
User internalApiUser = internalApiUserOpt.get();
// move to team internal API user
if (!internalApiUser.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
log.info(
"Moving internal API user to team: {}", TeamService.INTERNAL_TEAM_NAME);
Team internalTeam = teamService.getOrCreateInternalTeam();
userService.changeUserTeam(internalApiUser, internalTeam);
}
}
} }
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey()); userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
} }

View File

@ -1,8 +1,10 @@
package stirling.software.proprietary.security; package stirling.software.proprietary.security;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import stirling.software.proprietary.security.filter.IPRateLimitingFilter; import stirling.software.proprietary.security.filter.IPRateLimitingFilter;
@Component @Component

View File

@ -1,11 +0,0 @@
package stirling.software.proprietary.security.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Annotation to mark endpoints that require a Pro or higher license. */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PremiumEndpoint {}

View File

@ -1,30 +0,0 @@
package stirling.software.proprietary.security.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
@Aspect
@Component
public class PremiumEndpointAspect {
private final boolean runningProOrHigher;
public PremiumEndpointAspect(@Qualifier("runningProOrHigher") boolean runningProOrHigher) {
this.runningProOrHigher = runningProOrHigher;
}
@Around(
"@annotation(stirling.software.proprietary.security.config.PremiumEndpoint) || @within(stirling.software.proprietary.security.config.PremiumEndpoint)")
public Object checkPremiumAccess(ProceedingJoinPoint joinPoint) throws Throwable {
if (!runningProOrHigher) {
throw new ResponseStatusException(
HttpStatus.FORBIDDEN, "This endpoint requires a Pro or higher license");
}
return joinPoint.proceed();
}
}

View File

@ -1,8 +1,7 @@
package stirling.software.proprietary.security.configuration; package stirling.software.proprietary.security.configuration;
import javax.sql.DataSource; import javax.sql.DataSource;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.domain.EntityScan;
@ -10,8 +9,11 @@ import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
@ -19,12 +21,8 @@ import stirling.software.common.model.exception.UnsupportedProviderException;
@Slf4j @Slf4j
@Getter @Getter
@Configuration @Configuration
@EnableJpaRepositories( @EnableJpaRepositories(basePackages = "stirling.software.proprietary.security.database.repository")
basePackages = { @EntityScan({"stirling.software.proprietary.security.model"})
"stirling.software.proprietary.security.database.repository",
"stirling.software.proprietary.security.repository"
})
@EntityScan({"stirling.software.proprietary.security.model", "stirling.software.proprietary.model"})
public class DatabaseConfig { public class DatabaseConfig {
public final String DATASOURCE_DEFAULT_URL; public final String DATASOURCE_DEFAULT_URL;
@ -57,7 +55,6 @@ public class DatabaseConfig {
*/ */
@Bean @Bean
@Qualifier("dataSource") @Qualifier("dataSource")
@Primary
public DataSource dataSource() throws UnsupportedProviderException { public DataSource dataSource() throws UnsupportedProviderException {
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create(); DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();

View File

@ -1,13 +1,16 @@
package stirling.software.proprietary.security.configuration; package stirling.software.proprietary.security.configuration;
import java.util.Properties; import java.util.Properties;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.JavaMailSenderImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
/** /**

View File

@ -1,7 +1,7 @@
package stirling.software.proprietary.security.configuration; package stirling.software.proprietary.security.configuration;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
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.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -27,6 +27,9 @@ import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.AppConfig; import stirling.software.common.configuration.AppConfig;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.proprietary.security.CustomAuthenticationFailureHandler; import stirling.software.proprietary.security.CustomAuthenticationFailureHandler;

View File

@ -2,12 +2,12 @@ package stirling.software.proprietary.security.configuration.ee;
import static stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License; import static stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.EnterpriseEdition; import stirling.software.common.model.ApplicationProperties.EnterpriseEdition;
import stirling.software.common.model.ApplicationProperties.Premium; import stirling.software.common.model.ApplicationProperties.Premium;
@ -28,23 +28,18 @@ public class EEAppConfig {
migrateEnterpriseSettingsToPremium(this.applicationProperties); migrateEnterpriseSettingsToPremium(this.applicationProperties);
} }
@Profile("security")
@Bean(name = "runningProOrHigher") @Bean(name = "runningProOrHigher")
@Primary @Qualifier("runningProOrHigher")
public boolean runningProOrHigher() { public boolean runningProOrHigher() {
return licenseKeyChecker.getPremiumLicenseEnabledResult() != License.NORMAL; return licenseKeyChecker.getPremiumLicenseEnabledResult() != License.NORMAL;
} }
@Profile("security")
@Bean(name = "license") @Bean(name = "license")
@Primary
public String licenseType() { public String licenseType() {
return licenseKeyChecker.getPremiumLicenseEnabledResult().name(); return licenseKeyChecker.getPremiumLicenseEnabledResult().name();
} }
@Profile("security")
@Bean(name = "runningEE") @Bean(name = "runningEE")
@Primary
public boolean runningEnterprise() { public boolean runningEnterprise() {
return licenseKeyChecker.getPremiumLicenseEnabledResult() == License.ENTERPRISE; return licenseKeyChecker.getPremiumLicenseEnabledResult() == License.ENTERPRISE;
} }
@ -54,9 +49,7 @@ public class EEAppConfig {
return applicationProperties.getPremium().getProFeatures().isSsoAutoLogin(); return applicationProperties.getPremium().getProFeatures().isSsoAutoLogin();
} }
@Profile("security")
@Bean(name = "GoogleDriveEnabled") @Bean(name = "GoogleDriveEnabled")
@Primary
public boolean googleDriveEnabled() { public boolean googleDriveEnabled() {
return runningProOrHigher() return runningProOrHigher()
&& applicationProperties.getPremium().getProFeatures().getGoogleDrive().isEnabled(); && applicationProperties.getPremium().getProFeatures().getGoogleDrive().isEnabled();
@ -80,9 +73,9 @@ public class EEAppConfig {
// Copy the license key if it's set in enterprise but not in premium // Copy the license key if it's set in enterprise but not in premium
if (premium.getKey() == null if (premium.getKey() == null
|| "00000000-0000-0000-0000-000000000000".equals(premium.getKey())) { || premium.getKey().equals("00000000-0000-0000-0000-000000000000")) {
if (enterpriseEdition.getKey() != null if (enterpriseEdition.getKey() != null
&& !"00000000-0000-0000-0000-000000000000".equals(enterpriseEdition.getKey())) { && !enterpriseEdition.getKey().equals("00000000-0000-0000-0000-000000000000")) {
premium.setKey(enterpriseEdition.getKey()); premium.setKey(enterpriseEdition.getKey());
} }
} }

View File

@ -1,20 +1,24 @@
package stirling.software.proprietary.security.configuration.ee; package stirling.software.proprietary.security.configuration.ee;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.posthog.java.shaded.org.json.JSONException;
import com.posthog.java.shaded.org.json.JSONObject;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.util.Base64; import java.util.Base64;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters; import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer; import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.posthog.java.shaded.org.json.JSONException;
import com.posthog.java.shaded.org.json.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils; import stirling.software.common.util.GeneralUtils;

View File

@ -4,9 +4,12 @@ import java.io.IOException;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils; import stirling.software.common.util.GeneralUtils;
import stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License; import stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License;

View File

@ -1,17 +1,12 @@
package stirling.software.proprietary.security.controller.api; package stirling.software.proprietary.security.controller.api;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
@ -23,6 +18,15 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.proprietary.security.database.H2SQLCondition; import stirling.software.proprietary.security.database.H2SQLCondition;
import stirling.software.proprietary.security.service.DatabaseService; import stirling.software.proprietary.security.service.DatabaseService;

View File

@ -1,11 +1,5 @@
package stirling.software.proprietary.security.controller.api; package stirling.software.proprietary.security.controller.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.mail.MessagingException;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -14,6 +8,16 @@ import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.mail.MessagingException;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.proprietary.security.model.api.Email; import stirling.software.proprietary.security.model.api.Email;
import stirling.software.proprietary.security.service.EmailService; import stirling.software.proprietary.security.service.EmailService;

View File

@ -1,126 +0,0 @@
package stirling.software.proprietary.security.controller.api;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.transaction.Transactional;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.config.PremiumEndpoint;
import stirling.software.proprietary.security.database.repository.UserRepository;
import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.repository.TeamRepository;
import stirling.software.proprietary.security.service.TeamService;
@Controller
@RequestMapping("/api/v1/team")
@Tag(name = "Team", description = "Team Management APIs")
@Slf4j
@RequiredArgsConstructor
@PremiumEndpoint
public class TeamController {
private final TeamRepository teamRepository;
private final UserRepository userRepository;
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/create")
public RedirectView createTeam(@RequestParam("name") String name) {
if (teamRepository.existsByNameIgnoreCase(name)) {
return new RedirectView("/adminSettings?messageType=teamExists");
}
Team team = new Team();
team.setName(name);
teamRepository.save(team);
return new RedirectView("/adminSettings?messageType=teamCreated");
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/rename")
public RedirectView renameTeam(
@RequestParam("teamId") Long teamId, @RequestParam("newName") String newName) {
Optional<Team> existing = teamRepository.findById(teamId);
if (existing.isEmpty()) {
return new RedirectView("/adminSettings?messageType=teamNotFound");
}
if (teamRepository.existsByNameIgnoreCase(newName)) {
return new RedirectView("/adminSettings?messageType=teamNameExists");
}
Team team = existing.get();
// Prevent renaming the Internal team
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return new RedirectView("/adminSettings?messageType=internalTeamNotAccessible");
}
team.setName(newName);
teamRepository.save(team);
return new RedirectView("/adminSettings?messageType=teamRenamed");
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/delete")
@Transactional
public RedirectView deleteTeam(@RequestParam("teamId") Long teamId) {
Optional<Team> teamOpt = teamRepository.findById(teamId);
if (teamOpt.isEmpty()) {
return new RedirectView("/adminSettings?messageType=teamNotFound");
}
Team team = teamOpt.get();
// Prevent deleting the Internal team
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return new RedirectView("/adminSettings?messageType=internalTeamNotAccessible");
}
long memberCount = userRepository.countByTeam(team);
if (memberCount > 0) {
return new RedirectView("/adminSettings?messageType=teamHasUsers");
}
teamRepository.delete(team);
return new RedirectView("/adminSettings?messageType=teamDeleted");
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/addUser")
@Transactional
public RedirectView addUserToTeam(
@RequestParam("teamId") Long teamId, @RequestParam("userId") Long userId) {
// Find the team
Team team =
teamRepository
.findById(teamId)
.orElseThrow(() -> new RuntimeException("Team not found"));
// Prevent adding users to the Internal team
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return new RedirectView("/teams?error=internalTeamNotAccessible");
}
// Find the user
User user =
userRepository
.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
// Check if user is in the Internal team - prevent moving them
if (user.getTeam() != null
&& user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return new RedirectView("/teams/" + teamId + "?error=cannotMoveInternalUsers");
}
// Assign user to team
user.setTeam(team);
userRepository.save(user);
// Redirect back to team details page
return new RedirectView("/teams/" + teamId + "?messageType=userAdded");
}
}

View File

@ -1,17 +1,12 @@
package stirling.software.proprietary.security.controller.api; package stirling.software.proprietary.security.controller.api;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.transaction.Transactional;
import java.io.IOException; import java.io.IOException;
import java.security.Principal; import java.security.Principal;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@ -25,17 +20,22 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.enumeration.Role; import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.database.repository.UserRepository;
import stirling.software.proprietary.security.model.AuthenticationType; import stirling.software.proprietary.security.model.AuthenticationType;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.model.api.user.UsernameAndPass; import stirling.software.proprietary.security.model.api.user.UsernameAndPass;
import stirling.software.proprietary.security.repository.TeamRepository;
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.proprietary.security.service.TeamService;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;
import stirling.software.proprietary.security.session.SessionPersistentRegistry; import stirling.software.proprietary.security.session.SessionPersistentRegistry;
@ -50,8 +50,6 @@ public class UserController {
private final UserService userService; private final UserService userService;
private final SessionPersistentRegistry sessionRegistry; private final SessionPersistentRegistry sessionRegistry;
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final TeamRepository teamRepository;
private final UserRepository userRepository;
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/register") @PostMapping("/register")
@ -62,13 +60,7 @@ public class UserController {
return "register"; return "register";
} }
try { try {
Team team = teamRepository.findByName(TeamService.DEFAULT_TEAM_NAME).orElse(null); userService.saveUser(requestModel.getUsername(), requestModel.getPassword());
userService.saveUser(
requestModel.getUsername(),
requestModel.getPassword(),
team,
Role.USER.getRoleId(),
false);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return "redirect:/login?messageType=invalidUsername"; return "redirect:/login?messageType=invalidUsername";
} }
@ -208,7 +200,6 @@ public class UserController {
@RequestParam(name = "username", required = true) String username, @RequestParam(name = "username", required = true) String username,
@RequestParam(name = "password", required = false) String password, @RequestParam(name = "password", required = false) String password,
@RequestParam(name = "role") String role, @RequestParam(name = "role") String role,
@RequestParam(name = "teamId", required = false) Long teamId,
@RequestParam(name = "authType") String authType, @RequestParam(name = "authType") String authType,
@RequestParam(name = "forceChange", required = false, defaultValue = "false") @RequestParam(name = "forceChange", required = false, defaultValue = "false")
boolean forceChange) boolean forceChange)
@ -242,32 +233,13 @@ public class UserController {
// If the role ID is not valid, redirect with an error message // If the role ID is not valid, redirect with an error message
return new RedirectView("/adminSettings?messageType=invalidRole", true); return new RedirectView("/adminSettings?messageType=invalidRole", true);
} }
// Use teamId if provided, otherwise use default team
Long effectiveTeamId = teamId;
if (effectiveTeamId == null) {
Team defaultTeam =
teamRepository.findByName(TeamService.DEFAULT_TEAM_NAME).orElse(null);
if (defaultTeam != null) {
effectiveTeamId = defaultTeam.getId();
}
} else {
// Check if the selected team is Internal - prevent assigning to it
Team selectedTeam = teamRepository.findById(effectiveTeamId).orElse(null);
if (selectedTeam != null
&& TeamService.INTERNAL_TEAM_NAME.equals(selectedTeam.getName())) {
return new RedirectView(
"/adminSettings?messageType=internalTeamNotAccessible", true);
}
}
if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) { if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) {
userService.saveUser(username, AuthenticationType.SSO, effectiveTeamId, role); userService.saveUser(username, AuthenticationType.SSO, role);
} else { } else {
if (password.isBlank()) { if (password.isBlank()) {
return new RedirectView("/adminSettings?messageType=invalidPassword", true); return new RedirectView("/adminSettings?messageType=invalidPassword", true);
} }
userService.saveUser(username, password, effectiveTeamId, role, forceChange); userService.saveUser(username, password, role, forceChange);
} }
return new RedirectView( return new RedirectView(
"/adminSettings", // Redirect to account page after adding the user "/adminSettings", // Redirect to account page after adding the user
@ -276,11 +248,9 @@ public class UserController {
@PreAuthorize("hasRole('ROLE_ADMIN')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/changeRole") @PostMapping("/admin/changeRole")
@Transactional
public RedirectView changeRole( public RedirectView changeRole(
@RequestParam(name = "username") String username, @RequestParam(name = "username") String username,
@RequestParam(name = "role") String role, @RequestParam(name = "role") String role,
@RequestParam(name = "teamId", required = false) Long teamId,
Authentication authentication) Authentication authentication)
throws SQLException, UnsupportedProviderException { throws SQLException, UnsupportedProviderException {
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username); Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
@ -308,29 +278,6 @@ public class UserController {
return new RedirectView("/adminSettings?messageType=invalidRole", true); return new RedirectView("/adminSettings?messageType=invalidRole", true);
} }
User user = userOpt.get(); User user = userOpt.get();
// Update the team if a teamId is provided
if (teamId != null) {
Team team = teamRepository.findById(teamId).orElse(null);
if (team != null) {
// Prevent assigning to Internal team
if (TeamService.INTERNAL_TEAM_NAME.equals(team.getName())) {
return new RedirectView(
"/adminSettings?messageType=internalTeamNotAccessible", true);
}
// Prevent moving users from Internal team
if (user.getTeam() != null
&& TeamService.INTERNAL_TEAM_NAME.equals(user.getTeam().getName())) {
return new RedirectView(
"/adminSettings?messageType=cannotMoveInternalUsers", true);
}
user.setTeam(team);
userRepository.save(user);
}
}
userService.changeRole(user, role); userService.changeRole(user, role);
return new RedirectView( return new RedirectView(
"/adminSettings", // Redirect to account page after adding the user "/adminSettings", // Redirect to account page after adding the user

View File

@ -1,11 +1,7 @@
package stirling.software.proprietary.security.config; package stirling.software.proprietary.security.controller.web;
import static stirling.software.common.util.ProviderUtils.validateProvider; import static stirling.software.common.util.ProviderUtils.validateProvider;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Date; import java.util.Date;
@ -14,7 +10,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier; 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;
@ -23,6 +19,16 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security; import stirling.software.common.model.ApplicationProperties.Security;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
@ -32,14 +38,11 @@ import stirling.software.common.model.enumeration.Role;
import stirling.software.common.model.oauth2.GitHubProvider; import stirling.software.common.model.oauth2.GitHubProvider;
import stirling.software.common.model.oauth2.GoogleProvider; import stirling.software.common.model.oauth2.GoogleProvider;
import stirling.software.common.model.oauth2.KeycloakProvider; import stirling.software.common.model.oauth2.KeycloakProvider;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.database.repository.UserRepository; import stirling.software.proprietary.security.database.repository.UserRepository;
import stirling.software.proprietary.security.model.Authority; import stirling.software.proprietary.security.model.Authority;
import stirling.software.proprietary.security.model.SessionEntity; import stirling.software.proprietary.security.model.SessionEntity;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.repository.TeamRepository;
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.proprietary.security.service.TeamService;
import stirling.software.proprietary.security.session.SessionPersistentRegistry; import stirling.software.proprietary.security.session.SessionPersistentRegistry;
@Controller @Controller
@ -54,19 +57,16 @@ public class AccountWebController {
// Assuming you have a repository for user operations // Assuming you have a repository for user operations
private final UserRepository userRepository; private final UserRepository userRepository;
private final boolean runningEE; private final boolean runningEE;
private final TeamRepository teamRepository;
public AccountWebController( public AccountWebController(
ApplicationProperties applicationProperties, ApplicationProperties applicationProperties,
SessionPersistentRegistry sessionPersistentRegistry, SessionPersistentRegistry sessionPersistentRegistry,
UserRepository userRepository, UserRepository userRepository,
TeamRepository teamRepository,
@Qualifier("runningEE") boolean runningEE) { @Qualifier("runningEE") boolean runningEE) {
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
this.sessionPersistentRegistry = sessionPersistentRegistry; this.sessionPersistentRegistry = sessionPersistentRegistry;
this.userRepository = userRepository; this.userRepository = userRepository;
this.runningEE = runningEE; this.runningEE = runningEE;
this.teamRepository = teamRepository;
} }
@GetMapping("/login") @GetMapping("/login")
@ -210,7 +210,7 @@ public class AccountWebController {
@GetMapping("/adminSettings") @GetMapping("/adminSettings")
public String showAddUserForm( public String showAddUserForm(
HttpServletRequest request, Model model, Authentication authentication) { HttpServletRequest request, Model model, Authentication authentication) {
List<User> allUsers = userRepository.findAllWithTeam(); List<User> allUsers = userRepository.findAll();
Iterator<User> iterator = allUsers.iterator(); Iterator<User> iterator = allUsers.iterator();
Map<String, String> roleDetails = Role.getAllRoleDetails(); Map<String, String> roleDetails = Role.getAllRoleDetails();
// Map to store session information and user activity status // Map to store session information and user activity status
@ -221,28 +221,14 @@ public class AccountWebController {
while (iterator.hasNext()) { while (iterator.hasNext()) {
User user = iterator.next(); User user = iterator.next();
if (user != null) { if (user != null) {
boolean shouldRemove = false;
// Check if user is an INTERNAL_API_USER
for (Authority authority : user.getAuthorities()) { for (Authority authority : user.getAuthorities()) {
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
shouldRemove = true; iterator.remove();
roleDetails.remove(Role.INTERNAL_API_USER.getRoleId()); roleDetails.remove(Role.INTERNAL_API_USER.getRoleId());
// Break out of the inner loop once the user is removed
break; break;
} }
} }
// Also check if user is part of the Internal team
if (user.getTeam() != null
&& user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
shouldRemove = true;
}
// Remove the user if either condition is true
if (shouldRemove) {
iterator.remove();
continue;
}
// Determine the user's session status and last request time // Determine the user's session status and last request time
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval(); int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();
boolean hasActiveSession = false; boolean hasActiveSession = false;
@ -345,19 +331,6 @@ public class AccountWebController {
model.addAttribute("activeUsers", activeUsers); model.addAttribute("activeUsers", activeUsers);
model.addAttribute("disabledUsers", disabledUsers); model.addAttribute("disabledUsers", disabledUsers);
// Get all teams but filter out the Internal team
List<Team> allTeams =
teamRepository.findAll().stream()
.filter(
team ->
!team.getName()
.equals(
stirling.software.proprietary.security
.service.TeamService
.INTERNAL_TEAM_NAME))
.toList();
model.addAttribute("teams", allTeams);
model.addAttribute("maxPaidUsers", applicationProperties.getPremium().getMaxUsers()); model.addAttribute("maxPaidUsers", applicationProperties.getPremium().getMaxUsers());
return "adminSettings"; return "adminSettings";
} }

View File

@ -1,14 +1,19 @@
package stirling.software.proprietary.security.controller.web; package stirling.software.proprietary.security.controller.web;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor;
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.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import stirling.software.common.model.FileInfo; import stirling.software.common.model.FileInfo;
import stirling.software.proprietary.security.service.DatabaseService; import stirling.software.proprietary.security.service.DatabaseService;

View File

@ -1,114 +0,0 @@
package stirling.software.proprietary.security.controller.web;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.model.dto.TeamWithUserCountDTO;
import stirling.software.proprietary.security.database.repository.SessionRepository;
import stirling.software.proprietary.security.database.repository.UserRepository;
import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.repository.TeamRepository;
import stirling.software.proprietary.security.service.TeamService;
@Controller
@RequestMapping("/teams")
@RequiredArgsConstructor
@Slf4j
public class TeamWebController {
private final TeamRepository teamRepository;
private final SessionRepository sessionRepository;
private final UserRepository userRepository;
@GetMapping
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String listTeams(Model model) {
// Get teams with user counts using a DTO projection
List<TeamWithUserCountDTO> allTeamsWithCounts = teamRepository.findAllTeamsWithUserCount();
// Filter out the Internal team
List<TeamWithUserCountDTO> teamsWithCounts =
allTeamsWithCounts.stream()
.filter(team -> !team.getName().equals(TeamService.INTERNAL_TEAM_NAME))
.toList();
// Get the latest activity for each team
List<Object[]> teamActivities = sessionRepository.findLatestActivityByTeam();
// Convert the query results to a map for easy access in the view
Map<Long, Date> teamLastRequest = new HashMap<>();
for (Object[] result : teamActivities) {
Long teamId = (Long) result[0]; // teamId alias
Date lastActivity = (Date) result[1]; // lastActivity alias
teamLastRequest.put(teamId, lastActivity);
}
// Add data to the model
model.addAttribute("teamsWithCounts", teamsWithCounts);
model.addAttribute("teamLastRequest", teamLastRequest);
return "accounts/teams";
}
@GetMapping("/{id}")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String viewTeamDetails(@PathVariable("id") Long id, Model model) {
// Get the team
Team team =
teamRepository
.findById(id)
.orElseThrow(() -> new RuntimeException("Team not found"));
// Prevent access to Internal team
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
return "redirect:/teams?error=internalTeamNotAccessible";
}
// Get users for this team directly using the direct query
List<User> teamUsers = userRepository.findAllByTeamId(id);
// Get all users not in this team for the Add User to Team dropdown
// Exclude users that are in the Internal team
List<User> allUsers = userRepository.findAllWithTeam();
List<User> availableUsers =
allUsers.stream()
.filter(
user ->
(user.getTeam() == null
|| !user.getTeam().getId().equals(id))
&& (user.getTeam() == null
|| !user.getTeam()
.getName()
.equals(
TeamService
.INTERNAL_TEAM_NAME)))
.toList();
// Get the latest session for each user in the team
List<Object[]> userSessions = sessionRepository.findLatestSessionByTeamId(id);
// Create a map of username to last request date
Map<String, Date> userLastRequest = new HashMap<>();
for (Object[] result : userSessions) {
String username = (String) result[0]; // username alias
Date lastRequest = (Date) result[1]; // lastRequest alias
userLastRequest.put(username, lastRequest);
}
model.addAttribute("team", team);
model.addAttribute("teamUsers", teamUsers);
model.addAttribute("availableUsers", availableUsers);
model.addAttribute("userLastRequest", userLastRequest);
return "accounts/team-details";
}
}

View File

@ -1,10 +1,13 @@
package stirling.software.proprietary.security.database; package stirling.software.proprietary.security.database;
import java.sql.SQLException; import java.sql.SQLException;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import stirling.software.common.model.exception.UnsupportedProviderException; import stirling.software.common.model.exception.UnsupportedProviderException;
import stirling.software.proprietary.security.service.DatabaseServiceInterface; import stirling.software.proprietary.security.service.DatabaseServiceInterface;

View File

@ -1,8 +1,10 @@
package stirling.software.proprietary.security.database.repository; package stirling.software.proprietary.security.database.repository;
import java.util.Set; import java.util.Set;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import stirling.software.proprietary.security.model.Authority; import stirling.software.proprietary.security.model.Authority;
@Repository @Repository

View File

@ -1,9 +1,11 @@
package stirling.software.proprietary.security.database.repository; package stirling.software.proprietary.security.database.repository;
import java.util.Date; import java.util.Date;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import stirling.software.proprietary.security.model.PersistentLogin; import stirling.software.proprietary.security.model.PersistentLogin;
public class JPATokenRepositoryImpl implements PersistentTokenRepository { public class JPATokenRepositoryImpl implements PersistentTokenRepository {

View File

@ -2,6 +2,7 @@ package stirling.software.proprietary.security.database.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import stirling.software.proprietary.security.model.PersistentLogin; import stirling.software.proprietary.security.model.PersistentLogin;
@Repository @Repository

View File

@ -1,13 +1,16 @@
package stirling.software.proprietary.security.database.repository; package stirling.software.proprietary.security.database.repository;
import jakarta.transaction.Transactional;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import jakarta.transaction.Transactional;
import stirling.software.proprietary.security.model.SessionEntity; import stirling.software.proprietary.security.model.SessionEntity;
@Repository @Repository
@ -26,20 +29,4 @@ public interface SessionRepository extends JpaRepository<SessionEntity, String>
@Param("expired") boolean expired, @Param("expired") boolean expired,
@Param("lastRequest") Date lastRequest, @Param("lastRequest") Date lastRequest,
@Param("principalName") String principalName); @Param("principalName") String principalName);
@Query(
"SELECT t.id as teamId, MAX(s.lastRequest) as lastActivity "
+ "FROM stirling.software.proprietary.model.Team t "
+ "LEFT JOIN t.users u "
+ "LEFT JOIN SessionEntity s ON u.username = s.principalName "
+ "GROUP BY t.id")
List<Object[]> findLatestActivityByTeam();
@Query(
"SELECT u.username as username, MAX(s.lastRequest) as lastRequest "
+ "FROM stirling.software.proprietary.security.model.User u "
+ "LEFT JOIN SessionEntity s ON u.username = s.principalName "
+ "WHERE u.team.id = :teamId "
+ "GROUP BY u.username")
List<Object[]> findLatestSessionByTeamId(@Param("teamId") Long teamId);
} }

View File

@ -2,11 +2,12 @@ package stirling.software.proprietary.security.database.repository;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import stirling.software.proprietary.model.Team;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
@Repository @Repository
@ -21,18 +22,4 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByApiKey(String apiKey); Optional<User> findByApiKey(String apiKey);
List<User> findByAuthenticationTypeIgnoreCase(String authenticationType); List<User> findByAuthenticationTypeIgnoreCase(String authenticationType);
@Query("SELECT u FROM User u WHERE u.team IS NULL")
List<User> findAllWithoutTeam();
@Query(value = "SELECT u FROM User u LEFT JOIN FETCH u.team")
List<User> findAllWithTeam();
@Query(
"SELECT u FROM User u JOIN FETCH u.authorities JOIN FETCH u.team WHERE u.team.id = :teamId")
List<User> findAllByTeamId(@Param("teamId") Long teamId);
long countByTeam(Team team);
List<User> findAllByTeam(Team team);
} }

View File

@ -1,14 +1,16 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component @Component
public class EnterpriseEndpointFilter extends OncePerRequestFilter { public class EnterpriseEndpointFilter extends OncePerRequestFilter {

View File

@ -1,20 +1,24 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import stirling.software.common.util.RequestUriUtils; import stirling.software.common.util.RequestUriUtils;
import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.model.User;
import stirling.software.proprietary.security.service.UserService; import stirling.software.proprietary.security.service.UserService;

View File

@ -1,15 +1,18 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import jakarta.servlet.Filter; import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import stirling.software.common.util.RequestUriUtils; import stirling.software.common.util.RequestUriUtils;
@RequiredArgsConstructor @RequiredArgsConstructor

View File

@ -1,13 +1,9 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -20,6 +16,14 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2; import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.common.model.ApplicationProperties.Security.SAML2; import stirling.software.common.model.ApplicationProperties.Security.SAML2;

View File

@ -1,17 +1,10 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe;
import io.github.pixee.security.Newlines;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -20,6 +13,17 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe;
import io.github.pixee.security.Newlines;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.common.model.enumeration.Role; import stirling.software.common.model.enumeration.Role;
@Component @Component

View File

@ -1,6 +1,7 @@
package stirling.software.proprietary.security.model; package stirling.software.proprietary.security.model;
import java.util.Collection; import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;

View File

@ -1,5 +1,7 @@
package stirling.software.proprietary.security.model; package stirling.software.proprietary.security.model;
import java.io.Serializable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
@ -8,7 +10,7 @@ import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.io.Serializable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View File

@ -1,10 +1,12 @@
package stirling.software.proprietary.security.model; package stirling.software.proprietary.security.model;
import java.util.Date;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.util.Date;
import lombok.Data; import lombok.Data;
@Entity @Entity

View File

@ -1,10 +1,12 @@
package stirling.software.proprietary.security.model; package stirling.software.proprietary.security.model;
import java.io.Serializable;
import java.util.Date;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.io.Serializable;
import java.util.Date;
import lombok.Data; import lombok.Data;
@Entity @Entity

View File

@ -1,19 +1,21 @@
package stirling.software.proprietary.security.model; package stirling.software.proprietary.security.model;
import jakarta.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jakarta.persistence.*;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import stirling.software.common.model.enumeration.Role; import stirling.software.common.model.enumeration.Role;
import stirling.software.proprietary.model.Team;
@Entity @Entity
@Table(name = "users") @Table(name = "users")
@ -55,10 +57,6 @@ public class User implements Serializable {
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user") @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
private Set<Authority> authorities = new HashSet<>(); private Set<Authority> authorities = new HashSet<>();
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "team_id")
private Team team;
@ElementCollection @ElementCollection
@MapKeyColumn(name = "setting_key") @MapKeyColumn(name = "setting_key")
@Lob @Lob

View File

@ -1,10 +1,13 @@
package stirling.software.proprietary.security.model.api; package stirling.software.proprietary.security.model.api;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import stirling.software.common.model.api.GeneralFile; import stirling.software.common.model.api.GeneralFile;
@Data @Data

View File

@ -1,6 +1,7 @@
package stirling.software.proprietary.security.model.api.user; package stirling.software.proprietary.security.model.api.user;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;

View File

@ -1,6 +1,7 @@
package stirling.software.proprietary.security.model.api.user; package stirling.software.proprietary.security.model.api.user;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;

View File

@ -1,6 +1,7 @@
package stirling.software.proprietary.security.model.api.user; package stirling.software.proprietary.security.model.api.user;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;

View File

@ -1,6 +1,7 @@
package stirling.software.proprietary.security.model.api.user; package stirling.software.proprietary.security.model.api.user;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;

View File

@ -1,10 +1,7 @@
package stirling.software.proprietary.security.oauth2; package stirling.software.proprietary.security.oauth2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
@ -13,6 +10,12 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class CustomOAuth2AuthenticationFailureHandler public class CustomOAuth2AuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler { extends SimpleUrlAuthenticationFailureHandler {

Some files were not shown because too many files have changed in this diff Show More