Commit a8101b7f authored by Claudio Marforio's avatar Claudio Marforio
Browse files

Update SDK to version 1.2.0, which adds offline QR Code functionality. Update Demo App and README

parent 7f111082
......@@ -48,7 +48,7 @@ dependencies {
implementation 'com.google.firebase:firebase-core:16.0.7'
implementation 'com.google.firebase:firebase-messaging:17.4.0'
implementation('com.futurae.sdk:futuraekit:1.1.1')
implementation('com.futurae.sdk:futuraekit:1.2.0')
// Use these if you are NOT using Maven
// implementation 'com.squareup.retrofit2:retrofit:2.4.0'
......
......@@ -50,6 +50,8 @@ public class FTRQRCodeActivity extends AppCompatActivity implements QRCapturable
public static final int RESULT_BARCODE = 10000;
public static final int RESULT_BARCODE_AUTH = 20000;
public static final int RESULT_BARCODE_OFFLINE = 30000;
public static final int RESULT_BARCODE_GENERIC = 40000;
public static final String PARAM_BARCODE = "ftr_barcode";
public static final String PARAM_AUTOFOCUS = "ftr_autofocus";
......
......@@ -22,6 +22,7 @@ import com.futurae.futuraedemo.R;
import com.futurae.sdk.FuturaeCallback;
import com.futurae.sdk.FuturaeClient;
import com.futurae.sdk.FuturaeResultCallback;
import com.futurae.sdk.MalformedQRCodeException;
import com.futurae.sdk.approve.ApproveSession;
import com.futurae.sdk.model.Account;
import com.futurae.sdk.model.ApproveInfo;
......@@ -55,6 +56,12 @@ public class MainActivity extends AppCompatActivity {
case FTRQRCodeActivity.RESULT_BARCODE_AUTH:
onAuthQRCodeScanned(data);
break;
case FTRQRCodeActivity.RESULT_BARCODE_OFFLINE:
onOfflineAuthQRCodeScanned(data);
break;
case FTRQRCodeActivity.RESULT_BARCODE_GENERIC:
onQRCodeScanned(data);
break;
}
}
}
......@@ -96,17 +103,17 @@ public class MainActivity extends AppCompatActivity {
String userId = FuturaeClient.getUserIdFromUri(uriCall);
String sessionToken = FuturaeClient.getSessionTokenFromUri(uriCall);
FuturaeClient.sharedClient().sessionInfoByToken(userId, sessionToken,
new FuturaeResultCallback<SessionInfo>() {
@Override
public void success(SessionInfo sessionInfo) {
showApproveAlertDialog(new ApproveSession(sessionInfo), true);
}
new FuturaeResultCallback<SessionInfo>() {
@Override
public void success(SessionInfo sessionInfo) {
showApproveAlertDialog(new ApproveSession(sessionInfo), true);
}
@Override
public void failure(Throwable t) {
Log.e(TAG, "QR Code authentication failed: " + t.getLocalizedMessage());
}
});
@Override
public void failure(Throwable t) {
Log.e(TAG, "QR Code authentication failed: " + t.getLocalizedMessage());
}
});
return;
}
......@@ -149,13 +156,6 @@ public class MainActivity extends AppCompatActivity {
});
}
@OnClick(R.id.main_btn_qr_code_auth)
protected void onQRCodeAuth() {
Log.i(TAG, "QR Code factor authentication started");
startActivityForResult(FTRQRCodeActivity.getIntent(this, true, false),
FTRQRCodeActivity.RESULT_BARCODE_AUTH);
}
@OnClick(R.id.main_btn_totp)
protected void onTOTPAuth() {
List<Account> accounts = FuturaeClient.sharedClient().getAccounts();
......@@ -170,6 +170,26 @@ public class MainActivity extends AppCompatActivity {
showAlert("TOTP", "Code: " + totp.getPasscode() + "\nRemaining seconds: " + totp.getRemainingSecs());
}
@OnClick(R.id.main_btn_qr_code_auth)
protected void onQRCodeAuth() {
Log.i(TAG, "QR Code factor authentication started");
startActivityForResult(FTRQRCodeActivity.getIntent(this, true, false),
FTRQRCodeActivity.RESULT_BARCODE_AUTH);
}
@OnClick(R.id.main_btn_qr_code_offline_auth)
protected void onOfflineQRCodeAuth() {
Log.i(TAG, "Offline QR Code factor authentication started");
startActivityForResult(FTRQRCodeActivity.getIntent(this, true, false),
FTRQRCodeActivity.RESULT_BARCODE_OFFLINE);
}
@OnClick(R.id.main_btn_qr_code_generic)
protected void onQRCodeGeneric() {
Log.i(TAG, "Generic QR Code started");
startActivityForResult(FTRQRCodeActivity.getIntent(this, true, false), FTRQRCodeActivity.RESULT_BARCODE_GENERIC);
}
// QRCode callbacks
private void onEnrollQRCodeScanned(Intent data) {
// TODO: Handle enrollment response
......@@ -177,38 +197,67 @@ public class MainActivity extends AppCompatActivity {
Barcode qrcode = data.getParcelableExtra(FTRQRCodeActivity.PARAM_BARCODE);
Log.i(TAG, "Scanned activation code from the QR code; will enroll device");
FuturaeClient.sharedClient().enroll(qrcode.rawValue,
new FuturaeCallback() {
@Override
public void success() {
Log.i(TAG, "Enrollment successful");
showAlert("Success", "Enrollment successful");
}
new FuturaeCallback() {
@Override
public void success() {
Log.i(TAG, "Enrollment successful");
showAlert("Success", "Enrollment successful");
}
@Override
public void failure(Throwable throwable) {
Log.e(TAG, "Enrollment failed: " + throwable.getLocalizedMessage());
showAlert("Error", "Enrollment failed");
}
});
@Override
public void failure(Throwable throwable) {
Log.e(TAG, "Enrollment failed: " + throwable.getLocalizedMessage());
showAlert("Error", "Enrollment failed");
}
});
}
private void onAuthQRCodeScanned(Intent data) {
Barcode qrcode = data.getParcelableExtra(FTRQRCodeActivity.PARAM_BARCODE);
String userId = FuturaeClient.getUserIdFromQrcode(qrcode.rawValue);
String sessionToken = FuturaeClient.getSessionTokenFromQrcode(qrcode.rawValue);
Log.i(TAG, "Scanned online QR Code");
String userId = null;
String sessionToken = null;
try {
userId = FuturaeClient.getUserIdFromQrcode(qrcode.rawValue);
sessionToken = FuturaeClient.getSessionTokenFromQrcode(qrcode.rawValue);
} catch (MalformedQRCodeException e) {
e.printStackTrace();
return;
}
FuturaeClient.sharedClient().sessionInfoByToken(userId, sessionToken,
new FuturaeResultCallback<SessionInfo>() {
@Override
public void success(SessionInfo sessionInfo) {
showApproveAlertDialog(new ApproveSession(sessionInfo), false);
}
new FuturaeResultCallback<SessionInfo>() {
@Override
public void success(SessionInfo sessionInfo) {
showApproveAlertDialog(new ApproveSession(sessionInfo), false);
}
@Override
public void failure(Throwable t) {
Log.e(TAG, "QR Code authentication failed: " + t.getLocalizedMessage());
}
});
@Override
public void failure(Throwable t) {
Log.e(TAG, "QR Code authentication failed: " + t.getLocalizedMessage());
}
});
}
private void onOfflineAuthQRCodeScanned(Intent data) {
Barcode qrcode = data.getParcelableExtra(FTRQRCodeActivity.PARAM_BARCODE);
showOfflineQrcodeDialog(qrcode.rawValue);
}
private void onQRCodeScanned(Intent data) {
Barcode qrcode = data.getParcelableExtra(FTRQRCodeActivity.PARAM_BARCODE);
Log.i(TAG, "Scanned QR code checking which type it is");
switch (FuturaeClient.getQrcodeType(qrcode.rawValue)) {
case FuturaeClient.QR_ENROLL:
this.onEnrollQRCodeScanned(data);
break;
case FuturaeClient.QR_ONLINE:
this.onAuthQRCodeScanned(data);
break;
case FuturaeClient.QR_OFFLINE:
this.onOfflineAuthQRCodeScanned(data);
break;
}
}
// Approve dialog
......@@ -269,6 +318,69 @@ public class MainActivity extends AppCompatActivity {
approveDialog.show();
}
void showOfflineQrcodeSignatureDialog(final String signature) {
// TODO: For demo pusposed, we simply show the signature of the OfflineQRCode
StringBuffer sb = new StringBuffer();
sb.append("To Approve the transaction, enter: " + signature + " in the browser");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Confirm Transaction");
builder.setMessage(sb.toString());
builder.setPositiveButton("Done", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// nothing
}
});
approveDialog = builder.create();
approveDialog.show();
}
void showOfflineQrcodeDialog(final String qrCode) {
// TODO: For demo purposes we simply show an alert
ApproveInfo[] extras = new ApproveInfo[0];
try {
extras = FuturaeClient.getExtraInfoFromOfflineQrcode(qrCode);
} catch (MalformedQRCodeException e) {
e.printStackTrace();
return;
}
StringBuffer sb = new StringBuffer();
if (extras != null) {
sb.append("\n");
for (ApproveInfo info : extras) {
sb.append(info.getKey()).append(": ").append(info.getValue()).append("\n");
}
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Approve");
builder.setMessage("Request Information" + sb.toString());
builder.setPositiveButton("Approve", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
String verificationSignature = "";
try {
verificationSignature = FuturaeClient.sharedClient().computeVerificationCodeFromQrcode(qrCode);
} catch (MalformedQRCodeException e) {
e.printStackTrace();
}
showOfflineQrcodeSignatureDialog(verificationSignature);
}
});
builder.setNegativeButton("Deny", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// nothing to do
}
});
approveDialog = builder.create();
approveDialog.show();
}
// private
private void checkAndAskForPermission(final String permission, final String message, final int requestID) {
final Activity activity = this;
......@@ -353,7 +465,7 @@ public class MainActivity extends AppCompatActivity {
case NotificationUtils.INTENT_APPROVE_CANCEL_MESSAGE:
if (approveDialog != null && approveDialog.isShowing()) {
approveDialog.dismiss();
approveDialog.dismiss();
}
break;
......
......@@ -24,6 +24,14 @@
android:layout_gravity="center"
android:text="@string/main_btn_logout" />
<Button
android:id="@+id/main_btn_totp"
style="@style/AppTheme.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/main_btn_totp" />
<Button
android:id="@+id/main_btn_qr_code_auth"
style="@style/AppTheme.Borderless"
......@@ -34,11 +42,19 @@
/>
<Button
android:id="@+id/main_btn_totp"
android:id="@+id/main_btn_qr_code_offline_auth"
style="@style/AppTheme.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/main_btn_totp" />
android:text="@string/main_btn_qr_code_offline_auth" />
<Button
android:id="@+id/main_btn_qr_code_generic"
style="@style/AppTheme.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/main_btn_qr_code_generic" />
</LinearLayout>
......@@ -4,8 +4,10 @@
<!--MAIN-->
<string name="main_btn_enroll">Enroll</string>
<string name="main_btn_qr_code">Scan to Enroll</string>
<string name="main_btn_qr_code_auth">QR Code Auth</string>
<string name="main_btn_totp">TOTP Auth</string>
<string name="main_btn_qr_code_auth">Online QR Code Auth</string>
<string name="main_btn_qr_code_offline_auth">Offline QR Code Auth</string>
<string name="main_btn_qr_code_generic">Scan QR Code</string>
<!--QR CODE-->
<string name="qrcode_title">QR Code</string>
......
......@@ -17,23 +17,25 @@ This is the Android SDK of Futurae. You can read more about Futurae™ at [futur
* [Push Notifications](#push-notifications)
* [FCM Token Registration](#fcm-token-registration)
* [FCM Listener Service](#fcm-listener-service)
* [Local Intents](#local-intents)
* [Local Intents](#local-intents)
* [QR Codes](#qr-codes)
* [Enroll User](#enroll-user)
* [Logout User](#logout-user)
* [Account Status](#account-status)
* [Authenticate User](#authenticate-user)
* [QR Code Factor](#qr-code-factor)
* [Online QR Code Factor](#online-qr-code-factor)
* [Push Notification Factor](#push-notification-factor)
* [Approve Authentication](#approve-authentication)
* [Reject Authentication](#reject-authentication)
* [Offline QR Code Factor](#offline-qr-code-factor)
* [TOTP Factor](#totp-factor)
* [Session Information](#session-information)
## <a id="basic-integration" />Basic integration
## Basic integration
We will describe the steps to integrate the FuturaeKit SDK into your Android project. We are going to assume that you are using Android Studio for your development.
### <a id="get-futuraekit-sdk-for-android-via-maven" />Get FuturaeKit SDK for Android via Maven
### Get FuturaeKit SDK for Android via Maven
The *preferred* way to get the FuturaeKit SDK fro Android is through maven and the Futurae private repository.
To do so add the following lines to your `build.gradle` file:
......@@ -56,7 +58,7 @@ dependencies {
Of course, make sure to specify the correct version number.
### <a id="get-futuraekit-sdk-for-android-via-maven" />Get FuturaeKit SDK for Android Manually
### Get FuturaeKit SDK for Android Manually
Alternatively, *although discouraged*, you can download the latest SDK from the [releases](https://git.futurae.com/futurae-public/futurae-android-sdk/tags), or clone this repository directly.
This repository also contains a simple demo app to show how the SDK can be integrated.
......@@ -77,20 +79,20 @@ implementation files('src/main/libs/futuraekit.aar')
And in the projects `build.gradle` adjust the repositories to include the `libs` folder:
```
allprojects {
repositories {
google()
jcenter()
flatDir {
dirs 'src/main/libs'
}
repositories {
google()
jcenter()
flatDir {
dirs 'src/main/libs'
}
}
}
```
![][gradle-project]
### <a id="add-permissions" />Add permissions
### Add permissions
Please add the following permissions, which the FuturaeKit SDK needs, if they are not already present in your AndroidManifest.xml file:
```
......@@ -101,12 +103,12 @@ Please add the following permissions, which the FuturaeKit SDK needs, if they ar
<uses-feature android:name="android.hardware.camera" />
<meta-data
android:name="com.google.android.gms.vision.DEPENDENCIES"
android:value="barcode"/>
android:name="com.google.android.gms.vision.DEPENDENCIES"
android:value="barcode"/>
```
### <a id="basic-setup" />Basic setup
### Basic setup
We recommend using an android [Application][android_application] class to initialize the SDK. If you already have one in your app already, follow these steps:
Firstly, in your `Application` class find or create the `onCreate` method and add the following code to initialize the FuturaeKit SDK:
......@@ -116,14 +118,13 @@ import com.futurae.sdk.FuturaeClient;
public class AppMain extends Application {
// overrides
@Override
public final void onCreate() {
super.onCreate();
// overrides
@Override
public final void onCreate() {
super.onCreate();
FuturaeClient.launch(this);
}
FuturaeClient.launch(this);
}
}
```
......@@ -134,81 +135,81 @@ Secondly, in your `res/values` folder make sure to create a file `futurae.xml` w
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ftr_sdk_id">{FuturaeSdkId}</string>
<string name="ftr_sdk_key">{FuturaeSdkKey}</string>
<string name="ftr_base_url">https://api.futurae.com:443</string>
<string name="ftr_sdk_id">{FuturaeSdkId}</string>
<string name="ftr_sdk_key">{FuturaeSdkKey}</string>
<string name="ftr_base_url">https://api.futurae.com:443</string>
</resources>
```
**Note**: Initializing the FuturaeKit SDK like this is `very important`. Replace `{FuturaeSdkId}` and `{FuturaeSdkKey}` with your SDK ID and key.
### <a id="build-your-app" />Build your app
### Build your app
Build and run your app. If the build succeeds, you should carefully read the SDK logs in the console.
### <a id="r8-proguard" />R8 / ProGuard
### R8 / ProGuard
If you are using R8 or ProGuard to obfuscate your app, you need to include the proguard rules of the [futurae.pro](https://git.futurae.com/futurae-public/futurae-android-sdk/-/blob/master/FuturaeDemo/app/proguard/futurae.pro) file. See the [build.gradle](https://git.futurae.com/futurae-public/futurae-android-sdk/-/blob/master/FuturaeDemo/app/build.gradle) file of the FuturaeDemo app, as an example on how to do this.
## <a id="features" />Features
### <a id="callbacks" />Callbacks
## Features
### Callbacks
The SDK methods that perform API calls use callbacks as the feedback mechanism. These calls expect an object of the `FuturaeCallback` interface as an argument:
```java
public interface FuturaeCallback {
void success();
void failure(Throwable throwable);
void success();
void failure(Throwable throwable);
}
```
### <a id="uri-schemes" />URI Schemes
### URI Schemes
The SDK is able to handle URI scheme calls, which can be used to either **enroll** or **authenticate** users.
Once your activity has been set up to handle the URI scheme call intents, get the intent data in the `onCreate()` method of your activity, which contains the URI that should be passed in the SDK, using the `handleUri()` method:
```java
FuturaeClient.sharedClient().handleUri(uriString, new FuturaeCallback() {
@Override
public void success() {
@Override
public void success() {
}
}
@Override
public void failure(Throwable throwable) {
@Override
public void failure(Throwable throwable) {
}
}
});
```
**Note**: In case you attempt an authentication with a URI scheme call, and this authentication includes extra information to be displayed to the user, you must retrieve this information from the server and include it in the authentication response; see section [Session Information](#session-information) for details on how to do that.
### <a id="push-notifications" />Push Notifications
### Push Notifications
Your app must be set up to receive Firebase Cloud Messaging (FCM) push notifications from our server. You can choose to receive and handle these notifications yourself, or alternatively you can use the existing infrastructure provided in the SDK. You can find more information on how to setup FCM push notifications for your app in the [Firebase Cloud Messaging Developer Guide](https://firebase.google.com/docs/cloud-messaging/).
In order to be able to receive FCM notifications, you need to specify the Firebase Messaging service inside the application section of your Manifest:
```xml
<service android:name="com.futurae.sdk.messaging.FTRFcmMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
```
For this purpose, you can either use the one included in the SDK (`com.futurae.sdk.messaging.FTRFcmMessagingService`), or write your own. This service overrides two important methods:
```java
@Override
public void onNewToken(String token);
@Override
public void onNewToken(String token);
@Override
public void onMessageReceived(RemoteMessage message);
@Override
public void onMessageReceived(RemoteMessage message);
```
The first one is invoked whenever a new FCM push token has been generated for the app; it is important to register this token with the Futurae backend in order to continue receiving push notifications. The `FTRFcmMessagingService` implements this functionality.
The second one is invoked whenever a new push notification (or cloud message) is received by the app. The `FTRFcmMessagingService` then processes this message and invokes the SDK accordingly.
#### <a id="fcm-token-registration" />FCM Token Registration
#### FCM Token Registration
The `FTRFcmMessagingService` is responsible for registering the app's FCM token to the Futurae server. This is important for the server to be able to issue FCM notifications for your app. The provided service handles this, however if you need to, you can write your own or extend the existing one.
If you are implementing **your own FCM notification handling**, you should register the FCM token to the Futurae server every time it changes. The call that registers the FCM token to the Futurae server is `registerPushToken()`, and it is necessary every time the FCM token is generated or is changed by FCM.
......@@ -216,19 +217,19 @@ If you are implementing **your own FCM notification handling**, you should regis
For example, once the app receives a new FCM token (e.g. via an `onNewToken()` callback in your own `FirebaseMessagingService` subclass), the token needs to be obtained and registered to the Futurae server using the following code:
```java
FuturaeClient.sharedClient().registerPushToken(fcmToken, new FuturaeCallback() {
@Override
public void success() {
@Override
public void success() {
}
}
@Override
public void failure(Throwable t) {
@Override
public void failure(Throwable t) {
}
}
});
```
#### <a id="fcm-listener-service" />FCM Listener Service
#### FCM Listener Service
The `FTRFcmMessagingService` receives FCM push notifications and handles them, according to the actions dictated by the Futurae server. You can use or extend the service provided by the SDK, or write your own. There are two distinct push notification types issued by the Futurae server: **Aprove** or **Unenroll**.
In case you want to process and handle the FCM notifications without using `FTRFcmMessagingService`, you must use the following code in order to process and handle the notifications sent by the Futurae server, inside the implementation of your own `FirebaseMessagingService` subclass:
......@@ -236,13 +237,13 @@ In case you want to process and handle the FCM notifications without using `FTRF
@Override
public void onMessageReceived(RemoteMessage message) {
// Create and handle a Futurae notification, containing a Bundle with any data from message.getData()
FTRNotification notification = notificationFactory.createNotification(service, data);
notification.handle();
// Create and handle a Futurae notification, containing a Bundle with any data from message.getData()
FTRNotification notification = notificationFactory.createNotification(service, data);
notification.handle();
}
```
#### <a id="local-intents" />Local Intents
#### Local Intents
Once a Futurae FCM notification has been handled, the SDK will notify the host app using **local broadcasts**. The app should register a broadcast receiver for these intents and react accordingly. There are three distinct Intents that the notification handlers might send in a local broadcast:
* `INTENT_GENERIC_NOTIFICATION_ERROR`: Indicates that an error was encountered during the processing or handling of a FCM notification.
* `INTENT_APPROVE_AUTH_MESSAGE`: Indicates that a Push Notification Authentication has been initiated.
......@@ -257,27 +258,49 @@ intentFilter.addAction(Shared.INTENT_ACCOUNT_UNENROLL_MESSAGE); // Logout use
LocalBroadcastManager.getInstance(getContext()).registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@Override
public void onReceive(Context context, Intent intent) {
switch(intent.getAction()) {
case Shared.INTENT_ACCOUNT_UNENROLL_MESSAGE:
// Handle unenroll notification (e.g. refresh lists)
break;
switch(intent.getAction()) {
case Shared.INTENT_ACCOUNT_UNENROLL_MESSAGE:
// Handle unenroll notification (e.g. refresh lists)
break;
case Shared.INTENT_APPROVE_AUTH_MESSAGE:
// Handle approve notification (e.g. show approve view)
break;
case Shared.INTENT_APPROVE_AUTH_MESSAGE:
// Handle approve notification (e.g. show approve view)
break;
case Shared.INTENT_GENERIC_NOTIFICATION_ERROR:
// Handle FCM notification error
break;