diff options
12 files changed, 1066 insertions, 1 deletions
| 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 @@        <artifactId>spring-webmvc</artifactId>        <scope>provided</scope>      </dependency> +     +    <dependency> +      <groupId>com.fasterxml.jackson.dataformat</groupId> +      <artifactId>jackson-dataformat-yaml</artifactId> +      <scope>provided</scope> +    </dependency> +    <dependency> +      <groupId>com.fasterxml.jackson.datatype</groupId> +      <artifactId>jackson-datatype-jdk8</artifactId> +      <scope>provided</scope> +    </dependency>     +    <dependency> +      <groupId>com.fasterxml.jackson.datatype</groupId> +      <artifactId>jackson-datatype-jsr310</artifactId> +      <scope>provided</scope> +    </dependency>   +    <dependency> +      <groupId>com.fasterxml.jackson.module</groupId> +      <artifactId>jackson-module-parameter-names</artifactId> +      <scope>provided</scope> +    </dependency>       +          <dependency>        <groupId>org.slf4j</groupId>        <artifactId>slf4j-api</artifactId> @@ -98,6 +120,11 @@      <!-- For testing -->      <dependency> +      <groupId>org.mockito</groupId> +      <artifactId>mockito-junit-jupiter</artifactId> +      <scope>test</scope> +    </dependency>     +    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-test</artifactId>        <scope>test</scope> 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() { + +  } +} 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> T getServiceProviderConfiguration(final String arg0, final Class<T> 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<ch.qos.logback.classic.spi.ILoggingEvent> appender; +  @Captor +  ArgumentCaptor<ILoggingEvent> 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<String> valueCapture = ArgumentCaptor.forClass(String.class); +    doNothing().when(guiBuilder).putCustomParameter(any(), any(), valueCapture.capture()); + +    ArgumentCaptor<String> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" +  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +  xmlns:context="http://www.springframework.org/schema/context" +  xmlns:tx="http://www.springframework.org/schema/tx" +  xmlns:aop="http://www.springframework.org/schema/aop" +  xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd +    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd +    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd +    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> + +  <bean id="DummyAuthConfig" +        class="at.gv.egiz.eaaf.core.impl.idp.auth.dummy.DummyAuthConfig" /> + +  <bean id="errorTicketService" +        class="at.gv.egiz.eaaf.core.impl.idp.auth.service.JunitTicketErrorService"> +  </bean> + +  <bean id="junitStatusMessanger" +        class="at.gv.egiz.eaaf.core.impl.logging.JUnitTestStatusMessenger" /> + +  <bean id="simplePendingReqStrategy"  +        class="at.gv.egiz.eaaf.core.impl.utils.SimplePendingRequestIdGenerationStrategy"/> + +</beans> 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 @@ -91,6 +91,7 @@      <!-- jUnit testing -->      <surefire.version>2.22.2</surefire.version>      <junit-jupiter-api.version>5.8.2</junit-jupiter-api.version> +    <mockito-junit-jupiter.version>4.9.0</mockito-junit-jupiter.version>      <com.squareup.okhttp3.version>4.9.3</com.squareup.okhttp3.version>      <org.powermock.version>2.0.9</org.powermock.version> @@ -622,6 +623,26 @@          <artifactId>jackson-databind</artifactId>          <version>${com.fasterxml.jackson.databind.version}</version>        </dependency> +      <dependency> +        <groupId>com.fasterxml.jackson.dataformat</groupId> +        <artifactId>jackson-dataformat-yaml</artifactId> +        <version>${com.fasterxml.jackson.core.version}</version> +      </dependency> +      <dependency> +        <groupId>com.fasterxml.jackson.datatype</groupId> +        <artifactId>jackson-datatype-jdk8</artifactId> +        <version>${com.fasterxml.jackson.core.version}</version> +      </dependency> +      <dependency> +        <groupId>com.fasterxml.jackson.datatype</groupId> +        <artifactId>jackson-datatype-jsr310</artifactId> +        <version>${com.fasterxml.jackson.core.version}</version> +      </dependency>   +      <dependency> +        <groupId>com.fasterxml.jackson.module</groupId> +        <artifactId>jackson-module-parameter-names</artifactId> +        <version>${com.fasterxml.jackson.core.version}</version> +      </dependency>          <dependency>          <groupId>com.google.code.gson</groupId> @@ -693,6 +714,12 @@          <version>${org.powermock.version}</version>          <scope>test</scope>        </dependency>       +      <dependency> +        <groupId>org.mockito</groupId> +        <artifactId>mockito-junit-jupiter</artifactId> +        <version>${mockito-junit-jupiter.version}</version> +        <scope>test</scope> +      </dependency>             </dependencies>    </dependencyManagement>    <dependencies> @@ -707,7 +734,7 @@        <artifactId>junit-jupiter-migrationsupport</artifactId>        <version>${junit-jupiter-api.version}</version>        <scope>test</scope> -    </dependency>    +    </dependency>            <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId> | 
