Skip to content

Commit

Permalink
Fix #348: Expired processes are not handled correctly (#357)
Browse files Browse the repository at this point in the history
- Add CleaningTask
- Cascade cleanup
  • Loading branch information
banterCZ authored Sep 8, 2022
1 parent 9929ca8 commit a32c66f
Show file tree
Hide file tree
Showing 18 changed files with 789 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@

package com.wultra.app.onboardingserver.common.database;

import com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus;
import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin;
import com.wultra.app.onboardingserver.common.database.entity.DocumentVerificationEntity;
import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity;
import com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.Date;
import java.util.List;

Expand All @@ -44,19 +45,13 @@ public interface DocumentVerificationRepository extends JpaRepository<DocumentVe
" d.usedForVerification = false, " +
" d.timestampLastUpdated = :timestamp " +
"WHERE d.activationId = :activationId " +
"AND d.status IN (:statuses)")
"AND d.status IN :statuses")
int failVerifications(String activationId, Date timestamp, List<DocumentStatus> statuses);

@Modifying
@Query("UPDATE DocumentVerificationEntity d " +
"SET d.status = com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus.FAILED, " +
" d.usedForVerification = false, " +
" d.errorDetail = :errorMessage, " +
" d.errorOrigin = :errorOrigin, " +
" d.timestampLastUpdated = :timestamp " +
@Query("SELECT d.id FROM DocumentVerificationEntity d " +
"WHERE d.timestampLastUpdated < :cleanupDate " +
"AND d.status IN (:statuses)")
int failExpiredVerifications(Date cleanupDate, Date timestamp, String errorMessage, ErrorOrigin errorOrigin, List<DocumentStatus> statuses);
"AND d.status IN :statuses")
List<String> findExpiredVerifications(Date cleanupDate, List<DocumentStatus> statuses);

@Modifying
@Query("UPDATE DocumentVerificationEntity d " +
Expand All @@ -67,7 +62,7 @@ public interface DocumentVerificationRepository extends JpaRepository<DocumentVe
@Query("SELECT d " +
"FROM DocumentVerificationEntity d " +
"WHERE d.identityVerification = :identityVerification " +
"AND d.status IN (:statuses)")
"AND d.status IN :statuses")
List<DocumentVerificationEntity> findAllDocumentVerifications(IdentityVerificationEntity identityVerification, List<DocumentStatus> statuses);

@Query("SELECT d " +
Expand All @@ -93,4 +88,32 @@ public interface DocumentVerificationRepository extends JpaRepository<DocumentVe
"AND d.usedForVerification = true")
List<DocumentVerificationEntity> findAllWithPhoto(IdentityVerificationEntity identityVerification);

/**
* Return document verification IDs by the given identity verification ID and document statuses.
*
* @param identityVerificationIds identity verification IDs
* @param statuses document statuses
* @return identity verification IDs
*/
@Query("SELECT d.id FROM DocumentVerificationEntity d " +
"WHERE d.identityVerification.id IN :identityVerificationIds " +
"AND d.status IN :statuses")
List<String> findDocumentVerifications(Collection<String> identityVerificationIds, Collection<DocumentStatus> statuses);

/**
* Mark the given document verifications as failed.
*
* @param ids Document verification IDs
* @param timestamp last updated and failed timestamp
* @param errorDetail error detail
* @param errorOrigin error origin
*/
@Modifying
@Query("UPDATE DocumentVerificationEntity d " +
"SET d.status = com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus.FAILED, " +
" d.errorDetail = :errorDetail, " +
" d.errorOrigin = :errorOrigin, " +
" d.timestampLastUpdated = :timestamp " +
"WHERE d.id IN :ids")
void terminate(Collection<String> ids, Date timestamp, String errorDetail, ErrorOrigin errorOrigin);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@

package com.wultra.app.onboardingserver.common.database;

import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin;
import com.wultra.app.onboardingserver.common.database.entity.IdentityVerificationEntity;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -89,4 +91,46 @@ public interface IdentityVerificationRepository extends CrudRepository<IdentityV
" AND id.status = com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationStatus.IN_PROGRESS)"
)
Stream<IdentityVerificationEntity> streamAllIdentityVerificationsToChangeState();


/**
* Return identity verification IDs by the given process ID. Include only not yet finished entities.
*
* @param processIds process IDs
* @return identity verification IDs
*/
@Query("SELECT i.id FROM IdentityVerificationEntity i " +
"WHERE i.processId IN :processIds " +
"AND i.phase <> com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationPhase.COMPLETED")
List<String> findNotCompletedIdentityVerifications(Collection<String> processIds);

