com.wultra.app.onboardingserver.provider.zenid.ZenidRestApiService Maven / Gradle / Ivy
The newest version!
/*
* PowerAuth Enrollment Server
* Copyright (C) 2021 Wultra s.r.o.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.wultra.app.onboardingserver.provider.zenid;
import com.wultra.app.enrollmentserver.model.enumeration.CardSide;
import com.wultra.app.enrollmentserver.model.enumeration.DocumentType;
import com.wultra.app.enrollmentserver.model.integration.OwnerId;
import com.wultra.app.enrollmentserver.model.integration.SubmittedDocument;
import com.wultra.app.onboardingserver.provider.zenid.model.api.*;
import com.wultra.core.rest.client.base.RestClient;
import com.wultra.core.rest.client.base.RestClientException;
import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.util.List;
/**
* Implementation of the REST service to ZenID.
*
* @author Lukas Lukovsky, [email protected]
*/
@ConditionalOnProperty(value = "enrollment-server-onboarding.document-verification.provider", havingValue = "zenid")
@Service
@Slf4j
class ZenidRestApiService {
private static final MultiValueMap EMPTY_ADDITIONAL_HEADERS = new LinkedMultiValueMap<>();
private static final MultiValueMap EMPTY_QUERY_PARAMS = new LinkedMultiValueMap<>();
private static final ParameterizedTypeReference RESPONSE_TYPE_BYTE_ARRAY =
new ParameterizedTypeReference<>() { };
private static final ParameterizedTypeReference RESPONSE_TYPE_REFERENCE_DELETE =
new ParameterizedTypeReference<>() { };
private static final ParameterizedTypeReference RESPONSE_TYPE_REFERENCE_INIT_SDK =
new ParameterizedTypeReference<>() { };
private static final ParameterizedTypeReference RESPONSE_TYPE_REFERENCE_INVESTIGATE =
new ParameterizedTypeReference<>() { };
private static final ParameterizedTypeReference RESPONSE_TYPE_REFERENCE_UPLOAD_SAMPLE =
new ParameterizedTypeReference<>() { };
/**
* Configuration properties.
*/
private final ZenidConfigProps configProps;
/**
* REST client for ZenID calls.
*/
private final RestClient restClient;
/**
* Service constructor.
*
* @param configProps Configuration properties.
* @param restClient REST client for ZenID calls.
*/
@Autowired
public ZenidRestApiService(
ZenidConfigProps configProps,
@Qualifier("restClientZenid") RestClient restClient) {
this.configProps = configProps;
this.restClient = restClient;
}
/**
* Uploads photo data as a sample DocumentPicture
*
* @param ownerId Owner identification.
* @param document Submitted document.
* @return Response entity with the upload result
*/
public ResponseEntity uploadSample(OwnerId ownerId, SubmittedDocument document)
throws RestClientException {
Validate.notNull(document.getPhoto(), "Missing photo in " + document);
final MultiValueMap queryParams = buildQueryParams(ownerId, document);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", new ByteArrayResource(document.getPhoto().getData()) {
@Override
public String getFilename() {
return document.getPhoto().getFilename();
}
}
);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
final ResponseEntity response =
restClient.post("/api/sample", bodyBuilder.build(), queryParams, httpHeaders, RESPONSE_TYPE_REFERENCE_UPLOAD_SAMPLE);
logger.debug("/api/sample response status code: {}, {}", response.getStatusCode(), ownerId);
logger.trace("/api/sample response: {}, {}", response, ownerId);
return response;
}
/**
* Synchronizes submitted document result of a previous upload.
*
* @param documentId Submitted document id.
* @return Response entity with the upload result
*/
public ResponseEntity syncSample(String documentId)
throws RestClientException {
String apiPath = "/api/sample/" + documentId;
final ResponseEntity response = restClient.get(apiPath, RESPONSE_TYPE_REFERENCE_UPLOAD_SAMPLE);
logger.debug("{} response status code: {}", apiPath, response.getStatusCode());
logger.trace("{} response {}", apiPath, response);
return response;
}
/**
* Investigates uploaded samples
*
* @param sampleIds Ids of previously uploaded samples.
* @return Response entity with the investigation result
*/
public ResponseEntity investigateSamples(List sampleIds) throws RestClientException {
Validate.notEmpty(sampleIds, "Missing sample ids for investigation");
MultiValueMap queryParams = new LinkedMultiValueMap<>();
sampleIds.forEach(sampleId -> queryParams.add("sampleIDs", sampleId));
queryParams.add("async", String.valueOf(configProps.isAsyncProcessingEnabled()).toLowerCase());
configProps.getProfile().ifPresent(profile ->
queryParams.add("profile", profile));
final ResponseEntity response =
restClient.get("/api/investigateSamples", queryParams, EMPTY_ADDITIONAL_HEADERS, RESPONSE_TYPE_REFERENCE_INVESTIGATE);
logger.debug("/api/investigateSamples response status code: {} for IDs: {}", response.getStatusCode(), sampleIds);
logger.trace("/api/investigateSamples response: {} for IDs: {}", response, sampleIds);
return response;
}
/**
* Deletes an uploaded sample
*
* @param sampleId Id of previously uploaded sample.
* @return Response entity with the deletion result
*/
public ResponseEntity deleteSample(String sampleId) throws RestClientException {
MultiValueMap queryParams = new LinkedMultiValueMap<>();
queryParams.add("sampleId", sampleId);
final ResponseEntity response =
restClient.get("/api/deleteSample", queryParams, EMPTY_ADDITIONAL_HEADERS, RESPONSE_TYPE_REFERENCE_DELETE);
logger.debug("/api/deleteSample/{} response: {}", sampleId, response);
return response;
}
/**
* Gets image data belonging to the specified hash
* @param imageHash Image hash
* @return Response entity with the image data
*/
public ResponseEntity getImage(String imageHash) throws RestClientException {
final String apiPath = String.format("/History/Image/%s", imageHash);
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setAccept(List.of(MediaType.APPLICATION_OCTET_STREAM));
final ResponseEntity result = restClient.get(apiPath, EMPTY_QUERY_PARAMS, httpHeaders, RESPONSE_TYPE_BYTE_ARRAY);
logger.debug("{} called", apiPath);
return result;
}
/**
* Provides result of an investigation.
*
*
* Only failed validation results are returned. All document samples without a validation constraint
* are considered as passed.
*
*
* @param investigationId Id of a previously run investigation
* @return Response entity with the investigation result
*/
public ResponseEntity getInvestigation(String investigationId)
throws RestClientException {
String apiPath = String.format("/api/investigation/%s", investigationId);
final ResponseEntity response = restClient.get(apiPath, RESPONSE_TYPE_REFERENCE_INVESTIGATE);
logger.debug("{} response status code: {}", apiPath, response.getStatusCode());
logger.trace("{} response: {}", apiPath, response);
return response;
}
/**
* Initializes the SDK of ZenID
*
* @param token Initialization token
* @return Response entity with the SDK initialization result
*/
public ResponseEntity initSdk(String token) throws RestClientException {
MultiValueMap queryParams = new LinkedMultiValueMap<>();
queryParams.add("token", token);
final ResponseEntity response =
restClient.get("/api/initSdk", queryParams, EMPTY_ADDITIONAL_HEADERS, RESPONSE_TYPE_REFERENCE_INIT_SDK);
logger.debug("/api/initSdk response: {}", response);
return response;
}
private MultiValueMap buildQueryParams(OwnerId ownerId, SubmittedDocument document) {
MultiValueMap queryParams = new LinkedMultiValueMap<>();
queryParams.add("async", String.valueOf(configProps.isAsyncProcessingEnabled()).toLowerCase());
queryParams.add("expectedSampleType", toSampleType(document.getType()).toString());
queryParams.add("customData", ownerId.getActivationId());
queryParams.add("country", configProps.getDocumentCountry().toString());
configProps.getProfile().ifPresent(profile ->
queryParams.add("profile", profile));
ZenidSharedMineAllResult.DocumentCodeEnum documentCode = toDocumentCode(document.getType());
if (documentCode != null) {
queryParams.add("documentCode", documentCode.toString());
}
if (document.getSide() != null) {
queryParams.add("pageCode", toPageCodeEnum(document.getSide()).toString());
}
ZenidSharedMineAllResult.DocumentRoleEnum documentRole = toDocumentRole(document.getType());
if (documentRole != null) {
queryParams.add("role", documentRole.toString());
}
return queryParams;
}
private @Nullable ZenidSharedMineAllResult.DocumentCodeEnum toDocumentCode(DocumentType documentType) {
return switch (documentType) {
case DRIVING_LICENSE -> ZenidSharedMineAllResult.DocumentCodeEnum.DRV;
case PASSPORT -> ZenidSharedMineAllResult.DocumentCodeEnum.PAS;
// case ID_CARD ->
// // Not supported more than one version of a document
// List.of(ZenidSharedMineAllResult.DocumentCodeEnum.IDC1, ZenidSharedMineAllResult.DocumentCodeEnum.IDC2);
default -> null;
};
}
private @Nullable ZenidSharedMineAllResult.DocumentRoleEnum toDocumentRole(DocumentType documentType) {
return switch (documentType) {
case DRIVING_LICENSE -> ZenidSharedMineAllResult.DocumentRoleEnum.DRV;
case ID_CARD -> ZenidSharedMineAllResult.DocumentRoleEnum.IDC;
case PASSPORT -> ZenidSharedMineAllResult.DocumentRoleEnum.PAS;
default -> null;
};
}
private ZenidSharedMineAllResult.PageCodeEnum toPageCodeEnum(CardSide cardSide) {
return switch (cardSide) {
case FRONT -> ZenidSharedMineAllResult.PageCodeEnum.F;
case BACK -> ZenidSharedMineAllResult.PageCodeEnum.B;
};
}
private ZenidWebUploadSampleResponse.SampleTypeEnum toSampleType(DocumentType type) {
return switch (type) {
case ID_CARD, DRIVING_LICENSE, PASSPORT -> ZenidWebUploadSampleResponse.SampleTypeEnum.DOCUMENT_PICTURE;
case SELFIE_PHOTO -> ZenidWebUploadSampleResponse.SampleTypeEnum.SELFIE;
default -> throw new IllegalStateException("Not supported documentType: " + type);
};
}
}