Skip to end of metadata
Go to start of metadata

You are viewing an old version of this content. View the current version.

Compare with Current Restore this Version View Version History

« Previous Version 10 Next »

Implementation:

The match-sdk library is implemented in reference to the mock-sdk, which provides the biometric match validation APIs. The match-sdk is built for Android to ensure compatibility with the Android reg client Android app. Some of the classes were reused, while others were modified to be compatible with Android. The Spring-related annotations and code have been replaced with Android annotations.

The match-sdk library provides the implementation in the MatchSDK class, which implements the methods and properties of the IBioApiV2 interface. The match method is as follows:

@Override

public Response<MatchDecision[]> match(BiometricRecord sample, BiometricRecord[] gallery, List<BiometricType> modalitiesToMatch, Map<String, String> flags) {

MatchService service = new MatchService(sample, gallery, modalitiesToMatch, flags);
return service.getMatchDecisionInfo();

}

 

This method requires the biometric record of the current applicant and the biometric records of existing operators based on the modality to validate the duplication of biometric data between the current applicant and operators.

The match-sdk internally utilizes the dependencies kernel-core, kernel-biometric-api, kernel-bio-converter, and biometric-util to implement match validation.

Integration with the Android reg client app:

Option 1: Add match-sdk .aar file as library and add the local path dependency in build gradle.

Option 2: Add dependency in build gradle to get the match-sdk lib remotely. This requires match-sdk to be published to remote repo for example: maven central.

For now, Option 1 is being followed to verify the correctness of the match-sdk and its implementation. It is integrated as follows:

The matchsdk-debug.aar or matchsdk-release.aar file is added to the libs folder of the module in the Android reg client app where the match-sdk validation needs to be invoked. In the client manager module, we are using it for comparing the biometric data of the applicant against the biometric data of the operators.

image-20240702-054028.png

Adding this to client manager build.gradle as:

dependencies {
    . . .

    implementation files('libs/matchsdk-debug.aar')

    . . .
}

In the code where the match logic must be called, we need to add:

private IBioApiV2 iBioApiV2;

To perform the validation, it is necessary to invoke the match() method of the match-sdk. This can be achieved by initializing a MatchSDK() object in the constructor of the Biometric095Service class, where the match logic is required.

  • this.iBioApiV2 = new MatchSDK();
  • match method will be called as

Response<MatchDecision[]> response = iBioApiV2.match(biometricRecord, biometricRecords, biometricTypes, new HashMap<>());

The code to validate the biometric data is added in the below method of Biometric095Service class.

public List<BiometricsDto> handleRCaptureResponse(Modality modality, InputStream response, List<String> exceptionAttributes) throws BiometricsServiceException {

. . .
if(RegistrationConstants.ENABLE.equalsIgnoreCase(this.globalParamRepository.getCachedStringGlobalParam(RegistrationConstants.DEDUPLICATION_ENABLE_FLAG))) {
    boolean isMatched = MatchUtil.validateBiometricData(modality, captureDto, biometricsDtoList, userBiometricRepository, iBioApiV2);
    if(isMatched){
         Log.i(TAG, "Biometrics Matched With Operator Biometrics, Please Try Again");
         return null;
    }
  }
}

MatchUtil class is created to have the validate biometric logic and provide required methods and properties. The validateBiometricData() method is called based on the modality and biometric data of current applicant and operator biometrics.  

 

/**
Responsible for validating the biometric records
@param modality {@link Modality}
@param captureDto {@link CaptureDto}
@param biometricsDtoList {@link List<BiometricsDto>}
@param userBiometricRepository {@link UserBiometricRepository}
@param iBioApiV2 {@link IBioApiV2}
@return boolean value based on the match result
*/
 public static boolean validateBiometricData(Modality modality, CaptureDto captureDto, List<BiometricsDto> biometricsDtoList, UserBiometricRepository userBiometricRepository, IBioApiV2 iBioApiV2) {
     BiometricType biometricType = BiometricType.fromValue(modality == Modality.EXCEPTION_PHOTO ?modality.getSingleType().value() : captureDto.getBioType());
     String lowerCase = biometricType.toString().toLowerCase();
     String biometricCode = StringUtils.capitalizeFirstLetter(lowerCase);
     List<UserBiometric> userBiometrics = userBiometricRepository.findAllOperatorBiometrics(biometricCode);
     if(userBiometrics.isEmpty()){
         return false;
     }
     return matchBiometrics(biometricType, userBiometrics, biometricsDtoList, iBioApiV2);
}