/**
* Return identity verification IDs last updated before the given timestamp. Include only not yet finished entities.
*
* @param timestamp last updated older than timestamp
* @return identity verification IDs
*/
@Query("SELECT i.id FROM IdentityVerificationEntity i " +
"WHERE i.timestampLastUpdated < :timestamp " +
"AND i.phase <> com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationPhase.COMPLETED")
List<String> findNotCompletedIdentityVerifications(Date timestamp);

/**
* Mark the given identity verifications as failed.
*
* @param ids Identity verification IDs
* @param timestampExpired last updated and failed timestamp
* @param errorDetail error detail
* @param errorOrigin error origin
*/
@Modifying
@Query("UPDATE IdentityVerificationEntity i SET " +
"i.phase = com.wultra.app.enrollmentserver.model.enumeration.IdentityVerificationPhase.COMPLETED, " +
"i.status = com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FAILED, " +
"i.timestampLastUpdated = :timestampExpired, " +
"i.timestampFailed = :timestampExpired, " +
"i.errorDetail = :errorDetail, " +
"i.errorOrigin = :errorOrigin " +
"WHERE i.id IN :ids")
void terminate(Collection<String> ids, Date timestampExpired, String errorDetail, ErrorOrigin errorOrigin);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@
package com.wultra.app.onboardingserver.common.database;

import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin;
import com.wultra.app.onboardingserver.common.database.entity.OnboardingProcessEntity;
import com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus;
import com.wultra.app.onboardingserver.common.database.entity.OnboardingProcessEntity;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
Expand All @@ -53,27 +55,45 @@ public interface OnboardingProcessRepository extends CrudRepository<OnboardingPr
@Query("SELECT count(p) FROM OnboardingProcessEntity p WHERE p.userId = :userId AND p.timestampCreated > :dateAfter")
int countProcessesAfterTimestamp(String userId, Date dateAfter);

