Skip to content
Snippets Groups Projects
Commit 742ea9c7 authored by Henry Borasch's avatar Henry Borasch
Browse files

Merge branch 'main' into 978_remove_spring-web

# Conflicts:
#	api/src/main/java/dev/fitko/fitconnect/api/config/ApplicationConfig.java
#	api/src/main/java/dev/fitko/fitconnect/api/config/ResourcePaths.java
#	client/src/main/java/dev/fitko/fitconnect/client/bootstrap/ClientFactory.java
#	core/src/main/java/dev/fitko/fitconnect/core/events/EventLogApiService.java
#	core/src/main/java/dev/fitko/fitconnect/core/http/RestService.java
#	core/src/main/java/dev/fitko/fitconnect/core/http/UserAgentInterceptor.java
#	core/src/main/java/dev/fitko/fitconnect/core/schema/SchemaResourceProvider.java
#	core/src/main/java/dev/fitko/fitconnect/core/submission/SubmissionApiService.java
#	core/src/test/java/dev/fitko/fitconnect/core/http/ProxyRestTemplateTest.java
#	core/src/test/java/dev/fitko/fitconnect/core/http/RestServiceTest.java
#	core/src/test/java/dev/fitko/fitconnect/core/http/RestTemplateTest.java
#	integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/AuthenticationIT.java
#	integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/SubscriberClientIT.java
#	pom.xml
parents 9060c4e3 87b8f41e
No related branches found
No related tags found
1 merge request!169978: remove spring-web
Showing
with 195 additions and 155 deletions
......@@ -84,6 +84,7 @@ See the projects' module documentation for more specific information:
* [API Module ](api/README.md)
* [Core Module ](core/README.md)
* [Client Module ](client/README.md)
* [Integration-Test Module ](integration-tests/README.md)
## Setup
......@@ -110,20 +111,20 @@ _The following steps show how to get the SDK running_
````java
final ApplicationConfig config = ApplicationConfigLoader.loadConfigFromPath(Path.of("path/to/config.yml"));
````
7. Afterwards the config is used to initialize clients with the `ClientFactory` that offer clients for sender, subscriber and routing:
7. Afterwards the config is used to initialize clients with the `ClientFactory` that offer clients for sender, subscriber and routing:
````java
final SenderClient senderClient = ClientFactory.getSenderClient(config);
//
final SubscriberClient subscriberClient = ClientFactory.getSubscriberClient(config);
//
final RoutingClient routingClient = ClientFactory.getRoutingClient(config);
final RouterClient routerClient = ClientFactory.getRouterClient(config);
````
<p align="right">(<a href="#top">back to top</a>)</p>
## API Usage for Routing
The Routing-Client allows to retrieve data about areas and services as well as their service destination.
A typical workflow using the `RoutingClient` and `SenderClient` would be:
The Router-Client allows to retrieve data about areas and services as well as their service destination.
A typical workflow using the `RouterClient` and `SenderClient` would be:
1) Find the `areaId` for an area via routing
2) Find the destination for a `leikaKey` and an `areaId` via routing
......@@ -133,13 +134,13 @@ A typical workflow using the `RoutingClient` and `SenderClient` would be:
Areas can be searched with one or more search criteria:
```java
final RoutingClient routingClient = ClientFactory.getRoutingClient(config);
final RouterClient routerClient = ClientFactory.getRouterClient(config);
final var citySearchCriterion = "Leip*";
final var zipCodeSearchCriterion = "04229";
// get first 5 area results
final List<Area> areas = routingClient.findAreas(List.of(citySearchCriterion, zipCodeSearchCriterion), 0, 5);
final List<Area> areas = routerClient.findAreas(List.of(citySearchCriterion, zipCodeSearchCriterion), 0, 5);
LOGGER.info("Found {} areas", areas.size());
for (final Area area : areas){
......@@ -163,7 +164,7 @@ __Note:__ Both, the `leikaKey` service-identifier and the region keys `ars/ags`c
#### Find destination by service identifier and *areaId*
```java
final RoutingClient routingClient = ClientFactory.getRoutingClient(config);
final RouterClient routerClient = ClientFactory.getRouterClient(config);
final DestinationSearch search = DestinationSearch.Builder()
.withLeikaKey("99123456760610")
......@@ -172,7 +173,7 @@ final DestinationSearch search = DestinationSearch.Builder()
.build();
// get first 3 route results
final List<Route> routes = routingClient.findDestinations(search);
final List<Route> routes = routerClient.findDestinations(search);
LOGGER.info("Found {} routes for service identifier {}", routes.size(), leikaKey);
for (final Route route : routes){
......@@ -185,7 +186,7 @@ for (final Route route : routes){
Besides the areaId another search criterion for the area/region can be used as well:
```java
final RoutingClient routingClient = ClientFactory.getRoutingClient(config);
final RouterClient routerClient = ClientFactory.getRouterClient(config);
final DestinationSearch search = DestinationSearch.Builder()
.withLeikaKey("99123456760610")
......@@ -194,7 +195,7 @@ final DestinationSearch search = DestinationSearch.Builder()
.build();
// get first 3 route results
final List<Route> routes = routingClient.findDestinations(search);
final List<Route> routes = routerClient.findDestinations(search);
LOGGER.info("Found {} routes for service identifier {}", routes.size(), leikaKey);
for (final Route route : routes){
......@@ -202,7 +203,6 @@ for (final Route route : routes){
}
```
## API Usage for Sender
For sending submission and already encrypted submissions builder are provided to construct the necessary payload to be sent.
......@@ -218,8 +218,12 @@ If all data, metadata and attachments are encrypted outside the SDK the sender c
#### 1. Retrieve public encryption key:
```java
final SenderClient senderClient = ClientFactory.getSenderClient(config);
// destination id that was retrieved via the router client
final var destinationId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14");
final String publicJwkAsJsonString = ClientFactory.getSenderClient(config).getPublicKey(destinationId);
final String publicJwkAsJsonString = senderClient.getPublicKeyForDestination(destinationId);
```
#### 2. Send encrypted data
......@@ -469,9 +473,25 @@ if(validationResult.hasError()){
}
```
## Error Handling
### Exceptions
All clients accessible via the ``ClientFactory`` throw a custom `RuntimeException` if a technical error occurred:
- SenderClient => throws ``FitConnectSenderException``
- SubscriberClient => throws ``FitConnectSubscriberException``
- RouterClient => throws ``FitConnectRouterException``
For all other issues regarding the configuration and set-up of the SDK a ``FitConnectInitialisationException`` will be thrown.
### Auto-Reject
The SDK performs validations on all kind of requests e.g. checks for data integrity and JWT-validity.
If one of the validations fails on subscriber side, whilst retrieving, the submission it will be auto-rejected.
This means a ``REJECT`` event is sent to the event-log with a subsequent deletion of that invalid submission.
Please see the [documentation on verification](https://docs.fitko.de/fit-connect/docs/receiving/verification) for all tests and checks that are executed.
## Integration Tests
Integration tests do not run per default with `mvn test`, but they can be executed with the maven profile `IntegrationTests` via `mvn -PIntegrationTests test`.
Integration tests do not run per default with `mvn test`, but they can be executed with the maven via `mvn verify`.
They expect the following environment variables to be set in the rn configuration of the IDE or on the local terminal:
* SENDER_CLIENT_ID
......@@ -491,7 +511,6 @@ var submissionBaseUrl = "https://submission-api-testing.fit-connect.fitko.dev";
```
## Roadmap
- [ ] Add auto-reject on technical errors
- [ ] Maven central release of 1.0.0-beta
See the [open issues](https://git.fitko.de/fit-connect/planning/-/boards/44?search=SDK) for a full list of proposed features (and known issues).
......
package dev.fitko.fitconnect.api.config;
import dev.fitko.fitconnect.api.exceptions.InitializationException;
import dev.fitko.fitconnect.api.config.defaults.ResourcePaths;
import dev.fitko.fitconnect.api.config.defaults.SchemaConfig;
import dev.fitko.fitconnect.api.exceptions.client.FitConnectInitialisationException;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.With;
import lombok.experimental.Accessors;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@Data
@Getter
@Builder
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ApplicationConfig {
......@@ -29,9 +34,6 @@ public class ApplicationConfig {
@Builder.Default
private Integer requestTimeoutInSeconds = 30;
@Builder.Default
private boolean enableAutoReject = true;
@Builder.Default
private URI setSchemaWriteVersion = SchemaConfig.SET_V_1_0_1.getSchemaUri();
......@@ -41,31 +43,40 @@ public class ApplicationConfig {
@Builder.Default
private URI destinationSchemaVersion = SchemaConfig.XZUFI_DESTINATION_SCHEMA.getSchemaUri();
private String submissionDataSchemaPath;
private SenderConfig senderConfig;
private SubscriberConfig subscriberConfig;
@With
@Builder.Default
private Map<EnvironmentName, Environment> environments = Collections.emptyMap();
private Map<EnvironmentName, Environment> environments = new HashMap<>();
private EnvironmentName activeEnvironment;
private String submissionDataSchemaPath;
public boolean isAutoRejectEnabled() {
return getCurrentEnvironment().getEnableAutoReject();
}
private Environment getEnvironmentByName(final EnvironmentName environmentName) {
if (environments.containsKey(environmentName)) {
return environments.get(environmentName);
} else {
throw new InitializationException("No environment with name '" + environmentName.getName() + "' found. Available environments are: " + getAvailableEnvironmentNames());
}
public boolean isSkipSubmissionDataValidation() {
return getCurrentEnvironment().getSkipSubmissionDataValidation();
}
public boolean isAllowInsecurePublicKey() {
return getCurrentEnvironment().getAllowInsecurePublicKey();
}
public Environment getCurrentEnvironment() {
return getEnvironmentByName(activeEnvironment);
if (environments.containsKey(activeEnvironment)) {
return environments.get(activeEnvironment);
} else {
throw new FitConnectInitialisationException("No environment with name '" + activeEnvironment.getName() + "' found. Available environments are: " + getAvailableEnvironmentNames());
}
}
public String getOAuthTokenEndpoint() {
return getCurrentEnvironment().getAuthBaseUrl() + ResourcePaths.AUTH_TOKEN_PATH;
return getAuthBaseUrl() + ResourcePaths.AUTH_TOKEN_PATH;
}
public String getSubmissionsEndpoint() {
......@@ -113,7 +124,10 @@ public class ApplicationConfig {
}
private String getSubmissionBaseUrl() {
return getCurrentEnvironment().getSubmissionBaseUrl();
return getCurrentEnvironment().getSubmissionBaseUrls()
.stream()
.findFirst()
.orElseThrow(() -> new FitConnectInitialisationException("No submission base url found, expected at least one"));
}
private String getSelfServicePortalBaseUrl() {
......@@ -124,6 +138,10 @@ public class ApplicationConfig {
return getCurrentEnvironment().getRoutingBaseUrl();
}
private String getAuthBaseUrl() {
return getCurrentEnvironment().getAuthBaseUrl();
}
private String getAvailableEnvironmentNames() {
return environments.keySet()
.stream()
......
......@@ -4,14 +4,48 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collections;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Environment {
private String authBaseUrl;
private String routingBaseUrl;
private String submissionBaseUrl;
private List<String> submissionBaseUrls = Collections.emptyList();
private String selfServicePortalBaseUrl;
private boolean allowInsecurePublicKey;
private boolean skipSubmissionDataValidation;
private Boolean enableAutoReject = true;
private Boolean allowInsecurePublicKey = false;
private Boolean skipSubmissionDataValidation = false;
/**
* Merges current env with provided other env.
* If a field of the current env is null or empty, the value from other will be set.
*
* @param other environment to merge the current environment with
* @return merged environment
*/
public Environment merge(final Environment other){
final Environment mergedEnv = new Environment();
mergedEnv.setAuthBaseUrl(isNullOrEmpty(authBaseUrl) ? other.getAuthBaseUrl() : authBaseUrl);
mergedEnv.setRoutingBaseUrl(isNullOrEmpty(routingBaseUrl) ? other.getRoutingBaseUrl() : routingBaseUrl);
mergedEnv.setSubmissionBaseUrls(submissionBaseUrls.isEmpty() ? other.getSubmissionBaseUrls() : submissionBaseUrls);
mergedEnv.setSelfServicePortalBaseUrl(isNullOrEmpty(selfServicePortalBaseUrl) ? other.getSelfServicePortalBaseUrl() : selfServicePortalBaseUrl);
mergedEnv.setEnableAutoReject(enableAutoReject == null ? other.getEnableAutoReject() : enableAutoReject);
mergedEnv.setAllowInsecurePublicKey(allowInsecurePublicKey == null ? other.getAllowInsecurePublicKey() : allowInsecurePublicKey);
mergedEnv.setSkipSubmissionDataValidation(skipSubmissionDataValidation == null ? other.getSkipSubmissionDataValidation() : skipSubmissionDataValidation);
return mergedEnv;
}
private boolean isNullOrEmpty(final String s){
return s == null || s.isEmpty();
}
}
package dev.fitko.fitconnect.api.config;
final class ResourcePaths {
private ResourcePaths() {
}
static final String AUTH_TOKEN_PATH = "/token";
static final String DESTINATIONS_PATH = "/v1/destinations/%s";
static final String DESTINATIONS_KEY_PATH = "/v1/destinations/%s/keys/%s";
static final String EVENTS_PATH = "/v1/cases/%s/events";
static final String SUBMISSION_PATH = "/v1/submissions/%s";
static final String SUBMISSIONS_PATH = "/v1/submissions";
static final String SUBMISSION_ATTACHMENT_PATH = "/v1/submissions/%s/attachments/%s";
static final String ROUTING_AREA_PATH = "/v1/areas";
static final String ROUTING_ROUTE_PATH = "/v1/routes";
static final String WELL_KNOWN_KEYS_PATH = "/.well-known/jwks.json";
}
package dev.fitko.fitconnect.api.config;
package dev.fitko.fitconnect.api.config.build;
import lombok.Data;
......
package dev.fitko.fitconnect.api.config.defaults;
import dev.fitko.fitconnect.api.config.Environment;
import dev.fitko.fitconnect.api.config.EnvironmentName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
public enum DefaultEnvironments {
PROD(new EnvironmentName("PROD"), new Environment(
"https://auth-prod.fit-connect.fitko.net",
"https://routing-api-prod.fit-connect.fitko.net",
List.of("https://submission-api-prod.fit-connect.niedersachsen.de"),
"https://portal.auth-prod.fit-connect.fitko.net",
true,
false,
false)),
STAGE(new EnvironmentName("STAGE"), new Environment(
"https://auth-refz.fit-connect.fitko.net",
"https://routing-api-prod.fit-connect.fitko.net",
List.of("https://submission-api-refz.fit-connect.niedersachsen.de"),
"https://portal.auth-refz.fit-connect.fitko.net",
true,
false,
false)
),
TEST(new EnvironmentName("TEST"), new Environment(
"https://auth-testing.fit-connect.fitko.dev",
"https://routing-api-testing.fit-connect.fitko.dev",
List.of("https://submission-api-testing.fit-connect.fitko.dev"),
"https://portal.auth-testing.fit-connect.fitko.dev",
true,
true,
false));
private final EnvironmentName environmentName;
private final Environment environment;
public static Map<EnvironmentName, Environment> getEnvironmentsAsMap(){
return Arrays.stream(values()).collect(Collectors.toMap(DefaultEnvironments::getEnvironmentName, DefaultEnvironments::getEnvironment));
}
}
package dev.fitko.fitconnect.api.config.defaults;
public final class ResourcePaths {
private ResourcePaths() {
}
public static final String AUTH_TOKEN_PATH = "/token";
public static final String DESTINATIONS_PATH = "/v1/destinations/{destinationId}";
public static final String DESTINATIONS_KEY_PATH = "/v1/destinations/{destinationId}/keys/{kid}";
public static final String EVENTS_PATH = "/v1/cases/{caseId}/events";
public static final String SUBMISSION_PATH = "/v1/submissions/{submissionId}";
public static final String SUBMISSIONS_PATH = "/v1/submissions";
public static final String SUBMISSION_ATTACHMENT_PATH = "/v1/submissions/{submissionId}/attachments/{attachmentId}";
public static final String ROUTING_AREA_PATH = "/v1/areas";
public static final String ROUTING_ROUTE_PATH = "/v1/routes";
public static final String WELL_KNOWN_KEYS_PATH = "/.well-known/jwks.json";
}
package dev.fitko.fitconnect.api.config;
package dev.fitko.fitconnect.api.config.defaults;
import lombok.Getter;
......
......@@ -4,7 +4,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static dev.fitko.fitconnect.api.config.SchemaConfig.EVENTS_SCHEMA_PATH;
import static dev.fitko.fitconnect.api.config.defaults.SchemaConfig.EVENTS_SCHEMA_PATH;
public enum Event {
......
package dev.fitko.fitconnect.api.domain.validation;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.exceptions.ValidationException;
import dev.fitko.fitconnect.api.exceptions.internal.ValidationException;
import lombok.Getter;
import java.util.ArrayList;
......
package dev.fitko.fitconnect.api.domain.validation;
import dev.fitko.fitconnect.api.domain.model.event.problems.Problem;
import dev.fitko.fitconnect.api.exceptions.internal.ValidationException;
import java.util.ArrayList;
import java.util.List;
......@@ -61,6 +62,15 @@ public final class ValidationResult {
return new ValidationResult(false, exception);
}
/**
* Create new failed result with an error message that's wrapped in a ValidationException.
*
* @return the invalid result
*/
public static ValidationResult error(final String errorMessage) {
return new ValidationResult(false, new ValidationException(errorMessage));
}
/**
* Create new failed result with a {@link Problem}.
*
......
package dev.fitko.fitconnect.api.exceptions;
public class AttachmentCreationException extends RuntimeException {
public AttachmentCreationException(final String errorMessage, final Throwable error) {
super(errorMessage, error);
}
public AttachmentCreationException(final String errorMessage) {
super(errorMessage);
}
}
package dev.fitko.fitconnect.api.exceptions;
public class AttachmentUploadException extends RuntimeException {
public AttachmentUploadException(final String errorMessage, final Throwable error) {
super(errorMessage, error);
}
}
package dev.fitko.fitconnect.api.exceptions;
public class AuthenticationException extends RuntimeException {
public AuthenticationException(final String errorMessage, final Throwable error) {
super(errorMessage, error);
}
}
package dev.fitko.fitconnect.api.exceptions;
public class DataNotPresentException extends RuntimeException {
public DataNotPresentException(final String errorMessage, final Throwable error) {
super(errorMessage, error);
}
public DataNotPresentException(final String errorMessage) {
super(errorMessage);
}
}
package dev.fitko.fitconnect.api.exceptions;
public class FileHandlingException extends RuntimeException {
public FileHandlingException(Exception exception) {
super(exception);
}
}
package dev.fitko.fitconnect.api.exceptions;
public class InitializationException extends RuntimeException {
public InitializationException(final String errorMessage) {
super(errorMessage);
}
public InitializationException(final String errorMessage, final Throwable error) {
super(errorMessage, error);
}
}
package dev.fitko.fitconnect.api.exceptions;
public class KeyNotRetrievedException extends RuntimeException {
public KeyNotRetrievedException(final String errorMessage, final Throwable error) {
super(errorMessage, error);
}
public KeyNotRetrievedException(final String errorMessage) {
super(errorMessage);
}
}
package dev.fitko.fitconnect.api.exceptions;
public class MetadataNotCreatedException extends RuntimeException {
public MetadataNotCreatedException(final String errorMessage, final Throwable error) {
super(errorMessage, error);
}
}
package dev.fitko.fitconnect.api.exceptions;
public class RoutingException extends RuntimeException {
public RoutingException(final String errorMessage) {
super(errorMessage);
}
public RoutingException(final String errorMessage, final Throwable error) {
super(errorMessage, error);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment