Skip to content

Commit

Permalink
feat: rich consents (#122)
Browse files Browse the repository at this point in the history
* feat: rich consents api client (WIP)

* feat: rich consents API client

* feat: provide rich consents client in api client

* feat: bootstrap rich consents api client

* feat: get public key from signing key

* feat(app): notificaiton with consent details

Implement an example of fetching rich consent record when transaction
linking id is present in the notificaiton.

* fix(guardian): upgrade java-jwt

* refactor(app): fetch consent in main activity

Fetch consent details in main activity and pass it as intent extra. If
the rich consent record is not found render the standrad notification
activity.

* doc: fetch consent

* fix(guardian): strip guardian subdomain

* doc: add methods docs

* fix: typos

* chore: change min and target sdk versions

* chore: add compile sdk version

* Add public key to enrollment and move key decoding out of sdk to app code

* Improve documentation about key generation

* Small doc fix

* Small doc fix

* Fix version references

* removed base url manipulation

fixed tests

rollback build version

Revert "rollback build version"

This reverts commit ee67e36.

Revert "fixed tests"

This reverts commit d06fee5.

Revert "removed base url manipulation"

This reverts commit ec4d416.

removed base url manipulation

fixed tests

rollback build version

changed dependency between Guardian, GuardianAPIClient, RichConsentsAPIClient. Updated tests

removed personal data

updated README.md

fixed shouldCreateValidRichConsentsAPI test

fixed verifyBasicJWT, verifyAccessApprovalJWT tests

updated documentation, resources,

created tests for appendingPathComponentIfNeeded

fixed tests

* fix: upgrade bouncy castle to 1.78

* chore: upgrade java-jwt version to 4.5

---------

Co-authored-by: Sam Muncke <[email protected]>
Co-authored-by: Serhii Kobeza <[email protected]>
  • Loading branch information
3 people authored Jan 29, 2025
1 parent f6ca29c commit dfc63f5
Show file tree
Hide file tree
Showing 37 changed files with 1,533 additions and 264 deletions.
101 changes: 74 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Guardian SDK for Android
============
# Guardian SDK for Android

[![CircleCI](https://img.shields.io/circleci/project/github/auth0/Guardian.Android.svg)](https://circleci.com/gh/auth0/Guardian.Android)
[![Coverage Status](https://img.shields.io/codecov/c/github/auth0/Guardian.Android/master.svg)](https://codecov.io/github/auth0/Guardian.Android)
[![License](http://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org)
Expand Down Expand Up @@ -30,7 +30,7 @@ credentials, otherwise you would not receive any push notifications. Please read

GuardianSDK is available both in [Maven Central](http://search.maven.org) and
[JCenter](https://bintray.com/bintray/jcenter).
To start using *GuardianSDK* add these lines to your `build.gradle` dependencies file:
To start using _GuardianSDK_ add these lines to your `build.gradle` dependencies file:

```gradle
implementation 'com.auth0.android:guardian:0.8.1'
Expand All @@ -42,7 +42,7 @@ implementation 'com.auth0.android:guardian:0.8.1'
tenant/url.

```java
Uri url = Uri.parse("https://<AUTH0_TENANT_DOMAIN>/appliance-mfa");
Uri url = Uri.parse("https://<tenant>.<region>.auth0.com");

Guardian guardian = new Guardian.Builder()
.url(url)
Expand All @@ -52,7 +52,7 @@ Guardian guardian = new Guardian.Builder()
alternatively you can use the custom domain if you configured one

```java
Uri url = Uri.parse("https://<CUSTOM_DOMAIN>/appliance-mfa");
Uri url = Uri.parse("<custom>");

Guardian guardian = new Guardian.Builder()
.url(url)
Expand Down Expand Up @@ -113,11 +113,27 @@ guardian
The `deviceName` and `fcmToken` are data that you must provide:

- The `deviceName` is the name that you want for the enrollment. It will be displayed to the user
when the second factor is required.
when the second factor is required.

- The FCM token is the token for Firebase Cloud Messaging push notification service. In case your app
is not yet using FCM or you're not familiar with it, you should check their
[docs](https://firebase.google.com/docs/cloud-messaging/android/client#sample-register).
is not yet using FCM or you're not familiar with it, you should check their
[docs](https://firebase.google.com/docs/cloud-messaging/android/client#sample-register).

#### A note about key generation

The Guardian SDK does not provide methods for generating and storing cryptographic keys used for enrollment
as this is an application specific concern and could vary between targeted versions of Android and
OEM-specific builds. The example given above and that used in the sample application is a naive implementation
which may not be suitable for production applications. It is recommended that you follow [OWASP guidelines
for Android Cryptographic APIs](https://mas.owasp.org/MASTG/0x05e-Testing-Cryptography/) for your implementation.

As of version 0.9.0 the public key used for enrollment was added to the Enrollment Interface as it is
required for [fetching rich-consent details](#fetch-rich-consent-details). For new installs,
this is not a a concern. For enrollments created prior to this version, depending on implementation,
this key may or may not have been stored with the enrollment information. If this key was discarded,
it may be possible to reconstruct from the stored signing key. The sample app provides
[an example](app/src/main/java/com/auth0/guardian/sample/ParcelableEnrollment.java#L188) of this. If
this is not possible, devices will require re-enrollment to make use of this functionality.

### Unenroll

Expand All @@ -136,8 +152,8 @@ Once you have the enrollment in place, you will receive a FCM push notification
has to validate his identity with MFA.

Guardian provides a method to parse the `Map<String, String>` data inside the
[RemoteMessage](https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/RemoteMessage)
received from FCM and return a `Notification` instance ready to be used.
[RemoteMessage](https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/RemoteMessage)
received from FCM and return a `Notification` instance ready to be used.

```java
// at your FCM listener you receive a RemoteMessage
Expand All @@ -154,12 +170,12 @@ public void onMessageReceived(RemoteMessage message) {
```

> If the `RemoteMessage` you receive is not from a Guardian notification this method will return null,
so you should always check before using it.
> so you should always check before using it.
Once you have the notification instance, you can easily allow the authentication request by using the
`allow` method. You'll also need the enrollment that you obtained previously. In case you have more
than one enrollment, you'll have to find the one that has the same id as the notification (you can
get the enrollment id with `getEnrollmentId()`.
`allow` method. You'll also need the enrollment that you obtained previously. In case you have more
than one enrollment, you'll have to find the one that has the same id as the notification (you can
get the enrollment id with `getEnrollmentId()`.

```java
guardian
Expand All @@ -178,23 +194,54 @@ guardian
.execute(); // or start(new Callback<> ...) asynchronously
```

### Fetch rich consent details

When you receive a push notification, the presence of the property `transactionLinkingId` indicates a
rich consent record may be associated to the transaction.

To fetch the rich consent details, you can use the `fetchConsent` method.

```java
if (notification.getTransctionLinkingId() != null) {
guardian
.fetchConsent(notification, enrollment)
.start(new Callback<Enrollment> {
@Override
void onSuccess(RichConsent consentDetails) {
// we have the consent details
}

@Override
void onFailure(Throwable exception) {
if (exception instanceof GuardianException) {
GuardianException guardianException = (GuardianException) exception;
if (guardianException.isResourceNotFound()) {
// there is no consent associated with the transaction
}
}
// something went wrong
}
});
}
```

## What is Auth0?

Auth0 helps you to:

* Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders),
either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce,
among others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory,
ADFS or any SAML Identity Provider**.
* Add authentication through more traditional
**[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**.
* Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with
the same user.
* Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and
**flow the user identity** securely.
* Analytics of how, when and where users are logging in.
* Pull data from other sources and add it to the user profile, through
[JavaScript rules](https://docs.auth0.com/rules).
- Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders),
either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce,
among others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory,
ADFS or any SAML Identity Provider**.
- Add authentication through more traditional
**[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**.
- Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with
the same user.
- Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and
**flow the user identity** securely.
- Analytics of how, when and where users are logging in.
- Pull data from other sources and add it to the user profile, through
[JavaScript rules](https://docs.auth0.com/rules).

## Create a free account in Auth0

Expand Down
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'com.auth0.guardian.sample'
}

dependencies {
Expand All @@ -39,4 +40,6 @@ dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
// ZXing QR decoder deps
implementation 'com.google.zxing:core:3.5.0'

implementation 'org.bouncycastle:bcprov-jdk15to18:1.78'
}
9 changes: 7 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.auth0.guardian.sample">
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
Expand Down Expand Up @@ -37,6 +36,12 @@

</activity>

<activity
android:name=".NotificationWithConsentDetailsActivity"
android:label="@string/title_notification_with_consent_details">

</activity>

<!-- [START FCM services] -->

<service android:name=".fcm.FcmListenerService"
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/auth0/guardian/sample/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ public class Constants {

public static final String ENROLLMENT = "com.auth0.guardian.sample.Constants.ENROLLMENT";
public static final String NOTIFICATION = "com.auth0.guardian.sample.Constants.NOTIFICATION";
public static final String CONSENT = "com.auth0.guardian.sample.Constants.CONSENT";
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ private void setupGuardian() {
}

guardian = new Guardian.Builder()
.url(Uri.parse(getString(R.string.guardian_url)))
.url(Uri.parse(getString(R.string.tenant_url)))
.enableLogging()
.build();
}
Expand Down
44 changes: 40 additions & 4 deletions app/src/main/java/com/auth0/guardian/sample/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.auth0.android.guardian.sdk.Guardian;
import com.auth0.android.guardian.sdk.GuardianException;
import com.auth0.android.guardian.sdk.ParcelableNotification;
import com.auth0.android.guardian.sdk.RichConsent;
import com.auth0.android.guardian.sdk.networking.Callback;
import com.auth0.guardian.sample.events.GuardianNotificationReceivedEvent;
import com.auth0.guardian.sample.fcm.FcmUtils;
Expand All @@ -50,6 +51,9 @@
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

public class MainActivity extends AppCompatActivity implements FcmUtils.FcmTokenListener {

private static final String TAG = MainActivity.class.getName();
Expand Down Expand Up @@ -87,7 +91,7 @@ protected void onCreate(Bundle savedInstanceState) {
eventBus.register(this);

guardian = new Guardian.Builder()
.url(Uri.parse(getString(R.string.guardian_url)))
.url(Uri.parse(getString(R.string.tenant_url)))
.enableLogging()
.build();

Expand Down Expand Up @@ -223,9 +227,41 @@ private void updateEnrollment(ParcelableEnrollment enrollment) {
}

private void onPushNotificationReceived(ParcelableNotification notification) {
Intent intent = NotificationActivity
.getStartIntent(this, notification, enrollment);
startActivity(intent);
Context context = this;
Intent standardNotificationActivityIntent = NotificationActivity.getStartIntent(context, notification, enrollment);

if (notification.getTransactionLinkingId() == null) {
startActivity(standardNotificationActivityIntent);
} else {
try {
guardian.fetchConsent(notification, enrollment).start(new Callback<RichConsent>() {
@Override
public void onSuccess(RichConsent consent) {
Intent intent = NotificationWithConsentDetailsActivity.getStartIntent(
context,
notification,
enrollment,
new ParcelableRichConsent(consent)
);
startActivity(intent);
}

@Override
public void onFailure(Throwable exception) {
if (exception instanceof GuardianException) {
GuardianException guardianException = (GuardianException) exception;
if (guardianException.isResourceNotFound()) {
startActivity(standardNotificationActivityIntent);
}
}
Log.e(TAG, "Error obtaining consent details", exception);

}
});
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
Log.e(TAG, "Error requesting consent details", e);
}
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_notification);

guardian = new Guardian.Builder()
.url(Uri.parse(getString(R.string.guardian_url)))
.url(Uri.parse(getString(R.string.tenant_url)))
.enableLogging()
.build();

Expand Down
Loading

0 comments on commit dfc63f5

Please sign in to comment.