This method internally calls the matchBiometrics() method to check the match between the operator biometrics and applicant biometric.

/**
Converts the {@link UserBiometric} and {@link BiometricsDto} to {@link BiometricRecord} and
{@link BiometricRecord array} respectively and calls the match method of Match-SDK library
to get the response of the decisions whether {@link Match MATCHED or NOT_MATCHED}
@param biometricType {@link BiometricType}
@param userBiometrics {@link UserBiometric}
@param biometricsDto {@link BiometricsDto}
@param iBioApiV2 {@link IBioApiV2}
@return boolean value matched or not based on the match result flag Match.MATCHED or Match.NOT_MATCHED.
*/

    private static boolean matchBiometrics(BiometricType biometricType, List<UserBiometric> userBiometrics, List<BiometricsDto> biometricsDto, IBioApiV2 iBioApiV2) {
        Map<String, List<BIR>> birMap = new HashMap<>();

        for (UserBiometric userBiometric : userBiometrics) {
            String userId = userBiometric.getUsrId();
            birMap.computeIfAbsent(userId, k -> new ArrayList<>())
                    .add(buildBir(userBiometric.getBioAttributeCode(),
                     userBiometric.getQualityScore(), userBiometric.getBioTemplate(), biometricType, ProcessedLevelType.PROCESSED, true));
        }

        List<BIR> birList = new ArrayList<>(biometricsDto.size());
        biometricsDto.forEach(biometricDto -> {
            birList.add(buildBir(biometricDto.getBioSubType(),
                    (long) biometricDto.getQualityScore(),
                    CryptoUtil.base64decoder.decode(biometricDto.getBioValue()), biometricType,
                    ProcessedLevelType.RAW, false));
        });

        BiometricRecord biometricRecord = new BiometricRecord();
        biometricRecord.getSegments().addAll(birList);

        List<BiometricRecord> biometricRecordList = new ArrayList<>();
        for(List<BIR> birListValue: birMap.values()){
            BiometricRecord biometricRecordValue = new BiometricRecord();
            biometricRecordValue.getSegments().addAll(birListValue);
            biometricRecordList.add(biometricRecordValue);
        }
        BiometricRecord [] biometricRecords = biometricRecordList.toArray(new BiometricRecord[0]);

        List<BiometricType> biometricTypes = new ArrayList<>();
        biometricTypes.add(biometricType);

        try {
            Response<MatchDecision[]> response = iBioApiV2.match(biometricRecord, biometricRecords, biometricTypes, new HashMap<>());
            if (response != null && response.getResponse() != null)
            {
                Match decision = Objects.requireNonNull(response.getResponse()[0].getDecisions().get(biometricType)).
                        getMatch();
                if(decision.equals(Match.MATCHED)){
                    return true;
                }
            }
        } catch (Exception e) {
            Log.e("Failed in dedupe check >> ", e.getMessage());
        }
        return false;
    }

Design consideration:

The kernel-biometric-api library is obtained from the bio-utils repository's develop branch - bio-utils/kernel-biometrics-api

The inclusion of kernel-biometric-api in the Android reg client app, in order to reference the IBioApiV2 interface, is implemented to prevent transitive dependency errors caused by the lombok and jackson libraries. This is necessary because those libraries were referencing older versions of the same dependencies.

Here are the specific changes done to the pom file:

<properties>
    ...
    <java.version>17</java.version> <!--changed to 17-->
	...
	<!--newly added versions-->
	<jackson.version>2.15.0</jackson.version> 
	<lombok.version>1.18.24</lombok.version>
	<junit.version>5.10.3</junit.version>
	...
	<!--<kernel.bom.version>1.2.1-SNAPSHOT</kernel.bom.version>--> <!--commented-->
	<kernel.core.version>1.2.0.1</kernel.core.version> <!--1.2.1-SNAPSHOT-->
</properties>	

<!-- Commented this section, kernel-bom not available to get downloaded as dependency
    <dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>io.mosip.kernel</groupId>
				<artifactId>kernel-bom</artifactId>
				<version>${kernel.bom.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>-->
	
    <dependencies>
		...
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>${lombok.version}</version> <!--version, newly added-->
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${jackson.version}</version> <!--version, newly added-->
		</dependency>
		<dependency>
			<groupId>org.junit.vintage</groupId>
			<artifactId>junit-vintage-engine</artifactId>
			<version>${junit.version}</version> <!--version, newly added-->
		</dependency>
	</dependencies>