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

Fix #539: Polish iProov provider #540

Merged
merged 5 commits into from
Nov 23, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public void initPresenceCheck(OwnerId id, Image photo) throws PresenceCheckExcep

final ResponseEntity<String> responseEntityEnrol;
try {
responseEntityEnrol = iProovRestApiService.enrolUserImageForToken(token, photo);
responseEntityEnrol = iProovRestApiService.enrolUserImageForToken(token, photo, id);
} catch (RestClientException e) {
throw new RemoteCommunicationException(
String.format("Failed to enrol a user image to iProov, statusCode=%s, responseBody='%s', %s",
Expand Down Expand Up @@ -206,25 +206,9 @@ public PresenceCheckResult getResult(OwnerId id, SessionInfo sessionInfo) throws
}

@Override
public void cleanupIdentityData(OwnerId id) throws PresenceCheckException, RemoteCommunicationException {
final ResponseEntity<String> responseEntity;
try {
responseEntity = iProovRestApiService.deleteUserPersona(id);
} catch (RestClientException e) {
throw new RemoteCommunicationException(
String.format("Failed REST call to delete a user persona from iProov, statusCode=%s, responseBody='%s', %s",
e.getStatusCode(), e.getResponse(), id),
e);
} catch (Exception e) {
throw new RemoteCommunicationException("Unexpected error when deleting a user persona in iProov, " + id, e);
}

if (responseEntity.getBody() == null) {
throw new RemoteCommunicationException("Missing response body when validating a verification in iProov, " + id);
}

UserResponse userResponse = parseResponse(responseEntity.getBody(), UserResponse.class);
logger.info("Deleted a user persona in iProov, status={}, {}", userResponse.getStatus(), id);
public void cleanupIdentityData(final OwnerId id) {
// https://docs.iproov.com/docs/Content/ImplementationGuide/security/data-retention.htm
logger.info("No data deleted, retention policy left to iProov server, {}", id);
}

private ResponseEntity<String> callGenerateEnrolToken(OwnerId id) throws RemoteCommunicationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
import com.wultra.app.onboardingserver.presencecheck.iproov.model.api.ServerClaimRequest;
import com.wultra.core.rest.client.base.RestClient;
import com.wultra.core.rest.client.base.RestClientException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ConditionalOnProperty;
Expand All @@ -43,7 +42,7 @@
import java.util.regex.Pattern;

/**
* Implementation of the REST service to iProov (https://www.iproov.com/)
* Implementation of the REST service to <a href="https://www.iproov.com/">iProov</a>.
*
* <p>
* The userId is filled with a secured user identification
Expand All @@ -57,25 +56,24 @@
*/
@ConditionalOnProperty(value = "enrollment-server-onboarding.presence-check.provider", havingValue = "iproov")
@Service
@Slf4j
public class IProovRestApiService {

private static final Logger logger = LoggerFactory.getLogger(IProovRestApiService.class);
private static final MultiValueMap<String, String> EMPTY_QUERY_PARAMS = new LinkedMultiValueMap<>();

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

public static final ParameterizedTypeReference<String> STRING_TYPE_REFERENCE = new ParameterizedTypeReference<>() { };
private static final ParameterizedTypeReference<String> STRING_TYPE_REFERENCE = new ParameterizedTypeReference<>() { };

/**
* Max length of the user id value defined by iProov
*/
public static final int USER_ID_MAX_LENGTH = 256;
protected static final int USER_ID_MAX_LENGTH = 256;

/**
* Regex used by iProov on user id values
*/
public static final Pattern USER_ID_REGEX_PATTERN = Pattern.compile("[a-zA-Z0-9'+_@.-]{1,256}");
private static final Pattern USER_ID_REGEX_PATTERN = Pattern.compile("[a-zA-Z0-9'+_@.-]{1,256}");

public static final String I_PROOV_RESOURCE_CONTEXT = "presence_check/";
private static final String I_PROOV_RESOURCE_CONTEXT = "presence_check/";

/**
* Configuration properties.
Expand Down Expand Up @@ -108,8 +106,9 @@ public IProovRestApiService(
* @return Response entity with the result json
*/
public ResponseEntity<String> generateEnrolToken(OwnerId id) throws RestClientException {
ServerClaimRequest request = createServerClaimRequest(id);
final ServerClaimRequest request = createServerClaimRequest(id);

logger.debug("Calling /claim/enrol/token userId={}, {}", request.getUserId(), id);
return restClient.post("/claim/enrol/token", request, STRING_TYPE_REFERENCE);
}

Expand All @@ -118,9 +117,10 @@ public ResponseEntity<String> generateEnrolToken(OwnerId id) throws RestClientEx
*
* @param token An enrolment token value
* @param photo Trusted photo of a person
* @param id Owner identification.
* @return Response entity with the result json
*/
public ResponseEntity<String> enrolUserImageForToken(String token, Image photo) throws RestClientException {
public ResponseEntity<String> enrolUserImageForToken(String token, Image photo, OwnerId id) throws RestClientException {
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("api_key", configProps.getApiKey());
bodyBuilder.part("secret", configProps.getApiSecret());
Expand All @@ -139,6 +139,7 @@ public String getFilename() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);

logger.debug("Calling /claim/enroll/image token={}, {}", token, id);
return restClient.post("/claim/enrol/image", bodyBuilder.build(), EMPTY_QUERY_PARAMS, httpHeaders, STRING_TYPE_REFERENCE);
}

Expand All @@ -149,8 +150,9 @@ public String getFilename() {
* @return Response entity with the result json
*/
public ResponseEntity<String> generateVerificationToken(OwnerId id) throws RestClientException {
ServerClaimRequest request = createServerClaimRequest(id);
final ServerClaimRequest request = createServerClaimRequest(id);

logger.debug("Calling /claim/verify/token userId={}, {}", request.getUserId(), id);
return restClient.post("/claim/verify/token", request, STRING_TYPE_REFERENCE);
}

Expand All @@ -162,45 +164,30 @@ public ResponseEntity<String> generateVerificationToken(OwnerId id) throws RestC
* @return Response entity with the result json
*/
public ResponseEntity<String> validateVerification(OwnerId id, String token) throws RestClientException {
ClaimValidateRequest request = new ClaimValidateRequest();
final ClaimValidateRequest request = new ClaimValidateRequest();
request.setApiKey(configProps.getApiKey());
request.setSecret(configProps.getApiSecret());
request.setClient("Wultra Enrollment Server, activationId: " + id.getActivationId()); // TODO value from the device
request.setIp("192.168.1.1"); // TODO deprecated but still required
request.setRiskProfile(configProps.getRiskProfile());
request.setToken(token);

String userId = getUserId(id);
final String userId = getUserId(id);
request.setUserId(userId);

logger.debug("Calling /claim/verify/validate userId={}, token={}, {}", userId, token, id);
return restClient.post("/claim/verify/validate", request, STRING_TYPE_REFERENCE);
}

/**
* Deletes user person data
*
* @param id Owner identification.
* @return Response entity with the result json
*/
public ResponseEntity<String> deleteUserPersona(OwnerId id) throws RestClientException {
// TODO implement this, oauth call on DELETE /users/activationId
logger.warn("Not deleting user in iProov (not implemented yet), {}", id);
return ResponseEntity.ok("{\n" +
" \"user_id\": \"" + id.getUserIdSecured() + "\",\n" +
" \"name\": \"user name\",\n" +
" \"status\": \"Deleted\"\n" +
"}");
}

private ServerClaimRequest createServerClaimRequest(OwnerId id) {
ServerClaimRequest request = new ServerClaimRequest();
final ServerClaimRequest request = new ServerClaimRequest();
request.setApiKey(configProps.getApiKey());
request.setSecret(configProps.getApiSecret());
request.setAssuranceType(configProps.getAssuranceType());
request.setResource(I_PROOV_RESOURCE_CONTEXT + id.getActivationId());
request.setRiskProfile(configProps.getRiskProfile());

String userId = getUserId(id);
final String userId = getUserId(id);
request.setUserId(userId);

return request;
Expand All @@ -209,7 +196,7 @@ private ServerClaimRequest createServerClaimRequest(OwnerId id) {
public static String ensureValidUserIdValue(String value) {
if (value.length() > USER_ID_MAX_LENGTH) {
value = value.substring(0, USER_ID_MAX_LENGTH);
logger.error("The userId value: '{}', was too long for iProov, shortened to {} characters", value, USER_ID_MAX_LENGTH);
logger.warn("The userId value: '{}', was too long for iProov, shortened to {} characters", value, USER_ID_MAX_LENGTH);
}
if (!USER_ID_REGEX_PATTERN.matcher(value).matches()) {
throw new IllegalArgumentException(String.format("The userId value: '%s', does not match the iProov regex pattern", value));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
import com.wultra.app.onboardingserver.common.annotation.PublicApi;
import com.wultra.app.onboardingserver.provider.OnboardingProvider;
import com.wultra.app.onboardingserver.provider.model.request.ApproveConsentRequest;
import lombok.ToString;

/**
* Response object for {@link OnboardingProvider#approveConsent(ApproveConsentRequest)}.
*
* @author Lubos Racansky, lubos.racansky@wultra.com
*/
@PublicApi
@ToString
public final class ApproveConsentResponse {
// empty so far, open to change in the future
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@
class IProovRestApiServiceTest {

@Test
void ensureValidUserIdValueTest() throws Exception {
assertThrows(IllegalArgumentException.class, () -> {
IProovRestApiService.ensureValidUserIdValue("invalidChars,[=");
});
void ensureValidUserIdValueTest() {
assertThrows(IllegalArgumentException.class, () ->
IProovRestApiService.ensureValidUserIdValue("invalidChars,[="));

String userIdTooLong = RandomStringUtils.randomAlphabetic(IProovRestApiService.USER_ID_MAX_LENGTH + 1);
String userIdEnsured = IProovRestApiService.ensureValidUserIdValue(userIdTooLong);
Expand Down