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

Tech5 Integration - Enrollment updates #2701

Merged
merged 23 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
6 changes: 5 additions & 1 deletion app/src/org/commcare/provider/IdentityCalloutHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.content.Intent;

import org.commcare.android.javarosa.IntentCallout;
import org.commcare.commcaresupportlibrary.BiometricUtils;
import org.commcare.commcaresupportlibrary.identity.IdentityResponseBuilder;
import org.commcare.commcaresupportlibrary.identity.model.IdentificationMatch;
import org.commcare.commcaresupportlibrary.identity.model.MatchResult;
Expand Down Expand Up @@ -36,7 +37,7 @@ public class IdentityCalloutHandler {
private static final String REF_MATCH_STRENGTH = "match_strength";

public static final String GENERALIZED_IDENTITY_PROVIDER = "generalized_identity_provider";

private static final String REF_TEMPLATES = "templates";

@StringDef({GENERALIZED_IDENTITY_PROVIDER, SimprintsCalloutProcessing.SIMPRINTS_IDENTITY_PROVIDER})
@Retention(RetentionPolicy.SOURCE)
Expand Down Expand Up @@ -109,7 +110,10 @@ private static boolean processRegistrationReponse(FormDef formDef,
Hashtable<String, Vector<TreeReference>> responseToRefMap) {
RegistrationResult registrationResult = intent.getParcelableExtra(IdentityResponseBuilder.REGISTRATION);
String guid = registrationResult.getGuid();
String templates = BiometricUtils.convertMapTemplatesToBase64String(registrationResult.getTemplates());

storeValueFromCalloutInForm(formDef, responseToRefMap, intentQuestionRef, REF_GUID, guid);
storeValueFromCalloutInForm(formDef, responseToRefMap, intentQuestionRef, REF_TEMPLATES, templates);
IntentCallout.setNodeValue(formDef, intentQuestionRef, guid);

// Empty out any references present for duplicate handling
Expand Down
2 changes: 2 additions & 0 deletions commcare-support-library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.google.code.gson:gson:2.9.0'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, it's not needed.


testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.commcare.commcaresupportlibrary;

import android.util.Base64;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;

import java.util.HashMap;
import java.util.Map;

public class BiometricUtils {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code formatting is off in the class.


public enum BiometricIdentifier {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potentially worth to put into a separate file of it's own.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RIGHT_THUMB, RIGHT_INDEX_FINGER, RIGHT_MIDDLE_FINGER, RIGHT_RING_FINGER, RIGHT_PINKY_FINGER,
LEFT_THUMB, LEFT_INDEX_FINGER, LEFT_MIDDLE_FINGER, LEFT_RING_FINGER, LEFT_PINKY_FINGER,
FACE, UNKNOWN, INVALID
}

/**
* This method converts the biometrics templates into a Base64 encoded String to send back to
* CommCare, as part of the response of the callout
* @param templates Map containing biometric templates
* @return Base64 encoded string
*/
public static String convertMapTemplatesToBase64String(Map<BiometricUtils.BiometricIdentifier,
byte[]> templates){
// In order to reduce the size of the templates, we are converting each byte array into a
// Base64 encoded String
Map<Integer, String> templatesBase64Encoded = new HashMap<>(templates.size());
for (Map.Entry<BiometricUtils.BiometricIdentifier, byte[]> template : templates.entrySet()) {
templatesBase64Encoded.put(template.getKey().ordinal(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason we are double encoding templates, here and then later again at the end of this method ?

Copy link
Contributor Author

@avazirna avazirna Sep 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because JSON is used to represent the Map object, and considering that the values are byte arrays, the first encoding avoids representing them in JSON, which would increment the size due to the comas used to split the elements.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am struggling to understand how will this gets used in the form. With current design, Seems like we are going to store into form the whole template string instead of storing the individual templates in individula field like Simprint. Any reason we are deviating away from Simprints pattern ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shubham1g5 the principle here is to use CC just to store the biometric templates and in the most simple way, so in a case property. any biometric operations will be preceded by the retrieval and restoration of the templates to their original format.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant why not store individiual templates in separate fields in form like here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more to avoid having to handle different form questions and case properties, when there is no apparent advantage to doing that. From my understanding, we are not doing anything with the biometric templates in CC. But I think that for the sake of standartization we could adopt the same pattern, but I wonder if you see any other gains from this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we store it together and an app is capturing multiple templates, how will they separate this data out in future without writing a script ? I think storing them seprately makes any future data management on those templates much simpler for project teams.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bee8e72b93a3b35d429a6ebc260b5df36b1bfa35

Base64.encodeToString(template.getValue(), Base64.DEFAULT));
}
String templatesInJson = new Gson().toJson(templatesBase64Encoded);
return Base64.encodeToString(templatesInJson.getBytes(), Base64.DEFAULT);
}

/**
* This method converts the Base64 encoded biometric templates to its original form
* @param base64EncodedTemplates String containing Base64 encoded biometric templates
* @return Map containing biometric templates
*/
public static Map<BiometricUtils.BiometricIdentifier, byte[]> convertBase64StringTemplatesToMap(String base64EncodedTemplates){
if (base64EncodedTemplates == null || base64EncodedTemplates.isEmpty()) {
return null;
}

Map<Integer, String> templatesBase64Encoded = null;
try {
String templatesInJson = new String(Base64.decode(base64EncodedTemplates.getBytes(),
Base64.DEFAULT));
templatesBase64Encoded = new Gson().fromJson(templatesInJson,
(new TypeToken<HashMap<Integer, String>>() {}.getType()));
}
catch(IllegalArgumentException | UnsupportedOperationException | JsonSyntaxException e){
return null;
}

Map<BiometricUtils.BiometricIdentifier, byte[]> templates
= new HashMap<>(templatesBase64Encoded.size());
for (Map.Entry<Integer, String> template : templatesBase64Encoded.entrySet()) {
templates.put(BiometricIdentifier.values()[template.getKey()],
Base64.decode(template.getValue(), Base64.DEFAULT));
}
return templates;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import android.app.Activity;
import android.content.Intent;

import org.commcare.commcaresupportlibrary.BiometricUtils;
import org.commcare.commcaresupportlibrary.identity.model.IdentificationMatch;
import org.commcare.commcaresupportlibrary.identity.model.MatchResult;
import org.commcare.commcaresupportlibrary.identity.model.RegistrationResult;
import org.commcare.commcaresupportlibrary.identity.model.VerificationMatch;

import java.util.ArrayList;
import java.util.Map;

import static android.app.Activity.RESULT_OK;

Expand Down Expand Up @@ -41,6 +43,18 @@ public static IdentityResponseBuilder registrationResponse(String guid) {
return new IdentityResponseBuilder(intent);
}

/**
* Creates response for result of a new Identity Registration with biometric templates
*
* @param templates data captured as part of the new registration in the Identity Provider
* @return IdentityResponseBuilder for a registration workflow response
*/
public static IdentityResponseBuilder registrationResponse(String guid, Map<BiometricUtils.BiometricIdentifier, byte[]> templates) {
Intent intent = new Intent();
intent.putExtra(REGISTRATION, new RegistrationResult(guid, templates));
return new IdentityResponseBuilder(intent);
}

/**
* Creates response for result of a identification workflow
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

import android.os.Parcel;
import android.os.Parcelable;
import org.commcare.commcaresupportlibrary.BiometricUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("unused")
public class RegistrationResult implements Parcelable {

private String guid;
private Map<BiometricUtils.BiometricIdentifier, byte[]> templates;

/**
* Result of the identity enrollment workflow
Expand All @@ -15,10 +20,25 @@ public class RegistrationResult implements Parcelable {
*/
public RegistrationResult(String guid) {
this.guid = guid;
this.templates = new HashMap<>(0);
}

public RegistrationResult(String guid, Map<BiometricUtils.BiometricIdentifier, byte[]> templates) {
this.guid = guid;
this.templates = templates;
}

protected RegistrationResult(Parcel in) {
guid = in.readString();
int numTemplates = in.readInt();
templates = new HashMap<>(numTemplates);
for (int i=0;i < numTemplates; i++){
BiometricUtils.BiometricIdentifier biometricIdentifier = BiometricUtils.BiometricIdentifier.values()[in.readInt()];
int templateSize = in.readInt();
byte[] template = new byte[templateSize];
in.readByteArray(template);
templates.put(biometricIdentifier, template);
}
}

public static final Creator<RegistrationResult> CREATOR = new Creator<RegistrationResult>() {
Expand All @@ -41,12 +61,26 @@ public int describeContents() {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(guid);
dest.writeInt(getNumberOfTemplates());
for (Map.Entry<BiometricUtils.BiometricIdentifier, byte[]> template : templates.entrySet()){
dest.writeInt(template.getKey().ordinal());
dest.writeInt(template.getValue().length);
dest.writeByteArray(template.getValue());
}
}

public String getGuid() {
return guid;
}

public Map<BiometricUtils.BiometricIdentifier, byte[]> getTemplates() {
return templates;
}

public int getNumberOfTemplates() {
return templates.size();
}

@Override
public boolean equals(Object o) {
if (o == this) {
Expand All @@ -59,12 +93,25 @@ public boolean equals(Object o) {
if (!guid.equals(other.guid)) {
return false;
}
if (getNumberOfTemplates() != other.getNumberOfTemplates()){
return false;
}

for (Map.Entry<BiometricUtils.BiometricIdentifier, byte[]> template : templates.entrySet()){
byte[] otherTemplate = other.getTemplates().get(template.getKey());
if (!Arrays.equals(template.getValue(), otherTemplate)) {
return false;
}
}
return true;
}

@Override
public int hashCode() {
int hash = guid.hashCode();
for (Map.Entry<BiometricUtils.BiometricIdentifier, byte[]> template : templates.entrySet()){
hash += Arrays.hashCode(template.getValue());
}
return hash;
}
}