Skip to content

Commit

Permalink
Fix #1560: Create Operation does not return proximity OTP (#1561)
Browse files Browse the repository at this point in the history
* Fix #1560: Create Operation does not return proximity OTP

Co-authored-by: Zdeněk Černý <zdenek.cerny@wultra.com>
  • Loading branch information
banterCZ and zcgandcomp authored Jun 5, 2024
1 parent 527074b commit e17f6f6
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public OperationDetailResponse createOperation(OperationCreateRequest request) t

final OperationEntity savedEntity = operationRepository.save(operationEntity);
callbackUrlBehavior.notifyCallbackListenersOnOperationChange(savedEntity);
return convertFromEntity(savedEntity);
return convertFromEntityAndFillOtp(savedEntity);
} catch (GenericServiceException ex) {
// already logged
throw ex;
Expand Down Expand Up @@ -731,9 +731,8 @@ public OperationDetailResponse operationDetail(OperationDetailRequest request) t
claimOperation(operationOptional.get(), userId, currentTimestamp),
currentTimestamp
);
final OperationDetailResponse operationDetailResponse = convertFromEntity(operationEntity);
final OperationDetailResponse operationDetailResponse = convertFromEntityAndFillOtp(operationEntity);
extendAndSetOperationDetailData(operationDetailResponse);
generateAndSetOtpToOperationDetail(operationEntity, operationDetailResponse);
return operationDetailResponse;
} catch (GenericServiceException ex) {
// already logged
Expand Down Expand Up @@ -821,8 +820,7 @@ public OperationListResponse findPendingOperationsForUser(OperationListForUserRe
final OperationEntity operationEntity = expireOperation(op, currentTimestamp);
// Skip operation that just expired
if (OperationStatusDo.PENDING.equals(operationEntity.getStatus())) {
final OperationDetailResponse operationDetail = convertFromEntity(operationEntity);
generateAndSetOtpToOperationDetail(operationEntity, operationDetail);
final OperationDetailResponse operationDetail = convertFromEntityAndFillOtp(operationEntity);
result.add(operationDetail);
}
});
Expand Down Expand Up @@ -883,6 +881,14 @@ public OperationListResponse findOperationsByExternalId(OperationExtIdRequest re
}
}

/**
* Convert the given entity to the response class.
* Mind that it does not fill the proximity OTP. If you need so, use {@link #convertFromEntityAndFillOtp(OperationEntity)} instead.
*
* @param source Entity to convert.
* @return response class
* @see #convertFromEntityAndFillOtp(OperationEntity)
*/
private OperationDetailResponse convertFromEntity(OperationEntity source) {
final OperationDetailResponse destination = new OperationDetailResponse();
destination.setId(source.getId());
Expand Down Expand Up @@ -1000,10 +1006,20 @@ private Map<String, Object> mapMerge(Map<String, Object> m1, Map<String, Object>
return m3;
}

/**
* Convert the given entity to the response class.
* Unlike {@link #convertFromEntity(OperationEntity)} also fill proximity OTP value if needed.
*
* @param source Entity to convert.
* @return response class
* @see #convertFromEntity(OperationEntity)
*/
@SneakyThrows(GenericServiceException.class)
private void generateAndSetOtpToOperationDetail(final OperationEntity operation, final OperationDetailResponse operationDetailResponse) {
final String totp = generateTotp(operation, powerAuthServiceConfiguration.getProximityCheckOtpLength());
operationDetailResponse.setProximityOtp(totp);
private OperationDetailResponse convertFromEntityAndFillOtp(final OperationEntity source) {
final String totp = generateTotp(source, powerAuthServiceConfiguration.getProximityCheckOtpLength());
final OperationDetailResponse target = convertFromEntity(source);
target.setProximityOtp(totp);
return target;
}

private String generateTotp(final OperationEntity operation, final int otpLength) throws GenericServiceException {
Expand All @@ -1016,9 +1032,9 @@ private String generateTotp(final OperationEntity operation, final int otpLength
}

try {
byte[] seedBytes = Base64.getDecoder().decode(seed);
final byte[] seedBytes = Base64.getDecoder().decode(seed);
final Instant now = Instant.now();
byte[] totp = Totp.generateTotpSha256(seedBytes, now, otpLength);
final byte[] totp = Totp.generateTotpSha256(seedBytes, now, otpLength);

return new String(totp, StandardCharsets.UTF_8);
} catch (CryptoProviderException | IllegalArgumentException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,50 @@ void testCreateOperationWithActivationId() throws Exception {
request.setUserId(USER_ID);

final OperationDetailResponse operationDetailResponse = operationService.createOperation(request);
final OperationEntity savedEntity = operationRepository.findOperation(operationDetailResponse.getId()).get();
assertTrue(operationRepository.findOperation(operationDetailResponse.getId()).isPresent());
assertEquals(ACTIVATION_ID, savedEntity.getActivationId());
final Optional<OperationEntity> savedEntity = operationRepository.findOperation(operationDetailResponse.getId());

assertTrue(savedEntity.isPresent());
assertEquals(ACTIVATION_ID, savedEntity.get().getActivationId());
assertNull(operationDetailResponse.getProximityOtp());
}

@Test
void testCreateOperationWithoutActivationIdAndExplicitProximityCheck() throws Exception {
final OperationCreateRequest request = new OperationCreateRequest();
request.setTemplateName("test-template");
request.setApplications(List.of(APP_ID));
request.setUserId("test-user");
request.setProximityCheckEnabled(true);

final OperationDetailResponse operationDetailResponse = operationService.createOperation(request);
assertNotNull(operationDetailResponse.getProximityOtp());

final OperationDetailRequest detailRequest = new OperationDetailRequest();
detailRequest.setOperationId(operationDetailResponse.getId());

final OperationDetailResponse operationDetail = operationService.operationDetail(detailRequest);
assertNotNull(operationDetail);
assertNotNull(operationDetail.getProximityOtp());
assertNull(operationDetail.getActivationId());
}

@Test
void testCreateOperationWithoutActivationIdAndImplicitProximityCheck() throws Exception {
final OperationCreateRequest request = new OperationCreateRequest();
request.setTemplateName("test-template-proximity-check");
request.setApplications(List.of(APP_ID));
request.setUserId("test-user");

final OperationDetailResponse operationDetailResponse = operationService.createOperation(request);
assertNotNull(operationDetailResponse.getProximityOtp());

final OperationDetailRequest detailRequest = new OperationDetailRequest();
detailRequest.setOperationId(operationDetailResponse.getId());

final OperationDetailResponse operationDetail = operationService.operationDetail(detailRequest);
assertNotNull(operationDetail);
assertNotNull(operationDetail.getProximityOtp());
assertNull(operationDetail.getActivationId());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
INSERT INTO pa_operation_template (id, template_name, operation_type, data_template, signature_type, max_failure_count, expiration, proximity_check_enabled)
VALUES (100, 'test-template', 'test-template', 'A2', 'POSSESSION_KNOWLEDGE', 5, 300, false);
INSERT INTO pa_operation_template (id, template_name, operation_type, data_template, signature_type, max_failure_count, expiration, proximity_check_enabled) VALUES
(100, 'test-template', 'test-template', 'A2', 'POSSESSION_KNOWLEDGE', 5, 300, false),
(101, 'test-template-proximity-check', 'test-template', 'A2', 'POSSESSION_KNOWLEDGE', 5, 300, true);

INSERT INTO pa_application (id, name) VALUES
(21, 'PA_Tests');
Expand Down

0 comments on commit e17f6f6

Please sign in to comment.