@@ -0,0 +1,182 @@
|
||||
package stirling.software.SPDF.EE;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@Service
|
||||
public class KeygenLicenseVerifier {
|
||||
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
|
||||
private static final String PRODUCT_ID = "f9bb2423-62c9-4d39-8def-4fdc5aca751e";
|
||||
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
|
||||
private static final String PUBLIC_KEY =
|
||||
"-----BEGIN PUBLIC KEY-----\n"
|
||||
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzJaf7jPx/bamT/ctmvrf\n"
|
||||
+ "5HfzV9CrTx39Hv48NvRIjw9jBAlmcSndLbgcrTUWFrd7pJPPEhzmfJ9tLRg0a3Si\n"
|
||||
+ "34Ed9gQ24mODj0Wpos5uwwxu1M5wzsKPjkLZDigB3d9L/79nyKvSUo+mx+dZmZnD\n"
|
||||
+ "D19TMM93ZDxG+Bru5/rvvxaZzMHZAnqrTdoO55vFjpss5XJNt6kz4jxr+D6a3lFU\n"
|
||||
+ "GGCx7bjeanHCNGRw84dLYbU8s5DGsx5JNX1xPGR1kODocvsHfHJvsxfdNtpH4vke\n"
|
||||
+ "yOrtEUCp01Mh2kr3zM8R4Yjh4ae2qHiZne0FiVhiUaHmbf2dmcA9O1Kynz33634s\n"
|
||||
+ "fwIDAQAB\n"
|
||||
+ "-----END PUBLIC KEY-----";
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public static boolean verifyLicense(String licenseKey) {
|
||||
try {
|
||||
String machineFingerprint = generateMachineFingerprint();
|
||||
|
||||
// First, try to validate the license
|
||||
boolean isValid = validateLicense(licenseKey, machineFingerprint);
|
||||
|
||||
// If validation fails, try to activate the machine
|
||||
if (!isValid) {
|
||||
System.out.println(
|
||||
"License validation failed. Attempting to activate the machine...");
|
||||
isValid = activateMachine(licenseKey, machineFingerprint);
|
||||
|
||||
if (isValid) {
|
||||
// If activation is successful, try to validate again
|
||||
isValid = validateLicense(licenseKey, machineFingerprint);
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error verifying license: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean validateLicense(String licenseKey, String machineFingerprint)
|
||||
throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
String requestBody =
|
||||
String.format(
|
||||
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\",\"product\":\"%s\"}}}",
|
||||
licenseKey, machineFingerprint, PRODUCT_ID);
|
||||
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder()
|
||||
.uri(
|
||||
URI.create(
|
||||
BASE_URL
|
||||
+ "/"
|
||||
+ ACCOUNT_ID
|
||||
+ "/licenses/actions/validate-key"))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Authorization", "license " + licenseKey)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 200) {
|
||||
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||
JsonNode metaNode = jsonResponse.path("meta");
|
||||
boolean isValid = metaNode.path("valid").asBoolean();
|
||||
String detail = metaNode.path("detail").asText();
|
||||
String code = metaNode.path("code").asText();
|
||||
|
||||
System.out.println("License validity: " + isValid);
|
||||
System.out.println("Validation detail: " + detail);
|
||||
System.out.println("Validation code: " + code);
|
||||
|
||||
if (isValid) {
|
||||
return verifySignature(metaNode);
|
||||
}
|
||||
} else {
|
||||
System.out.println("Error validating license. Status code: " + response.statusCode());
|
||||
System.out.println("Response body: " + response.body());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean activateMachine(String licenseKey, String machineFingerprint)
|
||||
throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
String requestBody =
|
||||
String.format(
|
||||
"{\"data\":{\"type\":\"machines\",\"attributes\":{\"fingerprint\":\"%s\"},\"relationships\":{\"license\":{\"data\":{\"type\":\"licenses\",\"id\":\"%s\"}}}}}",
|
||||
machineFingerprint, licenseKey);
|
||||
|
||||
String licenseId = "8e072b67-3cea-454b-98bb-bb73bbc04bd4";
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder()
|
||||
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines"))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Authorization", "license " + licenseId)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 201) {
|
||||
System.out.println("Machine activated successfully");
|
||||
return true;
|
||||
} else {
|
||||
System.out.println("Error activating machine. Status code: " + response.statusCode());
|
||||
System.out.println("Response body: " + response.body());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean verifySignature(JsonNode metaNode) throws Exception {
|
||||
String signature = metaNode.path("signature").asText();
|
||||
String data = metaNode.path("data").asText();
|
||||
|
||||
PublicKey publicKey =
|
||||
KeyFactory.getInstance("RSA")
|
||||
.generatePublic(
|
||||
new X509EncodedKeySpec(
|
||||
Base64.getDecoder()
|
||||
.decode(
|
||||
PUBLIC_KEY
|
||||
.replace(
|
||||
"-----BEGIN PUBLIC KEY-----",
|
||||
"")
|
||||
.replace(
|
||||
"-----END PUBLIC KEY-----",
|
||||
"")
|
||||
.replaceAll("\\s", ""))));
|
||||
|
||||
Signature sig = Signature.getInstance("SHA256withRSA");
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(data.getBytes());
|
||||
|
||||
boolean isSignatureValid = sig.verify(Base64.getDecoder().decode(signature));
|
||||
System.out.println("Signature validity: " + isSignatureValid);
|
||||
return isSignatureValid;
|
||||
}
|
||||
|
||||
private static String generateMachineFingerprint() {
|
||||
// This is a simplified example. In a real-world scenario, you'd want to generate
|
||||
// a more robust and unique fingerprint based on hardware characteristics.
|
||||
return "example-fingerprint-" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static void test() {
|
||||
String[] testKeys = {
|
||||
"FYKJ-YK7F-MEVX-RYKK-JYWE-77WW-3TKN-PJRU", "EFDB57-92B4C2-EDFA20-51146E-E1AF4A-V3"
|
||||
};
|
||||
|
||||
for (String licenseKey : testKeys) {
|
||||
System.out.println("Testing license key: " + licenseKey);
|
||||
boolean isValid = verifyLicense(licenseKey);
|
||||
System.out.println("License is valid: " + isValid);
|
||||
System.out.println("--------------------");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package stirling.software.SPDF.EE;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Component
|
||||
public class LicenseKeyChecker implements CommandLineRunner {
|
||||
|
||||
private final KeygenLicenseVerifier licenseService;
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
// Inject your license service or configuration
|
||||
public LicenseKeyChecker(
|
||||
KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {
|
||||
this.licenseService = licenseService;
|
||||
this.applicationProperties = new ApplicationProperties();
|
||||
}
|
||||
|
||||
// Validate on startup
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
checkLicense();
|
||||
}
|
||||
|
||||
// Periodic license check - runs every 7 days
|
||||
@Scheduled(fixedRate = 604800000) // 7 days in milliseconds
|
||||
public void checkLicensePeriodically() {
|
||||
checkLicense();
|
||||
}
|
||||
|
||||
// License validation logic
|
||||
private void checkLicense() {
|
||||
boolean isValid =
|
||||
licenseService.verifyLicense(applicationProperties.getEnterpriseEdition().getKey());
|
||||
if (!isValid) {
|
||||
// Handle invalid license (shut down the app, log, etc.)
|
||||
System.out.println("License key is invalid!");
|
||||
// Optionally stop the application
|
||||
// System.exit(1); // Uncomment if you want to stop the app
|
||||
} else {
|
||||
System.out.println("License key is valid.");
|
||||
}
|
||||
}
|
||||
|
||||
// Method to update the license key dynamically
|
||||
public void updateLicenseKey(String newKey) {
|
||||
// Update the key in ApplicationProperties
|
||||
applicationProperties.getEnterpriseEdition().setKey(newKey);
|
||||
|
||||
// Immediately validate the new key
|
||||
System.out.println("License key has been updated. Checking new key...");
|
||||
checkLicense();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
|
||||
@Component
|
||||
@@ -32,10 +33,11 @@ public class MetricsFilter extends OncePerRequestFilter {
|
||||
String uri = request.getRequestURI();
|
||||
|
||||
if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) {
|
||||
|
||||
HttpSession session = request.getSession(false);
|
||||
String sessionId = (session != null) ? session.getId() : "no-session";
|
||||
Counter counter =
|
||||
Counter.builder("http.requests")
|
||||
.tag("session", request.getSession().getId())
|
||||
.tag("session", sessionId)
|
||||
.tag("method", request.getMethod())
|
||||
.tag("uri", uri)
|
||||
.register(meterRegistry);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -14,9 +16,12 @@ import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FirstLoginFilter extends OncePerRequestFilter {
|
||||
|
||||
@@ -50,6 +55,20 @@ public class FirstLoginFilter extends OncePerRequestFilter {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HttpSession session = request.getSession(true);
|
||||
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
|
||||
String creationTime = timeFormat.format(new Date(session.getCreationTime()));
|
||||
|
||||
log.info(
|
||||
"Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}",
|
||||
session.isNew(),
|
||||
creationTime,
|
||||
session.getId(),
|
||||
request.getRemoteAddr(),
|
||||
request.getHeader("User-Agent"),
|
||||
request.getHeader("Referer"),
|
||||
request.getRequestURL().toString());
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -30,13 +29,18 @@ import stirling.software.SPDF.model.User;
|
||||
@Component
|
||||
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired @Lazy private UserService userService;
|
||||
private final UserService userService;
|
||||
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||
private final boolean loginEnabledValue;
|
||||
|
||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("loginEnabled")
|
||||
public boolean loginEnabledValue;
|
||||
public UserAuthenticationFilter(
|
||||
@Lazy UserService userService,
|
||||
SessionPersistentRegistry sessionPersistentRegistry,
|
||||
@Qualifier("loginEnabled") boolean loginEnabledValue) {
|
||||
this.userService = userService;
|
||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||
this.loginEnabledValue = loginEnabledValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@@ -51,6 +55,19 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
String requestURI = request.getRequestURI();
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
// Check for session expiration (unsure if needed)
|
||||
// if (authentication != null && authentication.isAuthenticated()) {
|
||||
// String sessionId = request.getSession().getId();
|
||||
// SessionInformation sessionInfo =
|
||||
// sessionPersistentRegistry.getSessionInformation(sessionId);
|
||||
//
|
||||
// if (sessionInfo != null && sessionInfo.isExpired()) {
|
||||
// SecurityContextHolder.clearContext();
|
||||
// response.sendRedirect(request.getContextPath() + "/login?expired=true");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Check for API key in the request headers if no authentication exists
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
String apiKey = request.getHeader("X-API-Key");
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package stirling.software.SPDF.config.security.session;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -11,16 +13,32 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public class CustomHttpSessionListener implements HttpSessionListener {
|
||||
|
||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
|
||||
private final AtomicInteger activeSessions;
|
||||
|
||||
@Autowired
|
||||
public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) {
|
||||
super();
|
||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||
activeSessions = new AtomicInteger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionCreated(HttpSessionEvent se) {
|
||||
log.info("Session created: " + se.getSession().getId());
|
||||
log.info(
|
||||
"Session created: {} with count {}",
|
||||
se.getSession().getId(),
|
||||
activeSessions.incrementAndGet());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionDestroyed(HttpSessionEvent se) {
|
||||
log.info("Session destroyed: " + se.getSession().getId());
|
||||
log.info(
|
||||
"Session destroyed: {} with count {}",
|
||||
se.getSession().getId(),
|
||||
activeSessions.decrementAndGet());
|
||||
sessionPersistentRegistry.expireSession(se.getSession().getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,14 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
||||
}
|
||||
|
||||
if (principalName != null) {
|
||||
// Clear old sessions for the principal (unsure if needed)
|
||||
// List<SessionEntity> existingSessions =
|
||||
// sessionRepository.findByPrincipalName(principalName);
|
||||
// for (SessionEntity session : existingSessions) {
|
||||
// session.setExpired(true);
|
||||
// sessionRepository.save(session);
|
||||
// }
|
||||
|
||||
SessionEntity sessionEntity = new SessionEntity();
|
||||
sessionEntity.setSessionId(sessionId);
|
||||
sessionEntity.setPrincipalName(principalName);
|
||||
|
||||
@@ -108,6 +108,13 @@ public class GeneralWebController {
|
||||
return "split-pdf-by-sections";
|
||||
}
|
||||
|
||||
@GetMapping("/split-pdf-by-chapters")
|
||||
@Hidden
|
||||
public String splitPdfByChapters(Model model) {
|
||||
model.addAttribute("currentPage", "split-pdf-by-chapters");
|
||||
return "split-pdf-by-chapters";
|
||||
}
|
||||
|
||||
@GetMapping("/view-pdf")
|
||||
@Hidden
|
||||
public String ViewPdfForm2(Model model) {
|
||||
|
||||
Reference in New Issue
Block a user