Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge upstream #130

Merged
merged 5 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/Mobile-Token-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,7 @@ Claim an operation for a user.
```json
{
"requestObject": {
"id": "7e0ba60f-bf22-4ff5-b999-2733784e5eaa",
"userId": "user12345"
"id": "7e0ba60f-bf22-4ff5-b999-2733784e5eaa"
}
}
```
Expand Down
2 changes: 2 additions & 0 deletions docs/onboarding/Configuration-Properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ The Onboarding Server uses the following public configuration properties:
| `enrollment-server-onboarding.provider.innovatrics.serviceBaseUrl` | | Base REST service URL for Innovatrics. |
| `enrollment-server-onboarding.provider.innovatrics.serviceToken` | | Authentication token for Innovatrics. |
| `enrollment-server-onboarding.provider.innovatrics.serviceUserAgent` | `Wultra/OnboardingServer` | User agent to use when making HTTP calls to Innovatrics REST service. |
| `enrollment-server-onboarding.provider.innovatrics.presenceCheck.score` | 0.875 | Presence check minimal score threshold. |
| `enrollment-server-onboarding.provider.innovatrics.restClientConfig.acceptInvalidSslCertificate` | `false` | Whether invalid SSL certificate is accepted when calling Zen ID REST service. |
| `enrollment-server-onboarding.provider.innovatrics.restClientConfig.maxInMemorySize` | `10485760` | Maximum in memory size of HTTP requests when calling Innovatrics REST service. |
| `enrollment-server-onboarding.provider.innovatrics.restClientConfig.proxyEnabled` | `false` | Whether proxy server is enabled when calling Innovatrics REST service. |
Expand All @@ -150,6 +151,7 @@ The Onboarding Server uses the following public configuration properties:
| `enrollment-server-onboarding.provider.innovatrics.restClientConfig.proxyUsername` | | Proxy username to be used when calling Innovatrics REST service. |
| `enrollment-server-onboarding.provider.innovatrics.restClientConfig.proxyPassword` | | Proxy password to be used when calling Innovatrics REST service. |

