diff --git a/README.md b/README.md index 4310a50..c9cd0b2 100644 --- a/README.md +++ b/README.md @@ -122,17 +122,17 @@ The `deviceName` and `fcmToken` are data that you must provide: #### 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 +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 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 @@ -208,7 +208,7 @@ if (notification.getTransctionLinkingId() != null) { .start(new Callback { @Override void onSuccess(RichConsent consentDetails) { - // we have the consent details + // we have the consent details } @Override @@ -219,12 +219,72 @@ if (notification.getTransctionLinkingId() != null) { // there is no consent associated with the transaction } } - // something went wrong + // something went wrong } }); } ``` +#### Authorization Details + +If Rich Authorization Rich Authorization Requests are being used, the consent record will contains the `authorization_details` values from the initial authenication request ([RFC 9396](https://datatracker.ietf.org/doc/html/rfc9396)) for rendering to the user for consent. You can access them in the `getAuthorizationDetails()` method of the requested details object which returns an array of objects containing each of the types. `authorization_details` values are essentially arbitary JSON objects but are guaranteed to have a `type` property which must be pre-registered with the Authorization Server. As such the can be queried in a dynamic manor like you might with JSON. + +```java +void onSuccess(RichConsent consentDetails) { + List> authorizationDetails = consentDetails + .getRequestedDetails() + .getAuthorizationDetails(); + + String type = (String) authorizationDetails.get(0).get("type"); + int amount = (int) authorizationDetails.get(0).get("amount"); +} +``` +Typically the shape and type of `authorization_details` will be known at compile time. In such a case, `authorization_details` can be queried in a strongly-typed manor by first defining a class decorated with `@AuthorizationDetailsType("")` to represent your object and making use of the `filterAuthorizationDetailsByType` helper function, which will return all authorization details that match this type. + +Guardian SDK uses Gson for desiariliazing JSON API responses. Although, your app is not required to depend on Gson directly, the Authorization Details Type classes you define must be compatible with Gson's [Objects deserialization rules](https://github.com/google/gson/blob/main/UserGuide.md#object-examples). + +```java +@AuthorizationDetailsType("payment") +class PaymentDetails { + private final String type; + private final int amount; + private final String currency; + + public MyAuthorizationDetails(String type, int amount, Strinc currencty) { + this.type = type; + this.amount = amount; + this.currency = currency; + } + + public String getType() { + return type; + } + + public int getAmount() { + return amount; + } + + public String getCurrency() { + return currency; + } +} + + +void onSuccess(RichConsent consentDetails) { + List authorizationDetails = consentDetails + .getRequestedDetails() + .filterAuthorizationDetailsByType(PaymentDetails.class); + + PaymentDetails firstPaymentDetails = authorizationDetails.get(0); + + int amount = firstPaymentDetails.getAmount(); + String currencty = firstPaymentDetails.getCurrency(); +} +``` + +> [!WARNING] +> When using the filter helper, you still need to check if there are other authorization details in the rich consent record to prevent the user giving consent to something they didn't see rendered. + ## What is Auth0? Auth0 helps you to: diff --git a/app/src/main/java/com/auth0/guardian/sample/MainActivity.java b/app/src/main/java/com/auth0/guardian/sample/MainActivity.java index c346b6e..bb29e43 100644 --- a/app/src/main/java/com/auth0/guardian/sample/MainActivity.java +++ b/app/src/main/java/com/auth0/guardian/sample/MainActivity.java @@ -227,41 +227,7 @@ private void updateEnrollment(ParcelableEnrollment enrollment) { } private void onPushNotificationReceived(ParcelableNotification notification) { - 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() { - @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); - } - } + startActivity(NotificationActivity.getStartIntent(this, notification, enrollment)); } @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 d357254..82c297b 100644 --- a/app/src/main/java/com/auth0/guardian/sample/NotificationActivity.java +++ b/app/src/main/java/com/auth0/guardian/sample/NotificationActivity.java @@ -26,28 +26,39 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; +import android.util.Log; import android.view.View; import android.widget.Button; -import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; 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.fragments.AuthenticationRequestDetailsFragment; +import com.auth0.guardian.sample.fragments.consent.ConsentBasicDetailsFragment; +import com.auth0.guardian.sample.fragments.consent.ConsentPaymentInitiationFragment; +import com.auth0.guardian.sample.fragments.consent.DynamicAuthorizationDetailsFragment; +import com.auth0.guardian.sample.consent.authorization.details.payments.PaymentInitiationDetails; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.List; public class NotificationActivity extends AppCompatActivity { - private TextView userText; - private TextView browserText; - private TextView osText; - private TextView locationText; - private TextView dateText; + private static final String TAG = NotificationActivity.class.getName(); private Guardian guardian; private ParcelableEnrollment enrollment; private ParcelableNotification notification; + private RichConsent richConsent; + static Intent getStartIntent(@NonNull Context context, @NonNull ParcelableNotification notification, @NonNull ParcelableEnrollment enrollment) { @@ -79,16 +90,39 @@ protected void onCreate(Bundle savedInstanceState) { setupUI(); - updateUI(); + if (notification.getTransactionLinkingId() != null) { + try { + guardian.fetchConsent(notification, enrollment).start(new Callback() { + @Override + public void onSuccess(RichConsent response) { + richConsent = response; + updateUI(); + } + + @Override + public void onFailure(Throwable exception) { + if (exception instanceof GuardianException) { + GuardianException guardianException = (GuardianException) exception; + if (guardianException.isResourceNotFound()) { + // Render regular authentication request details + updateUI(); + } + } else { + Log.e(TAG, "Error requesting consent details", exception); + throw new RuntimeException(exception); + } + } + }); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new RuntimeException(e); + } + } else { + updateUI(); + } } private void setupUI() { - userText = (TextView) findViewById(R.id.userText); - browserText = (TextView) findViewById(R.id.browserText); - osText = (TextView) findViewById(R.id.osText); - locationText = (TextView) findViewById(R.id.locationText); - dateText = (TextView) findViewById(R.id.dateText); - + // TODO: spinner fragment Button rejectButton = (Button) findViewById(R.id.rejectButton); assert rejectButton != null; rejectButton.setOnClickListener(new View.OnClickListener() { @@ -109,17 +143,75 @@ public void onClick(View v) { } private void updateUI() { - userText.setText(enrollment.getUserId()); - browserText.setText( + Fragment fragment; + if (richConsent == null) { + fragment = getStandardAuthenticationFragment(); + } else if (richConsent.getRequestedDetails().getAuthorizationDetails().isEmpty()) { + fragment = getBasicConsentFragment(); + } else { + List paymentInitiationDetailsList = richConsent + .getRequestedDetails() + .filterAuthorizationDetailsByType(PaymentInitiationDetails.class); + if (!paymentInitiationDetailsList.isEmpty()) { + // For simplicity, in this example we render one single type + fragment = getPaymentInitiationConsentFragment(paymentInitiationDetailsList.get(0)); + } else { + fragment = getDynamicAuthorizationDetailsConsentFragment(); + } + + } + + getSupportFragmentManager().beginTransaction() + .replace(R.id.authenticationDetailsFragmentContainer, fragment) + .commit(); + + } + + @NonNull + private DynamicAuthorizationDetailsFragment getDynamicAuthorizationDetailsConsentFragment() { + return DynamicAuthorizationDetailsFragment.newInstance( + richConsent.getRequestedDetails().getBindingMessage(), + notification.getDate().toString(), + // For simplicity, in this example we render one single type + richConsent.getRequestedDetails().getAuthorizationDetails().get(0) + ); + } + + @NonNull + private ConsentPaymentInitiationFragment getPaymentInitiationConsentFragment(PaymentInitiationDetails paymentDetails) { + return ConsentPaymentInitiationFragment.newInstance( + richConsent.getRequestedDetails().getBindingMessage(), + paymentDetails.getRemittanceInformation(), + paymentDetails.getCreditorAccount().getAccountNumber(), + paymentDetails.getInstructedAmount().getCurrency(), + paymentDetails.getInstructedAmount().getAmount() + ); + } + + @NonNull + private ConsentBasicDetailsFragment getBasicConsentFragment() { + return ConsentBasicDetailsFragment.newInstance( + richConsent.getRequestedDetails().getBindingMessage(), + richConsent.getRequestedDetails().getScope(), + notification.getDate().toString() + ); + } + + @NonNull + private AuthenticationRequestDetailsFragment getStandardAuthenticationFragment() { + return AuthenticationRequestDetailsFragment.newInstance( + enrollment.getUserId(), + String.format("%s, %s", notification.getBrowserName(), - notification.getBrowserVersion())); - osText.setText( + notification.getBrowserVersion()), String.format("%s, %s", notification.getOsName(), - notification.getOsVersion())); - locationText.setText(notification.getLocation()); - dateText.setText(notification.getDate().toString()); + notification.getOsVersion()), + + notification.getLocation(), + notification.getDate().toString() + ); } private void rejectRequested() { diff --git a/app/src/main/java/com/auth0/guardian/sample/NotificationWithConsentDetailsActivity.java b/app/src/main/java/com/auth0/guardian/sample/NotificationWithConsentDetailsActivity.java deleted file mode 100644 index 1a71f56..0000000 --- a/app/src/main/java/com/auth0/guardian/sample/NotificationWithConsentDetailsActivity.java +++ /dev/null @@ -1,138 +0,0 @@ -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/ParcelableRichConsent.java b/app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsent.java deleted file mode 100644 index ef419d6..0000000 --- a/app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsent.java +++ /dev/null @@ -1,96 +0,0 @@ -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 deleted file mode 100644 index 0aa5d6c..0000000 --- a/app/src/main/java/com/auth0/guardian/sample/ParcelableRichConsentRequestedDetails.java +++ /dev/null @@ -1,82 +0,0 @@ -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/java/com/auth0/guardian/sample/consent/authorization/details/payments/CreditorAccount.java b/app/src/main/java/com/auth0/guardian/sample/consent/authorization/details/payments/CreditorAccount.java new file mode 100644 index 0000000..7248616 --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/consent/authorization/details/payments/CreditorAccount.java @@ -0,0 +1,19 @@ +package com.auth0.guardian.sample.consent.authorization.details.payments; + +public class CreditorAccount { + private final String iban; + private final String accountNumber; + + public CreditorAccount(String iban, String accountNumber) { + this.iban = iban; + this.accountNumber = accountNumber; + } + + public String getIban() { + return iban; + } + + public String getAccountNumber() { + return accountNumber; + } +} diff --git a/app/src/main/java/com/auth0/guardian/sample/consent/authorization/details/payments/InstructedAmount.java b/app/src/main/java/com/auth0/guardian/sample/consent/authorization/details/payments/InstructedAmount.java new file mode 100644 index 0000000..878eb6e --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/consent/authorization/details/payments/InstructedAmount.java @@ -0,0 +1,19 @@ +package com.auth0.guardian.sample.consent.authorization.details.payments; + +public class InstructedAmount { + private final String currency; + private final String amount; + + public InstructedAmount(String currency, String amount) { + this.currency = currency; + this.amount = amount; + } + + public String getCurrency() { + return currency; + } + + public String getAmount() { + return amount; + } +} diff --git a/app/src/main/java/com/auth0/guardian/sample/consent/authorization/details/payments/PaymentInitiationDetails.java b/app/src/main/java/com/auth0/guardian/sample/consent/authorization/details/payments/PaymentInitiationDetails.java new file mode 100644 index 0000000..c0f28a8 --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/consent/authorization/details/payments/PaymentInitiationDetails.java @@ -0,0 +1,54 @@ +package com.auth0.guardian.sample.consent.authorization.details.payments; + +import com.auth0.android.guardian.sdk.annotations.AuthorizationDetailsType; + +import java.util.List; + +@AuthorizationDetailsType("payment_initiation") +public class PaymentInitiationDetails { + private final String type; + private final List actions; + private final List locations; + private final InstructedAmount instructedAmount; + private final String creditorName; + private final CreditorAccount creditorAccount; + private final String remittanceInformation; + + public PaymentInitiationDetails(String type, List actions, List locations, InstructedAmount instructedAmount, String creditorName, CreditorAccount creditorAccount, String remittanceInformation) { + this.type = type; + this.actions = actions; + this.locations = locations; + this.instructedAmount = instructedAmount; + this.creditorName = creditorName; + this.creditorAccount = creditorAccount; + this.remittanceInformation = remittanceInformation; + } + + public String getType() { + return type; + } + + public List getActions() { + return actions; + } + + public List getLocations() { + return locations; + } + + public InstructedAmount getInstructedAmount() { + return instructedAmount; + } + + public String getCreditorName() { + return creditorName; + } + + public CreditorAccount getCreditorAccount() { + return creditorAccount; + } + + public String getRemittanceInformation() { + return remittanceInformation; + } +} diff --git a/app/src/main/java/com/auth0/guardian/sample/fragments/AuthenticationRequestDetailsFragment.java b/app/src/main/java/com/auth0/guardian/sample/fragments/AuthenticationRequestDetailsFragment.java new file mode 100644 index 0000000..a8df360 --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/fragments/AuthenticationRequestDetailsFragment.java @@ -0,0 +1,119 @@ +package com.auth0.guardian.sample.fragments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.auth0.guardian.sample.R; + +import org.w3c.dom.Text; + +/** + * A simple {@link Fragment} subclass. + * Use the {@link AuthenticationRequestDetailsFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class AuthenticationRequestDetailsFragment extends Fragment { + + // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER + private static final String USER = "user"; + private static final String BROWSER = "browser"; + private static final String OS = "os"; + private static final String LOCATION = "location"; + private static final String DATE = "date"; + + private TextView userText; + private TextView browserText; + private TextView osText; + private TextView locationText; + private TextView dateText; + + private String user; + private String browser; + private String os; + private String location; + private String date; + + public AuthenticationRequestDetailsFragment() { + // Required empty public constructor + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param user User text + * @param browser Browser text + * @param os OS text + * @param location Location text + * @param date Date text + * + * @return A new instance of fragment AuthenticationRequestDetailsFragment. + */ + // TODO: Rename and change types and number of parameters + public static AuthenticationRequestDetailsFragment newInstance(String user, + String browser, + String os, + String location, + String date + ) { + AuthenticationRequestDetailsFragment fragment = new AuthenticationRequestDetailsFragment(); + Bundle args = new Bundle(); + args.putString(USER, user); + args.putString(BROWSER, browser); + args.putString(OS, os); + args.putString(LOCATION, location); + args.putString(DATE, date); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + user = getArguments().getString(USER); + browser = getArguments().getString(BROWSER); + os = getArguments().getString(OS); + location = getArguments().getString(LOCATION); + date = getArguments().getString(DATE); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_authentication_request_details, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setupUI(view); + updateUI(); + } + + private void setupUI(@NonNull View view) { + userText = view.findViewById(R.id.userText); + browserText = view.findViewById(R.id.browserText); + osText = view.findViewById(R.id.osText); + locationText = view.findViewById(R.id.locationText); + dateText = view.findViewById(R.id.dateText); + } + + private void updateUI() { + userText.setText(user); + browserText.setText(browser); + osText.setText(os); + locationText.setText(location); + dateText.setText(date); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/auth0/guardian/sample/fragments/consent/ConsentBasicDetailsFragment.java b/app/src/main/java/com/auth0/guardian/sample/fragments/consent/ConsentBasicDetailsFragment.java new file mode 100644 index 0000000..8958d8d --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/fragments/consent/ConsentBasicDetailsFragment.java @@ -0,0 +1,94 @@ +package com.auth0.guardian.sample.fragments.consent; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.auth0.guardian.sample.R; + +/** + * A simple {@link Fragment} subclass. + * Use the {@link ConsentBasicDetailsFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class ConsentBasicDetailsFragment extends Fragment { + + private static final String BINDING_MESSAGE = "binding_message"; + private static final String SCOPE = "scope"; + private static final String DATE = "date"; + + private TextView bindingMessageText; + private TextView scopeText; + private TextView dateText; + + private String bindingMessage; + private String[] scope; + private String date; + + public ConsentBasicDetailsFragment() { + // Required empty public constructor + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param bindingMessage Binding message. + * @param scope Scope. + * @param date Date. + * @return A new instance of fragment ConsentBasicDetailsFragment. + */ + public static ConsentBasicDetailsFragment newInstance(String bindingMessage, String[] scope, String date) { + ConsentBasicDetailsFragment fragment = new ConsentBasicDetailsFragment(); + Bundle args = new Bundle(); + args.putString(BINDING_MESSAGE, bindingMessage); + args.putStringArray(SCOPE, scope); + args.putString(DATE, date); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + bindingMessage = getArguments().getString(BINDING_MESSAGE); + scope = getArguments().getStringArray(SCOPE); + date = getArguments().getString(DATE); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_consent_basic_details, container, false); + } + + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setupUI(view); + updateUI(); + } + + private void setupUI(@NonNull View view) { + bindingMessageText = (TextView) view.findViewById(R.id.bindingMessage); + scopeText = (TextView) view.findViewById(R.id.scope); + dateText = (TextView) view.findViewById(R.id.dateText); + } + + private void updateUI() { + bindingMessageText.setText(bindingMessage); + scopeText.setText(String.join(", ", scope)); + dateText.setText(date); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/auth0/guardian/sample/fragments/consent/ConsentPaymentInitiationFragment.java b/app/src/main/java/com/auth0/guardian/sample/fragments/consent/ConsentPaymentInitiationFragment.java new file mode 100644 index 0000000..25d8ffb --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/fragments/consent/ConsentPaymentInitiationFragment.java @@ -0,0 +1,114 @@ +package com.auth0.guardian.sample.fragments.consent; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.auth0.guardian.sample.R; + +/** + * A simple {@link Fragment} subclass. + * Use the {@link ConsentPaymentInitiationFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class ConsentPaymentInitiationFragment extends Fragment { + + // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER + private static final String BINDING_MESSAGE = "binding_message"; + private static final String REMITTANCE_INFO = "remittance_info"; + private static final String CREDITOR_ACCOUNT = "creditor_account"; + private static final String AMOUNT_CURRENCY = "amount_currency"; + private static final String AMOUNT = "amount"; + + private TextView bindingMessageText; + private TextView remittanceInfoText; + private TextView creditorAccountText; + private TextView amountCurrencyText; + private TextView amountText; + + private String bindingMessage; + private String remittanceInfo; + private String creditorAccount; + private String amountCurrency; + private String amount; + + public ConsentPaymentInitiationFragment() { + // Required empty public constructor + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param bindingMessage Transaction binding message + * @param remittanceInfo Payment remittance info. + * @param creditorAccount Payment creditor account. + * @param amountCurrency Payment amount currencty. + * @param amount Payment amount + * + * @return A new instance of fragment ConsentPaymentInitiationFragment. + */ + public static ConsentPaymentInitiationFragment newInstance(String bindingMessage, + String remittanceInfo, + String creditorAccount, + String amountCurrency, + String amount) { + ConsentPaymentInitiationFragment fragment = new ConsentPaymentInitiationFragment(); + Bundle args = new Bundle(); + args.putString(BINDING_MESSAGE, bindingMessage); + args.putString(REMITTANCE_INFO, remittanceInfo); + args.putString(CREDITOR_ACCOUNT, creditorAccount); + args.putString(AMOUNT_CURRENCY, amountCurrency); + args.putString(AMOUNT, amount); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + bindingMessage = getArguments().getString(BINDING_MESSAGE); + remittanceInfo = getArguments().getString(REMITTANCE_INFO); + creditorAccount = getArguments().getString(CREDITOR_ACCOUNT); + amountCurrency = getArguments().getString(AMOUNT_CURRENCY); + amount = getArguments().getString(AMOUNT); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_consent_payment_initiation, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setupUI(view); + updateUI(); + } + + private void setupUI(@NonNull View view) { + bindingMessageText = view.findViewById(R.id.bindingMessage); + remittanceInfoText = view.findViewById(R.id.paymentRemittanceInformationText); + creditorAccountText = view.findViewById(R.id.paymentAccountText); + amountCurrencyText = view.findViewById(R.id.paymentAmountCurrencyText); + amountText = view.findViewById(R.id.paymentAmountText); + } + + private void updateUI() { + bindingMessageText.setText(bindingMessage); + remittanceInfoText.setText(remittanceInfo); + creditorAccountText.setText(creditorAccount); + amountCurrencyText.setText(amountCurrency); + amountText.setText(amount); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/auth0/guardian/sample/fragments/consent/DynamicAuthorizationDetailsFragment.java b/app/src/main/java/com/auth0/guardian/sample/fragments/consent/DynamicAuthorizationDetailsFragment.java new file mode 100644 index 0000000..b3b0731 --- /dev/null +++ b/app/src/main/java/com/auth0/guardian/sample/fragments/consent/DynamicAuthorizationDetailsFragment.java @@ -0,0 +1,145 @@ +package com.auth0.guardian.sample.fragments.consent; + +import android.os.Bundle; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.auth0.guardian.sample.R; + +import java.util.Map; + +/** + * A simple {@link Fragment} subclass. + * Use the {@link DynamicAuthorizationDetailsFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class DynamicAuthorizationDetailsFragment extends Fragment { + + private static final String BINDING_MESSAGE = "binding_message"; + private static final String DATE = "date"; + private static final String AUTHORIZATION_DETAILS = "authorization_details"; + + private String bindingMessage; + private String date; + private Bundle authorizationDetails; + + private LinearLayout layout; + private TextView bindingMessageText; + private TextView dateText; + + public DynamicAuthorizationDetailsFragment() { + // Required empty public constructor + } + + /** + * Creates a new instance of the fragment. + * + * @param bindingMessage Binding message. + * @param date Date. + * @param authorizationDetails Authorization Details Item. + * @return A new instance of fragment DynamicAuthorizationDetailsView. + */ + public static DynamicAuthorizationDetailsFragment newInstance(String bindingMessage, String date, Map authorizationDetails) { + DynamicAuthorizationDetailsFragment fragment = new DynamicAuthorizationDetailsFragment(); + Bundle args = new Bundle(); + args.putString(BINDING_MESSAGE, bindingMessage); + args.putString(DATE, date); + args.putBundle(AUTHORIZATION_DETAILS, convertAuthorizationDetailsToBundle(authorizationDetails)); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + bindingMessage = getArguments().getString(BINDING_MESSAGE); + date = getArguments().getString(DATE); + authorizationDetails = getArguments().getBundle(AUTHORIZATION_DETAILS); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_dynamic_authorization_details, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setupUI(view); + updateUI(); + } + + private static Bundle convertAuthorizationDetailsToBundle(Map authorizationDetails) { + Bundle bundle = new Bundle(); + for (String key : authorizationDetails.keySet()) { + Object value = authorizationDetails.get(key); + // For simplicity we don't search nested objects or arrays in this example + if (key.equals("type") || value == null || value.getClass().isArray() || value instanceof Map) { + continue; + } + bundle.putString(key, String.valueOf(value)); + } + return bundle; + } + + private void setupUI(@NonNull View view) { + layout = view.findViewById(R.id.dynamicAuthorizationDetailsLayout); + bindingMessageText = view.findViewById(R.id.bindingMessage); + dateText = view.findViewById(R.id.dateText); + } + + private void updateUI() { + bindingMessageText.setText(bindingMessage); + dateText.setText(date); + + layout.removeAllViews(); + for (String key : authorizationDetails.keySet()) { + drawDetail(key.replace("_", " "), authorizationDetails.getString(key)); + } + } + + private void drawDetail(String label, String value) { + LinearLayout row = createDetailRow(); + + TextView labelText = new TextView(new ContextThemeWrapper(this.getActivity(), R.style.Label_Header)); + labelText.setText(label); + + TextView valueText = new TextView(new ContextThemeWrapper(this.getActivity(), R.style.Label_Small)); + valueText.setText(value); + + row.addView(labelText); + row.addView(valueText); + + this.layout.addView(row); + } + + private LinearLayout createDetailRow() { + + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + layoutParams.bottomMargin = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 12, getResources().getDisplayMetrics()); + + LinearLayout row = new LinearLayout(this.getActivity()); + row.setOrientation(LinearLayout.VERTICAL); + row.setLayoutParams(layoutParams); + + return row; + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml index 95e796f..f7d1af3 100644 --- a/app/src/main/res/layout/activity_notification.xml +++ b/app/src/main/res/layout/activity_notification.xml @@ -36,75 +36,10 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - - - - - - - - - - - - - - - - - + android:layout_height="match_parent"/> 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 deleted file mode 100644 index 98602db..0000000 --- a/app/src/main/res/layout/activity_notification_with_consent_details.xml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -