From 133e6d3de644f6fc7ca93379092fcf7a45ad4fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Sz=C3=BCcs?= <127139797+balazs-szucs@users.noreply.github.com> Date: Sun, 28 Sep 2025 17:41:20 +0200 Subject: [PATCH] refactor(core,common,proprietary): Replace Date with Instant/modern Date API alternative for improved time handling (#4497) --- .../software/common/model/PdfMetadata.java | 6 +- .../common/service/PdfMetadataService.java | 90 ++++++++++++++++--- .../software/common/util/EmlParser.java | 10 ++- .../common/util/PdfAttachmentHandler.java | 17 ++-- .../api/converters/ConvertPDFToPDFA.java | 41 ++++++--- .../api/misc/MetadataController.java | 34 ++----- .../api/security/CertSignController.java | 2 +- .../controller/api/security/GetInfoOnPDF.java | 10 ++- .../security/ValidateSignatureController.java | 7 +- .../SPDF/service/AttachmentService.java | 12 ++- .../service/PdfMetadataServiceBasicTest.java | 15 +++- .../SPDF/service/PdfMetadataServiceTest.java | 19 +++- .../security/config/AccountWebController.java | 33 +++---- .../controller/web/TeamWebController.java | 21 ++--- .../repository/JPATokenRepositoryImpl.java | 9 +- .../repository/SessionRepository.java | 4 +- .../security/filter/FirstLoginFilter.java | 13 ++- .../security/model/PersistentLogin.java | 4 +- .../security/model/SessionEntity.java | 4 +- .../security/service/JwtService.java | 7 +- .../session/SessionPersistentRegistry.java | 11 +-- 21 files changed, 244 insertions(+), 125 deletions(-) diff --git a/app/common/src/main/java/stirling/software/common/model/PdfMetadata.java b/app/common/src/main/java/stirling/software/common/model/PdfMetadata.java index be588ec86..7991300d7 100644 --- a/app/common/src/main/java/stirling/software/common/model/PdfMetadata.java +++ b/app/common/src/main/java/stirling/software/common/model/PdfMetadata.java @@ -1,6 +1,6 @@ package stirling.software.common.model; -import java.util.Calendar; +import java.time.ZonedDateTime; import lombok.AllArgsConstructor; import lombok.Builder; @@ -18,6 +18,6 @@ public class PdfMetadata { private String creator; private String subject; private String keywords; - private Calendar creationDate; - private Calendar modificationDate; + private ZonedDateTime creationDate; + private ZonedDateTime modificationDate; } diff --git a/app/common/src/main/java/stirling/software/common/service/PdfMetadataService.java b/app/common/src/main/java/stirling/software/common/service/PdfMetadataService.java index 621e19d46..0d2eebc10 100644 --- a/app/common/src/main/java/stirling/software/common/service/PdfMetadataService.java +++ b/app/common/src/main/java/stirling/software/common/service/PdfMetadataService.java @@ -1,5 +1,9 @@ package stirling.software.common.service; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Calendar; import org.apache.pdfbox.pdmodel.PDDocument; @@ -29,17 +33,19 @@ public class PdfMetadataService { this.runningProOrHigher = runningProOrHigher; } - public PdfMetadata extractMetadataFromPdf(PDDocument pdf) { - return PdfMetadata.builder() - .author(pdf.getDocumentInformation().getAuthor()) - .producer(pdf.getDocumentInformation().getProducer()) - .title(pdf.getDocumentInformation().getTitle()) - .creator(pdf.getDocumentInformation().getCreator()) - .subject(pdf.getDocumentInformation().getSubject()) - .keywords(pdf.getDocumentInformation().getKeywords()) - .creationDate(pdf.getDocumentInformation().getCreationDate()) - .modificationDate(pdf.getDocumentInformation().getModificationDate()) - .build(); + /** + * Converts ZonedDateTime to Calendar for PDFBox compatibility. + * + * @param zonedDateTime the ZonedDateTime to convert + * @return Calendar instance or null if input is null + */ + public static Calendar toCalendar(ZonedDateTime zonedDateTime) { + if (zonedDateTime == null) { + return null; + } + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(zonedDateTime.toInstant().toEpochMilli()); + return calendar; } public void setDefaultMetadata(PDDocument pdf) { @@ -58,6 +64,52 @@ public class PdfMetadataService { setCommonMetadata(pdf, pdfMetadata); } + /** + * Parses a date string and converts it to Calendar for PDFBox compatibility. + * + * @param dateString the date string in "yyyy/MM/dd HH:mm:ss" format + * @return Calendar instance or null if parsing fails or input is empty + */ + public static Calendar parseToCalendar(String dateString) { + if (dateString == null || dateString.trim().isEmpty()) { + return null; + } + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); + ZonedDateTime zonedDateTime = + LocalDateTime.parse(dateString, formatter).atZone(ZoneId.systemDefault()); + return toCalendar(zonedDateTime); + } catch (Exception e) { + return null; + } + } + + public PdfMetadata extractMetadataFromPdf(PDDocument pdf) { + Calendar creationCal = pdf.getDocumentInformation().getCreationDate(); + Calendar modificationCal = pdf.getDocumentInformation().getModificationDate(); + + ZonedDateTime creationDate = + creationCal != null + ? ZonedDateTime.ofInstant(creationCal.toInstant(), ZoneId.systemDefault()) + : null; + ZonedDateTime modificationDate = + modificationCal != null + ? ZonedDateTime.ofInstant( + modificationCal.toInstant(), ZoneId.systemDefault()) + : null; + + return PdfMetadata.builder() + .author(pdf.getDocumentInformation().getAuthor()) + .producer(pdf.getDocumentInformation().getProducer()) + .title(pdf.getDocumentInformation().getTitle()) + .creator(pdf.getDocumentInformation().getCreator()) + .subject(pdf.getDocumentInformation().getSubject()) + .keywords(pdf.getDocumentInformation().getKeywords()) + .creationDate(creationDate) + .modificationDate(modificationDate) + .build(); + } + private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) { String creator = stirlingPDFLabel; @@ -79,7 +131,13 @@ public class PdfMetadataService { } pdf.getDocumentInformation().setCreator(creator); - pdf.getDocumentInformation().setCreationDate(Calendar.getInstance()); + + // Use existing creation date if available, otherwise create new one + Calendar creationCal = + pdfMetadata.getCreationDate() != null + ? toCalendar(pdfMetadata.getCreationDate()) + : Calendar.getInstance(); + pdf.getDocumentInformation().setCreationDate(creationCal); } private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) { @@ -88,7 +146,13 @@ public class PdfMetadataService { pdf.getDocumentInformation().setProducer(stirlingPDFLabel); pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject()); pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords()); - pdf.getDocumentInformation().setModificationDate(Calendar.getInstance()); + + // Convert ZonedDateTime to Calendar for PDFBox compatibility + Calendar modificationCal = + pdfMetadata.getModificationDate() != null + ? toCalendar(pdfMetadata.getModificationDate()) + : Calendar.getInstance(); + pdf.getDocumentInformation().setModificationDate(modificationCal); String author = pdfMetadata.getAuthor(); if (applicationProperties diff --git a/app/common/src/main/java/stirling/software/common/util/EmlParser.java b/app/common/src/main/java/stirling/software/common/util/EmlParser.java index 42e418daa..6b4882fab 100644 --- a/app/common/src/main/java/stirling/software/common/util/EmlParser.java +++ b/app/common/src/main/java/stirling/software/common/util/EmlParser.java @@ -6,6 +6,8 @@ import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -145,7 +147,11 @@ public class EmlParser { extractRecipients(message, messageClass, content); Method getSentDate = messageClass.getMethod("getSentDate"); - content.setDate((Date) getSentDate.invoke(message)); + Date legacyDate = (Date) getSentDate.invoke(message); + if (legacyDate != null) { + content.setDate( + ZonedDateTime.ofInstant(legacyDate.toInstant(), ZoneId.systemDefault())); + } Method getContent = messageClass.getMethod("getContent"); Object messageContent = getContent.invoke(message); @@ -616,7 +622,7 @@ public class EmlParser { private String to; private String cc; private String bcc; - private Date date; + private ZonedDateTime date; private String dateString; // For basic parsing fallback private String htmlBody; private String textBody; diff --git a/app/common/src/main/java/stirling/software/common/util/PdfAttachmentHandler.java b/app/common/src/main/java/stirling/software/common/util/PdfAttachmentHandler.java index edbf04fb8..8f64e4261 100644 --- a/app/common/src/main/java/stirling/software/common/util/PdfAttachmentHandler.java +++ b/app/common/src/main/java/stirling/software/common/util/PdfAttachmentHandler.java @@ -8,7 +8,9 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Base64; import java.util.Date; @@ -18,7 +20,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -290,11 +291,15 @@ public class PdfAttachmentHandler { public static String formatEmailDate(Date date) { if (date == null) return ""; + return formatEmailDate(ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())); + } - SimpleDateFormat formatter = - new SimpleDateFormat("EEE, MMM d, yyyy 'at' h:mm a z", Locale.ENGLISH); - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); - return formatter.format(date); + public static String formatEmailDate(ZonedDateTime dateTime) { + if (dateTime == null) return ""; + + DateTimeFormatter formatter = + DateTimeFormatter.ofPattern("EEE, MMM d, yyyy 'at' h:mm a z", Locale.ENGLISH); + return dateTime.withZoneSameInstant(ZoneId.of("UTC")).format(formatter); } @Data diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index 17d6f4635..f0e83f6c0 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -7,16 +7,18 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.Collections; +import java.util.GregorianCalendar; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.TimeZone; import org.apache.commons.io.FileUtils; import org.apache.pdfbox.Loader; @@ -564,18 +566,31 @@ public class ConvertPDFToPDFA { adobePdfSchema.setKeywords(keywords); } - // Set creation and modification dates - Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - Calendar originalCreationDate = docInfo.getCreationDate(); - if (originalCreationDate == null) { - originalCreationDate = now; - } - docInfo.setCreationDate(originalCreationDate); - xmpBasicSchema.setCreateDate(originalCreationDate); + // Set creation and modification dates using java.time and convert to GregorianCalendar + Instant nowInstant = Instant.now(); + ZonedDateTime nowZdt = ZonedDateTime.ofInstant(nowInstant, ZoneId.of("UTC")); + GregorianCalendar nowCal = GregorianCalendar.from(nowZdt); - docInfo.setModificationDate(now); - xmpBasicSchema.setModifyDate(now); - xmpBasicSchema.setMetadataDate(now); + java.util.Calendar originalCreationDate = docInfo.getCreationDate(); + GregorianCalendar creationCal; + if (originalCreationDate == null) { + creationCal = nowCal; + } else if (originalCreationDate instanceof GregorianCalendar) { + creationCal = (GregorianCalendar) originalCreationDate; + } else { + // convert other Calendar implementations to GregorianCalendar preserving instant + creationCal = + GregorianCalendar.from( + ZonedDateTime.ofInstant( + originalCreationDate.toInstant(), ZoneId.of("UTC"))); + } + + docInfo.setCreationDate(creationCal); + xmpBasicSchema.setCreateDate(creationCal); + + docInfo.setModificationDate(nowCal); + xmpBasicSchema.setModifyDate(nowCal); + xmpBasicSchema.setMetadataDate(nowCal); // Serialize the created metadata so it can be attached to the existent metadata ByteArrayOutputStream xmpOut = new ByteArrayOutputStream(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java index 3cce6e856..b7bb3bc7d 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/MetadataController.java @@ -1,8 +1,6 @@ package stirling.software.SPDF.controller.api.misc; import java.io.IOException; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Map; import java.util.Map.Entry; @@ -25,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.api.misc.MetadataRequest; import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.PdfMetadataService; import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor; @@ -144,30 +143,13 @@ public class MetadataController { } } } - if (creationDate != null && creationDate.length() > 0) { - Calendar creationDateCal = Calendar.getInstance(); - try { - creationDateCal.setTime( - new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate)); - } catch (ParseException e) { - log.error("exception", e); - } - info.setCreationDate(creationDateCal); - } else { - info.setCreationDate(null); - } - if (modificationDate != null && modificationDate.length() > 0) { - Calendar modificationDateCal = Calendar.getInstance(); - try { - modificationDateCal.setTime( - new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate)); - } catch (ParseException e) { - log.error("exception", e); - } - info.setModificationDate(modificationDateCal); - } else { - info.setModificationDate(null); - } + // Set creation date using utility method + Calendar creationDateCal = PdfMetadataService.parseToCalendar(creationDate); + info.setCreationDate(creationDateCal); + + // Set modification date using utility method + Calendar modificationDateCal = PdfMetadataService.parseToCalendar(modificationDate); + info.setModificationDate(modificationDateCal); info.setCreator(creator); info.setKeywords(keywords); info.setAuthor(author); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index e32f4fac6..4aef19260 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -120,7 +120,7 @@ public class CertSignController { signature.setName(name); signature.setLocation(location); signature.setReason(reason); - signature.setSignDate(Calendar.getInstance()); + signature.setSignDate(Calendar.getInstance()); // PDFBox requires Calendar if (Boolean.TRUE.equals(showSignature)) { SignatureOptions signatureOptions = new SignatureOptions(); signatureOptions.setVisualSignature( diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java index 50be762c4..1e8888634 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java @@ -3,7 +3,9 @@ package stirling.software.SPDF.controller.api.security; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; import org.apache.pdfbox.cos.COSInputStream; @@ -817,8 +819,10 @@ public class GetInfoOnPDF { private String formatDate(Calendar calendar) { if (calendar != null) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - return sdf.format(calendar.getTime()); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + ZonedDateTime zonedDateTime = + ZonedDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault()); + return zonedDateTime.format(formatter); } else { return null; } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java index a98f0c0d1..f020c5bbe 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/ValidateSignatureController.java @@ -7,8 +7,8 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; +import java.time.Instant; import java.util.ArrayList; -import java.util.Date; import java.util.List; import org.apache.pdfbox.pdmodel.PDDocument; @@ -134,11 +134,12 @@ public class ValidateSignatureController { : certValidationService.validateTrustStore(cert)); result.setNotRevoked(!certValidationService.isRevoked(cert)); - result.setNotExpired(!cert.getNotAfter().before(new Date())); + result.setNotExpired( + Instant.now().isBefore(cert.getNotAfter().toInstant())); // Set basic signature info result.setSignerName(sig.getName()); - result.setSignatureDate(sig.getSignDate().getTime().toString()); + result.setSignatureDate(sig.getSignDate().toInstant().toString()); result.setReason(sig.getReason()); result.setLocation(sig.getLocation()); diff --git a/app/core/src/main/java/stirling/software/SPDF/service/AttachmentService.java b/app/core/src/main/java/stirling/software/SPDF/service/AttachmentService.java index 4aa6dfe41..6eb74dfb8 100644 --- a/app/core/src/main/java/stirling/software/SPDF/service/AttachmentService.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/AttachmentService.java @@ -3,6 +3,9 @@ package stirling.software.SPDF.service; import static stirling.software.common.util.AttachmentUtils.setCatalogViewerPreferences; import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; @@ -54,8 +57,13 @@ public class AttachmentService implements AttachmentServiceInterface { PDEmbeddedFile embeddedFile = new PDEmbeddedFile(document, attachment.getInputStream()); embeddedFile.setSize((int) attachment.getSize()); - embeddedFile.setCreationDate(new GregorianCalendar()); - embeddedFile.setModDate(new GregorianCalendar()); + // use java.time.Instant and convert to GregorianCalendar for PDFBox + Instant now = Instant.now(); + GregorianCalendar nowCal = + GregorianCalendar.from( + ZonedDateTime.ofInstant(now, ZoneId.systemDefault())); + embeddedFile.setCreationDate(nowCal); + embeddedFile.setModDate(nowCal); String contentType = attachment.getContentType(); if (StringUtils.isNotBlank(contentType)) { embeddedFile.setSubtype(contentType); diff --git a/app/core/src/test/java/stirling/software/SPDF/service/PdfMetadataServiceBasicTest.java b/app/core/src/test/java/stirling/software/SPDF/service/PdfMetadataServiceBasicTest.java index f09156ca8..ffdaf6e2f 100644 --- a/app/core/src/test/java/stirling/software/SPDF/service/PdfMetadataServiceBasicTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/service/PdfMetadataServiceBasicTest.java @@ -7,6 +7,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Calendar; import org.apache.pdfbox.pdmodel.PDDocument; @@ -81,6 +83,12 @@ class PdfMetadataServiceBasicTest { // Act PdfMetadata metadata = pdfMetadataService.extractMetadataFromPdf(testDocument); + // Convert Calendar to ZonedDateTime for comparison + ZonedDateTime expectedCreationDate = + ZonedDateTime.ofInstant(creationDate.toInstant(), ZoneId.systemDefault()); + ZonedDateTime expectedModificationDate = + ZonedDateTime.ofInstant(modificationDate.toInstant(), ZoneId.systemDefault()); + // Assert assertEquals(testAuthor, metadata.getAuthor(), "Author should match"); assertEquals(testProducer, metadata.getProducer(), "Producer should match"); @@ -88,9 +96,12 @@ class PdfMetadataServiceBasicTest { assertEquals(testCreator, metadata.getCreator(), "Creator should match"); assertEquals(testSubject, metadata.getSubject(), "Subject should match"); assertEquals(testKeywords, metadata.getKeywords(), "Keywords should match"); - assertEquals(creationDate, metadata.getCreationDate(), "Creation date should match"); assertEquals( - modificationDate, metadata.getModificationDate(), "Modification date should match"); + expectedCreationDate, metadata.getCreationDate(), "Creation date should match"); + assertEquals( + expectedModificationDate, + metadata.getModificationDate(), + "Modification date should match"); } @Test diff --git a/app/core/src/test/java/stirling/software/SPDF/service/PdfMetadataServiceTest.java b/app/core/src/test/java/stirling/software/SPDF/service/PdfMetadataServiceTest.java index 9d3270014..f7f682b4d 100644 --- a/app/core/src/test/java/stirling/software/SPDF/service/PdfMetadataServiceTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/service/PdfMetadataServiceTest.java @@ -7,6 +7,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Calendar; import org.apache.pdfbox.pdmodel.PDDocument; @@ -84,6 +86,12 @@ class PdfMetadataServiceTest { // Act PdfMetadata metadata = pdfMetadataService.extractMetadataFromPdf(testDocument); + // Convert Calendar to ZonedDateTime for comparison + ZonedDateTime expectedCreationDate = + ZonedDateTime.ofInstant(creationDate.toInstant(), ZoneId.systemDefault()); + ZonedDateTime expectedModificationDate = + ZonedDateTime.ofInstant(modificationDate.toInstant(), ZoneId.systemDefault()); + // Assert assertEquals(testAuthor, metadata.getAuthor(), "Author should match"); assertEquals(testProducer, metadata.getProducer(), "Producer should match"); @@ -91,9 +99,12 @@ class PdfMetadataServiceTest { assertEquals(testCreator, metadata.getCreator(), "Creator should match"); assertEquals(testSubject, metadata.getSubject(), "Subject should match"); assertEquals(testKeywords, metadata.getKeywords(), "Keywords should match"); - assertEquals(creationDate, metadata.getCreationDate(), "Creation date should match"); assertEquals( - modificationDate, metadata.getModificationDate(), "Modification date should match"); + expectedCreationDate, metadata.getCreationDate(), "Creation date should match"); + assertEquals( + expectedModificationDate, + metadata.getModificationDate(), + "Modification date should match"); } @Test @@ -190,6 +201,8 @@ class PdfMetadataServiceTest { // Prepare test metadata with existing creation date Calendar existingCreationDate = Calendar.getInstance(); existingCreationDate.add(Calendar.DAY_OF_MONTH, -1); // Yesterday + ZonedDateTime existingCreationDateZdt = + ZonedDateTime.ofInstant(existingCreationDate.toInstant(), ZoneId.systemDefault()); PdfMetadata testMetadata = PdfMetadata.builder() @@ -197,7 +210,7 @@ class PdfMetadataServiceTest { .title("Test Title") .subject("Test Subject") .keywords("Test Keywords") - .creationDate(existingCreationDate) + .creationDate(existingCreationDateZdt) .build(); // Act diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java index a61a7b0fa..4f602aa59 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java @@ -4,7 +4,6 @@ import static stirling.software.common.util.ProviderUtils.validateProvider; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -218,7 +217,7 @@ public class AccountWebController { Map roleDetails = Role.getAllRoleDetails(); // Map to store session information and user activity status Map userSessions = new HashMap<>(); - Map userLastRequest = new HashMap<>(); + Map userLastRequest = new HashMap<>(); int activeUsers = 0; int disabledUsers = 0; while (iterator.hasNext()) { @@ -249,27 +248,29 @@ public class AccountWebController { // Determine the user's session status and last request time int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval(); boolean hasActiveSession = false; - Date lastRequest = null; + Instant lastRequest = null; Optional latestSession = sessionPersistentRegistry.findLatestSession(user.getUsername()); if (latestSession.isPresent()) { SessionEntity sessionEntity = latestSession.get(); - Date lastAccessedTime = sessionEntity.getLastRequest(); + // sessionEntity stores Instant directly + Instant lastAccessedTime = + Optional.ofNullable(sessionEntity.getLastRequest()) + .orElse(Instant.EPOCH); + Instant now = Instant.now(); // Calculate session expiration and update session status accordingly Instant expirationTime = - lastAccessedTime - .toInstant() - .plus(maxInactiveInterval, ChronoUnit.SECONDS); + lastAccessedTime.plus(maxInactiveInterval, ChronoUnit.SECONDS); if (now.isAfter(expirationTime)) { sessionPersistentRegistry.expireSession(sessionEntity.getSessionId()); } else { hasActiveSession = !sessionEntity.isExpired(); } - lastRequest = sessionEntity.getLastRequest(); + lastRequest = lastAccessedTime; } else { // No session, set default last request time - lastRequest = new Date(0); + lastRequest = Instant.EPOCH; } userSessions.put(user.getUsername(), hasActiveSession); userLastRequest.put(user.getUsername(), lastRequest); @@ -286,19 +287,21 @@ public class AccountWebController { allUsers.stream() .sorted( (u1, u2) -> { - boolean u1Active = userSessions.get(u1.getUsername()); - boolean u2Active = userSessions.get(u2.getUsername()); + boolean u1Active = + userSessions.getOrDefault(u1.getUsername(), false); + boolean u2Active = + userSessions.getOrDefault(u2.getUsername(), false); if (u1Active && !u2Active) { return -1; } else if (!u1Active && u2Active) { return 1; } else { - Date u1LastRequest = + Instant u1LastRequest = userLastRequest.getOrDefault( - u1.getUsername(), new Date(0)); - Date u2LastRequest = + u1.getUsername(), Instant.EPOCH); + Instant u2LastRequest = userLastRequest.getOrDefault( - u2.getUsername(), new Date(0)); + u2.getUsername(), Instant.EPOCH); return u2LastRequest.compareTo(u1LastRequest); } }) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java index 2dd9b3478..59b67848e 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java @@ -1,6 +1,6 @@ package stirling.software.proprietary.security.controller.web; -import java.util.Date; +import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -44,17 +44,17 @@ public class TeamWebController { // Filter out the Internal team List teamsWithCounts = allTeamsWithCounts.stream() - .filter(team -> !team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) + .filter(team -> !TeamService.INTERNAL_TEAM_NAME.equals(team.getName())) .toList(); // Get the latest activity for each team List teamActivities = sessionRepository.findLatestActivityByTeam(); // Convert the query results to a map for easy access in the view - Map teamLastRequest = new HashMap<>(); + Map teamLastRequest = new HashMap<>(); for (Object[] result : teamActivities) { Long teamId = (Long) result[0]; // teamId alias - Date lastActivity = (Date) result[1]; // lastActivity alias + Instant lastActivity = (Instant) result[1]; // lastActivity alias teamLastRequest.put(teamId, lastActivity); } @@ -97,7 +97,7 @@ public class TeamWebController { .orElseThrow(() -> new RuntimeException("Team not found")); // Prevent access to Internal team - if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) { + if (TeamService.INTERNAL_TEAM_NAME.equals(team.getName())) { return "redirect:/teams?error=internalTeamNotAccessible"; } @@ -114,21 +114,18 @@ public class TeamWebController { (user.getTeam() == null || !user.getTeam().getId().equals(id)) && (user.getTeam() == null - || !user.getTeam() - .getName() - .equals( - TeamService - .INTERNAL_TEAM_NAME))) + || !TeamService.INTERNAL_TEAM_NAME.equals( + user.getTeam().getName()))) .toList(); // Get the latest session for each user in the team List userSessions = sessionRepository.findLatestSessionByTeamId(id); // Create a map of username to last request date - Map userLastRequest = new HashMap<>(); + Map userLastRequest = new HashMap<>(); for (Object[] result : userSessions) { String username = (String) result[0]; // username alias - Date lastRequest = (Date) result[1]; // lastRequest alias + Instant lastRequest = (Instant) result[1]; // lastRequest alias userLastRequest.put(username, lastRequest); } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/JPATokenRepositoryImpl.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/JPATokenRepositoryImpl.java index ec7a0078b..fe92f07be 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/JPATokenRepositoryImpl.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/JPATokenRepositoryImpl.java @@ -23,7 +23,7 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository { newToken.setSeries(token.getSeries()); newToken.setUsername(token.getUsername()); newToken.setToken(token.getTokenValue()); - newToken.setLastUsed(token.getDate()); + newToken.setLastUsed(token.getDate().toInstant()); persistentLoginRepository.save(newToken); } @@ -33,7 +33,7 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository { PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null); if (existingToken != null) { existingToken.setToken(tokenValue); - existingToken.setLastUsed(lastUsed); + existingToken.setLastUsed(lastUsed.toInstant()); persistentLoginRepository.save(existingToken); } } @@ -43,7 +43,10 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository { PersistentLogin token = persistentLoginRepository.findById(seriesId).orElse(null); if (token != null) { return new PersistentRememberMeToken( - token.getUsername(), token.getSeries(), token.getToken(), token.getLastUsed()); + token.getUsername(), + token.getSeries(), + token.getToken(), + Date.from(token.getLastUsed())); } return null; } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/SessionRepository.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/SessionRepository.java index 3eb1ad90b..db5d5a9b3 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/SessionRepository.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/SessionRepository.java @@ -1,6 +1,6 @@ package stirling.software.proprietary.security.database.repository; -import java.util.Date; +import java.time.Instant; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; @@ -27,7 +27,7 @@ public interface SessionRepository extends JpaRepository "UPDATE SessionEntity s SET s.expired = :expired, s.lastRequest = :lastRequest WHERE s.principalName = :principalName") void saveByPrincipalName( @Param("expired") boolean expired, - @Param("lastRequest") Date lastRequest, + @Param("lastRequest") Instant lastRequest, @Param("principalName") String principalName); @Query( diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/FirstLoginFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/FirstLoginFilter.java index 3bae72195..c0295948c 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/FirstLoginFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/FirstLoginFilter.java @@ -1,8 +1,9 @@ package stirling.software.proprietary.security.filter; import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Optional; import org.springframework.context.annotation.Lazy; @@ -60,8 +61,12 @@ public class FirstLoginFilter extends OncePerRequestFilter { } if (log.isDebugEnabled()) { HttpSession session = request.getSession(true); - SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss"); - String creationTime = timeFormat.format(new Date(session.getCreationTime())); + DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss"); + String creationTime = + timeFormat.format( + Instant.ofEpochMilli(session.getCreationTime()) + .atZone(ZoneId.systemDefault()) + .toLocalTime()); log.debug( "Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}", session.isNew(), diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/PersistentLogin.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/PersistentLogin.java index ef096f7fb..fe9c9f420 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/PersistentLogin.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/PersistentLogin.java @@ -1,6 +1,6 @@ package stirling.software.proprietary.security.model; -import java.util.Date; +import java.time.Instant; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -25,5 +25,5 @@ public class PersistentLogin { private String token; @Column(name = "last_used", nullable = false) - private Date lastUsed; + private Instant lastUsed; } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/SessionEntity.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/SessionEntity.java index db94eae6f..7e9da2cb7 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/SessionEntity.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/SessionEntity.java @@ -1,7 +1,7 @@ package stirling.software.proprietary.security.model; import java.io.Serializable; -import java.util.Date; +import java.time.Instant; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -17,7 +17,7 @@ public class SessionEntity implements Serializable { private String principalName; - private Date lastRequest; + private Instant lastRequest; private boolean expired; } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java index af32a183a..a1511a9dd 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java @@ -4,6 +4,7 @@ import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; +import java.time.Instant; import java.time.LocalDateTime; import java.util.Date; import java.util.HashMap; @@ -92,8 +93,8 @@ public class JwtService implements JwtServiceInterface { .claims(claims) .subject(username) .issuer(ISSUER) - .issuedAt(new Date()) - .expiration(new Date(System.currentTimeMillis() + EXPIRATION)) + .issuedAt(Date.from(Instant.now())) + .expiration(Date.from(Instant.now().plusMillis(EXPIRATION))) .signWith(keyPair.getPrivate(), Jwts.SIG.RS256); String keyId = activeKey.getKeyId(); @@ -129,7 +130,7 @@ public class JwtService implements JwtServiceInterface { @Override public boolean isTokenExpired(String token) { - return extractExpiration(token).before(new Date()); + return extractExpiration(token).before(Date.from(Instant.now())); } private Date extractExpiration(String token) { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/session/SessionPersistentRegistry.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/session/SessionPersistentRegistry.java index 8931866ad..8dbb97eee 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/session/SessionPersistentRegistry.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/session/SessionPersistentRegistry.java @@ -1,6 +1,7 @@ package stirling.software.proprietary.security.session; import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -67,7 +68,7 @@ public class SessionPersistentRegistry implements SessionRegistry { new SessionInformation( sessionEntity.getPrincipalName(), sessionEntity.getSessionId(), - sessionEntity.getLastRequest())); + Date.from(sessionEntity.getLastRequest()))); } } } @@ -101,7 +102,7 @@ public class SessionPersistentRegistry implements SessionRegistry { SessionEntity sessionEntity = new SessionEntity(); sessionEntity.setSessionId(sessionId); sessionEntity.setPrincipalName(principalName); - sessionEntity.setLastRequest(new Date()); // Set lastRequest to the current date + sessionEntity.setLastRequest(Instant.now()); // Set lastRequest to the current date sessionEntity.setExpired(false); sessionRepository.save(sessionEntity); } @@ -119,7 +120,7 @@ public class SessionPersistentRegistry implements SessionRegistry { Optional sessionEntityOpt = sessionRepository.findById(sessionId); if (sessionEntityOpt.isPresent()) { SessionEntity sessionEntity = sessionEntityOpt.get(); - sessionEntity.setLastRequest(new Date()); // Update lastRequest to the current date + sessionEntity.setLastRequest(Instant.now()); // Update lastRequest to the current date sessionRepository.save(sessionEntity); } } @@ -132,7 +133,7 @@ public class SessionPersistentRegistry implements SessionRegistry { return new SessionInformation( sessionEntity.getPrincipalName(), sessionEntity.getSessionId(), - sessionEntity.getLastRequest()); + Date.from(sessionEntity.getLastRequest())); } return null; } @@ -170,7 +171,7 @@ public class SessionPersistentRegistry implements SessionRegistry { // Update session details by principal name public void updateSessionByPrincipalName( String principalName, boolean expired, Date lastRequest) { - sessionRepository.saveByPrincipalName(expired, lastRequest, principalName); + sessionRepository.saveByPrincipalName(expired, lastRequest.toInstant(), principalName); } // Find the latest session for a given principal name