From 20f485434680151111cf7cc7eaf33ca3b92221cb Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Tue, 29 Nov 2022 09:07:30 +0100 Subject: feat(core): add ticket-based error-handling service as EAAF core functionality --- eaaf_core/pom.xml | 27 ++ .../gv/egiz/eaaf/core/impl/data/ErrorConfig.java | 65 +++++ .../impl/idp/auth/services/TicketErrorService.java | 234 ++++++++++++++++++ .../eaaf/core/impl/utils/DefaultJsonMapper.java | 106 ++++++++ .../eaaf/core/impl/utils/DefaultYamlMapper.java | 38 +++ .../core/impl/idp/auth/dummy/DummyAuthConfig.java | 100 ++++++++ .../impl/idp/auth/dummy/DummyPendingRequest.java | 10 + .../idp/auth/service/JunitTicketErrorService.java | 21 ++ .../idp/auth/service/TicketErrorServiceTest.java | 272 +++++++++++++++++++++ .../SpringTest-authcommon-errorService.xml | 25 ++ .../src/test/resources/config/error_conf.yaml | 140 +++++++++++ 11 files changed, 1038 insertions(+) create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/data/ErrorConfig.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/services/TicketErrorService.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultJsonMapper.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/DefaultYamlMapper.java create mode 100644 eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/dummy/DummyAuthConfig.java create mode 100644 eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/dummy/DummyPendingRequest.java create mode 100644 eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/service/JunitTicketErrorService.java create mode 100644 eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/service/TicketErrorServiceTest.java create mode 100644 eaaf_core/src/test/resources/SpringTest-authcommon-errorService.xml create mode 100644 eaaf_core/src/test/resources/config/error_conf.yaml (limited to 'eaaf_core') diff --git a/eaaf_core/pom.xml b/eaaf_core/pom.xml index d74bd9c2..89041b00 100644 --- a/eaaf_core/pom.xml +++ b/eaaf_core/pom.xml @@ -41,6 +41,28 @@ spring-webmvc provided + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + provided + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + provided + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + provided + + + com.fasterxml.jackson.module + jackson-module-parameter-names + provided + + org.slf4j slf4j-api @@ -97,6 +119,11 @@ + + org.mockito + mockito-junit-jupiter + test + org.springframework spring-test 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 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 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 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 Object deserialize(final String value, final Class 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() { + + } +} diff --git a/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/dummy/DummyAuthConfig.java b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/dummy/DummyAuthConfig.java new file mode 100644 index 00000000..03f5b759 --- /dev/null +++ b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/dummy/DummyAuthConfig.java @@ -0,0 +1,100 @@ +package at.gv.egiz.eaaf.core.impl.idp.auth.dummy; + +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import org.apache.commons.lang3.StringUtils; + +import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP; +import at.gv.egiz.eaaf.core.api.idp.IExtendedConfiguration; +import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.idp.conf.AbstractSpringBootConfigurationImpl; + + +public class DummyAuthConfig extends AbstractSpringBootConfigurationImpl + implements IConfigurationWithSP, IExtendedConfiguration { + + private ISpConfiguration spconfig = null; + + private String configPropPrefix = StringUtils.EMPTY; + + private boolean useConfigRootDirFromConfig = false; + + private URI internalConfigRootDirHolder = null; + + public void setSpConfig(final ISpConfiguration spConfig) { + this.spconfig = spConfig; + + } + + + + @Override + public URI getConfigurationRootDirectory() { + if (useConfigRootDirFromConfig) { + if (internalConfigRootDirHolder == null) { + try { + internalConfigRootDirHolder = new URI(getBasicConfiguration("core.configRootDir")); + } catch (final URISyntaxException e) { + e.printStackTrace(); + + } + } + return internalConfigRootDirHolder; + + } else { + return new java.io.File(".").toURI(); + + } + } + + @Override + public ISpConfiguration getServiceProviderConfiguration(final String arg0) + throws EaafConfigurationException { + return spconfig; + } + + @SuppressWarnings("unchecked") + @Override + public T getServiceProviderConfiguration(final String arg0, final Class arg1) + throws EaafConfigurationException { + return (T) spconfig; + + } + + @Override + public String validateIdpUrl(final URL arg0) throws EaafException { + return arg0.toString(); + } + + + @Override + protected String getBackupConfigPath() { + return null; + } + + + @Override + public String getApplicationSpecificKeyPrefix() { + return configPropPrefix; + + } + + + + public void setConfigPropPrefix(final String configPropPrefix) { + this.configPropPrefix = configPropPrefix; + } + + + + public void setUseConfigRootDirFromConfig(boolean useConfigRootDirFromConfig) { + this.useConfigRootDirFromConfig = useConfigRootDirFromConfig; + } + + + +} diff --git a/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/dummy/DummyPendingRequest.java b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/dummy/DummyPendingRequest.java new file mode 100644 index 00000000..1a18ada2 --- /dev/null +++ b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/dummy/DummyPendingRequest.java @@ -0,0 +1,10 @@ +package at.gv.egiz.eaaf.core.impl.idp.auth.dummy; + +import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; + +public class DummyPendingRequest extends RequestImpl { + + + private static final long serialVersionUID = 8136280395622411505L; + +} diff --git a/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/service/JunitTicketErrorService.java b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/service/JunitTicketErrorService.java new file mode 100644 index 00000000..a8ccc54a --- /dev/null +++ b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/service/JunitTicketErrorService.java @@ -0,0 +1,21 @@ +package at.gv.egiz.eaaf.core.impl.idp.auth.service; + +import org.apache.commons.lang3.RandomStringUtils; + +import at.gv.egiz.eaaf.core.impl.idp.auth.services.TicketErrorService; + +/** + * Ticket based error-handling service for jUnit tests. + * + * @author tlenz + * + */ +public class JunitTicketErrorService extends TicketErrorService { + + @Override + protected String generateSupportTicket() { + return RandomStringUtils.randomAlphabetic(10); + + } + +} diff --git a/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/service/TicketErrorServiceTest.java b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/service/TicketErrorServiceTest.java new file mode 100644 index 00000000..2c89e49f --- /dev/null +++ b/eaaf_core/src/test/java/at/gv/egiz/eaaf/core/impl/idp/auth/service/TicketErrorServiceTest.java @@ -0,0 +1,272 @@ +package at.gv.egiz.eaaf.core.impl.idp.auth.service; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; + +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import at.gv.egiz.eaaf.core.api.gui.ModifyableGuiBuilderConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.dummy.DummyPendingRequest; +import at.gv.egiz.eaaf.core.impl.idp.auth.services.IErrorService; +import at.gv.egiz.eaaf.core.impl.idp.auth.services.IErrorService.LogLevel; +import ch.qos.logback.classic.spi.ILoggingEvent; +import lombok.val; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("/SpringTest-authcommon-errorService.xml") +@TestPropertySource( + properties = {"core.error.handling.config=src/test/resources/config/error_conf.yaml"}) +public class TicketErrorServiceTest { + @Autowired + private IErrorService ticketErrorService; + + @Mock + ch.qos.logback.core.AppenderBase appender; + @Captor + ArgumentCaptor logCaptor; + + + /** + * Setup jUnit test. + */ + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + + } + + @Test + public void cofigurationLodingAndExternalCodeMapping() { + String external = ticketErrorService.getExternalCodeFromInternal("auth.01"); + + Assert.assertNotNull(external); + Assert.assertEquals(external, "9199"); + } + + @Test + public void coverDifferentExceptions() throws EaafException { + + val pendingReq = new DummyPendingRequest(); + pendingReq.setPendingRequestId("324"); + + val errorData = ticketErrorService.createHandleData(new Exception("sl20.07.02"), true); + val errorData1 = ticketErrorService.createHandleData( + new EaafException("sl20.07.02", new Object[]{"dummy"}), false); + val errorData2 = ticketErrorService.createHandleData( + new TaskExecutionException(pendingReq, "dummy", new EaafException("auth.00", new Object[]{"dummy"})), false); + val errorData3 = ticketErrorService.createHandleData(new EaafException("auth.21", null), false); + val errorData4 = ticketErrorService.createHandleData(new EaafException("internal.pendingreqid.01", null), false); + val errorData5 = ticketErrorService.createHandleData(new EaafException("junit.01", null), false); + + Assert.assertNotNull(errorData); + Assert.assertEquals(errorData.getActionType(), IErrorService.ActionType.TICKET); + Assert.assertEquals(errorData.getInternalErrorCode(), "internal.00"); + Assert.assertEquals(errorData.getLogLevel(), IErrorService.LogLevel.ERROR); + Assert.assertEquals(ticketErrorService.getExternalCodeFromInternal(errorData.getInternalErrorCode()), "9199"); + Assert.assertNotNull(errorData.getErrorIdTokenForRedirect()); + + Assert.assertNotNull(errorData1); + Assert.assertEquals(errorData1.getActionType(), IErrorService.ActionType.NO_TICKET); + Assert.assertEquals(errorData1.getInternalErrorCode(), "sl20.07.02"); + Assert.assertEquals(errorData1.getLogLevel(), IErrorService.LogLevel.WARN); + Assert.assertEquals(ticketErrorService.getExternalCodeFromInternal(errorData1.getInternalErrorCode()), "1003"); + Assert.assertNull(errorData1.getErrorIdTokenForRedirect()); + + Assert.assertNotNull(errorData2); + Assert.assertEquals(errorData2.getActionType(), IErrorService.ActionType.TICKET); + Assert.assertEquals(errorData2.getInternalErrorCode(), "auth.00"); + Assert.assertEquals(errorData2.getLogLevel(), IErrorService.LogLevel.ERROR); + Assert.assertEquals(ticketErrorService.getExternalCodeFromInternal(errorData2.getInternalErrorCode()), "9199"); + + Assert.assertNotNull(errorData3); + Assert.assertEquals(errorData3.getActionType(), IErrorService.ActionType.NO_TICKET); + Assert.assertEquals(errorData3.getInternalErrorCode(), "auth.21"); + Assert.assertEquals(errorData3.getLogLevel(), IErrorService.LogLevel.INFO); + Assert.assertEquals(ticketErrorService.getExternalCodeFromInternal(errorData3.getInternalErrorCode()), "1005"); + + Assert.assertNotNull(errorData4); + Assert.assertEquals(errorData4.getActionType(), IErrorService.ActionType.TICKET); + Assert.assertEquals(errorData4.getInternalErrorCode(), "internal.pendingreqid.01"); + Assert.assertEquals(errorData4.getLogLevel(), IErrorService.LogLevel.WARN); + Assert.assertEquals(ticketErrorService.getExternalCodeFromInternal(errorData4.getInternalErrorCode()), + "9199"); + + Assert.assertNotNull(errorData5); + Assert.assertEquals(errorData5.getActionType(), IErrorService.ActionType.ERRORPAGE); + Assert.assertEquals(errorData5.getInternalErrorCode(), "junit.01"); + Assert.assertEquals(errorData5.getLogLevel(), IErrorService.LogLevel.ERROR); + Assert.assertEquals(ticketErrorService.getExternalCodeFromInternal(errorData5.getInternalErrorCode()), + "junit.01"); + + } + + @Test + public void handleDataTicketNoRedirect() { + val logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("at.gv.egiz.eaaf.core"); + logger.addAppender(appender); + + IErrorService.IHandleData errorData = null; + try { + errorData = ticketErrorService.createHandleData(new EaafException("auth.00", new Object[]{"dummy"}), false); + } catch (EaafException e) { + e.printStackTrace(); + } + + Assert.assertNotNull(errorData); + Assert.assertEquals(errorData.getActionType(), IErrorService.ActionType.TICKET); + // Assert.assertEquals(errorData.getThrowable(), new EaafException("auth.00", new Object[] {"dummy"})); //TODO + // wrong excepiton + Assert.assertEquals(errorData.getInternalErrorCode(), "auth.00"); + + assertEquals("wrong errorLevel", LogLevel.ERROR, errorData.getLogLevel()); + assertTrue("ticket", errorData.getPreFormatedErrorMessage().contains("Ticket=")); + + } + + @Test + public void handleDataNoTicketNoRedirect() { + val logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("at.gv.egiz.eaaf.core"); + logger.addAppender(appender); + + + IErrorService.IHandleData errorData = null; + try { + errorData = ticketErrorService.createHandleData( + new EaafException("internal.pendingreqid.00", new Object[]{"dummy"}), false); + } catch (EaafException e) { + e.printStackTrace(); + } + + Assert.assertNotNull(errorData); + Assert.assertEquals(errorData.getActionType(), IErrorService.ActionType.NO_TICKET); + // Assert.assertEquals(errorData.getThrowable(), new EaafException("auth.00", new Object[] {"dummy"})); //TODO + // wrong excepiton + Assert.assertEquals(errorData.getInternalErrorCode(), "internal.pendingreqid.00"); + + assertEquals("wrong errorLevel", LogLevel.WARN, errorData.getLogLevel()); + assertFalse("ticket", errorData.getPreFormatedErrorMessage().contains("Ticket=")); + } + + @Test + public void handleDataTicketRedirect() { + val logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("at.gv.egiz.eaaf.core"); + logger.addAppender(appender); + + IErrorService.IHandleData errorData = null; + try { + errorData = ticketErrorService.createHandleData(new EaafException("auth.01", new Object[]{"dummy"}), true); + } catch (EaafException e) { + e.printStackTrace(); + } + + Assert.assertNotNull(errorData); + Assert.assertEquals(IErrorService.ActionType.TICKET, errorData.getActionType()); + // Assert.assertEquals(errorData.getThrowable(), new EaafException("auth.00", new Object[] {"dummy"})); //TODO + // wrong excepiton + Assert.assertEquals(errorData.getInternalErrorCode(), "auth.01"); + + assertEquals("wrong errorLevel", LogLevel.ERROR, errorData.getLogLevel()); + assertTrue("ticket", errorData.getPreFormatedErrorMessage().contains("Ticket=")); + } + + @Test + public void handleDataNoTicketRedirect() { + val logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("at.gv.egiz.eaaf.core"); + logger.addAppender(appender); + + IErrorService.IHandleData errorData = null; + try { + errorData = ticketErrorService + .createHandleData(new EaafException("module.binding.14", new Object[]{"dummy"}), false); + } catch (EaafException e) { + e.printStackTrace(); + } + + Assert.assertNotNull(errorData); + Assert.assertEquals(IErrorService.ActionType.NO_TICKET, errorData.getActionType()); + // Assert.assertEquals(errorData.getThrowable(), new EaafException("auth.00", new Object[] {"dummy"})); //TODO + // wrong excepiton + Assert.assertEquals(errorData.getInternalErrorCode(), "module.binding.14"); + + assertEquals("wrong errorLevel", LogLevel.WARN, errorData.getLogLevel()); + assertFalse("ticket", errorData.getPreFormatedErrorMessage().contains("Ticket=")); + } + + @Test + public void handleDataNoTicketAutoRedirect() { + val logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("at.gv.egiz.eaaf.core"); + logger.addAppender(appender); + + IErrorService.IHandleData errorData = null; + try { + errorData = ticketErrorService.createHandleData(new EaafException("auth.21", new Object[]{"dummy"}), true); + } catch (EaafException e) { + e.printStackTrace(); + } + + Assert.assertNotNull(errorData); + Assert.assertEquals(IErrorService.ActionType.NO_TICKET, errorData.getActionType()); + // Assert.assertEquals(errorData.getThrowable(), new EaafException("auth.00", new Object[] {"dummy"})); //TODO + // wrong excepiton + Assert.assertEquals(errorData.getInternalErrorCode(), "auth.21"); + + assertEquals("wrong errorLevel", LogLevel.INFO, errorData.getLogLevel()); + assertFalse("ticket", errorData.getPreFormatedErrorMessage().contains("Ticket=")); + } + + @Test + public void testErrorDataDisplay() throws EaafException { + + IErrorService.IHandleData errorData = null; + errorData = ticketErrorService.createHandleData(new EaafException("auth.01", new Object[]{"dummy"}), true); + Assert.assertNotNull(errorData); + + val guiBuilder = Mockito.mock(ModifyableGuiBuilderConfiguration.class); + ArgumentCaptor valueCapture = ArgumentCaptor.forClass(String.class); + doNothing().when(guiBuilder).putCustomParameter(any(), any(), valueCapture.capture()); + + ArgumentCaptor valueCapture2 = ArgumentCaptor.forClass(String.class); + doNothing().when(guiBuilder).putCustomParameterWithOutEscaption(any(), any(), valueCapture2.capture()); + + ticketErrorService.displayErrorData(guiBuilder, errorData, getMocReq()); + Assert.assertTrue("Incorrect Ticketvalue", + Pattern.matches("[A-Za-z0-9]{10}", valueCapture.getAllValues().get(0))); + Assert.assertEquals( + "http://iaik.dummy/blub/public/secure/errorRedirect?errorid=" + errorData.getErrorIdTokenForRedirect(), + valueCapture2.getAllValues().get(0)); + } + + private HttpServletRequest getMocReq() { + val req = Mockito.mock(HttpServletRequest.class); + when(req.getServerPort()).thenReturn(80); + when(req.getScheme()).thenReturn("http"); + when(req.getServerName()).thenReturn("iaik.dummy"); + when(req.getContextPath()).thenReturn("/blub"); + return req; + + } + +} diff --git a/eaaf_core/src/test/resources/SpringTest-authcommon-errorService.xml b/eaaf_core/src/test/resources/SpringTest-authcommon-errorService.xml new file mode 100644 index 00000000..35bd9d6f --- /dev/null +++ b/eaaf_core/src/test/resources/SpringTest-authcommon-errorService.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/eaaf_core/src/test/resources/config/error_conf.yaml b/eaaf_core/src/test/resources/config/error_conf.yaml new file mode 100644 index 00000000..0e204e97 --- /dev/null +++ b/eaaf_core/src/test/resources/config/error_conf.yaml @@ -0,0 +1,140 @@ +## If no externalCode specified -> 9199 is used + +## internal errors with tickets ## +- action: ticket + externalCode: 9199 + logLevel: ERROR + defaultConfig: True + +- action: ticket + externalCode: 1002 + logLevel: WARN + internalCode: + - auth.39 + - auth.40 + - auth.41 + - auth.42 + - builder.08 + - builder.11 + - builder.13 + - builder.15 + - builder.16 + - builder.17 + +- action: ticket + externalCode: 1003 + logLevel: WARN + internalCode: + - sl20.07.00 + - sl20.07.01 + - sl20.07.04 + - sl20.07.05 + - sl20.07.99 + +- action: ticket + externalCode: 1200 + logLevel: WARN + internalCode: + - module.binding.16 + - module.binding.21 + - module.binding.32 + +- action: ticket + externalCode: 9199 + logLevel: WARN + internalCode: + - internal.pendingreqid.01 + - internal.pendingreqid.03 + - internal.pendingreqid.04 + - internal.pendingreqid.05 + - internal.token.01 + - internal.token.04 + - internal.token.08 + - internal.token.09 + - internal.token.10 + +- action: ticket + useInternalAsExternal: true + logLevel: ERROR + internalCode: + - config.06 + +## internal errors without tickets ## +- action: no_ticket + externalCode: 1001 + logLevel: INFO + internalCode: + - internal.pendingreqid.06 + - internal.token.11 + +- action: no_ticket + externalCode: 1003 + logLevel: WARN + internalCode: + - sl20.07.02 + - sl20.07.03 + +- action: no_ticket + externalCode: 1005 + internalCode: + - auth.21 + logLevel: INFO + +- action: no_ticket + externalCode: 1201 + logLevel: WARN + internalCode: + - module.binding.17 + - module.binding.19 + - module.binding.15 + +- action: no_ticket + externalCode: 1200 + logLevel: info + internalCode: + - module.binding.33 + - module.binding.34 + - module.binding.35 + +- action: no_ticket + externalCode: 9199 + logLevel: WARN + internalCode: + - module.binding.14 + - module.binding.25 + - module.binding.29 + - module.binding.30 + - module.eidasauth.06 + - module.mis.04 + - module.mis.05 + - module.mis.06 + - module.mis.07 + - module.mis.08 + - module.mis.vemo.01 + - module.mis.vemo.02 + - module.mis.vemo.03 + - module.mis.vemo.gui.01 + - auth.eidbind.02 + - internal.pendingreqid.00 + - module.binding.04 + - module.binding.05 + - shibboleth.00 + +- action: no_ticket + externalCode: 9199 + internalCode: + - module.binding.31 + - module.binding.36 + logLevel: INFO + +- action: no_ticket + useInternalAsExternal: true + logLevel: ERROR + internalCode: + - auth.28 + - auth.26 +- action: errorpage + useInternalAsExternal: true + logLevel: ERROR + internalCode: + - junit.01 \ No newline at end of file -- cgit v1.2.3