See [Innovatrics documentation](https://developers.innovatrics.com/digital-onboarding/docs/functionalities/face/active-liveness-check/#magnifeye-liveness) for details how the score affects false acceptances (FAR) and false rejections (FRR).

## Correlation HTTP Header Configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ public interface DocumentVerificationProvider {
*/
DocumentsSubmitResult submitDocuments(OwnerId id, List<SubmittedDocument> documents) throws RemoteCommunicationException, DocumentVerificationException;

/**
* A feature flag whether the selfie result gained by {@link PresenceCheckProvider} should be stored by {@link #submitDocuments(OwnerId, List)}.
* <p>
* Some implementation may require this cross-sending between providers by the Onboarding server, some providers may handle it internally.
*
* @return {@code true} if cross-sending between providers should be handled by Onboarding server, {@code false} otherwise.
*/
boolean shouldStoreSelfie();

/**
* Analyze previously submitted documents, detect frauds, return binary result
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ public interface PresenceCheckProvider {
*/
void initPresenceCheck(OwnerId id, Image photo) throws PresenceCheckException, RemoteCommunicationException;

/**
* A feature flag whether the trusted photo of the user should be passed to {@link #initPresenceCheck(OwnerId, Image)}.
* <p>
* Some implementation may require specific source to be called by Onboarding server, some providers may handle it internally.
*
* @return {@code true} if the trusted photo should be provided, {@code false} otherwise.
*/
boolean shouldProvideTrustedPhoto();

/**
* Starts the presence check process. The process has to be initialized before this call.
*
Expand All @@ -67,9 +76,10 @@ public interface PresenceCheckProvider {
* Cleans up all presence check data related to the identity.
*
* @param id Owner identification.
* @param sessionInfo Session info with presence check relevant data.
* @throws PresenceCheckException In case of business logic error.
* @throws RemoteCommunicationException In case of remote communication error.
*/
void cleanupIdentityData(OwnerId id) throws PresenceCheckException, RemoteCommunicationException;
void cleanupIdentityData(OwnerId id, SessionInfo sessionInfo) throws PresenceCheckException, RemoteCommunicationException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@
*/
package com.wultra.app.onboardingserver.provider.innovatrics;

import com.wultra.app.enrollmentserver.model.integration.OwnerId;
import com.wultra.app.onboardingserver.common.errorhandling.RemoteCommunicationException;
import com.wultra.app.onboardingserver.provider.innovatrics.model.api.CustomerInspectResponse;
import com.wultra.app.onboardingserver.provider.innovatrics.model.api.EvaluateCustomerLivenessRequest;
import com.wultra.app.onboardingserver.provider.innovatrics.model.api.EvaluateCustomerLivenessResponse;
import com.wultra.core.rest.client.base.RestClient;
import com.wultra.core.rest.client.base.RestClientException;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

/**
* Implementation of the REST service to<a href="https://www.innovatrics.com/">Innovatrics</a>.
Expand All @@ -43,7 +49,9 @@
@Slf4j
class InnovatricsApiService {

private static final ParameterizedTypeReference<String> STRING_TYPE_REFERENCE = new ParameterizedTypeReference<>() { };
private static final MultiValueMap<String, String> EMPTY_ADDITIONAL_HEADERS = new LinkedMultiValueMap<>();

private static final MultiValueMap<String, String> EMPTY_QUERY_PARAMS = new LinkedMultiValueMap<>();

/**
* REST client for Innovatrics calls.
Expand All @@ -60,11 +68,81 @@ public InnovatricsApiService(@Qualifier("restClientInnovatrics") final RestClien
this.restClient = restClient;
}

// TODO remove - temporal test call
@PostConstruct
public void testCall() throws RestClientException {
logger.info("Trying a test call");
final ResponseEntity<String> response = restClient.get("/api/v1/metadata", STRING_TYPE_REFERENCE);
logger.info("Result of test call: {}", response.getBody());
public EvaluateCustomerLivenessResponse evaluateLiveness(final String customerId, final OwnerId ownerId) throws RemoteCommunicationException {
final EvaluateCustomerLivenessRequest request = new EvaluateCustomerLivenessRequest();
request.setType(EvaluateCustomerLivenessRequest.TypeEnum.MAGNIFEYE_LIVENESS);

final String apiPath = "/api/v1/customers/%s/liveness/evaluation".formatted(customerId);

try {
logger.info("Calling liveness/evaluation, {}", ownerId);
logger.debug("Calling {}, {}", apiPath, request);
final ResponseEntity<EvaluateCustomerLivenessResponse> response = restClient.post(apiPath, request, EMPTY_QUERY_PARAMS, EMPTY_ADDITIONAL_HEADERS, new ParameterizedTypeReference<>() {});
logger.info("Got {} for liveness/evaluation, {}", response.getStatusCode(), ownerId);
logger.debug("{} response status code: {}", apiPath, response.getStatusCode());
logger.trace("{} response: {}", apiPath, response);
return response.getBody();
} catch (RestClientException e) {
throw new RemoteCommunicationException(
String.format("Failed REST call to evaluate liveness for customerId=%s, statusCode=%s, responseBody='%s'", customerId, e.getStatusCode(), e.getResponse()), e);
} catch (Exception e) {
throw new RemoteCommunicationException("Unexpected error when evaluating liveness for customerId=" + customerId, e);
}
}

public CustomerInspectResponse inspectCustomer(final String customerId, final OwnerId ownerId) throws RemoteCommunicationException {
final String apiPath = "/api/v1/customers/%s/inspect".formatted(customerId);

try {
logger.info("Calling /inspect, {}", ownerId);
logger.debug("Calling {}", apiPath);
final ResponseEntity<CustomerInspectResponse> response = restClient.post(apiPath, null, EMPTY_QUERY_PARAMS, EMPTY_ADDITIONAL_HEADERS, new ParameterizedTypeReference<>() {});
logger.info("Got {} for /inspect, {}", response.getStatusCode(), ownerId);
logger.debug("{} response status code: {}", apiPath, response.getStatusCode());
logger.trace("{} response: {}", apiPath, response);
return response.getBody();
} catch (RestClientException e) {
throw new RemoteCommunicationException(
String.format("Failed REST call to evaluate inspect for customerId=%s, statusCode=%s, responseBody='%s'", customerId, e.getStatusCode(), e.getResponse()), e);
} catch (Exception e) {
throw new RemoteCommunicationException("Unexpected error when evaluating inspect for customerId=" + customerId, e);
}
}

public void deleteLiveness(final String customerId, final OwnerId ownerId) throws RemoteCommunicationException {
final String apiPath = "/api/v1/customers/%s/liveness".formatted(customerId);

try {
logger.info("Deleting liveness, {}", ownerId);
logger.debug("Deleting {}", apiPath);
final ResponseEntity<Void> response = restClient.delete(apiPath, new ParameterizedTypeReference<>() {});
logger.info("Got {} for liveness delete, {}", response.getStatusCode(), ownerId);
logger.debug("{} response status code: {}", apiPath, response.getStatusCode());
logger.trace("{} response: {}", apiPath, response);
} catch (RestClientException e) {
throw new RemoteCommunicationException(
String.format("Failed REST call to delete liveness for customerId=%s, statusCode=%s, responseBody='%s'", customerId, e.getStatusCode(), e.getResponse()), e);
} catch (Exception e) {
throw new RemoteCommunicationException("Unexpected error when deleting liveness for customerId=" + customerId, e);
}
}

public void deleteSelfie(final String customerId, final OwnerId ownerId) throws RemoteCommunicationException {
final String apiPath = "/api/v1/customers/%s/selfie".formatted(customerId);

try {
logger.info("Deleting selfie, {}", ownerId);
logger.debug("Deleting {}", apiPath);
final ResponseEntity<Void> response = restClient.delete(apiPath, new ParameterizedTypeReference<>() {});
logger.info("Got {} for selfie delete, {}", response.getStatusCode(), ownerId);
logger.debug("{} response status code: {}", apiPath, response.getStatusCode());
logger.trace("{} response: {}", apiPath, response);
} catch (RestClientException e) {
throw new RemoteCommunicationException(
String.format("Failed REST call to delete selfie for customerId=%s, statusCode=%s, responseBody='%s'", customerId, e.getStatusCode(), e.getResponse()), e);
} catch (Exception e) {
throw new RemoteCommunicationException("Unexpected error when deleting selfie for customerId=" + customerId, e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,14 @@ class InnovatricsConfigProps {
*/
private RestClientConfiguration restClientConfig;

private PresenceCheckConfiguration presenceCheckConfiguration;

@Getter @Setter
public static class PresenceCheckConfiguration {
/**
* Presence check minimal score threshold.
*/
private double score = 0.875;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public DocumentsSubmitResult submitDocuments(OwnerId id, List<SubmittedDocument>
return null;
}

@Override
public boolean shouldStoreSelfie() {
return false;
}

@Override
public DocumentsVerificationResult verifyDocuments(OwnerId id, List<String> uploadIds) throws RemoteCommunicationException, DocumentVerificationException {
return null;
Expand Down
Loading