From 5d073909cc6f0a58a8e012a2a76d6ee67552a569 Mon Sep 17 00:00:00 2001 From: "stirlingbot[bot]" <195170888+stirlingbot[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 17:53:51 +0100 Subject: [PATCH 1/5] Update 3rd Party Licenses (#3484) Auto-generated by StirlingBot Signed-off-by: stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com> Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com> --- .../resources/static/3rdPartyLicenses.json | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/main/resources/static/3rdPartyLicenses.json b/src/main/resources/static/3rdPartyLicenses.json index c20671d97..4395d1fa0 100644 --- a/src/main/resources/static/3rdPartyLicenses.json +++ b/src/main/resources/static/3rdPartyLicenses.json @@ -270,7 +270,7 @@ { "moduleName": "com.opencsv:opencsv", "moduleUrl": "http://opencsv.sf.net", - "moduleVersion": "5.10", + "moduleVersion": "5.11", "moduleLicense": "Apache 2", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -623,21 +623,21 @@ { "moduleName": "io.swagger.core.v3:swagger-annotations-jakarta", "moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-annotations", - "moduleVersion": "2.2.29", + "moduleVersion": "2.2.30", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "io.swagger.core.v3:swagger-core-jakarta", "moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-core", - "moduleVersion": "2.2.29", + "moduleVersion": "2.2.30", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "io.swagger.core.v3:swagger-models-jakarta", "moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-models", - "moduleVersion": "2.2.29", + "moduleVersion": "2.2.30", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, @@ -1235,6 +1235,12 @@ "moduleLicense": "GNU Library General Public License v2.1 or later", "moduleLicenseUrl": "https://www.opensource.org/licenses/LGPL-2.1" }, + { + "moduleName": "org.hibernate.validator:hibernate-validator", + "moduleVersion": "8.0.2.Final", + "moduleLicense": "Apache License 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "org.jboss.logging:jboss-logging", "moduleUrl": "http://www.jboss.org", @@ -1423,19 +1429,19 @@ }, { "moduleName": "org.springdoc:springdoc-openapi-starter-common", - "moduleVersion": "2.8.6", + "moduleVersion": "2.8.8", "moduleLicense": "The Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "org.springdoc:springdoc-openapi-starter-webmvc-api", - "moduleVersion": "2.8.6", + "moduleVersion": "2.8.8", "moduleLicense": "The Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "org.springdoc:springdoc-openapi-starter-webmvc-ui", - "moduleVersion": "2.8.6", + "moduleVersion": "2.8.8", "moduleLicense": "The Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -1544,6 +1550,13 @@ "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, + { + "moduleName": "org.springframework.boot:spring-boot-starter-validation", + "moduleUrl": "https://spring.io/projects/spring-boot", + "moduleVersion": "3.4.5", + "moduleLicense": "Apache License, Version 2.0", + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" + }, { "moduleName": "org.springframework.boot:spring-boot-starter-web", "moduleUrl": "https://spring.io/projects/spring-boot", @@ -1746,7 +1759,7 @@ { "moduleName": "org.webjars:swagger-ui", "moduleUrl": "https://www.webjars.org", - "moduleVersion": "5.20.1", + "moduleVersion": "5.21.0", "moduleLicense": "Apache-2.0" }, { From e2a5874a887a7ca895b59ce70d2b5d26913257d4 Mon Sep 17 00:00:00 2001 From: Ludy Date: Wed, 7 May 2025 10:18:02 +0200 Subject: [PATCH 2/5] fix read wrong properties (#3472) # Description of Changes Please provide a summary of the changes, including: Test file: [12345678.pdf](https://github.com/user-attachments/files/20028981/12345678.pdf) Behavior without readOnly ```json { "creator": null, "modificationDate": "java.util.GregorianCalendar[time=1746381303000,areFieldsSet=true,areAllFieldsSet=true,lenient=false,zone=java.util.SimpleTimeZone[id=GMT,offset=0,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2025,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=4,DAY_OF_YEAR=124,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=5,HOUR_OF_DAY=17,MINUTE=55,SECOND=3,MILLISECOND=0,ZONE_OFFSET=0,DST_OFFSET=0]", "keywords": null, "author": "", "subject": null, "producer": "Stirling-PDF v0.46.0", "title": "Microsoft Word - Dokument1", "creationDate": "java.util.GregorianCalendar[time=1746381238000,areFieldsSet=true,areAllFieldsSet=true,lenient=false,zone=java.util.SimpleTimeZone[id=GMT+02:00,offset=7200000,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2025,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=4,DAY_OF_YEAR=124,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=7,HOUR_OF_DAY=19,MINUTE=53,SECOND=58,MILLISECOND=0,ZONE_OFFSET=7200000,DST_OFFSET=0]" } ``` with readOnly=true ```json { "creator": null, "modificationDate": "java.util.GregorianCalendar[time=1746381238000,areFieldsSet=true,areAllFieldsSet=true,lenient=false,zone=java.util.SimpleTimeZone[id=GMT+02:00,offset=7200000,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2025,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=4,DAY_OF_YEAR=124,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=7,HOUR_OF_DAY=19,MINUTE=53,SECOND=58,MILLISECOND=0,ZONE_OFFSET=7200000,DST_OFFSET=0]", "keywords": null, "author": "", "subject": null, "producer": "Microsoft: Print To PDF", "title": "Microsoft Word - Dokument1", "creationDate": "java.util.GregorianCalendar[time=1746381238000,areFieldsSet=true,areAllFieldsSet=true,lenient=false,zone=java.util.SimpleTimeZone[id=GMT+02:00,offset=7200000,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2025,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=4,DAY_OF_YEAR=124,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=7,HOUR_OF_DAY=19,MINUTE=53,SECOND=58,MILLISECOND=0,ZONE_OFFSET=7200000,DST_OFFSET=0]" } ``` --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../software/SPDF/controller/api/AnalysisController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/stirling/software/SPDF/controller/api/AnalysisController.java b/src/main/java/stirling/software/SPDF/controller/api/AnalysisController.java index 75f61e64e..49c5bc81c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/AnalysisController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/AnalysisController.java @@ -59,7 +59,8 @@ public class AnalysisController { description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO") public Map getDocumentProperties(@ModelAttribute PDFFile file) throws IOException { - try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) { + // Load the document in read-only mode to prevent modifications and ensure the integrity of the original file. + try (PDDocument document = pdfDocumentFactory.load(file.getFileInput(), true)) { PDDocumentInformation info = document.getDocumentInformation(); Map properties = new HashMap<>(); properties.put("title", info.getTitle()); From 2ac606608aa545f83c78b8baf95626cdf18c2536 Mon Sep 17 00:00:00 2001 From: Ludy Date: Wed, 7 May 2025 10:19:06 +0200 Subject: [PATCH 3/5] Fix cert-sign API NullPointerException when pageNumber is omitted for invisible signatures (#3463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes Please provide a summary of the changes, including: - **What was changed** - Updated `SignPDFWithCertRequest` to use `Boolean` for `showSignature` and `showLogo`, and made `pageNumber` nullable. - In `CertSignController`: - Added an `@InitBinder` to convert empty multipart fields to `null`. - Extended `@PostMapping` to consume both `multipart/form-data` and `application/x-www-form-urlencoded`. - Wrapped `pageNumber` calculation in a null-check (`pageNumber = request.getPageNumber() != null ? request.getPageNumber() - 1 : null`). - Changed signature-visualization and logo checks to `Boolean.TRUE.equals(...)` to avoid unboxing NPE. - Cleaned up imports and schema annotations in the request model. - **Why the change was made** - Prevent a 500 Internal Server Error caused by calling `.intValue()` on a null `pageNumber` when `showSignature=false` (invisible signatures). - Ensure that omitting `pageNumber` doesn’t break clients using the “try it out” swagger UI or `curl`-based requests. - **Any challenges encountered** - Configuring Spring’s data binder to treat empty file inputs as `null` required a custom `PropertyEditorSupport`. - Balancing backward compatibility with stricter type handling (switching from primitive `boolean` to boxed `Boolean`). Closes #3459 --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --- .../api/security/CertSignController.java | 39 ++++++++++++++----- .../api/security/SignPDFWithCertRequest.java | 12 +++--- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index 7e1f2601f..58e5b848c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -1,6 +1,7 @@ package stirling.software.SPDF.controller.api.security; import java.awt.*; +import java.beans.PropertyEditorSupport; import java.io.*; import java.nio.file.Files; import java.security.*; @@ -53,7 +54,10 @@ import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; import org.bouncycastle.pkcs.PKCSException; import org.springframework.core.io.ClassPathResource; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -82,6 +86,18 @@ public class CertSignController { Security.addProvider(new BouncyCastleProvider()); } + @InitBinder + public void initBinder(WebDataBinder binder) { + binder.registerCustomEditor( + MultipartFile.class, + new PropertyEditorSupport() { + @Override + public void setAsText(String text) throws IllegalArgumentException { + setValue(null); + } + }); + } + private final CustomPDFDocumentFactory pdfDocumentFactory; private static void sign( @@ -103,8 +119,7 @@ public class CertSignController { signature.setLocation(location); signature.setReason(reason); signature.setSignDate(Calendar.getInstance()); - - if (showSignature) { + if (Boolean.TRUE.equals(showSignature)) { SignatureOptions signatureOptions = new SignatureOptions(); signatureOptions.setVisualSignature( instance.createVisibleSignature(doc, signature, pageNumber, showLogo)); @@ -121,13 +136,18 @@ public class CertSignController { } } - @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") + @PostMapping( + consumes = { + MediaType.MULTIPART_FORM_DATA_VALUE, + MediaType.APPLICATION_FORM_URLENCODED_VALUE + }, + value = "/cert-sign") @Operation( summary = "Sign PDF with a Digital Certificate", description = "This endpoint accepts a PDF file, a digital certificate and related" - + " information to sign the PDF. It then returns the digitally signed PDF" - + " file. Input:PDF Output:PDF Type:SISO") + + " information to sign the PDF. It then returns the digitally signed PDF" + + " file. Input:PDF Output:PDF Type:SISO") public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) throws Exception { MultipartFile pdf = request.getFileInput(); @@ -137,12 +157,13 @@ public class CertSignController { MultipartFile p12File = request.getP12File(); MultipartFile jksfile = request.getJksFile(); String password = request.getPassword(); - Boolean showSignature = request.isShowSignature(); + Boolean showSignature = request.getShowSignature(); String reason = request.getReason(); String location = request.getLocation(); String name = request.getName(); - Integer pageNumber = request.getPageNumber() - 1; - Boolean showLogo = request.isShowLogo(); + // Convert 1-indexed page number (user input) to 0-indexed page number (API requirement) + Integer pageNumber = request.getPageNumber() != null ? (request.getPageNumber() - 1) : null; + Boolean showLogo = request.getShowLogo(); if (certType == null) { throw new IllegalArgumentException("Cert type must be provided"); @@ -279,7 +300,7 @@ public class CertSignController { widget.setAppearance(appearance); try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream)) { - if (showLogo) { + if (Boolean.TRUE.equals(showLogo)) { cs.saveGraphicsState(); PDExtendedGraphicsState extState = new PDExtendedGraphicsState(); extState.setBlendMode(BlendMode.MULTIPLY); diff --git a/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java b/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java index b0266f307..d9fe92def 100644 --- a/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java @@ -20,7 +20,8 @@ public class SignPDFWithCertRequest extends PDFFile { @Schema( description = - "The private key for the digital certificate (required for PEM type certificates)") + "The private key for the digital certificate (required for PEM type" + + " certificates)") private MultipartFile privateKeyFile; @Schema(description = "The digital certificate (required for PEM type certificates)") @@ -32,11 +33,11 @@ public class SignPDFWithCertRequest extends PDFFile { @Schema(description = "The JKS keystore file (Java Key Store)") private MultipartFile jksFile; - @Schema(description = "The password for the keystore or the private key") + @Schema(description = "The password for the keystore or the private key", format = "password") private String password; @Schema(description = "Whether to visually show the signature in the PDF file") - private boolean showSignature; + private Boolean showSignature; @Schema(description = "The reason for signing the PDF") private String reason; @@ -49,9 +50,10 @@ public class SignPDFWithCertRequest extends PDFFile { @Schema( description = - "The page number where the signature should be visible. This is required if showSignature is set to true") + "The page number where the signature should be visible. This is required if" + + " showSignature is set to true") private Integer pageNumber; @Schema(description = "Whether to visually show a signature logo along with the signature") - private boolean showLogo; + private Boolean showLogo; } From 5b0eaec4365ac2a9ba1cd300d96c2980c9cb51c6 Mon Sep 17 00:00:00 2001 From: Ludy Date: Wed, 7 May 2025 10:20:21 +0200 Subject: [PATCH 4/5] Add Email Sending Service with Attachment Support (#3455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes Please provide a summary of the changes, including: - **What was changed** - Introduced a new `EmailService` for asynchronous email delivery with attachment support. - Added `MailConfig` to configure a `JavaMailSender` bean using SMTP settings from `ApplicationProperties`. - Created `EmailController` endpoint (`/api/v1/general/send-email`) to accept multipart/form-data requests for sending emails. - Defined an `Email` API model to encapsulate recipient, subject, body, and file input. - Extended `ApplicationProperties` to include a nested `Mail` class for SMTP host, port, username/password, and sender address. - Updated `settings.yml.template` to include SMTP configuration placeholders. - Enhanced `.github/labeler-config.yml` to cover all new security- and API-related source files for automated labeling. - **Why the change was made** - To enable Stirling-PDF to notify users via email—particularly useful for sending generated PDFs or alerts—directly from the application. - To centralize mail server configuration in application properties and streamline onboarding for new environments. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --- .github/labeler-config.yml | 18 +++- build.gradle | 19 ++--- .../config/security/mail/EmailService.java | 63 ++++++++++++++ .../SPDF/config/security/mail/MailConfig.java | 54 ++++++++++++ .../SPDF/controller/api/EmailController.java | 57 +++++++++++++ .../SPDF/model/ApplicationProperties.java | 12 +++ .../software/SPDF/model/api/Email.java | 39 +++++++++ src/main/resources/settings.yml.template | 8 ++ .../security/mail/EmailServiceTest.java | 64 ++++++++++++++ .../controller/api/EmailControllerTest.java | 84 +++++++++++++++++++ 10 files changed, 406 insertions(+), 12 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/config/security/mail/EmailService.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/mail/MailConfig.java create mode 100644 src/main/java/stirling/software/SPDF/controller/api/EmailController.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/Email.java create mode 100644 src/test/java/stirling/software/SPDF/config/security/mail/EmailServiceTest.java create mode 100644 src/test/java/stirling/software/SPDF/controller/api/EmailControllerTest.java diff --git a/.github/labeler-config.yml b/.github/labeler-config.yml index a0a634840..bb52c7b85 100644 --- a/.github/labeler-config.yml +++ b/.github/labeler-config.yml @@ -27,18 +27,34 @@ Back End: Security: - changed-files: + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/interfaces/DatabaseInterface.java' - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/security/**/*' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/DatabaseController.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/EmailController.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/H2SQLController.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/DatabaseWebController.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/UserController.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/api/Email.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/exception/BackupNotFoundException.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/exception/NoProviderFoundExceptionjava' - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/provider/**/*' - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/AuthenticationType.java' - - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/BackupNotFoundException.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/ApiKeyAuthenticationToken.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/AttemptCounter.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/Authority.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/PersistentLogin.java' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/SessionEntity.java' - any-glob-to-any-file: 'scripts/download-security-jar.sh' - any-glob-to-any-file: '.github/workflows/dependency-review.yml' - any-glob-to-any-file: '.github/workflows/scorecards.yml' API: - changed-files: + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/config/OpenApiConfig.java' - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/web/MetricsController.java' - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/**/*' + - any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/api/**/*' - any-glob-to-any-file: 'scripts/png_to_webp.py' - any-glob-to-any-file: 'split_photos.py' - any-glob-to-any-file: '.github/workflows/swagger.yml' diff --git a/build.gradle b/build.gradle index 76277cc99..e91f0a467 100644 --- a/build.gradle +++ b/build.gradle @@ -51,16 +51,20 @@ sourceSets { main { java { if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") { + exclude "stirling/software/SPDF/config/interfaces/DatabaseInterface.java" exclude "stirling/software/SPDF/config/security/**" exclude "stirling/software/SPDF/controller/api/DatabaseController.java" - exclude "stirling/software/SPDF/controller/api/UserController.java" + exclude "stirling/software/SPDF/controller/api/EmailController.java" exclude "stirling/software/SPDF/controller/api/H2SQLCondition.java" + exclude "stirling/software/SPDF/controller/api/UserController.java" exclude "stirling/software/SPDF/controller/web/AccountWebController.java" exclude "stirling/software/SPDF/controller/web/DatabaseWebController.java" + exclude "stirling/software/SPDF/model/api/Email.java" exclude "stirling/software/SPDF/model/ApiKeyAuthenticationToken.java" exclude "stirling/software/SPDF/model/AttemptCounter.java" exclude "stirling/software/SPDF/model/Authority.java" - exclude "stirling/software/SPDF/model/BackupNotFoundException.java" + exclude "stirling/software/SPDF/model/exception/BackupNotFoundException.java" + exclude "stirling/software/SPDF/model/exception/NoProviderFoundException.java" exclude "stirling/software/SPDF/model/PersistentLogin.java" exclude "stirling/software/SPDF/model/SessionEntity.java" exclude "stirling/software/SPDF/model/User.java" @@ -78,16 +82,8 @@ sourceSets { java { if (System.getenv("DOCKER_ENABLE_SECURITY") == "false") { exclude "stirling/software/SPDF/config/security/**" - exclude "stirling/software/SPDF/controller/api/UserControllerTest.java" - exclude "stirling/software/SPDF/controller/api/DatabaseControllerTest.java" - exclude "stirling/software/SPDF/controller/web/AccountWebControllerTest.java" - exclude "stirling/software/SPDF/controller/web/DatabaseWebControllerTest.java" exclude "stirling/software/SPDF/model/ApiKeyAuthenticationTokenTest.java" - exclude "stirling/software/SPDF/model/AttemptCounterTest.java" - exclude "stirling/software/SPDF/model/AuthorityTest.java" - exclude "stirling/software/SPDF/model/PersistentLoginTest.java" - exclude "stirling/software/SPDF/model/SessionEntityTest.java" - exclude "stirling/software/SPDF/model/UserTest.java" + exclude "stirling/software/SPDF/controller/api/EmailControllerTest.java" exclude "stirling/software/SPDF/repository/**" } @@ -459,6 +455,7 @@ dependencies { implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE" implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion" + implementation "org.springframework.boot:spring-boot-starter-mail:$springBootVersion" implementation "org.springframework.session:spring-session-core:3.4.3" implementation "org.springframework:spring-jdbc:6.2.6" diff --git a/src/main/java/stirling/software/SPDF/config/security/mail/EmailService.java b/src/main/java/stirling/software/SPDF/config/security/mail/EmailService.java new file mode 100644 index 000000000..8939fbab6 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/mail/EmailService.java @@ -0,0 +1,63 @@ +package stirling.software.SPDF.config.security.mail; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; + +import lombok.RequiredArgsConstructor; + +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.api.Email; + +/** + * Service class responsible for sending emails, including those with attachments. It uses + * JavaMailSender to send the email and is designed to handle both the message content and file + * attachments. + */ +@Service +@RequiredArgsConstructor +@ConditionalOnProperty(value = "mail.enabled", havingValue = "true", matchIfMissing = false) +public class EmailService { + + private final JavaMailSender mailSender; + private final ApplicationProperties applicationProperties; + + /** + * Sends an email with an attachment asynchronously. This method is annotated with @Async, which + * means it will be executed asynchronously. + * + * @param email The Email object containing the recipient, subject, body, and file attachment. + * @throws MessagingException If there is an issue with creating or sending the email. + */ + @Async + public void sendEmailWithAttachment(Email email) throws MessagingException { + ApplicationProperties.Mail mailProperties = applicationProperties.getMail(); + MultipartFile file = email.getFileInput(); + + // Creates a MimeMessage to represent the email + MimeMessage message = mailSender.createMimeMessage(); + + // Helper class to set up the message content and attachments + MimeMessageHelper helper = new MimeMessageHelper(message, true); + + // Sets the recipient, subject, body, and sender email + helper.addTo(email.getTo()); + helper.setSubject(email.getSubject()); + helper.setText( + email.getBody(), + true); // The "true" here indicates that the body contains HTML content. + helper.setFrom(mailProperties.getFrom()); + + // Adds the attachment to the email + helper.addAttachment(file.getOriginalFilename(), file); + + // Sends the email via the configured mail sender + mailSender.send(message); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/mail/MailConfig.java b/src/main/java/stirling/software/SPDF/config/security/mail/MailConfig.java new file mode 100644 index 000000000..68c2fe35d --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/mail/MailConfig.java @@ -0,0 +1,54 @@ +package stirling.software.SPDF.config.security.mail; + +import java.util.Properties; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import stirling.software.SPDF.model.ApplicationProperties; + +/** + * This configuration class provides the JavaMailSender bean, which is used to send emails. It reads + * email server settings from the configuration (ApplicationProperties) and configures the mail + * client (JavaMailSender). + */ +@Configuration +@Slf4j +@AllArgsConstructor +@ConditionalOnProperty(value = "mail.enabled", havingValue = "true", matchIfMissing = false) +public class MailConfig { + + private final ApplicationProperties applicationProperties; + + @Bean + public JavaMailSender javaMailSender() { + + ApplicationProperties.Mail mailProperties = applicationProperties.getMail(); + + // Creates a new instance of JavaMailSenderImpl, which is a Spring implementation + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(mailProperties.getHost()); + mailSender.setPort(mailProperties.getPort()); + mailSender.setUsername(mailProperties.getUsername()); + mailSender.setPassword(mailProperties.getPassword()); + mailSender.setDefaultEncoding("UTF-8"); + + // Retrieves the JavaMail properties to configure additional SMTP parameters + Properties props = mailSender.getJavaMailProperties(); + + // Enables SMTP authentication + props.put("mail.smtp.auth", "true"); + + // Enables STARTTLS to encrypt the connection if supported by the SMTP server + props.put("mail.smtp.starttls.enable", "true"); + + // Returns the configured mail sender, ready to send emails + return mailSender; + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/EmailController.java b/src/main/java/stirling/software/SPDF/controller/api/EmailController.java new file mode 100644 index 000000000..3b91368ef --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/EmailController.java @@ -0,0 +1,57 @@ +package stirling.software.SPDF.controller.api; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +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.SPDF.config.security.mail.EmailService; +import stirling.software.SPDF.model.api.Email; + +/** + * Controller for handling email-related API requests. This controller exposes an endpoint for + * sending emails with attachments. + */ +@RestController +@RequestMapping("/api/v1/general") +@RequiredArgsConstructor +@Slf4j +@Tag(name = "General", description = "General APIs") +@ConditionalOnProperty(value = "mail.enabled", havingValue = "true", matchIfMissing = false) +public class EmailController { + private final EmailService emailService; + + /** + * Endpoint to send an email with an attachment. This method consumes a multipart/form-data + * request containing the email details and attachment. + * + * @param email The Email object containing recipient address, subject, body, and file + * attachment. + * @return ResponseEntity with success or error message. + */ + @PostMapping(consumes = "multipart/form-data", value = "/send-email") + public ResponseEntity sendEmailWithAttachment(@Valid @ModelAttribute Email email) { + try { + // Calls the service to send the email with attachment + emailService.sendEmailWithAttachment(email); + return ResponseEntity.ok("Email sent successfully"); + } catch (MessagingException e) { + // Catches any messaging exception (e.g., invalid email address, SMTP server issues) + String errorMsg = "Failed to send email: " + e.getMessage(); + log.error(errorMsg, e); // Logging the detailed error + // Returns an error response with status 500 (Internal Server Error) + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMsg); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index d42429619..82a17ff2c 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -82,6 +82,8 @@ public class ApplicationProperties { private Metrics metrics = new Metrics(); private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated(); + private Mail mail = new Mail(); + private Premium premium = new Premium(); private EnterpriseEdition enterpriseEdition = new EnterpriseEdition(); private AutoPipeline autoPipeline = new AutoPipeline(); @@ -420,6 +422,16 @@ public class ApplicationProperties { } } + @Data + public static class Mail { + private boolean enabled; + private String host; + private int port; + private String username; + @ToString.Exclude private String password; + private String from; + } + @Data public static class Premium { private boolean enabled; diff --git a/src/main/java/stirling/software/SPDF/model/api/Email.java b/src/main/java/stirling/software/SPDF/model/api/Email.java new file mode 100644 index 000000000..21b5152e5 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/Email.java @@ -0,0 +1,39 @@ +package stirling.software.SPDF.model.api; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import io.swagger.v3.oas.annotations.media.Schema; + + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ConditionalOnProperty(value = "mail.enabled", havingValue = "true", matchIfMissing = false) +public class Email extends GeneralFile { + + @Schema( + description = "The recipient's email address", + requiredMode = Schema.RequiredMode.REQUIRED, + format = "email") + private String to; + + @Schema( + description = "The subject of the email", + defaultValue = "Stirling Software PDF Notification", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + private String subject; + + @Schema( + description = "The body of the email", + requiredMode = Schema.RequiredMode.NOT_REQUIRED, + defaultValue = + "This message was automatically generated by Stirling-PDF, an innovative" + + " solution from Stirling Software. For more information, visit our website.

Please do" + + " not reply directly to this email.") + private String body; +} diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index 1c3ee32ae..201f875dd 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -76,6 +76,14 @@ premium: apiKey: '' appId: '' +mail: + enabled: true # set to 'true' to enable sending emails + host: smtp.example.com # SMTP server hostname + port: 587 # SMTP server port + username: '' # SMTP server username + password: '' # SMTP server password + from: '' # sender email address + legal: termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder diff --git a/src/test/java/stirling/software/SPDF/config/security/mail/EmailServiceTest.java b/src/test/java/stirling/software/SPDF/config/security/mail/EmailServiceTest.java new file mode 100644 index 000000000..63c38e9c3 --- /dev/null +++ b/src/test/java/stirling/software/SPDF/config/security/mail/EmailServiceTest.java @@ -0,0 +1,64 @@ +package stirling.software.SPDF.config.security.mail; + +import static org.mockito.Mockito.*; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.web.multipart.MultipartFile; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.api.Email; + +@ExtendWith(MockitoExtension.class) +public class EmailServiceTest { + + @Mock + private JavaMailSender mailSender; + + @Mock + private ApplicationProperties applicationProperties; + + @Mock + private ApplicationProperties.Mail mailProperties; + + @Mock + private MultipartFile fileInput; + + @InjectMocks + private EmailService emailService; + + @Test + void testSendEmailWithAttachment() throws MessagingException { + // Mock the values returned by ApplicationProperties + when(applicationProperties.getMail()).thenReturn(mailProperties); + when(mailProperties.getFrom()).thenReturn("no-reply@stirling-software.com"); + + // Create a mock Email object + Email email = new Email(); + email.setTo("test@example.com"); + email.setSubject("Test Email"); + email.setBody("This is a test email."); + email.setFileInput(fileInput); + + // Mock MultipartFile behavior + when(fileInput.getOriginalFilename()).thenReturn("testFile.txt"); + + // Mock MimeMessage + MimeMessage mimeMessage = mock(MimeMessage.class); + + // Configure mailSender to return the mocked MimeMessage + when(mailSender.createMimeMessage()).thenReturn(mimeMessage); + + // Call the service method + emailService.sendEmailWithAttachment(email); + + // Verify that the email was sent using mailSender + verify(mailSender).send(mimeMessage); + } +} diff --git a/src/test/java/stirling/software/SPDF/controller/api/EmailControllerTest.java b/src/test/java/stirling/software/SPDF/controller/api/EmailControllerTest.java new file mode 100644 index 000000000..25aa89479 --- /dev/null +++ b/src/test/java/stirling/software/SPDF/controller/api/EmailControllerTest.java @@ -0,0 +1,84 @@ +package stirling.software.SPDF.controller.api; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import jakarta.mail.MessagingException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.multipart.MultipartFile; +import stirling.software.SPDF.config.security.mail.EmailService; +import stirling.software.SPDF.model.api.Email; + +@ExtendWith(MockitoExtension.class) +public class EmailControllerTest { + + private MockMvc mockMvc; + + @Mock private EmailService emailService; + + @InjectMocks private EmailController emailController; + + @Mock private MultipartFile fileInput; + + @BeforeEach + void setUp() { + // Set up the MockMvc instance for testing + mockMvc = MockMvcBuilders.standaloneSetup(emailController).build(); + } + + @Test + void testSendEmailWithAttachmentSuccess() throws Exception { + // Create a mock Email object + Email email = new Email(); + email.setTo("test@example.com"); + email.setSubject("Test Email"); + email.setBody("This is a test email."); + email.setFileInput(fileInput); + + // Mock the service to not throw any exception + doNothing().when(emailService).sendEmailWithAttachment(any(Email.class)); + + // Perform the request and verify the response + mockMvc.perform( + multipart("/api/v1/general/send-email") + .file("fileInput", "dummy-content".getBytes()) + .param("to", email.getTo()) + .param("subject", email.getSubject()) + .param("body", email.getBody())) + .andExpect(status().isOk()) + .andExpect(content().string("Email sent successfully")); + } + + @Test + void testSendEmailWithAttachmentFailure() throws Exception { + // Create a mock Email object + Email email = new Email(); + email.setTo("test@example.com"); + email.setSubject("Test Email"); + email.setBody("This is a test email."); + email.setFileInput(fileInput); + + // Mock the service to throw a MessagingException + doThrow(new MessagingException("Failed to send email")) + .when(emailService) + .sendEmailWithAttachment(any(Email.class)); + + // Perform the request and verify the response + mockMvc.perform( + multipart("/api/v1/general/send-email") + .file("fileInput", "dummy-content".getBytes()) + .param("to", email.getTo()) + .param("subject", email.getSubject()) + .param("body", email.getBody())) + .andExpect(status().isInternalServerError()) + .andExpect(content().string("Failed to send email: Failed to send email")); + } +} From 512e9d72369d4195e04b4eb8d5f7e2299f0a0500 Mon Sep 17 00:00:00 2001 From: "stirlingbot[bot]" <195170888+stirlingbot[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 13:29:51 +0100 Subject: [PATCH 5/5] Update 3rd Party Licenses (#3487) Auto-generated by StirlingBot Signed-off-by: stirlingbot[bot] <1113334+stirlingbot[bot]@users.noreply.github.com> Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com> --- .../resources/static/3rdPartyLicenses.json | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/resources/static/3rdPartyLicenses.json b/src/main/resources/static/3rdPartyLicenses.json index 4395d1fa0..da8718489 100644 --- a/src/main/resources/static/3rdPartyLicenses.json +++ b/src/main/resources/static/3rdPartyLicenses.json @@ -1011,6 +1011,13 @@ "moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception", "moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html" }, + { + "moduleName": "org.eclipse.angus:jakarta.mail", + "moduleUrl": "https://www.eclipse.org", + "moduleVersion": "2.0.3", + "moduleLicense": "GPL2 w/ CPE", + "moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html" + }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client", "moduleUrl": "https://jetty.org/", @@ -1529,6 +1536,13 @@ "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, + { + "moduleName": "org.springframework.boot:spring-boot-starter-mail", + "moduleUrl": "https://spring.io/projects/spring-boot", + "moduleVersion": "3.4.5", + "moduleLicense": "Apache License, Version 2.0", + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" + }, { "moduleName": "org.springframework.boot:spring-boot-starter-oauth2-client", "moduleUrl": "https://spring.io/projects/spring-boot", @@ -1669,6 +1683,13 @@ "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, + { + "moduleName": "org.springframework:spring-context-support", + "moduleUrl": "https://github.com/spring-projects/spring-framework", + "moduleVersion": "6.2.6", + "moduleLicense": "Apache License, Version 2.0", + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" + }, { "moduleName": "org.springframework:spring-core", "moduleUrl": "https://github.com/spring-projects/spring-framework",