mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Merge branch 'main' into feat/auto-form-detection-server-only
This commit is contained in:
@@ -34,7 +34,6 @@ jobs:
|
||||
cache-dependency-glob: |
|
||||
engine/pyproject.toml
|
||||
engine/uv.lock
|
||||
cache-suffix: ai-engine
|
||||
|
||||
- name: Install Task
|
||||
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
|
||||
|
||||
@@ -42,7 +42,6 @@ jobs:
|
||||
cache-dependency-glob: |
|
||||
engine/pyproject.toml
|
||||
engine/uv.lock
|
||||
cache-suffix: generated-models
|
||||
|
||||
- name: Restore cache Gradle User Home
|
||||
if: inputs.use_shared_cache
|
||||
|
||||
@@ -31,7 +31,6 @@ jobs:
|
||||
cache-dependency-glob: |
|
||||
engine/pyproject.toml
|
||||
engine/uv.lock
|
||||
cache-suffix: pre-commit
|
||||
|
||||
- name: Install Task
|
||||
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
|
||||
|
||||
@@ -59,7 +59,6 @@ jobs:
|
||||
cache-dependency-glob: |
|
||||
engine/pyproject.toml
|
||||
engine/uv.lock
|
||||
cache-suffix: sync-files
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
|
||||
@@ -48,7 +48,7 @@ public class EndpointConfiguration {
|
||||
private final ApplicationProperties applicationProperties;
|
||||
@Getter private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
||||
private Set<String> disabledGroups = new HashSet<>();
|
||||
private Set<String> disabledGroups = ConcurrentHashMap.newKeySet();
|
||||
private Map<String, DisableReason> endpointDisableReasons = new ConcurrentHashMap<>();
|
||||
private Map<String, DisableReason> groupDisableReasons = new ConcurrentHashMap<>();
|
||||
private Map<String, Set<String>> endpointAlternatives = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -237,7 +237,7 @@ public class TabulaTableParser implements TableParser {
|
||||
score -= 0.3f;
|
||||
}
|
||||
|
||||
return Math.max(0f, Math.min(1f, score));
|
||||
return Math.clamp(score, 0f, 1f);
|
||||
}
|
||||
|
||||
private Bounds tableBounds(Table table) {
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@ public class StringToMapPropertyEditor extends PropertyEditorSupport {
|
||||
@Override
|
||||
public void setAsText(String text) throws IllegalArgumentException {
|
||||
try {
|
||||
TypeReference<HashMap<String, String>> typeRef = new TypeReference<>() {};
|
||||
TypeReference<HashMap<String, String>> typeRef =
|
||||
new TypeReference<HashMap<String, String>>() {};
|
||||
Map<String, String> map = objectMapper.readValue(text, typeRef);
|
||||
setValue(map);
|
||||
} catch (Exception e) {
|
||||
|
||||
+1
-1
@@ -237,7 +237,7 @@ public class EditTextController {
|
||||
|
||||
Matcher matcher = edit.pattern().matcher(joined);
|
||||
List<MatchSpan> spans = new ArrayList<>();
|
||||
StringBuffer interpolation = new StringBuffer();
|
||||
StringBuilder interpolation = new StringBuilder();
|
||||
int previousAppendPosition = 0;
|
||||
while (matcher.find()) {
|
||||
if (matcher.start() == matcher.end()) {
|
||||
|
||||
@@ -95,7 +95,8 @@ public class UIDataController {
|
||||
|
||||
try (InputStream is = resource.getInputStream()) {
|
||||
Map<String, List<Dependency>> licenseData =
|
||||
objectMapper.readValue(is, new TypeReference<>() {});
|
||||
objectMapper.readValue(
|
||||
is, new TypeReference<Map<String, List<Dependency>>>() {});
|
||||
data.setDependencies(licenseData.get("dependencies"));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to load licenses data", e);
|
||||
|
||||
+6
-3
@@ -25,12 +25,15 @@ final class FormPayloadParser {
|
||||
private static final String KEY_VALUE = "value";
|
||||
private static final String KEY_DEFAULT_VALUE = "defaultValue";
|
||||
|
||||
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {};
|
||||
private static final TypeReference<Map<String, Object>> MAP_TYPE =
|
||||
new TypeReference<Map<String, Object>>() {};
|
||||
private static final TypeReference<List<FormUtils.ModifyFormFieldDefinition>>
|
||||
MODIFY_FIELD_LIST_TYPE = new TypeReference<>() {};
|
||||
MODIFY_FIELD_LIST_TYPE =
|
||||
new TypeReference<List<FormUtils.ModifyFormFieldDefinition>>() {};
|
||||
private static final TypeReference<List<FormUtils.NewFormFieldDefinition>> NEW_FIELD_LIST_TYPE =
|
||||
new TypeReference<>() {};
|
||||
private static final TypeReference<List<String>> STRING_LIST_TYPE = new TypeReference<>() {};
|
||||
private static final TypeReference<List<String>> STRING_LIST_TYPE =
|
||||
new TypeReference<List<String>>() {};
|
||||
|
||||
private FormPayloadParser() {}
|
||||
|
||||
|
||||
+3
-1
@@ -96,7 +96,9 @@ public class AddCommentsController {
|
||||
|
||||
List<CommentSpecDto> dtos;
|
||||
try {
|
||||
dtos = objectMapper.readValue(commentsJson, new TypeReference<>() {});
|
||||
dtos =
|
||||
objectMapper.readValue(
|
||||
commentsJson, new TypeReference<List<CommentSpecDto>>() {});
|
||||
} catch (JacksonException e) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "comments must be a JSON array of CommentSpec objects");
|
||||
|
||||
+11
-8
@@ -4,7 +4,9 @@ import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -21,7 +23,7 @@ public class WeeklyActiveUsersService {
|
||||
private final Map<String, Instant> activeBrowsers = new ConcurrentHashMap<>();
|
||||
|
||||
// Track total unique browsers seen (overall)
|
||||
private long totalUniqueBrowsers = 0;
|
||||
private final AtomicLong totalUniqueBrowsers = new AtomicLong(0);
|
||||
|
||||
// Application start time
|
||||
private final Instant startTime = Instant.now();
|
||||
@@ -36,12 +38,12 @@ public class WeeklyActiveUsersService {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isNewBrowser = !activeBrowsers.containsKey(browserId);
|
||||
activeBrowsers.put(browserId, Instant.now());
|
||||
Instant now = Instant.now();
|
||||
Instant previous = activeBrowsers.put(browserId, now);
|
||||
|
||||
if (isNewBrowser) {
|
||||
totalUniqueBrowsers++;
|
||||
log.debug("New browser recorded: {} (Total: {})", browserId, totalUniqueBrowsers);
|
||||
if (previous == null) {
|
||||
long total = totalUniqueBrowsers.incrementAndGet();
|
||||
log.debug("New browser recorded: {} (Total: {})", browserId, total);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +63,7 @@ public class WeeklyActiveUsersService {
|
||||
* @return Total unique browsers count
|
||||
*/
|
||||
public long getTotalUniqueBrowsers() {
|
||||
return totalUniqueBrowsers;
|
||||
return totalUniqueBrowsers.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +90,8 @@ public class WeeklyActiveUsersService {
|
||||
activeBrowsers.entrySet().removeIf(entry -> entry.getValue().isBefore(sevenDaysAgo));
|
||||
}
|
||||
|
||||
/** Manual cleanup trigger (can be called by scheduled task if needed) */
|
||||
/** Scheduled cleanup trigger running every hour */
|
||||
@Scheduled(fixedRate = 3600000)
|
||||
public void performCleanup() {
|
||||
int sizeBefore = activeBrowsers.size();
|
||||
cleanupOldEntries();
|
||||
|
||||
@@ -59,7 +59,7 @@ public enum AuditLevel {
|
||||
*/
|
||||
public static AuditLevel fromInt(int level) {
|
||||
// Ensure level is within valid bounds
|
||||
int boundedLevel = Math.min(Math.max(level, 0), 3);
|
||||
int boundedLevel = Math.clamp(level, 0, 3);
|
||||
|
||||
for (AuditLevel auditLevel : values()) {
|
||||
if (auditLevel.level == boundedLevel) {
|
||||
|
||||
+11
-9
@@ -17,16 +17,16 @@ import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.cluster.JobStore;
|
||||
import stirling.software.common.cluster.JobStoreEntry;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Valkey-backed {@link JobStore}. Each job is one hash; a reverse index maps fileId to jobId.
|
||||
*
|
||||
@@ -44,8 +44,10 @@ public class ValkeyJobStore implements JobStore {
|
||||
private static final String FILE_INDEX_PREFIX = "stirling:file2job:";
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private static final TypeReference<List<String>> LIST_STRING = new TypeReference<>() {};
|
||||
private static final TypeReference<Map<String, String>> MAP_STRING = new TypeReference<>() {};
|
||||
private static final TypeReference<List<String>> LIST_STRING =
|
||||
new TypeReference<List<String>>() {};
|
||||
private static final TypeReference<Map<String, String>> MAP_STRING =
|
||||
new TypeReference<Map<String, String>>() {};
|
||||
|
||||
private final StringRedisTemplate template;
|
||||
|
||||
@@ -265,7 +267,7 @@ public class ValkeyJobStore implements JobStore {
|
||||
}
|
||||
try {
|
||||
return MAPPER.readValue(v.toString(), MAP_STRING);
|
||||
} catch (JsonProcessingException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.warn(
|
||||
"JobStore {} field 'resultMeta' is not valid JSON '{}' - treating as empty",
|
||||
key,
|
||||
@@ -277,7 +279,7 @@ public class ValkeyJobStore implements JobStore {
|
||||
private static String writeJson(Object value) {
|
||||
try {
|
||||
return MAPPER.writeValueAsString(value);
|
||||
} catch (JsonProcessingException e) {
|
||||
} catch (JacksonException e) {
|
||||
throw new IllegalStateException("Failed to JSON-serialize JobStore field", e);
|
||||
}
|
||||
}
|
||||
@@ -286,7 +288,7 @@ public class ValkeyJobStore implements JobStore {
|
||||
try {
|
||||
List<String> parsed = MAPPER.readValue(json, LIST_STRING);
|
||||
return parsed == null ? new ArrayList<>() : parsed;
|
||||
} catch (JsonProcessingException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.warn(
|
||||
"JobStore {} field 'fileIds' is not valid JSON '{}' - treating as empty",
|
||||
key,
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ public class AuditConfigurationProperties {
|
||||
|
||||
// Ensure level is within valid bounds (0-3)
|
||||
int configLevel = auditConfig.getLevel();
|
||||
this.level = Math.min(Math.max(configLevel, 0), 3);
|
||||
this.level = Math.clamp(configLevel, 0, 3);
|
||||
|
||||
// Retention days (0 means infinite)
|
||||
this.retentionDays = auditConfig.getRetentionDays();
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public class UsageRestController {
|
||||
@RequestParam(value = "dataType", defaultValue = "all") String dataType,
|
||||
@RequestParam(value = "days", defaultValue = "30") Integer days) {
|
||||
|
||||
int lookbackDays = Math.max(1, Math.min(days, 365));
|
||||
int lookbackDays = Math.clamp(days, 1, 365);
|
||||
|
||||
// Get audit events filtered by type
|
||||
List<PersistentAuditEvent> events = getEventsByDataType(dataType, lookbackDays);
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
@@ -19,7 +20,7 @@ import lombok.*;
|
||||
@ToString
|
||||
public class UserLicenseSettings implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final Long SINGLETON_ID = 1L;
|
||||
|
||||
|
||||
+17
-15
@@ -70,21 +70,23 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||
|
||||
if (!response.isCommitted()) {
|
||||
if (authentication != null) {
|
||||
if (authentication instanceof Saml2Authentication samlAuthentication) {
|
||||
// Handle SAML2 logout redirection
|
||||
getRedirect_saml2(request, response, samlAuthentication);
|
||||
} else if (authentication instanceof OAuth2AuthenticationToken oAuthToken) {
|
||||
// Handle OAuth2 logout redirection
|
||||
getRedirect_oauth2(request, response, oAuthToken);
|
||||
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
||||
// Handle Username/Password logout
|
||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
||||
} else {
|
||||
// Handle unknown authentication types
|
||||
log.error(
|
||||
"Authentication class unknown: {}",
|
||||
authentication.getClass().getSimpleName());
|
||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
||||
switch (authentication) {
|
||||
case Saml2Authentication samlAuthentication ->
|
||||
// Handle SAML2 logout redirection
|
||||
getRedirect_saml2(request, response, samlAuthentication);
|
||||
case OAuth2AuthenticationToken oAuthToken ->
|
||||
// Handle OAuth2 logout redirection
|
||||
getRedirect_oauth2(request, response, oAuthToken);
|
||||
case UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken ->
|
||||
// Handle Username/Password logout
|
||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
||||
default -> {
|
||||
// Handle unknown authentication types
|
||||
log.error(
|
||||
"Authentication class unknown: {}",
|
||||
authentication.getClass().getSimpleName());
|
||||
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (jwtService != null) {
|
||||
|
||||
+34
-34
@@ -393,40 +393,40 @@ public class SecurityConfiguration {
|
||||
// Handle OAUTH2 Logins
|
||||
if (securityProperties.isOauth2Active()) {
|
||||
http.oauth2Login(
|
||||
oauth2 -> {
|
||||
oauth2.loginPage("/login")
|
||||
.authorizationEndpoint(
|
||||
authorizationEndpoint -> {
|
||||
if (clientRegistrationRepository != null) {
|
||||
authorizationEndpoint
|
||||
.authorizationRequestResolver(
|
||||
new TauriAuthorizationRequestResolver(
|
||||
clientRegistrationRepository));
|
||||
}
|
||||
})
|
||||
.successHandler(
|
||||
new CustomOAuth2AuthenticationSuccessHandler(
|
||||
loginAttemptService,
|
||||
securityProperties.getOauth2(),
|
||||
userService,
|
||||
jwtService,
|
||||
licenseSettingsService,
|
||||
applicationProperties))
|
||||
.failureHandler(new CustomOAuth2AuthenticationFailureHandler())
|
||||
// Add existing Authorities from the database
|
||||
.userInfoEndpoint(
|
||||
userInfoEndpoint ->
|
||||
userInfoEndpoint
|
||||
.oidcUserService(
|
||||
new CustomOAuth2UserService(
|
||||
securityProperties
|
||||
.getOauth2(),
|
||||
userService,
|
||||
loginAttemptService))
|
||||
.userAuthoritiesMapper(
|
||||
oAuth2userAuthoritiesMapper))
|
||||
.permitAll();
|
||||
});
|
||||
oauth2 ->
|
||||
oauth2.loginPage("/login")
|
||||
.authorizationEndpoint(
|
||||
authorizationEndpoint -> {
|
||||
if (clientRegistrationRepository != null) {
|
||||
authorizationEndpoint
|
||||
.authorizationRequestResolver(
|
||||
new TauriAuthorizationRequestResolver(
|
||||
clientRegistrationRepository));
|
||||
}
|
||||
})
|
||||
.successHandler(
|
||||
new CustomOAuth2AuthenticationSuccessHandler(
|
||||
loginAttemptService,
|
||||
securityProperties.getOauth2(),
|
||||
userService,
|
||||
jwtService,
|
||||
licenseSettingsService,
|
||||
applicationProperties))
|
||||
.failureHandler(
|
||||
new CustomOAuth2AuthenticationFailureHandler())
|
||||
// Add existing Authorities from the database
|
||||
.userInfoEndpoint(
|
||||
userInfoEndpoint ->
|
||||
userInfoEndpoint
|
||||
.oidcUserService(
|
||||
new CustomOAuth2UserService(
|
||||
securityProperties
|
||||
.getOauth2(),
|
||||
userService,
|
||||
loginAttemptService))
|
||||
.userAuthoritiesMapper(
|
||||
oAuth2userAuthoritiesMapper))
|
||||
.permitAll());
|
||||
}
|
||||
// Handle SAML
|
||||
if (securityProperties.isSaml2Active() && runningProOrHigher) {
|
||||
|
||||
+12
-11
@@ -703,17 +703,18 @@ public class AuthController {
|
||||
}
|
||||
|
||||
private long extractEpochMillis(Object claimValue) {
|
||||
if (claimValue == null) {
|
||||
return -1L;
|
||||
}
|
||||
|
||||
if (claimValue instanceof java.util.Date date) {
|
||||
return date.getTime();
|
||||
}
|
||||
|
||||
if (claimValue instanceof Number number) {
|
||||
long epochSeconds = number.longValue();
|
||||
return epochSeconds * 1000L;
|
||||
switch (claimValue) {
|
||||
case null -> {
|
||||
return -1L;
|
||||
}
|
||||
case java.util.Date date -> {
|
||||
return date.getTime();
|
||||
}
|
||||
case Number number -> {
|
||||
long epochSeconds = number.longValue();
|
||||
return epochSeconds * 1000L;
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
|
||||
return -1L;
|
||||
|
||||
+8
-8
@@ -760,14 +760,14 @@ public class UserController {
|
||||
for (Object principal : principals) {
|
||||
List<SessionInformation> sessionsInformation =
|
||||
sessionRegistry.getAllSessions(principal, false);
|
||||
if (principal instanceof UserDetails detailsUser) {
|
||||
userNameP = detailsUser.getUsername();
|
||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||
userNameP = oAuth2User.getName();
|
||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||
userNameP = saml2User.name();
|
||||
} else if (principal instanceof String stringUser) {
|
||||
userNameP = stringUser;
|
||||
switch (principal) {
|
||||
case null -> {}
|
||||
case UserDetails detailsUser -> userNameP = detailsUser.getUsername();
|
||||
case OAuth2User oAuth2User -> userNameP = oAuth2User.getName();
|
||||
case CustomSaml2AuthenticatedPrincipal saml2User ->
|
||||
userNameP = saml2User.name();
|
||||
case String stringUser -> userNameP = stringUser;
|
||||
default -> {}
|
||||
}
|
||||
if (userNameP.equalsIgnoreCase(username)) {
|
||||
for (SessionInformation sessionInfo : sessionsInformation) {
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.security.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@@ -28,7 +29,7 @@ import lombok.Setter;
|
||||
@Setter
|
||||
public class Authority implements GrantedAuthority, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.security.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -18,7 +19,7 @@ import lombok.Setter;
|
||||
@Setter
|
||||
public class InviteToken implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
||||
+52
-47
@@ -36,57 +36,62 @@ public class CustomOAuth2AuthenticationFailureHandler
|
||||
AuthenticationException exception)
|
||||
throws IOException, ServletException {
|
||||
|
||||
if (exception instanceof BadCredentialsException) {
|
||||
log.error("BadCredentialsException", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/login?error=badCredentials");
|
||||
return;
|
||||
}
|
||||
if (exception instanceof DisabledException) {
|
||||
log.error("User is deactivated: ", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
|
||||
return;
|
||||
}
|
||||
if (exception instanceof LockedException) {
|
||||
log.error("Account locked: ", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||
return;
|
||||
}
|
||||
if (exception instanceof OAuth2AuthenticationException oAuth2Exception) {
|
||||
OAuth2Error error = oAuth2Exception.getError();
|
||||
|
||||
String errorCode = error.getErrorCode();
|
||||
|
||||
if ("Password must not be null".equals(error.getErrorCode())) {
|
||||
errorCode = "userAlreadyExistsWeb";
|
||||
switch (exception) {
|
||||
case BadCredentialsException badCredentialsException -> {
|
||||
log.error("BadCredentialsException", exception);
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(request, response, "/login?error=badCredentials");
|
||||
return;
|
||||
}
|
||||
case DisabledException disabledException -> {
|
||||
log.error("User is deactivated: ", exception);
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(request, response, "/logout?userIsDisabled=true");
|
||||
return;
|
||||
}
|
||||
case LockedException lockedException -> {
|
||||
log.error("Account locked: ", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||
return;
|
||||
}
|
||||
case OAuth2AuthenticationException oAuth2Exception -> {
|
||||
OAuth2Error error = oAuth2Exception.getError();
|
||||
|
||||
log.error(
|
||||
"OAuth2 Authentication error: {}",
|
||||
errorCode != null ? errorCode : exception.getMessage(),
|
||||
exception);
|
||||
String errorValue = errorCode != null ? errorCode : "oauth2AuthenticationError";
|
||||
clearRedirectCookie(response);
|
||||
boolean tauriState = TauriOAuthUtils.isTauriState(request);
|
||||
String redirectUrl;
|
||||
if (tauriState) {
|
||||
String basePath =
|
||||
TauriOAuthUtils.defaultTauriCallbackPath(request.getContextPath());
|
||||
redirectUrl = basePath;
|
||||
String stateParam = request.getParameter("state");
|
||||
if (stateParam != null && !stateParam.isBlank()) {
|
||||
redirectUrl = appendQueryParam(redirectUrl, "state", stateParam);
|
||||
// Extract and pass nonce for CSRF validation
|
||||
String nonce = TauriOAuthUtils.extractNonceFromState(stateParam);
|
||||
if (nonce != null) {
|
||||
redirectUrl = appendQueryParam(redirectUrl, "nonce", nonce);
|
||||
}
|
||||
String errorCode = error.getErrorCode();
|
||||
|
||||
if ("Password must not be null".equals(error.getErrorCode())) {
|
||||
errorCode = "userAlreadyExistsWeb";
|
||||
}
|
||||
redirectUrl = appendQueryParam(redirectUrl, "errorOAuth", errorValue);
|
||||
} else {
|
||||
redirectUrl = buildFailureRedirectUrl(request, errorValue);
|
||||
|
||||
log.error(
|
||||
"OAuth2 Authentication error: {}",
|
||||
errorCode != null ? errorCode : exception.getMessage(),
|
||||
exception);
|
||||
String errorValue = errorCode != null ? errorCode : "oauth2AuthenticationError";
|
||||
clearRedirectCookie(response);
|
||||
boolean tauriState = TauriOAuthUtils.isTauriState(request);
|
||||
String redirectUrl;
|
||||
if (tauriState) {
|
||||
String basePath =
|
||||
TauriOAuthUtils.defaultTauriCallbackPath(request.getContextPath());
|
||||
redirectUrl = basePath;
|
||||
String stateParam = request.getParameter("state");
|
||||
if (stateParam != null && !stateParam.isBlank()) {
|
||||
redirectUrl = appendQueryParam(redirectUrl, "state", stateParam);
|
||||
// Extract and pass nonce for CSRF validation
|
||||
String nonce = TauriOAuthUtils.extractNonceFromState(stateParam);
|
||||
if (nonce != null) {
|
||||
redirectUrl = appendQueryParam(redirectUrl, "nonce", nonce);
|
||||
}
|
||||
}
|
||||
redirectUrl = appendQueryParam(redirectUrl, "errorOAuth", errorValue);
|
||||
} else {
|
||||
redirectUrl = buildFailureRedirectUrl(request, errorValue);
|
||||
}
|
||||
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
||||
return;
|
||||
}
|
||||
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
||||
return;
|
||||
default -> {}
|
||||
}
|
||||
log.error("Unhandled authentication exception", exception);
|
||||
super.onAuthenticationFailure(request, response, exception);
|
||||
|
||||
+6
-1
@@ -61,7 +61,12 @@ public class CustomSaml2ResponseAuthenticationConverter
|
||||
|
||||
@Override
|
||||
public Saml2Authentication convert(ResponseToken responseToken) {
|
||||
Assertion assertion = responseToken.getResponse().getAssertions().getFirst();
|
||||
List<Assertion> assertions = responseToken.getResponse().getAssertions();
|
||||
if (assertions == null || assertions.isEmpty()) {
|
||||
log.error("SAML response contains no assertions");
|
||||
return null;
|
||||
}
|
||||
Assertion assertion = assertions.getFirst();
|
||||
Map<String, List<Object>> attributes = extractAttributes(assertion);
|
||||
|
||||
// Debug log with actual values
|
||||
|
||||
+5
-2
@@ -213,8 +213,11 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
||||
}
|
||||
|
||||
sb.append(
|
||||
"\nWARNING: this block contains PII. Set security.oauth2.debugLogging=false once"
|
||||
+ " troubleshooting is complete.\n");
|
||||
"""
|
||||
|
||||
WARNING: this block contains PII. Set security.oauth2.debugLogging=false once\
|
||||
troubleshooting is complete.
|
||||
""");
|
||||
sb.append("========== [/OAUTH2 DEBUG] ==========");
|
||||
|
||||
if (failure) {
|
||||
|
||||
+3
-1
@@ -132,7 +132,9 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
|
||||
verifyingKeyCache.put(
|
||||
key.getKeyId(), new JwtVerificationKey(key.getKeyId(), key.getVerifyingKey()));
|
||||
}
|
||||
activeKey = new JwtVerificationKey(keys.get(0).getKeyId(), keys.get(0).getVerifyingKey());
|
||||
activeKey =
|
||||
new JwtVerificationKey(
|
||||
keys.getFirst().getKeyId(), keys.getFirst().getVerifyingKey());
|
||||
log.info("Loaded {} JWT key(s) from DB, active key: {}", keys.size(), activeKey.getKeyId());
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -640,14 +640,14 @@ public class UserService implements UserServiceInterface {
|
||||
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
||||
for (SessionInformation sessionsInformation :
|
||||
sessionRegistry.getAllSessions(principal, false)) {
|
||||
if (principal instanceof UserDetails detailsUser) {
|
||||
usernameP = detailsUser.getUsername();
|
||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||
usernameP = oAuth2User.getName();
|
||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||
usernameP = saml2User.name();
|
||||
} else if (principal instanceof String stringUser) {
|
||||
usernameP = stringUser;
|
||||
switch (principal) {
|
||||
case null -> {}
|
||||
case UserDetails detailsUser -> usernameP = detailsUser.getUsername();
|
||||
case OAuth2User oAuth2User -> usernameP = oAuth2User.getName();
|
||||
case CustomSaml2AuthenticatedPrincipal saml2User ->
|
||||
usernameP = saml2User.name();
|
||||
case String stringUser -> usernameP = stringUser;
|
||||
default -> {}
|
||||
}
|
||||
if (usernameP.equalsIgnoreCase(username)) {
|
||||
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
||||
|
||||
+14
-16
@@ -47,14 +47,13 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
||||
List<SessionInformation> sessionInformations = new ArrayList<>();
|
||||
String principalName = null;
|
||||
|
||||
if (principal instanceof UserDetails detailsUser) {
|
||||
principalName = detailsUser.getUsername();
|
||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||
principalName = oAuth2User.getName();
|
||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||
principalName = saml2User.name();
|
||||
} else if (principal instanceof String stringUser) {
|
||||
principalName = stringUser;
|
||||
switch (principal) {
|
||||
case null -> {}
|
||||
case UserDetails detailsUser -> principalName = detailsUser.getUsername();
|
||||
case OAuth2User oAuth2User -> principalName = oAuth2User.getName();
|
||||
case CustomSaml2AuthenticatedPrincipal saml2User -> principalName = saml2User.name();
|
||||
case String stringUser -> principalName = stringUser;
|
||||
default -> {}
|
||||
}
|
||||
|
||||
if (principalName != null) {
|
||||
@@ -78,14 +77,13 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
||||
public void registerNewSession(String sessionId, Object principal) {
|
||||
String principalName = null;
|
||||
|
||||
if (principal instanceof UserDetails detailsUser) {
|
||||
principalName = detailsUser.getUsername();
|
||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||
principalName = oAuth2User.getName();
|
||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||
principalName = saml2User.name();
|
||||
} else if (principal instanceof String stringUser) {
|
||||
principalName = stringUser;
|
||||
switch (principal) {
|
||||
case null -> {}
|
||||
case UserDetails detailsUser -> principalName = detailsUser.getUsername();
|
||||
case OAuth2User oAuth2User -> principalName = oAuth2User.getName();
|
||||
case CustomSaml2AuthenticatedPrincipal saml2User -> principalName = saml2User.name();
|
||||
case String stringUser -> principalName = stringUser;
|
||||
default -> {}
|
||||
}
|
||||
|
||||
if (principalName != null) {
|
||||
|
||||
+8
-8
@@ -3,16 +3,16 @@ package stirling.software.proprietary.storage.converter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* JPA AttributeConverter for storing Map<String, Object> as JSON in database columns.
|
||||
*
|
||||
@@ -33,7 +33,7 @@ public class JsonMapConverter implements AttributeConverter<Map<String, Object>,
|
||||
|
||||
try {
|
||||
return objectMapper.writeValueAsString(attribute);
|
||||
} catch (JsonProcessingException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("Failed to convert map to JSON", e);
|
||||
throw new RuntimeException("Failed to convert map to JSON", e);
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public class JsonMapConverter implements AttributeConverter<Map<String, Object>,
|
||||
try {
|
||||
// Try normal parsing first
|
||||
return objectMapper.readValue(dbData, new TypeReference<Map<String, Object>>() {});
|
||||
} catch (JsonProcessingException e) {
|
||||
} catch (JacksonException e) {
|
||||
// Fallback: try double-parsing for legacy double-encoded data
|
||||
// This handles data that was stored as JSON strings instead of JSON objects
|
||||
log.debug("Attempting double-decode fallback for legacy metadata format");
|
||||
@@ -69,7 +69,7 @@ public class JsonMapConverter implements AttributeConverter<Map<String, Object>,
|
||||
return objectMapper.readValue(
|
||||
node.asText(), new TypeReference<Map<String, Object>>() {});
|
||||
}
|
||||
} catch (JsonProcessingException e2) {
|
||||
} catch (JacksonException e2) {
|
||||
log.error("Failed to parse metadata even with double-decode fallback", e2);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.storage.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -46,7 +47,7 @@ import stirling.software.proprietary.security.model.User;
|
||||
@Setter
|
||||
public class FileShare implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.storage.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -39,7 +40,7 @@ import stirling.software.proprietary.security.model.User;
|
||||
@Setter
|
||||
public class FileShareAccess implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.storage.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -24,7 +25,7 @@ import lombok.Setter;
|
||||
@Setter
|
||||
public class StorageCleanupEntry implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.storage.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
@@ -45,7 +46,7 @@ import stirling.software.proprietary.workflow.model.WorkflowSession;
|
||||
@Setter
|
||||
public class StoredFile implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.storage.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
@@ -19,7 +20,7 @@ import lombok.Setter;
|
||||
@Setter
|
||||
public class StoredFileBlob implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@Column(name = "storage_key", nullable = false, length = 128)
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.slf4j.MDC;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
@@ -64,7 +65,7 @@ public class AuditWebFilter extends OncePerRequestFilter {
|
||||
if (auth != null && auth.getAuthorities() != null) {
|
||||
String roles =
|
||||
auth.getAuthorities().stream()
|
||||
.map(a -> a.getAuthority())
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.reduce((a, b) -> a + "," + b)
|
||||
.orElse("");
|
||||
MDC.put("userRoles", roles);
|
||||
|
||||
+6
-3
@@ -20,8 +20,6 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@@ -39,11 +37,14 @@ import stirling.software.proprietary.workflow.dto.CertificateInfo;
|
||||
import stirling.software.proprietary.workflow.dto.CertificateValidationResponse;
|
||||
import stirling.software.proprietary.workflow.dto.ParticipantRequest;
|
||||
import stirling.software.proprietary.workflow.dto.WorkflowCreationRequest;
|
||||
import stirling.software.proprietary.workflow.model.WorkflowParticipant;
|
||||
import stirling.software.proprietary.workflow.model.WorkflowSession;
|
||||
import stirling.software.proprietary.workflow.service.CertificateSubmissionValidator;
|
||||
import stirling.software.proprietary.workflow.service.SigningFinalizationService;
|
||||
import stirling.software.proprietary.workflow.service.WorkflowSessionService;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/security")
|
||||
@@ -259,7 +260,9 @@ public class SigningSessionController {
|
||||
+ "database until manual cleanup.",
|
||||
sessionId,
|
||||
session.getParticipants() != null
|
||||
? session.getParticipants().stream().map(p -> p.getEmail()).toList()
|
||||
? session.getParticipants().stream()
|
||||
.map(WorkflowParticipant::getEmail)
|
||||
.toList()
|
||||
: "unknown",
|
||||
e);
|
||||
throw new ResponseStatusException(
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.ContentDisposition;
|
||||
@@ -429,7 +430,7 @@ public class WorkflowParticipantController {
|
||||
java.util.List<Map<String, Object>> wetSigs =
|
||||
objectMapper.readValue(
|
||||
request.getWetSignaturesData(),
|
||||
new TypeReference<java.util.List<Map<String, Object>>>() {});
|
||||
new TypeReference<List<Map<String, Object>>>() {});
|
||||
if (wetSigs.size() > WetSignatureMetadata.MAX_SIGNATURES_PER_PARTICIPANT) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "Too many wet signatures submitted");
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.workflow.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
@@ -51,7 +52,7 @@ import stirling.software.proprietary.storage.model.ShareAccessRole;
|
||||
@Setter
|
||||
public class WorkflowParticipant implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.workflow.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
@@ -53,7 +54,7 @@ import stirling.software.proprietary.storage.model.StoredFile;
|
||||
@Setter
|
||||
public class WorkflowSession implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
||||
+4
-10
@@ -217,16 +217,13 @@ public class SigningFinalizationService {
|
||||
wetSignatures.size(),
|
||||
session.getSessionId());
|
||||
|
||||
PDDocument document = pdfDocumentFactory.load(new ByteArrayInputStream(pdfBytes));
|
||||
try {
|
||||
try (PDDocument document = pdfDocumentFactory.load(new ByteArrayInputStream(pdfBytes))) {
|
||||
for (WetSignatureMetadata wetSig : wetSignatures) {
|
||||
applyWetSignatureToPage(document, wetSig);
|
||||
}
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
return baos.toByteArray();
|
||||
} finally {
|
||||
document.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,11 +239,10 @@ public class SigningFinalizationService {
|
||||
}
|
||||
|
||||
PDPage page = document.getPage(pageIndex);
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||
|
||||
try {
|
||||
try (PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
|
||||
// Use WetSignatureMetadata.extractBase64Data() to strip data URL prefix
|
||||
String base64Data = wetSig.extractBase64Data();
|
||||
if (base64Data == null || base64Data.isBlank()) {
|
||||
@@ -279,8 +275,6 @@ public class SigningFinalizationService {
|
||||
pdfY,
|
||||
width,
|
||||
height);
|
||||
} finally {
|
||||
contentStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-15
@@ -954,21 +954,22 @@ public class WorkflowSessionService {
|
||||
Object pemObject = pemParser.readObject();
|
||||
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
|
||||
PrivateKeyInfo keyInfo;
|
||||
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo encrypted) {
|
||||
InputDecryptorProvider decryptor =
|
||||
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password);
|
||||
keyInfo = encrypted.decryptPrivateKeyInfo(decryptor);
|
||||
} else if (pemObject instanceof PEMEncryptedKeyPair encryptedKeyPair) {
|
||||
PEMDecryptorProvider decryptor =
|
||||
new JcePEMDecryptorProviderBuilder().build(password);
|
||||
keyInfo = encryptedKeyPair.decryptKeyPair(decryptor).getPrivateKeyInfo();
|
||||
} else if (pemObject instanceof PEMKeyPair keyPair) {
|
||||
keyInfo = keyPair.getPrivateKeyInfo();
|
||||
} else if (pemObject instanceof PrivateKeyInfo info) {
|
||||
keyInfo = info;
|
||||
} else {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "Unsupported PEM private key format");
|
||||
switch (pemObject) {
|
||||
case PKCS8EncryptedPrivateKeyInfo encrypted -> {
|
||||
InputDecryptorProvider decryptor =
|
||||
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password);
|
||||
keyInfo = encrypted.decryptPrivateKeyInfo(decryptor);
|
||||
}
|
||||
case PEMEncryptedKeyPair encryptedKeyPair -> {
|
||||
PEMDecryptorProvider decryptor =
|
||||
new JcePEMDecryptorProviderBuilder().build(password);
|
||||
keyInfo = encryptedKeyPair.decryptKeyPair(decryptor).getPrivateKeyInfo();
|
||||
}
|
||||
case PEMKeyPair keyPair -> keyInfo = keyPair.getPrivateKeyInfo();
|
||||
case PrivateKeyInfo info -> keyInfo = info;
|
||||
case null, default ->
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "Unsupported PEM private key format");
|
||||
}
|
||||
return converter.getPrivateKey(keyInfo);
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,14 +4,14 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.proprietary.workflow.dto.ParticipantResponse;
|
||||
import stirling.software.proprietary.workflow.dto.WetSignatureMetadata;
|
||||
import stirling.software.proprietary.workflow.dto.WorkflowSessionResponse;
|
||||
import stirling.software.proprietary.workflow.model.WorkflowParticipant;
|
||||
import stirling.software.proprietary.workflow.model.WorkflowSession;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Utility class for mapping workflow entities to DTOs. Centralizes conversion logic for consistent
|
||||
* API responses.
|
||||
|
||||
@@ -26,8 +26,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -167,7 +167,7 @@ public class AiCreateController {
|
||||
if (request.constraints() != null) {
|
||||
try {
|
||||
constraintsPayload = objectMapper.writeValueAsString(request.constraints());
|
||||
} catch (JsonProcessingException exc) {
|
||||
} catch (JacksonException exc) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "Invalid constraints payload", exc);
|
||||
}
|
||||
@@ -202,7 +202,7 @@ public class AiCreateController {
|
||||
String payload;
|
||||
try {
|
||||
payload = objectMapper.writeValueAsString(request.draftSections());
|
||||
} catch (JsonProcessingException exc) {
|
||||
} catch (JacksonException exc) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "Invalid draft sections payload", exc);
|
||||
}
|
||||
@@ -392,7 +392,7 @@ public class AiCreateController {
|
||||
objectMapper
|
||||
.getTypeFactory()
|
||||
.constructCollectionType(List.class, DraftSection.class));
|
||||
} catch (JsonProcessingException exc) {
|
||||
} catch (JacksonException exc) {
|
||||
log.warn("Failed to parse draft sections payload", exc);
|
||||
return null;
|
||||
}
|
||||
@@ -408,7 +408,7 @@ public class AiCreateController {
|
||||
objectMapper
|
||||
.getTypeFactory()
|
||||
.constructMapType(Map.class, String.class, Object.class));
|
||||
} catch (JsonProcessingException exc) {
|
||||
} catch (JacksonException exc) {
|
||||
log.warn("Failed to parse outline constraints payload", exc);
|
||||
return null;
|
||||
}
|
||||
|
||||
+6
-6
@@ -14,8 +14,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -61,7 +61,7 @@ public class AiCreateInternalController {
|
||||
try {
|
||||
outlineConstraintsPayload =
|
||||
objectMapper.writeValueAsString(request.outlineConstraints());
|
||||
} catch (JsonProcessingException exc) {
|
||||
} catch (JacksonException exc) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "Invalid outline constraints payload", exc);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public class AiCreateInternalController {
|
||||
if (request.draftSections() != null) {
|
||||
try {
|
||||
draftSectionsPayload = objectMapper.writeValueAsString(request.draftSections());
|
||||
} catch (JsonProcessingException exc) {
|
||||
} catch (JacksonException exc) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "Invalid draft sections payload", exc);
|
||||
}
|
||||
@@ -136,7 +136,7 @@ public class AiCreateInternalController {
|
||||
.getTypeFactory()
|
||||
.constructCollectionType(
|
||||
List.class, AiCreateController.DraftSection.class));
|
||||
} catch (JsonProcessingException exc) {
|
||||
} catch (JacksonException exc) {
|
||||
log.warn("Failed to parse draft sections payload", exc);
|
||||
return null;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public class AiCreateInternalController {
|
||||
objectMapper
|
||||
.getTypeFactory()
|
||||
.constructMapType(Map.class, String.class, Object.class));
|
||||
} catch (JsonProcessingException exc) {
|
||||
} catch (JacksonException exc) {
|
||||
log.warn("Failed to parse outline constraints payload", exc);
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import java.util.regex.Pattern;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@@ -56,28 +56,26 @@ public class LegalDocumentRegistry {
|
||||
subprocessorUrl = root.path("subprocessorUrl").asText("");
|
||||
eulaUrl = root.path("eulaUrl").asText("");
|
||||
JsonNode docs = root.path("documents");
|
||||
docs.fieldNames()
|
||||
.forEachRemaining(
|
||||
id -> {
|
||||
JsonNode d = docs.get(id);
|
||||
List<String> parts =
|
||||
objectMapper.convertValue(
|
||||
d.path("parts"),
|
||||
objectMapper
|
||||
.getTypeFactory()
|
||||
.constructCollectionType(
|
||||
List.class, String.class));
|
||||
documents.put(
|
||||
docs.forEachEntry(
|
||||
(id, d) -> {
|
||||
List<String> parts =
|
||||
objectMapper.convertValue(
|
||||
d.path("parts"),
|
||||
objectMapper
|
||||
.getTypeFactory()
|
||||
.constructCollectionType(
|
||||
List.class, String.class));
|
||||
documents.put(
|
||||
id,
|
||||
new LegalDocumentMeta(
|
||||
id,
|
||||
new LegalDocumentMeta(
|
||||
id,
|
||||
d.path("label").asText(id),
|
||||
d.path("displayName").asText(id),
|
||||
d.path("version").asText("0"),
|
||||
d.path("effectiveDate").asText(""),
|
||||
d.path("status").asText("draft"),
|
||||
parts == null ? List.of() : parts));
|
||||
});
|
||||
d.path("label").asText(id),
|
||||
d.path("displayName").asText(id),
|
||||
d.path("version").asText("0"),
|
||||
d.path("effectiveDate").asText(""),
|
||||
d.path("status").asText("draft"),
|
||||
parts == null ? List.of() : parts));
|
||||
});
|
||||
log.info("[legal] loaded {} document(s) from {}", documents.size(), MANIFEST);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
+3
-3
@@ -10,8 +10,8 @@ import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -707,7 +707,7 @@ public class ProcurementService {
|
||||
private String writeLineItems(QuoteBreakdown breakdown) {
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(breakdown.lineItems());
|
||||
} catch (JsonProcessingException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.warn("[procurement] failed to serialise line items", e);
|
||||
return "[]";
|
||||
}
|
||||
|
||||
@@ -113,19 +113,13 @@ public class RateLimitService {
|
||||
public void cleanupExpiredBuckets() {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
int hourlyRemoved =
|
||||
(int)
|
||||
hourlyLimits.entrySet().stream()
|
||||
.filter(e -> e.getValue().getResetTime() < now)
|
||||
.peek(e -> hourlyLimits.remove(e.getKey()))
|
||||
.count();
|
||||
int hourlyBefore = hourlyLimits.size();
|
||||
hourlyLimits.entrySet().removeIf(e -> e.getValue().getResetTime() < now);
|
||||
int hourlyRemoved = hourlyBefore - hourlyLimits.size();
|
||||
|
||||
int dailyRemoved =
|
||||
(int)
|
||||
dailyLimits.entrySet().stream()
|
||||
.filter(e -> e.getValue().getResetTime() < now)
|
||||
.peek(e -> dailyLimits.remove(e.getKey()))
|
||||
.count();
|
||||
int dailyBefore = dailyLimits.size();
|
||||
dailyLimits.entrySet().removeIf(e -> e.getValue().getResetTime() < now);
|
||||
int dailyRemoved = dailyBefore - dailyLimits.size();
|
||||
|
||||
if (hourlyRemoved + dailyRemoved > 0) {
|
||||
log.debug(
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "إحداثي Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "فشل قصّ PDF"
|
||||
invalidArea = "منطقة القص تتجاوز حدود PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "معاينة منطقة القص"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y mövqeyi"
|
||||
|
||||
[crop.error]
|
||||
failed = "PDF-i kəsmək alınmadı"
|
||||
invalidArea = "Kəsmə sahəsi PDF sərhədlərini aşır"
|
||||
|
||||
[crop.preview]
|
||||
title = "Kəsmə sahəsinin seçimi"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y позиция"
|
||||
|
||||
[crop.error]
|
||||
failed = "Неуспешно изрязване на PDF"
|
||||
invalidArea = "Областта за изрязване излиза извън границите на PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Избор на област за изрязване"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Yཡི་གནས་བབ།"
|
||||
|
||||
[crop.error]
|
||||
failed = "སོན་བཟང་མ་འདང་བ། PDF"
|
||||
invalidArea = "སོན་འདེབས་རྒྱ་ཁྱོན་དེ་PDFམཚམས་ཐིག་ལས་བརྒལ་ཡོད།"
|
||||
|
||||
[crop.preview]
|
||||
title = "སོན་བཟང་ཁུལ་འདེམས་པ།"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Posició Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "No s'ha pogut retallar el PDF"
|
||||
invalidArea = "L'àrea de retall s'estén més enllà dels límits del PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Selecció de l'àrea de retall"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Pozice Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Oříznutí PDF se nezdařilo"
|
||||
invalidArea = "Oblast ořezu přesahuje hranice PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Výběr oblasti ořezu"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y-position"
|
||||
|
||||
[crop.error]
|
||||
failed = "Kunne ikke beskære PDF"
|
||||
invalidArea = "Beskæringsområdet strækker sig ud over PDF'ens grænser"
|
||||
|
||||
[crop.preview]
|
||||
title = "Valg af beskæringsområde"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y-Position"
|
||||
|
||||
[crop.error]
|
||||
failed = "PDF zuschneiden fehlgeschlagen"
|
||||
invalidArea = "Zuschneidebereich überschreitet die PDF-Grenzen"
|
||||
|
||||
[crop.preview]
|
||||
title = "Zuschneidebereich-Auswahl"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Θέση Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Αποτυχία περικοπής του PDF"
|
||||
invalidArea = "Η περιοχή περικοπής εκτείνεται πέρα από τα όρια του PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Επιλογή περιοχής περικοπής"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y Position"
|
||||
|
||||
[crop.error]
|
||||
failed = "Failed to crop PDF"
|
||||
invalidArea = "Crop area extends beyond PDF boundaries"
|
||||
|
||||
[crop.preview]
|
||||
title = "Crop Area Selection"
|
||||
|
||||
@@ -3931,7 +3931,6 @@ label = "Y Position"
|
||||
|
||||
[crop.error]
|
||||
failed = "Failed to crop PDF"
|
||||
invalidArea = "Crop area extends beyond PDF boundaries"
|
||||
|
||||
[crop.preview]
|
||||
title = "Crop Area Selection"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Posición Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Error al recortar PDF"
|
||||
invalidArea = "El área de recorte se extiende más allá de los límites del PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Selección de Área de Recorte"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y posizioa"
|
||||
|
||||
[crop.error]
|
||||
failed = "Huts egin du PDFa mozteak"
|
||||
invalidArea = "Mozketa-area PDFaren mugak baino harago doa"
|
||||
|
||||
[crop.preview]
|
||||
title = "Mozketa-arearen hautapena"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "موقعیت Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "برش PDF ناموفق بود"
|
||||
invalidArea = "ناحیه برش از مرزهای PDF فراتر رفته است"
|
||||
|
||||
[crop.preview]
|
||||
title = "انتخاب ناحیه برش"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Position Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Échec du recadrage du PDF"
|
||||
invalidArea = "La zone de recadrage dépasse les limites du PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Sélection de la zone de recadrage"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Suíomh Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Theip ar an PDF a bhearradh"
|
||||
invalidArea = "Téann an limistéar bearrtha thar theorainneacha an PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Roghnú Limistéir Bhearrtha"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y स्थान"
|
||||
|
||||
[crop.error]
|
||||
failed = "PDF क्रॉप करने में विफल"
|
||||
invalidArea = "क्रॉप क्षेत्र PDF सीमाओं से बाहर जा रहा है"
|
||||
|
||||
[crop.preview]
|
||||
title = "क्रॉप क्षेत्र चयन"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y položaj"
|
||||
|
||||
[crop.error]
|
||||
failed = "Izrezivanje PDF-a nije uspjelo"
|
||||
invalidArea = "Područje izrezivanja prelazi granice PDF-a"
|
||||
|
||||
[crop.preview]
|
||||
title = "Odabir područja izrezivanja"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y pozíció"
|
||||
|
||||
[crop.error]
|
||||
failed = "A PDF vágása sikertelen"
|
||||
invalidArea = "A vágási terület túlnyúlik a PDF határain"
|
||||
|
||||
[crop.preview]
|
||||
title = "Vágási terület kiválasztása"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Posisi Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Gagal memangkas PDF"
|
||||
invalidArea = "Area pangkas melampaui batas PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Pilihan Area Pangkas"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Posizione Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Impossibile ritagliare il PDF"
|
||||
invalidArea = "L’area di ritaglio supera i limiti del PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Selezione area di ritaglio"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y 位置"
|
||||
|
||||
[crop.error]
|
||||
failed = "PDF の切り抜きに失敗しました"
|
||||
invalidArea = "切り抜き範囲が PDF の境界を超えています"
|
||||
|
||||
[crop.preview]
|
||||
title = "切り抜き範囲の選択"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y 위치"
|
||||
|
||||
[crop.error]
|
||||
failed = "PDF 자르기에 실패했습니다"
|
||||
invalidArea = "자르기 영역이 PDF 경계를 벗어났습니다"
|
||||
|
||||
[crop.preview]
|
||||
title = "자르기 영역 선택"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y സ്ഥാനം"
|
||||
|
||||
[crop.error]
|
||||
failed = "PDF ക്രോപ്പ് ചെയ്യാൻ കഴിഞ്ഞില്ല"
|
||||
invalidArea = "ക്രോപ്പ് ഏരിയ PDF അതിരുകൾക്ക് പുറത്തേക്ക് നീളുന്നു"
|
||||
|
||||
[crop.preview]
|
||||
title = "ക്രോപ്പ് ഏരിയ തിരഞ്ഞെടുപ്പ്"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y-positie"
|
||||
|
||||
[crop.error]
|
||||
failed = "PDF bijsnijden mislukt"
|
||||
invalidArea = "Bijsnijgebied valt buiten PDF-randen"
|
||||
|
||||
[crop.preview]
|
||||
title = "Selectie bijsnijgebied"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y-posisjon"
|
||||
|
||||
[crop.error]
|
||||
failed = "Kunne ikke beskjære PDF"
|
||||
invalidArea = "Beskjæringsområdet går utenfor PDF-grensene"
|
||||
|
||||
[crop.preview]
|
||||
title = "Valg av beskjæringsområde"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Pozycja Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Nie udało się przyciąć PDF"
|
||||
invalidArea = "Obszar przycięcia wykracza poza granice PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Wybór obszaru przycięcia"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Posição Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Falha ao recortar o PDF"
|
||||
invalidArea = "A área de corte se estende além dos limites do PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Seleção da área de corte"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Posição Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Falha ao recortar o PDF"
|
||||
invalidArea = "A área de recorte excede os limites do PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Seleção da área de recorte"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Poziția Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Nu s-a putut decupa PDF-ul"
|
||||
invalidArea = "Zona de decupare depășește limitele PDF-ului"
|
||||
|
||||
[crop.preview]
|
||||
title = "Selecție zonă de decupare"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Положение Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Не удалось обрезать PDF"
|
||||
invalidArea = "Область обрезки выходит за границы PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Выбор области обрезки"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Pozícia Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Nepodarilo sa orezať PDF"
|
||||
invalidArea = "Oblasť orezania presahuje hranice PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Výber oblasti orezania"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Položaj Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Obrezovanje PDF-ja ni uspelo"
|
||||
invalidArea = "Območje obrezovanja presega meje PDF-ja"
|
||||
|
||||
[crop.preview]
|
||||
title = "Izbira območja obrezovanja"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y pozicija"
|
||||
|
||||
[crop.error]
|
||||
failed = "Nije uspelo isecanje PDF-a"
|
||||
invalidArea = "Oblast isečka prelazi granice PDF-a"
|
||||
|
||||
[crop.preview]
|
||||
title = "Izbor oblasti za isecanje"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y-position"
|
||||
|
||||
[crop.error]
|
||||
failed = "Det gick inte att beskära PDF"
|
||||
invalidArea = "Beskärningsområdet sträcker sig utanför PDF:ens gränser"
|
||||
|
||||
[crop.preview]
|
||||
title = "Val av beskärningsområde"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "ตำแหน่ง Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "ครอบตัด PDF ไม่สำเร็จ"
|
||||
invalidArea = "พื้นที่ครอบตัดเกินขอบเขตของ PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "การเลือกพื้นที่ครอบตัด"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y Konumu"
|
||||
|
||||
[crop.error]
|
||||
failed = "PDF kırpılamadı"
|
||||
invalidArea = "Kırpma alanı PDF sınırlarının dışına taşıyor"
|
||||
|
||||
[crop.preview]
|
||||
title = "Kırpma Alanı Seçimi"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Позиція Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Не вдалося обрізати PDF"
|
||||
invalidArea = "Область обрізки виходить за межі PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Вибір області обрізки"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Vị trí Y"
|
||||
|
||||
[crop.error]
|
||||
failed = "Không cắt được PDF"
|
||||
invalidArea = "Vùng cắt vượt quá ranh giới PDF"
|
||||
|
||||
[crop.preview]
|
||||
title = "Chọn vùng cắt"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y 位置"
|
||||
|
||||
[crop.error]
|
||||
failed = "裁剪 PDF 失败"
|
||||
invalidArea = "裁剪区域超出 PDF 边界"
|
||||
|
||||
[crop.preview]
|
||||
title = "裁剪区域选择"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y 位置"
|
||||
|
||||
[crop.error]
|
||||
failed = "裁剪 PDF 失败"
|
||||
invalidArea = "裁剪区域超出 PDF 边界"
|
||||
|
||||
[crop.preview]
|
||||
title = "裁剪区域选择"
|
||||
|
||||
@@ -3526,7 +3526,6 @@ label = "Y 位置"
|
||||
|
||||
[crop.error]
|
||||
failed = "裁切 PDF 失敗"
|
||||
invalidArea = "裁切區域超出 PDF 邊界"
|
||||
|
||||
[crop.preview]
|
||||
title = "裁切區域選擇"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import apiClient from "@app/services/apiClient";
|
||||
import type {
|
||||
SignRequestSummary,
|
||||
SessionSummary,
|
||||
} from "@app/types/signingSession";
|
||||
|
||||
export interface SigningSessions {
|
||||
signRequests: SignRequestSummary[];
|
||||
mySessions: SessionSummary[];
|
||||
}
|
||||
|
||||
/** The two lists the signing UI always needs together. */
|
||||
export async function fetchSigningSessions(): Promise<SigningSessions> {
|
||||
const [requests, sessions] = await Promise.all([
|
||||
apiClient.get<SignRequestSummary[]>(
|
||||
"/api/v1/security/cert-sign/sign-requests",
|
||||
),
|
||||
apiClient.get<SessionSummary[]>("/api/v1/security/cert-sign/sessions"),
|
||||
]);
|
||||
return { signRequests: requests.data, mySessions: sessions.data };
|
||||
}
|
||||
@@ -22,6 +22,7 @@ export interface QuickNavHostBridgeProps {
|
||||
requestNavigation?: (go: () => void) => void;
|
||||
onGoToDefaultState?: () => void;
|
||||
onSelectTool?: (toolId: ToolId) => void;
|
||||
activeTool?: ToolId | null;
|
||||
/** Merged over the reasons worked out here, for what only the app can see. */
|
||||
toolReasons?: QuickNavToolReasons;
|
||||
}
|
||||
@@ -34,6 +35,7 @@ export function QuickNavHostBridge({
|
||||
onOpenSettings,
|
||||
requestNavigation,
|
||||
onSelectTool,
|
||||
activeTool = null,
|
||||
onGoToDefaultState,
|
||||
toolReasons,
|
||||
}: QuickNavHostBridgeProps) {
|
||||
@@ -59,6 +61,7 @@ export function QuickNavHostBridge({
|
||||
signingBadge,
|
||||
portalAccess,
|
||||
readerMode,
|
||||
activeTool,
|
||||
notificationsOpen,
|
||||
toolReasons: mergedToolReasons,
|
||||
},
|
||||
|
||||
@@ -48,6 +48,8 @@ export function QuickNavRailHost() {
|
||||
else go(route);
|
||||
};
|
||||
|
||||
const openingTool = (id: ToolId) => ({ current: host?.activeTool === id });
|
||||
|
||||
const unusable = (id: ToolId) => {
|
||||
const reason = host?.toolReasons?.[id];
|
||||
return { disabled: Boolean(reason), reason };
|
||||
@@ -135,6 +137,7 @@ export function QuickNavRailHost() {
|
||||
icon: (
|
||||
<LocalIcon icon="rebase-outline-rounded" width={SIZE} height={SIZE} />
|
||||
),
|
||||
...openingTool("automate"),
|
||||
...unusable("automate"),
|
||||
onClick: () => openTool("automate", "/automate"),
|
||||
},
|
||||
@@ -146,6 +149,7 @@ export function QuickNavRailHost() {
|
||||
),
|
||||
badge: host?.signingBadge,
|
||||
badgeTone: "warning",
|
||||
...openingTool("sharedSign"),
|
||||
...unusable("sharedSign"),
|
||||
onClick: () => openTool("sharedSign", "/shared-sign"),
|
||||
},
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Stack,
|
||||
Text,
|
||||
Box,
|
||||
Group,
|
||||
Center,
|
||||
Alert,
|
||||
Checkbox,
|
||||
} from "@mantine/core";
|
||||
import { Stack, Text, Box, Group, Center, Checkbox } from "@mantine/core";
|
||||
import { ActionIcon } from "@app/ui/ActionIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import RestartAltIcon from "@mui/icons-material/RestartAlt";
|
||||
@@ -161,7 +153,6 @@ const CropSettings = ({ parameters, disabled = false }: CropSettingsProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
const isCropValid = parameters.isCropAreaValid(pdfBounds);
|
||||
const isFullCrop = parameters.isFullPDFCrop(pdfBounds);
|
||||
|
||||
return (
|
||||
@@ -239,18 +230,6 @@ const CropSettings = ({ parameters, disabled = false }: CropSettingsProps) => {
|
||||
showAutomationInfo={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Validation Alert - Only show when autoCrop is false */}
|
||||
{!parameters.parameters.autoCrop && !isCropValid && (
|
||||
<Alert color="red" variant="light">
|
||||
<Text size="xs">
|
||||
{t(
|
||||
"crop.error.invalidArea",
|
||||
"Crop area extends beyond PDF boundaries",
|
||||
)}
|
||||
</Text>
|
||||
</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user