From f6ca29ce73d444e45afb2e4590692513863a879c Mon Sep 17 00:00:00 2001 From: andy317 <129303424+andy317fe301f8c7@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:12:29 -0500 Subject: [PATCH 1/4] Release 0.8.1 (#123) Co-authored-by: Andy Jung --- CHANGELOG.md | 7 +++++++ README.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efe2a76..c80e5f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [0.8.1](https://github.com/auth0/Guardian.Android/tree/0.8.1) (2024-12-11) +[Full Changelog](https://github.com/auth0/Guardian.Android/compare/0.8.0...0.8.1) + +**Changed** +- Fix for JsonIOException when making request + + ## [0.8.0](https://github.com/auth0/Guardian.Android/tree/0.8.0) (2024-03-15) [Full Changelog](https://github.com/auth0/Guardian.Android/compare/0.7.0...0.8.0) diff --git a/README.md b/README.md index bfa5f27..de93d78 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ GuardianSDK is available both in [Maven Central](http://search.maven.org) and To start using *GuardianSDK* add these lines to your `build.gradle` dependencies file: ```gradle -implementation 'com.auth0.android:guardian:0.8.0' +implementation 'com.auth0.android:guardian:0.8.1' ``` ## Usage From dfc63f538bc2f9a187f9a3b2954777009dee8249 Mon Sep 17 00:00:00 2001 From: Carlos Galan Cladera <134488149+cgcladeraokta@users.noreply.github.com> Date: Wed, 29 Jan 2025 18:31:19 +0100 Subject: [PATCH 2/4] feat: rich consents (#122) * 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 ee67e36a9f3e5f50db2b7bce87e79037c43ad121. Revert "fixed tests" This reverts commit d06fee555a64aec5274c192b5f255e5c3f7d23cf. Revert "removed base url manipulation" This reverts commit ec4d416919fa503da2d89b28389eaa044a43f973. 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 Co-authored-by: Serhii Kobeza --- README.md | 101 ++++++--- app/build.gradle | 3 + app/src/main/AndroidManifest.xml | 9 +- .../com/auth0/guardian/sample/Constants.java | 1 + .../auth0/guardian/sample/EnrollActivity.java | 2 +- .../auth0/guardian/sample/MainActivity.java | 44 +++- .../guardian/sample/NotificationActivity.java | 2 +- ...otificationWithConsentDetailsActivity.java | 138 ++++++++++++ .../guardian/sample/ParcelableEnrollment.java | 53 +++++ .../sample/ParcelableRichConsent.java | 96 +++++++++ ...ParcelableRichConsentRequestedDetails.java | 82 +++++++ ...vity_notification_with_consent_details.xml | 121 +++++++++++ app/src/main/res/values/guardian.xml | 4 +- app/src/main/res/values/strings.xml | 1 + build.gradle | 4 +- gradle.properties | 3 + gradle/wrapper/gradle-wrapper.properties | 2 +- guardian/build.gradle | 45 ++-- .../android/guardian/sdk/EnrollRequest.java | 2 +- .../android/guardian/sdk/Enrollment.java | 11 + .../auth0/android/guardian/sdk/Guardian.java | 109 ++++++++-- .../guardian/sdk/GuardianAPIClient.java | 158 ++++++-------- .../guardian/sdk/GuardianEnrollment.java | 12 +- .../guardian/sdk/GuardianException.java | 38 +++- .../guardian/sdk/GuardianRichConsent.java | 47 ++++ .../GuardianRichConsentRequestedDetails.java | 39 ++++ .../android/guardian/sdk/RichConsent.java | 29 +++ .../sdk/RichConsentRequestedDetails.java | 23 ++ .../guardian/sdk/RichConsentsAPIClient.java | 129 +++++++++++ .../guardian/sdk/networking/Request.java | 4 +- .../sdk/oauth2/OAuth2AccessToken.java | 20 ++ .../guardian/sdk/GuardianAPIClientTest.java | 204 ++++++++++++++---- .../guardian/sdk/GuardianEnrollmentTest.java | 7 +- .../guardian/sdk/GuardianExceptionTest.java | 35 ++- .../android/guardian/sdk/GuardianTest.java | 81 ++++--- .../sdk/RichConsentsAPIClientTest.java | 121 +++++++++++ .../guardian/sdk/utils/MockWebService.java | 17 ++ 37 files changed, 1533 insertions(+), 264 deletions(-) create mode 100644 app/src/main/java/com/auth0/guardian/sample/NotificationWithConsentDetailsActivity.java create mode 100644 app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsent.java create mode 100644 app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsentRequestedDetails.java create mode 100644 app/src/main/res/layout/activity_notification_with_consent_details.xml create mode 100644 guardian/src/main/java/com/auth0/android/guardian/sdk/GuardianRichConsent.java create mode 100644 guardian/src/main/java/com/auth0/android/guardian/sdk/GuardianRichConsentRequestedDetails.java create mode 100644 guardian/src/main/java/com/auth0/android/guardian/sdk/RichConsent.java create mode 100644 guardian/src/main/java/com/auth0/android/guardian/sdk/RichConsentRequestedDetails.java create mode 100644 guardian/src/main/java/com/auth0/android/guardian/sdk/RichConsentsAPIClient.java create mode 100644 guardian/src/main/java/com/auth0/android/guardian/sdk/oauth2/OAuth2AccessToken.java create mode 100644 guardian/src/test/java/com/auth0/android/guardian/sdk/RichConsentsAPIClientTest.java diff --git a/README.md b/README.md index de93d78..4310a50 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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' @@ -42,7 +42,7 @@ implementation 'com.auth0.android:guardian:0.8.1' tenant/url. ```java -Uri url = Uri.parse("https:///appliance-mfa"); +Uri url = Uri.parse("https://..auth0.com"); Guardian guardian = new Guardian.Builder() .url(url) @@ -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:///appliance-mfa"); +Uri url = Uri.parse(""); Guardian guardian = new Guardian.Builder() .url(url) @@ -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 @@ -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` 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 @@ -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 @@ -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 { + @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 diff --git a/app/build.gradle b/app/build.gradle index c85696f..21f96ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,6 +21,7 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + namespace 'com.auth0.guardian.sample' } dependencies { @@ -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' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 00fea9d..6855ed5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> @@ -37,6 +36,12 @@ + + + + () { + @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 diff --git a/app/src/main/java/com/auth0/guardian/sample/NotificationActivity.java b/app/src/main/java/com/auth0/guardian/sample/NotificationActivity.java index 81b4d06..d357254 100644 --- a/app/src/main/java/com/auth0/guardian/sample/NotificationActivity.java +++ b/app/src/main/java/com/auth0/guardian/sample/NotificationActivity.java @@ -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(); diff --git a/app/src/main/java/com/auth0/guardian/sample/NotificationWithConsentDetailsActivity.java b/app/src/main/java/com/auth0/guardian/sample/NotificationWithConsentDetailsActivity.java new file mode 100644 index 0000000..1a71f56 --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/NotificationWithConsentDetailsActivity.java @@ -0,0 +1,138 @@ +package com.auth0.guardian.sample; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import com.auth0.android.guardian.sdk.Guardian; +import com.auth0.android.guardian.sdk.ParcelableNotification; +import com.auth0.android.guardian.sdk.RichConsent; +import com.auth0.android.guardian.sdk.networking.Callback; + +public class NotificationWithConsentDetailsActivity extends AppCompatActivity { + + private TextView bindingMessageText; + private TextView scopeText; + private TextView dateText; + + private Guardian guardian; + private ParcelableEnrollment enrollment; + private ParcelableNotification notification; + private RichConsent consentDetails; + + static Intent getStartIntent(@NonNull Context context, + @NonNull ParcelableNotification notification, + @NonNull ParcelableEnrollment enrollment, + @NonNull ParcelableRichConsent consent) { + if (!enrollment.getId().equals(notification.getEnrollmentId())) { + final String message = String.format("Notification doesn't match enrollment (%s != %s)", + notification.getEnrollmentId(), enrollment.getId()); + throw new IllegalArgumentException(message); + } + + Intent intent = new Intent(context, NotificationWithConsentDetailsActivity.class); + intent.putExtra(Constants.ENROLLMENT, enrollment); + intent.putExtra(Constants.NOTIFICATION, notification); + intent.putExtra(Constants.CONSENT, consent); + return intent; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_notification_with_consent_details); + + guardian = new Guardian.Builder() + .url(Uri.parse(getString(R.string.tenant_url))) + .enableLogging() + .build(); + + Intent intent = getIntent(); + enrollment = intent.getParcelableExtra(Constants.ENROLLMENT); + notification = intent.getParcelableExtra(Constants.NOTIFICATION); + consentDetails = intent.getParcelableExtra(Constants.CONSENT); + + setupUI(); + updateUI(); + } + + private void setupUI() { + bindingMessageText = (TextView) findViewById(R.id.bindingMessage); + scopeText = (TextView) findViewById(R.id.scope); + dateText = (TextView) findViewById(R.id.dateText); + + Button rejectButton = (Button) findViewById(R.id.rejectButton); + assert rejectButton != null; + rejectButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + rejectRequested(); + } + }); + + Button allowButton = (Button) findViewById(R.id.allowButton); + assert allowButton != null; + allowButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + allowRequested(); + } + }); + } + + private void updateUI() { + if (consentDetails != null) { + bindingMessageText.setText(consentDetails.getRequestedDetails().getBindingMessage()); + scopeText.setText(String.join(", ", consentDetails.getRequestedDetails().getScope())); + } else { + bindingMessageText.setText("N/A"); + scopeText.setText("N/A"); + } + dateText.setText(notification.getDate().toString()); + } + + private void rejectRequested() { + guardian + .reject(notification, enrollment) + .start(new DialogCallback<>(this, + R.string.progress_title_please_wait, + R.string.progress_message_reject, + new Callback() { + @Override + public void onSuccess(Void response) { + finish(); + } + + @Override + public void onFailure(Throwable exception) { + + } + })); + } + + private void allowRequested() { + guardian + .allow(notification, enrollment) + .start(new DialogCallback<>(this, + R.string.progress_title_please_wait, + R.string.progress_message_allow, + new Callback() { + @Override + public void onSuccess(Void response) { + finish(); + } + + @Override + public void onFailure(Throwable exception) { + + } + })); + } +} diff --git a/app/src/main/java/com/auth0/guardian/sample/ParcelableEnrollment.java b/app/src/main/java/com/auth0/guardian/sample/ParcelableEnrollment.java index cad4f79..db3eb56 100644 --- a/app/src/main/java/com/auth0/guardian/sample/ParcelableEnrollment.java +++ b/app/src/main/java/com/auth0/guardian/sample/ParcelableEnrollment.java @@ -32,11 +32,18 @@ import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateCrtKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; public class ParcelableEnrollment implements Enrollment, Parcelable { @@ -73,6 +80,9 @@ public class ParcelableEnrollment implements Enrollment, Parcelable { @SerializedName("privateKey") private final String privateKey; + @SerializedName("publicKey") + private final String publicKey; + public ParcelableEnrollment(Enrollment enrollment) { this.userId = enrollment.getUserId(); this.period = enrollment.getPeriod(); @@ -85,6 +95,7 @@ public ParcelableEnrollment(Enrollment enrollment) { this.deviceGCMToken = enrollment.getNotificationToken(); this.deviceToken = enrollment.getDeviceToken(); this.privateKey = Base64.encodeToString(enrollment.getSigningKey().getEncoded(), Base64.DEFAULT); + this.publicKey = Base64.encodeToString(enrollment.getPublicKey().getEncoded(), Base64.DEFAULT); } @NonNull @@ -155,6 +166,46 @@ public PrivateKey getSigningKey() { } } + @NonNull + @Override + public PublicKey getPublicKey() { + try { + if (publicKey == null || publicKey.isEmpty()) { + // For backwards-compatibility with enrollments encoded without a public key + // prior to version 0.9.0 of the Guardian.Android SDK + return getPublicKeyFromSigningKey(); + } + + byte[] key = Base64.decode(publicKey, Base64.DEFAULT); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchProviderException e) { + throw new IllegalStateException("Invalid public key!"); + } + } + + private PublicKey getPublicKeyFromSigningKey() throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { + // BouncyCastleProvider is used in order to ensure compatibility when targeting Android API Level 32 + // and lower due to changes to how the default provider handles decoding of RSA keys across versions. + // Prior to API Level 32 the default Security Provider will decode PKCS8Encoded private key to a + // RSAPrivateKey rather than RSAPrivateCrtKey which discards the public exponent required to reconstruct + // the public key. + KeyFactory keyFactory = KeyFactory.getInstance("RSA", new BouncyCastleProvider()); + + byte[] signingKeyBytes = Base64.decode(privateKey, Base64.DEFAULT); + PKCS8EncodedKeySpec signingKeySpec = new PKCS8EncodedKeySpec(signingKeyBytes); + PrivateKey signingKey = keyFactory.generatePrivate(signingKeySpec); + + if (signingKey instanceof RSAPrivateCrtKey) { + RSAPrivateCrtKey rsaPrivateKey = (RSAPrivateCrtKey) signingKey; + RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(rsaPrivateKey.getModulus(), rsaPrivateKey.getPublicExponent()); + return keyFactory.generatePublic(publicKeySpec); + } else { + throw new IllegalStateException("Not an RSA private key."); + } + } + // PARCELABLE protected ParcelableEnrollment(Parcel in) { id = in.readString(); @@ -168,6 +219,7 @@ protected ParcelableEnrollment(Parcel in) { deviceGCMToken = in.readString(); deviceToken = in.readString(); privateKey = in.readString(); + publicKey = in.readString(); } @Override @@ -188,6 +240,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(deviceGCMToken); dest.writeString(deviceToken); dest.writeString(privateKey); + dest.writeString(publicKey); } @SuppressWarnings("unused") diff --git a/app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsent.java b/app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsent.java new file mode 100644 index 0000000..ef419d6 --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsent.java @@ -0,0 +1,96 @@ +package com.auth0.guardian.sample; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.auth0.android.guardian.sdk.RichConsent; +import com.auth0.android.guardian.sdk.RichConsentRequestedDetails; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; + +public class ParcelableRichConsent implements RichConsent, Parcelable { + public static final Creator CREATOR = new Creator() { + @Override + public ParcelableRichConsent createFromParcel(Parcel in) { + return new ParcelableRichConsent(in); + } + + @Override + public ParcelableRichConsent[] newArray(int size) { + return new ParcelableRichConsent[size]; + } + }; + + private static final Gson JSON = new GsonBuilder().create(); + + @SerializedName("id") + private final String id; + @SerializedName("requested_details") + private final ParcelableRichConsentRequestedDetails requestedDetails; + @SerializedName("created_at") + private final String createdAt; + @SerializedName("expires_at") + private final String expiresAt; + + public ParcelableRichConsent(RichConsent richConsent) { + this.id = richConsent.getId(); + this.createdAt = this.getCreatedAt(); + this.expiresAt = this.getExpiresAt(); + this.requestedDetails = new ParcelableRichConsentRequestedDetails(richConsent.getRequestedDetails()); + } + + protected ParcelableRichConsent(Parcel in) { + id = in.readString(); + createdAt = in.readString(); + expiresAt = in.readString(); + requestedDetails = in.readParcelable(ParcelableRichConsentRequestedDetails.class.getClassLoader()); + } + + public static ParcelableEnrollment fromJSON(String json) { + return JSON.fromJson(json, ParcelableEnrollment.class); + } + + public String toJSON() { + return JSON.toJson(this); + } + + @NonNull + @Override + public String getId() { + return id; + } + + @NonNull + @Override + public RichConsentRequestedDetails getRequestedDetails() { + return requestedDetails; + } + + @NonNull + @Override + public String getCreatedAt() { + return createdAt; + } + + @NonNull + @Override + public String getExpiresAt() { + return expiresAt; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(createdAt); + dest.writeString(expiresAt); + dest.writeParcelable(requestedDetails, flags); + } +} diff --git a/app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsentRequestedDetails.java b/app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsentRequestedDetails.java new file mode 100644 index 0000000..0aa5d6c --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsentRequestedDetails.java @@ -0,0 +1,82 @@ +package com.auth0.guardian.sample; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.auth0.android.guardian.sdk.RichConsentRequestedDetails; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; + +public class ParcelableRichConsentRequestedDetails implements RichConsentRequestedDetails, Parcelable { + public static final Creator CREATOR = new Creator() { + @Override + public ParcelableRichConsentRequestedDetails createFromParcel(Parcel in) { + return new ParcelableRichConsentRequestedDetails(in); + } + + @Override + public ParcelableRichConsentRequestedDetails[] newArray(int size) { + return new ParcelableRichConsentRequestedDetails[size]; + } + }; + private static final Gson JSON = new GsonBuilder().create(); + + @SerializedName("audience") + private final String audience; + @SerializedName("scope") + private final String[] scope; + @SerializedName("bindingMessage") + private final String bindingMessage; + + public ParcelableRichConsentRequestedDetails(RichConsentRequestedDetails requestedDetails) { + audience = requestedDetails.getAudience(); + scope = requestedDetails.getScope(); + bindingMessage = requestedDetails.getBindingMessage(); + } + + protected ParcelableRichConsentRequestedDetails(Parcel in) { + audience = in.readString(); + scope = in.createStringArray(); + bindingMessage = in.readString(); + } + + public static ParcelableEnrollment fromJSON(String json) { + return JSON.fromJson(json, ParcelableEnrollment.class); + } + + public String toJSON() { + return JSON.toJson(this); + } + + @NonNull + @Override + public String getAudience() { + return audience; + } + + @NonNull + @Override + public String[] getScope() { + return scope; + } + + @Override + public String getBindingMessage() { + return bindingMessage; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(audience); + dest.writeStringArray(scope); + dest.writeString(bindingMessage); + } +} diff --git a/app/src/main/res/layout/activity_notification_with_consent_details.xml b/app/src/main/res/layout/activity_notification_with_consent_details.xml new file mode 100644 index 0000000..98602db --- /dev/null +++ b/app/src/main/res/layout/activity_notification_with_consent_details.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +