Skip to content

Commit

Permalink
Android: Fix #253: Don't allow simultaneous biometric auth. requests.
Browse files Browse the repository at this point in the history
  • Loading branch information
hvge committed Oct 25, 2019
1 parent 413da89 commit d7dae5c
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 4 deletions.
Expand Up @@ -41,6 +41,7 @@
import io.getlime.security.powerauth.networking.interfaces.ICancelable;
import io.getlime.security.powerauth.sdk.impl.CancelableTask;
import io.getlime.security.powerauth.sdk.impl.DefaultCallbackDispatcher;
import io.getlime.security.powerauth.system.PA2Log;

/**
* The {@code BiometricAuthentication} class is a high level interface that provides interfaces related
Expand Down Expand Up @@ -111,11 +112,25 @@ public static boolean isBiometricAuthenticationAvailable(@NonNull final Context
@NonNull final BiometricAuthenticationRequest request,
@NonNull final IBiometricAuthenticationCallback callback) {
synchronized (SharedContext.class) {
// Acquire authenticator from the context
// Check whether there's already pending authentication request.
final SharedContext ctx = getContext();
if (!ctx.startBiometricAuthentication()) {
// There's already pending biometric authentication request.
return reportSimultaneousRequest(callback);
}

// Acquire authenticator from the shared context
final IBiometricAuthenticator device = ctx.getAuthenticator(context);
// Prepare essential authentication request data
final BiometricResultDispatcher dispatcher = new BiometricResultDispatcher(callback, new DefaultCallbackDispatcher());
final BiometricResultDispatcher dispatcher = new BiometricResultDispatcher(callback, new DefaultCallbackDispatcher(), new BiometricResultDispatcher.IResultCompletion() {
@Override
public void onCompletion() {
// Clear the pending request flag.
synchronized (SharedContext.class) {
ctx.finishPendingBiometricAuthentication();
}
}
});
final PrivateRequestData requestData = new PrivateRequestData(request, dispatcher, ctx.getBiometricDialogResources());

// Validate request status
Expand Down Expand Up @@ -235,6 +250,33 @@ public void onCancel() {
return cancelableTask;
}

/**
* Report cancel to provided callback in case that this is the simultaneous biometric authentication request.
* @param callback Callback to report the cancel.
* @return Dummy {@link ICancelable} object that does nothing.
*/
private static ICancelable reportSimultaneousRequest(@NonNull final IBiometricAuthenticationCallback callback) {
PA2Log.e("Cannot execute multiple biometric authentication requests. This request is going to be canceled.");
// Report cancel to the main thread.
new DefaultCallbackDispatcher().dispatchCallback(new Runnable() {
@Override
public void run() {
// Report cancel.
callback.onBiometricDialogCancelled(false);
}
});
// Return dummy cancelable object.
return new ICancelable() {
@Override
public void cancel() {
}

@Override
public boolean isCancelled() {
return true;
}
};
}

/**
* Sets shared {@link BiometricDialogResources} object to this class. You can use this method
Expand Down Expand Up @@ -308,6 +350,11 @@ private static class SharedContext {
*/
private boolean isBiometricPromptAuthenticationDisabled = false;

/**
* Contains {@code true} in case that there's already pending biometric authentication.
*/
private boolean isPendingBiometricAuthentication = false;

private SharedContext() {
biometricDialogResources = new BiometricDialogResources.Builder().build();
authenticator = null;
Expand Down Expand Up @@ -377,6 +424,27 @@ IBiometricAuthenticator getAuthenticator(@NonNull final Context context) {
authenticator = new DummyBiometricAuthenticator();
return authenticator;
}

/**
* Check whether there's a pending biometric authentication request. If no, then start
* a new one.
*
* @return {@code false} if there's already pending biometric authentication request.
*/
boolean startBiometricAuthentication() {
if (isPendingBiometricAuthentication) {
return false;
}
isPendingBiometricAuthentication = true;
return true;
}

/**
* Finish previously started biometric authentication request.
*/
void finishPendingBiometricAuthentication() {
isPendingBiometricAuthentication = false;
}
}

/**
Expand Down
Expand Up @@ -31,13 +31,32 @@
*/
public class BiometricResultDispatcher {

/**
* Interface used as a callback that the biometric request has been completed. The interface should be used
* only internally, to cleanup the state of {@link io.getlime.security.powerauth.biometry.BiometricAuthentication}
* shared instance.
*/
public interface IResultCompletion {
/**
* Called on any kind of completion (e.g. on success, failure or cancel)
*/
void onCompletion();
}

private @NonNull final IResultCompletion resultCompletion;
private @NonNull final IBiometricAuthenticationCallback callback;
private @NonNull final ICallbackDispatcher callbackDispatcher;
private @NonNull final CancelableTask cancelable;
private @Nullable CancelableTask.OnCancelListener onCancelListener;
private boolean isDispatched = false;

public BiometricResultDispatcher(@NonNull final IBiometricAuthenticationCallback callback, @NonNull final ICallbackDispatcher callbackDispatcher) {
/**
* @param callback Callback to the application. It's always executed after the {@link #resultCompletion}.
* @param callbackDispatcher The dispatcher that executes completion for both {{@link IBiometricAuthenticationCallback} and {{@link IResultCompletion}} interfaces.
* @param completion Callback back to the {@link io.getlime.security.powerauth.biometry.BiometricAuthentication} object that notifies that request has been processed
*/
public BiometricResultDispatcher(@NonNull final IBiometricAuthenticationCallback callback, @NonNull final ICallbackDispatcher callbackDispatcher, @NonNull IResultCompletion completion) {
this.resultCompletion = completion;
this.callback = callback;
this.callbackDispatcher = callbackDispatcher;
this.cancelable = new CancelableTask(new CancelableTask.OnCancelListener() {
Expand All @@ -50,11 +69,13 @@ public void onCancel() {
public void run() {
if (!isDispatched) {
isDispatched = true;
// Report request as completed.
resultCompletion.onCompletion();
// Call additional on-cancel listener first
if (onCancelListener != null) {
onCancelListener.onCancel();
}
// Now call result callback
// Now call back to the application.
callback.onBiometricDialogCancelled(false);
}
}
Expand Down Expand Up @@ -143,6 +164,9 @@ public void run() {
// Only one result can be reported back to the application.
if (!cancelable.isCancelled() && !isDispatched) {
isDispatched = true;
// Report request as completed.
resultCompletion.onCompletion();
// Now call back to the application.
runnable.run();
}
}
Expand Down

0 comments on commit d7dae5c

Please sign in to comment.