@Modifying
@Query("UPDATE OnboardingProcessEntity p SET " +
"p.status = com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FAILED, " +
"p.timestampLastUpdated = :timestampExpired, " +
"p.timestampFailed = :timestampExpired, " +
"p.errorDetail = :errorDetail, " +
"p.errorOrigin = :errorOrigin " +
/**
* Return onboarding process IDs by the given timestamp and status.
*
* @param dateCreatedBefore timestamp created must be before the given value
* @param status onboarding status
* @return onboarding process IDs
*/
@Query("SELECT p.id FROM OnboardingProcessEntity p " +
"WHERE p.status = :status " +
"AND p.timestampCreated < :dateCreatedBefore")
void terminateExpiredProcessesByStatus(Date dateCreatedBefore, Date timestampExpired, OnboardingStatus status, String errorDetail, ErrorOrigin errorOrigin);
List<String> findExpiredProcessIdsByStatusAndCreatedDate(Date dateCreatedBefore, OnboardingStatus status);

/**
* Mark the given onboarding processes as failed.
*
* @param ids Onboarding process IDs
* @param timestampExpired last updated and failed timestamp
* @param errorDetail error detail
* @param errorOrigin error origin
*/
@Modifying
@Query("UPDATE OnboardingProcessEntity p SET " +
"p.status = com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FAILED, " +
"p.timestampLastUpdated = :timestampExpired, " +
"p.timestampFailed = :timestampExpired, " +
"p.errorDetail = :errorDetail, " +
"p.errorOrigin = :errorOrigin " +
"WHERE p.status <> com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FINISHED " +
"AND p.status <> com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FAILED " +
"AND p.timestampCreated < :dateCreatedBefore")
void terminateExpiredProcesses(Date dateCreatedBefore, Date timestampExpired, String errorDetail, ErrorOrigin errorOrigin);
"WHERE p.id IN :ids")
void terminate(Collection<String> ids, Date timestampExpired, String errorDetail, ErrorOrigin errorOrigin);

}
/**
* Return onboarding process IDs by the given timestamp. Include only not yet finished entities.
*
* @param dateCreatedBefore timestamp created must be before the given value
* @return onboarding process IDs
*/
@Query("SELECT p.id FROM OnboardingProcessEntity p " +
"WHERE p.status <> com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FINISHED " +
"AND p.status <> com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FAILED " +
"AND p.timestampCreated < :dateCreatedBefore")
List<String> findExpiredProcessIdsByCreatedDate(Date dateCreatedBefore);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class OnboardingConfig extends CommonOnboardingConfig {
@Value("${enrollment-server-onboarding.identity-verification.otp.length:8}")
private int otpLength;

@Value("${enrollment-server-onboarding.onboarding-process.expiration:1h}")
@Value("${enrollment-server-onboarding.onboarding-process.expiration:3h}")
private Duration processExpirationTime;

@Value("${enrollment-server-onboarding.onboarding-process.activation.expiration:5m}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ public IdentityVerificationStatusResponse checkIdentityVerificationStatus(Identi

// Check for expiration of onboarding process
if (onboardingService.hasProcessExpired(onboardingProcess)) {
// Trigger immediate processing of expired processes
onboardingService.terminateInactiveProcesses();
response.setIdentityVerificationStatus(FAILED);
response.setIdentityVerificationPhase(IdentityVerificationPhase.COMPLETED);
return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,28 @@
import com.wultra.app.onboardingserver.provider.*;
import io.getlime.core.rest.model.base.response.Response;
import lombok.SneakyThrows;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Optional;

/**
* Service implementing specific behavior for the onboarding process. Shared behavior is inherited from {@link CommonOnboardingService}.
*
* @author Roman Strobl, roman.strobl@wultra.com
*/
@Service
@Slf4j
public class OnboardingServiceImpl extends CommonOnboardingService {

private static final Logger logger = LoggerFactory.getLogger(OnboardingServiceImpl.class);

private static final String IDENTIFICATION_DATA_DATE_FORMAT = "yyyy-MM-dd";

private final OnboardingConfig onboardingConfig;
Expand Down Expand Up @@ -203,8 +202,6 @@ public OnboardingStatusResponse getStatus(OnboardingStatusRequest request) throw

// Check for expiration of onboarding process
if (hasProcessExpired(process)) {
// Trigger immediate processing of expired processes
terminateInactiveProcesses();
response.setOnboardingStatus(OnboardingStatus.FAILED);
return response;
}
Expand Down Expand Up @@ -290,36 +287,6 @@ public OnboardingProcessEntity findProcessByActivationId(String activationId) th
return processOptional.get();
}

/**
* Check for inactive processes and terminate them.
*/
@Transactional
@Scheduled(fixedDelayString = "PT15S", initialDelayString = "PT15S")
@SchedulerLock(name = "terminateInactiveProcesses", lockAtLeastFor = "1s", lockAtMostFor = "5m")
public void terminateInactiveProcesses() {
final Date now = new Date();

// Terminate processes with activations in progress
final Duration activationExpiration = onboardingConfig.getActivationExpirationTime();
final Date createdDateExpiredActivations = DateUtil.convertExpirationToCreatedDate(activationExpiration);
onboardingProcessRepository.terminateExpiredProcessesByStatus(createdDateExpiredActivations, now, OnboardingStatus.ACTIVATION_IN_PROGRESS, OnboardingProcessEntity.ERROR_PROCESS_EXPIRED_ACTIVATION, ErrorOrigin.PROCESS_LIMIT_CHECK);

// Terminate processes with verifications in progress
final Duration verificationExpiration = identityVerificationConfig.getVerificationExpirationTime();
final Date createdDateExpiredVerifications = DateUtil.convertExpirationToCreatedDate(verificationExpiration);
onboardingProcessRepository.terminateExpiredProcessesByStatus(createdDateExpiredVerifications, now, OnboardingStatus.VERIFICATION_IN_PROGRESS, OnboardingProcessEntity.ERROR_PROCESS_EXPIRED_IDENTITY_VERIFICATION, ErrorOrigin.PROCESS_LIMIT_CHECK);

// Terminate OTP codes for all processes
final Duration otpExpiration = onboardingConfig.getOtpExpirationTime();
final Date createdDateExpiredOtp = DateUtil.convertExpirationToCreatedDate(otpExpiration);
otpService.terminateExpiredOtps(createdDateExpiredOtp);

// Terminate expired processes
final Duration processExpiration = onboardingConfig.getProcessExpirationTime();
final Date createdDateExpiredProcesses = DateUtil.convertExpirationToCreatedDate(processExpiration);
onboardingProcessRepository.terminateExpiredProcesses(createdDateExpiredProcesses, now, OnboardingProcessEntity.ERROR_PROCESS_EXPIRED_ONBOARDING, ErrorOrigin.PROCESS_LIMIT_CHECK);
}

/**
* Provide consent text.
*
Expand Down
Loading

0 comments on commit a32c66f

Please sign in to comment.