summaryrefslogtreecommitdiff
path: root/eaaf_core/src/main/java
diff options
context:
space:
mode:
authorThomas <>2022-11-29 09:07:30 +0100
committerThomas <>2022-11-29 09:07:30 +0100
commit20f485434680151111cf7cc7eaf33ca3b92221cb (patch)
tree0bb3460ecdf55324ff2c4c7dc8a84325c6f716ee /eaaf_core/src/main/java
parent49d02d8c7c19ab9bb763d3f7fe862273706cc73a (diff)
downloadEAAF-Components-20f485434680151111cf7cc7eaf33ca3b92221cb.tar.gz
EAAF-Components-20f485434680151111cf7cc7eaf33ca3b92221cb.tar.bz2
EAAF-Components-20f485434680151111cf7cc7eaf33ca3b92221cb.zip
feat(core): add ticket-based error-handling service as EAAF core functionality
Diffstat (limited to 'eaaf_core/src/main/java')
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/data/ErrorConfig.java65
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/TicketErrorService.java234
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java106
-rw-r--r--eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultYamlMapper.java38
4 files changed, 443 insertions, 0 deletions
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/data/ErrorConfig.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/data/ErrorConfig.java
new file mode 100644
index 00000000..b9cacb1c
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/data/ErrorConfig.java
@@ -0,0 +1,65 @@
+package at.gv.egiz.eaaf.core.impl.data;
+
+import java.util.List;
+
+import at.gv.egiz.eaaf.core.impl.idp.auth.services.IErrorService.ActionType;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+
+@ToString
+@Builder
+@Slf4j
+@NoArgsConstructor
+@AllArgsConstructor
+public class ErrorConfig {
+
+ @Getter
+ @Setter
+ private String action;
+
+ @Getter
+ @Setter
+ private String externalCode;
+
+ @Getter
+ @Setter
+ private String logLevel;
+
+
+ @Getter
+ @Setter
+ private List<String> internalCode;
+
+ @Getter
+ @Setter
+ private Boolean defaultConfig;
+
+ @Getter
+ @Setter
+ @Builder.Default
+ private Boolean useInternalAsExternal = false;
+
+ /**
+ * Get type of error-handling flow.
+ *
+ * @return flow type
+ */
+ public ActionType getActionType() {
+ ActionType actionType = ActionType.fromString(action);
+ if (actionType == null) {
+ log.warn("Find unsupported Error-Handling-Flow: {}. Use: {} as backup",
+ action, ActionType.TICKET);
+ actionType = ActionType.TICKET;
+
+ }
+
+ return actionType;
+
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/TicketErrorService.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/TicketErrorService.java
new file mode 100644
index 00000000..557614e6
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/TicketErrorService.java
@@ -0,0 +1,234 @@
+package at.gv.egiz.eaaf.core.impl.idp.auth.services;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.StringEscapeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+import at.gv.egiz.eaaf.core.api.IStatusMessenger;
+import at.gv.egiz.eaaf.core.api.data.EaafConstants;
+import at.gv.egiz.eaaf.core.api.gui.ModifyableGuiBuilderConfiguration;
+import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
+import at.gv.egiz.eaaf.core.api.utils.IPendingRequestIdGenerationStrategy;
+import at.gv.egiz.eaaf.core.exceptions.EaafException;
+import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;
+import at.gv.egiz.eaaf.core.impl.data.ErrorConfig;
+import at.gv.egiz.eaaf.core.impl.gui.AbstractGuiFormBuilderConfiguration;
+import at.gv.egiz.eaaf.core.impl.idp.controller.ProtocolFinalizationController;
+import at.gv.egiz.eaaf.core.impl.utils.DefaultYamlMapper;
+import at.gv.egiz.eaaf.core.impl.utils.FileUtils;
+import at.gv.egiz.eaaf.core.impl.utils.ServletUtils;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.var;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public abstract class TicketErrorService implements IErrorService {
+ private static final String CONFIG_PROP_ERROR_HANDLING_CONFIG_PATH = "core.error.handling.config";
+ private static final String TECH_LOG_MSG = "errorCode={0} Message={1}";
+ private static final String TICKET_LOG_MSG = "Ticket={2} errorCode={0} Message={1}";
+
+ private final List<ErrorConfig> errorConfigList = new ArrayList<>();
+
+
+ @Autowired
+ IConfiguration basicConfig;
+
+ @Autowired
+ ResourceLoader resourceLoader;
+
+ @Autowired
+ IPendingRequestIdGenerationStrategy requestIdValidationStragegy;
+
+ @Override
+ public String getExternalCodeFromInternal(String internalCode) {
+ ErrorConfig errorConfig = findByInternalCode(internalCode);
+ return StringUtils.isNotEmpty(errorConfig.getExternalCode())
+ ? errorConfig.getExternalCode()
+ : errorConfig.getUseInternalAsExternal()
+ ? internalCode
+ : IStatusMessenger.CODES_EXTERNAL_ERROR_GENERIC;
+
+ }
+
+ @Override
+ public IHandleData createHandleData(Throwable throwable, boolean supportRedirctToSp) throws EaafException {
+ String internalErrorId = extractInternalErrorCode(throwable);
+ ErrorConfig errorFlowConfig = findByInternalCode(internalErrorId);
+ ActionType errorHandlingFlow = errorFlowConfig.getActionType();
+
+ return HandleData.builder()
+ .throwable(throwable)
+ .internalErrorCode(internalErrorId)
+ .actionType(errorHandlingFlow)
+ .logLevel(LogLevel.fromString(errorFlowConfig.getLogLevel()))
+ .supportTicket(ActionType.TICKET.equals(errorHandlingFlow) ? generateSupportTicket() : null)
+ .errorIdTokenForRedirect(
+ supportRedirctToSp ? requestIdValidationStragegy.generateExternalPendingRequestId() : null)
+ .build();
+
+ }
+
+ @Override
+ public void displayErrorData(ModifyableGuiBuilderConfiguration c, IErrorService.IHandleData errorData,
+ HttpServletRequest httpReq) throws EaafException {
+ if (!(errorData instanceof TicketErrorService.HandleData)) {
+ throw new EaafException(IStatusMessenger.CODES_INTERNAL_ERROR_GENERIC);
+ }
+ var ed = (TicketErrorService.HandleData) errorData;
+
+ // set SupportTicket
+ c.putCustomParameter(AbstractGuiFormBuilderConfiguration.PARAM_GROUP_MSG, PARAM_GUI_TICKET, ed.getSupportTicket());
+
+ // set redirect to SP path
+ if (StringUtils.isNotEmpty(ed.getErrorIdTokenForRedirect())) {
+ c.putCustomParameterWithOutEscaption(
+ AbstractGuiFormBuilderConfiguration.PARAM_GROUP_MSG, PARAM_GUI_REDIRECT,
+ generateRedirect(httpReq, ed.getErrorIdTokenForRedirect()));
+
+ }
+ }
+
+ /**
+ * Generate a application-specific support-ticket.
+ *
+ * @return Support ticket for error screen
+ */
+ protected abstract String generateSupportTicket();
+
+
+ @Nonnull
+ private ErrorConfig findByInternalCode(@Nonnull String seekedInternalCode) {
+ return errorConfigList.stream()
+ .filter(c -> c.getInternalCode() != null && c.getInternalCode().contains(seekedInternalCode))
+ .findFirst()
+ .orElse(
+ errorConfigList.stream()
+ .filter(c -> c.getDefaultConfig() != null && c.getDefaultConfig().equals(true))
+ .findFirst()
+ .orElse(ErrorConfig.builder()
+ .action(ActionType.TICKET.toString())
+ .externalCode(IStatusMessenger.CODES_INTERNAL_ERROR_GENERIC)
+ .logLevel("ERROR")
+ .defaultConfig(true)
+ .build())
+ );
+
+ }
+
+ private String extractInternalErrorCode(Throwable throwable) {
+ Throwable originalException;
+ if (throwable instanceof TaskExecutionException
+ && ((TaskExecutionException) throwable).getOriginalException() != null) {
+ originalException = ((TaskExecutionException) throwable).getOriginalException();
+
+ } else {
+ originalException = throwable;
+
+ }
+
+ if (!(originalException instanceof EaafException)) {
+ return IStatusMessenger.CODES_INTERNAL_ERROR_GENERIC;
+
+ } else {
+ return ((EaafException) originalException).getErrorId();
+
+ }
+ }
+
+ private String generateRedirect(HttpServletRequest httpReq, String errorTokenId) {
+ String redirectUrl = ServletUtils.getBaseUrl(httpReq);
+ redirectUrl += ProtocolFinalizationController.ENDPOINT_ERROR_REDIRECT + "?"
+ + EaafConstants.PARAM_HTTP_ERROR_CODE + "=" + StringEscapeUtils
+ .escapeHtml4(errorTokenId);
+ return redirectUrl;
+
+ }
+
+ @PostConstruct
+ private void initialize() throws EaafException {
+ final String errorConfPath = basicConfig.getBasicConfiguration(CONFIG_PROP_ERROR_HANDLING_CONFIG_PATH);
+ log.info("Initializing error-handling service from configuration: {}", errorConfPath);
+
+ if (StringUtils.isEmpty(errorConfPath)) {
+ log.error("Error: Path to error handling config is not known");
+ throw new EaafException("internal.configuration.00", new Object[]{CONFIG_PROP_ERROR_HANDLING_CONFIG_PATH});
+ }
+
+ try {
+ final byte[] raw = readFromFile(errorConfPath);
+ ObjectMapper mapper = DefaultYamlMapper.getYamlMapper();
+ final TypeFactory typeFactory = mapper.getTypeFactory();
+ final CollectionType javaType = typeFactory.constructCollectionType(List.class, ErrorConfig.class);
+ errorConfigList.addAll(mapper.readValue(raw, javaType));
+
+ log.info("Found #{} configuration-elements for Error Handling", errorConfigList.size());
+
+ } catch (Exception e) {
+ log.error("Error reading Configurations file", e);
+ throw new EaafException("internal.configuration.01",
+ new Object[]{CONFIG_PROP_ERROR_HANDLING_CONFIG_PATH, "Error reading Configurations file"});
+ }
+
+ }
+
+ private byte[] readFromFile(final String filePath) throws URISyntaxException, IOException {
+ final String fullFilePath = FileUtils.makeAbsoluteUrl(filePath, basicConfig.getConfigurationRootDirectory());
+ final Resource ressource = resourceLoader.getResource(fullFilePath);
+ final InputStream is = ressource.getInputStream();
+ final byte[] result = IOUtils.toByteArray(is);
+ is.close();
+ return result;
+ }
+
+ @Builder
+ static class HandleData implements IHandleData {
+
+ @Getter
+ private String errorIdTokenForRedirect;
+
+ @Getter
+ private String supportTicket;
+
+ @Getter
+ private final Throwable throwable;
+
+ @Getter
+ private String internalErrorCode;
+
+ @Getter
+ private ActionType actionType;
+
+ @Getter
+ private LogLevel logLevel;
+
+ public String getPreFormatedErrorMessage() {
+ if (supportTicket != null) {
+ return MessageFormat.format(TICKET_LOG_MSG, internalErrorCode, throwable.getMessage(), supportTicket);
+
+ } else {
+ return MessageFormat.format(TECH_LOG_MSG, internalErrorCode, throwable.getMessage());
+
+ }
+ }
+ }
+
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java
new file mode 100644
index 00000000..8303e860
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java
@@ -0,0 +1,106 @@
+package at.gv.egiz.eaaf.core.impl.utils;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+import at.gv.egiz.eaaf.core.exceptions.EaafJsonMapperException;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Holder for Jackson JSON Mapper that sets some security features.
+ *
+ * @author tlenz
+ *
+ */
+@Slf4j
+public final class DefaultJsonMapper {
+
+ @Getter
+ private static final ObjectMapper jsonMapper = new ObjectMapper();
+
+ static {
+ // initialize JSON Mapper
+ jsonMapper.configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true);
+ jsonMapper.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true);
+ jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
+ jsonMapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
+ jsonMapper.setVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY);
+ jsonMapper.setVisibility(PropertyAccessor.IS_GETTER, Visibility.PUBLIC_ONLY);
+
+ jsonMapper.registerModule(new JavaTimeModule());
+ jsonMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+
+ }
+
+ /**
+ * private constructor for class with only static methods.
+ */
+ private DefaultJsonMapper() {
+
+ }
+
+ /**
+ * Serialize an object to a JSON string.
+ *
+ * @param value the object to serialize
+ * @return a JSON string
+ * @throws JsonProcessingException thrown when an error occurs during
+ * serialization
+ */
+ public static String serialize(final Object value) throws EaafJsonMapperException {
+ try {
+ return jsonMapper.writeValueAsString(value);
+
+ } catch (final JsonProcessingException e) {
+ log.warn("JSON mapping FAILED with error: {}", e.getMessage());
+ throw new EaafJsonMapperException(e.getMessage(), e);
+
+ }
+ }
+
+ /**
+ * Deserialize a JSON string.
+ *
+ * @param value the JSON to deserialize as {@link String}
+ * @param clazz optional parameter that determines the type of the returned
+ * object. If not set, an {@link Object} is returned.
+ * @param <T> Response class type
+ * @return the deserialized JSON string as an object of type {@code clazz} or
+ * {@link Object}
+ * @throws JsonMappingException if the input JSON structure does not match
+ * structure expected for result type
+ */
+ public static <T> Object deserialize(final String value, final Class<T> clazz)
+ throws EaafJsonMapperException {
+ try {
+ if (clazz != null) {
+ if (clazz.isAssignableFrom(TypeReference.class)) {
+ return jsonMapper.readValue(value, clazz);
+ } else {
+ final JavaType javaType = TypeFactory.defaultInstance().constructType(clazz);
+ return jsonMapper.readValue(value, javaType);
+ }
+ } else {
+ return jsonMapper.readValue(value, Object.class);
+ }
+
+ } catch (final IOException e) {
+ log.warn("JSON mapping FAILED with error: {}", e.getMessage());
+ throw new EaafJsonMapperException(e.getMessage(), e);
+
+ }
+
+ }
+}
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultYamlMapper.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultYamlMapper.java
new file mode 100644
index 00000000..6e8c4540
--- /dev/null
+++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultYamlMapper.java
@@ -0,0 +1,38 @@
+package at.gv.egiz.eaaf.core.impl.utils;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import lombok.Getter;
+
+/**
+ * Holder for Jackson JSON Mapper that sets some security features.
+ *
+ * @author tlenz
+ *
+ */
+public final class DefaultYamlMapper {
+
+ @Getter
+ private static final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
+
+ static {
+ // initialize JSON Mapper
+ yamlMapper.configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true);
+ yamlMapper.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true);
+ yamlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
+ yamlMapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
+ yamlMapper.setVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY);
+ yamlMapper.setVisibility(PropertyAccessor.IS_GETTER, Visibility.PUBLIC_ONLY);
+
+ }
+
+ /**
+ * private constructor for class with only static methods.
+ */
+ private DefaultYamlMapper() {
+
+ }
+}