Skip to content

Commit

Permalink
Save signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
Frooodle committed Oct 29, 2024
1 parent 6a410d8 commit 702f26f
Show file tree
Hide file tree
Showing 15 changed files with 397 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
bin/
tmp/
*.tmp
*.bak
*.exe
*.swp
*~.nib
local.properties
Expand Down
6 changes: 2 additions & 4 deletions src/main/java/stirling/software/SPDF/EE/EEAppConfig.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package stirling.software.SPDF.EE;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -20,6 +18,6 @@ public class EEAppConfig {

@Bean(name = "runningEE")
public boolean runningEnterpriseEdition() {
return licenseKeyChecker.getEnterpriseEnabledResult();
return licenseKeyChecker.getEnterpriseEnabledResult();
}
}
}
6 changes: 2 additions & 4 deletions src/main/java/stirling/software/SPDF/config/InitialSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.stereotype.Component;

import io.micrometer.common.util.StringUtils;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
Expand Down Expand Up @@ -40,7 +41,7 @@ public void initSecretKey() throws IOException {
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
}
}

@PostConstruct
public void initLegalUrls() throws IOException {
// Initialize Terms and Conditions
Expand All @@ -59,7 +60,4 @@ public void initLegalUrls() throws IOException {
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
}
}

}


Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public class UserService implements UserServiceInterface {

@Autowired DatabaseBackupInterface databaseBackupHelper;


// Handle OAUTH2 login and user auto creation.
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
throws IllegalArgumentException, IOException {
Expand Down Expand Up @@ -360,8 +359,8 @@ public String getCurrentUsername() {
}
}

@Override
public long getTotalUsersCount() {
return userRepository.count();
}
@Override
public long getTotalUsersCount() {
return userRepository.count();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ public interface UserServiceInterface {
String getApiKeyForUser(String username);

String getCurrentUsername();

long getTotalUsersCount();
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;

import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.SignatureFile;
import stirling.software.SPDF.service.SignatureService;

@Controller
@Tag(name = "General", description = "General APIs")
public class GeneralWebController {
Expand Down Expand Up @@ -171,11 +175,28 @@ public String splitPdfForm(Model model) {
return "split-pdfs";
}

private static final String SIGNATURE_BASE_PATH = "customFiles/static/signatures/";
private static final String ALL_USERS_FOLDER = "ALL_USERS";

@Autowired private SignatureService signatureService;

@Autowired(required = false)
private UserService userService;

@GetMapping("/sign")
@Hidden
public String signForm(Model model) {
String username = "";
if (userService != null) {
username = userService.getCurrentUsername();
}

// Get signatures from both personal and ALL_USERS folders
List<SignatureFile> signatures = signatureService.getAvailableSignatures(username);

model.addAttribute("currentPage", "sign");
model.addAttribute("fonts", getFontNames());
model.addAttribute("signatures", signatures);
return "sign";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package stirling.software.SPDF.controller.web;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import stirling.software.SPDF.service.SignatureService;

@Controller
@RequestMapping("/api/v1/general/")
public class SignatureController {

@Autowired private SignatureService signatureService;

@GetMapping("/sign/{fileName}")
public ResponseEntity<byte[]> getSignature(
@PathVariable(name = "fileName") String fileName, Authentication authentication)
throws IOException {
String username = authentication.getName();

// Verify access permission
if (!signatureService.hasAccessToFile(username, fileName)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}

byte[] imageBytes = signatureService.getSignatureBytes(username, fileName);
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_JPEG) // Adjust based on file type
.body(imageBytes);
}
}
11 changes: 11 additions & 0 deletions src/main/java/stirling/software/SPDF/model/SignatureFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package stirling.software.SPDF.model;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class SignatureFile {
private String fileName;
private String category; // "Personal" or "Shared"
}
11 changes: 5 additions & 6 deletions src/main/java/stirling/software/SPDF/service/PostHogService.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public class PostHogService {
private final ApplicationProperties applicationProperties;
private final UserServiceInterface userService;


@Autowired
public PostHogService(
PostHog postHog,
@Qualifier("UUID") String uuid,
ApplicationProperties applicationProperties, @Autowired(required = false) UserServiceInterface userService) {
ApplicationProperties applicationProperties,
@Autowired(required = false) UserServiceInterface userService) {
this.postHog = postHog;
this.uniqueId = uuid;
this.applicationProperties = applicationProperties;
Expand Down Expand Up @@ -137,10 +137,9 @@ public Map<String, Object> captureServerMetrics() {
metrics.put("docker_metrics", getDockerMetrics());
}
metrics.put("application_properties", captureApplicationProperties());


if(userService != null) {
metrics.put("total_users_created", userService.getTotalUsersCount());

if (userService != null) {
metrics.put("total_users_created", userService.getTotalUsersCount());
}

} catch (Exception e) {
Expand Down
92 changes: 92 additions & 0 deletions src/main/java/stirling/software/SPDF/service/SignatureService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package stirling.software.SPDF.service;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils;

import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.SignatureFile;

@Service
@Slf4j
public class SignatureService {

private static final String SIGNATURE_BASE_PATH = "customFiles/static/signatures/";
private static final String ALL_USERS_FOLDER = "ALL_USERS";

public boolean hasAccessToFile(String username, String fileName) throws IOException {
// Check if file exists in user's personal folder or ALL_USERS folder
Path userPath = Paths.get(SIGNATURE_BASE_PATH, username, fileName);
Path allUsersPath = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER, fileName);

return Files.exists(userPath) || Files.exists(allUsersPath);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
}

public List<SignatureFile> getAvailableSignatures(String username) {
List<SignatureFile> signatures = new ArrayList<>();

// Get signatures from user's personal folder
if (!StringUtils.isEmptyOrWhitespace(username)) {
Path userFolder = Paths.get(SIGNATURE_BASE_PATH, username);
if (Files.exists(userFolder)) {
try {
signatures.addAll(getSignaturesFromFolder(userFolder, "Personal"));
} catch (IOException e) {
log.error("Error reading user signatures folder", e);
}
}
}

// Get signatures from ALL_USERS folder
Path allUsersFolder = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER);
if (Files.exists(allUsersFolder)) {
try {
signatures.addAll(getSignaturesFromFolder(allUsersFolder, "Shared"));
} catch (IOException e) {
log.error("Error reading shared signatures folder", e);
}
}

return signatures;
}

private List<SignatureFile> getSignaturesFromFolder(Path folder, String category)
throws IOException {
return Files.list(folder)
.filter(path -> isImageFile(path))
.map(path -> new SignatureFile(path.getFileName().toString(), category))
.collect(Collectors.toList());
}

public byte[] getSignatureBytes(String username, String fileName) throws IOException {
// First try user's personal folder
Path userPath = Paths.get(SIGNATURE_BASE_PATH, username, fileName);
if (Files.exists(userPath)) {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
return Files.readAllBytes(userPath);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
}

// Then try ALL_USERS folder
Path allUsersPath = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER, fileName);
if (Files.exists(allUsersPath)) {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
return Files.readAllBytes(allUsersPath);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
}

throw new FileNotFoundException("Signature file not found");
}

private boolean isImageFile(Path path) {
String fileName = path.getFileName().toString().toLowerCase();
return fileName.endsWith(".jpg")
|| fileName.endsWith(".jpeg")
|| fileName.endsWith(".png")
|| fileName.endsWith(".gif");
}
}
5 changes: 5 additions & 0 deletions src/main/resources/messages_en_GB.properties
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,11 @@ sign.draw=Draw Signature
sign.text=Text Input
sign.clear=Clear
sign.add=Add
sign.saved=Saved Signatures
sign.save=Save Signature
sign.personalSigs=Personal Signatures
sign.sharedSigs=Shared Signatures
sign.noSavedSigs=No saved signatures found


#repair
Expand Down
50 changes: 50 additions & 0 deletions src/main/resources/static/css/sign.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,53 @@ select#font-select option {
background-color: rgba(52, 152, 219, 0.2);
/* Darken background on hover */
}
.signature-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
padding: 1rem;
max-height: 400px;
overflow-y: auto;
}

.signature-list {
max-height: 400px;
overflow-y: auto;
}

.signature-list-item {
padding: 0.75rem;
border: 1px solid #dee2e6;
border-radius: 4px;
margin-bottom: 0.5rem;
cursor: pointer;
transition: background-color 0.2s;
}

.signature-list-item:hover {
background-color: #f8f9fa;
}

.signature-list-info {
display: flex;
justify-content: space-between;
align-items: center;
}

.signature-list-name {
font-weight: 500;
}

.signature-list-details {
color: #6c757d;
font-size: 0.875rem;
}

.signature-list-details small:not(:last-child) {
margin-right: 1rem;
}

.view-toggle {
text-align: right;
padding: 0.5rem 1rem;
}
4 changes: 2 additions & 2 deletions src/main/resources/templates/misc/add-image.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</div>

<!-- pdf selector -->
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script>
let originalFileName = '';
Expand All @@ -46,7 +46,7 @@
</script>

<div class="tab-group show-on-file-selected">
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
<script>
const imageUpload = document.querySelector('input[name=image-upload]');
imageUpload.addEventListener('change', e => {
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/templates/misc/compress-pdf.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<span class="tool-header-text" th:text="#{compress.header}"></span>
</div>
<form action="#" th:action="@{'/api/v1/misc/compress-pdf'}" method="post" enctype="multipart/form-data">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
Expand Down
Loading

0 comments on commit 702f26f

Please sign in to comment.