Save signatures to server (#5080)

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/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/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] 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/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### 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/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: Anthony Stirling <[email protected]>
This commit is contained in:
Reece Browne
2025-11-29 19:29:06 +00:00
committed by GitHub
co-authored by Anthony Stirling
parent fde449e738
commit 651f17f1c6
19 changed files with 1674 additions and 425 deletions
@@ -23,7 +23,7 @@ import stirling.software.common.configuration.InstallationPathConfig;
class SignatureServiceTest {
@TempDir Path tempDir;
private SignatureService signatureService;
private SharedSignatureService signatureService;
private Path personalSignatureFolder;
private Path sharedSignatureFolder;
private final String ALL_USERS_FOLDER = "ALL_USERS";
@@ -53,7 +53,7 @@ class SignatureServiceTest {
.thenReturn(tempDir.toString());
// Initialize the service with our temp directory
signatureService = new SignatureService();
signatureService = new SharedSignatureService();
}
}
@@ -165,7 +165,7 @@ class SignatureServiceTest {
}
@Test
void testGetSignatureBytes_PersonalFile() throws IOException {
void testGetSharedSignatureBytes_SharedFile() throws IOException {
// Mock static method for each test
try (MockedStatic<InstallationPathConfig> mockedConfig =
mockStatic(InstallationPathConfig.class)) {
@@ -173,28 +173,8 @@ class SignatureServiceTest {
.when(InstallationPathConfig::getSignaturesPath)
.thenReturn(tempDir.toString());
// Test
byte[] bytes = signatureService.getSignatureBytes(TEST_USER, "personal.png");
// Verify
assertEquals(
"personal signature content",
new String(bytes),
"Should return the correct content for personal file");
}
}
@Test
void testGetSignatureBytes_SharedFile() throws IOException {
// Mock static method for each test
try (MockedStatic<InstallationPathConfig> mockedConfig =
mockStatic(InstallationPathConfig.class)) {
mockedConfig
.when(InstallationPathConfig::getSignaturesPath)
.thenReturn(tempDir.toString());
// Test
byte[] bytes = signatureService.getSignatureBytes(TEST_USER, "shared.jpg");
// Test - core service only reads shared signatures
byte[] bytes = signatureService.getSharedSignatureBytes("shared.jpg");
// Verify
assertEquals(
@@ -205,7 +185,7 @@ class SignatureServiceTest {
}
@Test
void testGetSignatureBytes_FileNotFound() {
void testGetSharedSignatureBytes_FileNotFound() {
// Mock static method for each test
try (MockedStatic<InstallationPathConfig> mockedConfig =
mockStatic(InstallationPathConfig.class)) {
@@ -216,13 +196,13 @@ class SignatureServiceTest {
// Test and verify
assertThrows(
FileNotFoundException.class,
() -> signatureService.getSignatureBytes(TEST_USER, "nonexistent.png"),
() -> signatureService.getSharedSignatureBytes("nonexistent.png"),
"Should throw exception for non-existent files");
}
}
@Test
void testGetSignatureBytes_InvalidFileName() {
void testGetSharedSignatureBytes_InvalidFileName() {
// Mock static method for each test
try (MockedStatic<InstallationPathConfig> mockedConfig =
mockStatic(InstallationPathConfig.class)) {
@@ -233,11 +213,28 @@ class SignatureServiceTest {
// Test and verify
assertThrows(
IllegalArgumentException.class,
() -> signatureService.getSignatureBytes(TEST_USER, "../invalid.png"),
() -> signatureService.getSharedSignatureBytes("../invalid.png"),
"Should throw exception for file names with directory traversal");
}
}
@Test
void testGetSharedSignatureBytes_CannotAccessPersonalFiles() {
// Mock static method for each test
try (MockedStatic<InstallationPathConfig> mockedConfig =
mockStatic(InstallationPathConfig.class)) {
mockedConfig
.when(InstallationPathConfig::getSignaturesPath)
.thenReturn(tempDir.toString());
// Test and verify - core service should NOT be able to read personal files
assertThrows(
FileNotFoundException.class,
() -> signatureService.getSharedSignatureBytes("personal.png"),
"Core service should not have access to personal signatures");
}
}
@Test
void testGetAvailableSignatures_EmptyUsername() throws IOException {
// Mock static method for each test