From 591582ec2e5624db14a9b2c4a3f6b35ec4bc92c3 Mon Sep 17 00:00:00 2001 From: Rajdeep Nanua Date: Tue, 7 Feb 2023 10:08:22 -0500 Subject: [PATCH] Add email magic link support to sample --- dynamic-app/build.gradle | 4 ++- dynamic-app/src/main/AndroidManifest.xml | 17 ++++++++++ .../dynamic/EmailMagicLinkRedirectActivity.kt | 34 +++++++++++++++++++ .../dynamic/EmailRedirectCoordinator.kt | 25 ++++++++++++++ .../okta/idx/android/dynamic/MainActivity.kt | 18 ++++++++++ .../dynamic/auth/DynamicAuthViewModel.kt | 30 +++++++++++++--- okta.properties | 2 ++ 7 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 dynamic-app/src/main/java/com/okta/idx/android/dynamic/EmailMagicLinkRedirectActivity.kt create mode 100644 dynamic-app/src/main/java/com/okta/idx/android/dynamic/EmailRedirectCoordinator.kt diff --git a/dynamic-app/build.gradle b/dynamic-app/build.gradle index d581b0fe..4cdef986 100644 --- a/dynamic-app/build.gradle +++ b/dynamic-app/build.gradle @@ -20,7 +20,9 @@ android { buildConfigField "String", 'REDIRECT_URI', "\"${oktaProperties.getProperty('signInRedirectUri')}\"" manifestPlaceholders = [ - "oktaIdxRedirectScheme": parseScheme(oktaProperties.getProperty('signInRedirectUri')) + "oktaIdxRedirectScheme": parseScheme(oktaProperties.getProperty('signInRedirectUri')), + "oktaIdxEmailHost": oktaProperties.getProperty('emailRedirectHost'), + "oktaIdxEmailPrefix": oktaProperties.getProperty('emailRedirectPrefix') ] testInstrumentationRunner 'io.cucumber.android.runner.CucumberAndroidJUnitRunner' diff --git a/dynamic-app/src/main/AndroidManifest.xml b/dynamic-app/src/main/AndroidManifest.xml index 86efb4f4..4079f693 100644 --- a/dynamic-app/src/main/AndroidManifest.xml +++ b/dynamic-app/src/main/AndroidManifest.xml @@ -38,5 +38,22 @@ + + + + + + + + + + + + + diff --git a/dynamic-app/src/main/java/com/okta/idx/android/dynamic/EmailMagicLinkRedirectActivity.kt b/dynamic-app/src/main/java/com/okta/idx/android/dynamic/EmailMagicLinkRedirectActivity.kt new file mode 100644 index 00000000..9ece2381 --- /dev/null +++ b/dynamic-app/src/main/java/com/okta/idx/android/dynamic/EmailMagicLinkRedirectActivity.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2023-Present Okta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.okta.idx.android.dynamic + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity + +class EmailMagicLinkRedirectActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val intent = Intent(this, MainActivity::class.java) + intent.action = MainActivity.EMAIL_REDIRECT_ACTION + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + intent.data = getIntent().data + startActivity(intent) + + finish() + } +} diff --git a/dynamic-app/src/main/java/com/okta/idx/android/dynamic/EmailRedirectCoordinator.kt b/dynamic-app/src/main/java/com/okta/idx/android/dynamic/EmailRedirectCoordinator.kt new file mode 100644 index 00000000..ca2a0838 --- /dev/null +++ b/dynamic-app/src/main/java/com/okta/idx/android/dynamic/EmailRedirectCoordinator.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2023-Present Okta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.okta.idx.android.dynamic + +import android.content.Context +import android.net.Uri + +object EmailRedirectCoordinator { + var listener: EmailRedirectListener? = null +} + +typealias EmailRedirectListener = ((Uri, Context) -> Unit) diff --git a/dynamic-app/src/main/java/com/okta/idx/android/dynamic/MainActivity.kt b/dynamic-app/src/main/java/com/okta/idx/android/dynamic/MainActivity.kt index eb33e20e..bdfa7d8e 100644 --- a/dynamic-app/src/main/java/com/okta/idx/android/dynamic/MainActivity.kt +++ b/dynamic-app/src/main/java/com/okta/idx/android/dynamic/MainActivity.kt @@ -18,10 +18,12 @@ package com.okta.idx.android.dynamic import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import timber.log.Timber class MainActivity : AppCompatActivity() { companion object { const val SOCIAL_REDIRECT_ACTION = "SocialRedirect" + const val EMAIL_REDIRECT_ACTION = "EmailRedirect" } override fun onCreate(savedInstanceState: Bundle?) { @@ -33,6 +35,22 @@ class MainActivity : AppCompatActivity() { public override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) + when (intent?.action) { + SOCIAL_REDIRECT_ACTION -> { + intent.data?.let { + SocialRedirectCoordinator.listener?.invoke(it) + } ?: run { + Timber.d("SocialRedirect intent data missing") + } + } + EMAIL_REDIRECT_ACTION -> { + intent.data?.let { + EmailRedirectCoordinator.listener?.invoke(it, this) + } ?: run { + Timber.d("EmailRedirect intent data missing") + } + } + } if (intent?.action == SOCIAL_REDIRECT_ACTION) { intent.data?.let { SocialRedirectCoordinator.listener?.invoke(it) diff --git a/dynamic-app/src/main/java/com/okta/idx/android/dynamic/auth/DynamicAuthViewModel.kt b/dynamic-app/src/main/java/com/okta/idx/android/dynamic/auth/DynamicAuthViewModel.kt index c886177d..8243c1bf 100644 --- a/dynamic-app/src/main/java/com/okta/idx/android/dynamic/auth/DynamicAuthViewModel.kt +++ b/dynamic-app/src/main/java/com/okta/idx/android/dynamic/auth/DynamicAuthViewModel.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.viewModelScope import com.okta.authfoundation.client.OidcClientResult import com.okta.authfoundationbootstrap.CredentialBootstrap import com.okta.idx.android.dynamic.BuildConfig +import com.okta.idx.android.dynamic.EmailRedirectCoordinator import com.okta.idx.android.dynamic.SocialRedirectCoordinator import com.okta.idx.kotlin.client.IdxRedirectResult import com.okta.idx.kotlin.client.InteractionCodeFlow @@ -56,11 +57,13 @@ internal class DynamicAuthViewModel(private val recoveryToken: String) : ViewMod init { createClient() - SocialRedirectCoordinator.listener = ::handleRedirect + SocialRedirectCoordinator.listener = ::handleSocialRedirect + EmailRedirectCoordinator.listener = ::handleEmailRedirect } override fun onCleared() { SocialRedirectCoordinator.listener = null + EmailRedirectCoordinator.listener = null } private fun createClient() { @@ -118,7 +121,7 @@ internal class DynamicAuthViewModel(private val recoveryToken: String) : ViewMod } } - private fun handleRedirect(uri: Uri) { + private fun handleSocialRedirect(uri: Uri) { viewModelScope.launch { when (val redirectResult = flow?.evaluateRedirectUri(uri)) { is IdxRedirectResult.Error -> { @@ -139,6 +142,23 @@ internal class DynamicAuthViewModel(private val recoveryToken: String) : ViewMod } } + private fun handleEmailRedirect(uri: Uri, context: Context) { + viewModelScope.launch { + val idxForm = _state.value as DynamicAuthState.Form + idxForm.idxResponse.remediations.firstOrNull { + it.type == IdxRemediation.Type.CHALLENGE_AUTHENTICATOR + }?.let { + val otpCode = uri.getQueryParameter("otp") + if (otpCode != null) { + it.form["credentials.passcode"]?.apply { + value = otpCode + proceed(it, context) + } + } + } + } + } + private suspend fun handleResponse(response: IdxResponse) { // If a response is successful, immediately exchange it for a token and exit. if (response.isLoginSuccessful) { @@ -224,9 +244,9 @@ internal class DynamicAuthViewModel(private val recoveryToken: String) : ViewMod * Get text fields, checkboxes, radio buttons and radio button groups from `IdxRemediation.form.visibleFields`. */ private fun IdxRemediation.Form.Field.asDynamicAuthFields(): List { - return when (true) { + return when { // Nested form inside a field. - form?.visibleFields?.isNullOrEmpty() == false -> { + !form?.visibleFields.isNullOrEmpty() -> { val result = mutableListOf() form?.visibleFields?.forEach { result += it.asDynamicAuthFields() @@ -234,7 +254,7 @@ internal class DynamicAuthViewModel(private val recoveryToken: String) : ViewMod result } // Options represent multiple choice items like authenticators and can be nested. - options?.isNullOrEmpty() == false -> { + !options.isNullOrEmpty() -> { options?.let { options -> val transformed = options.map { val fields = diff --git a/okta.properties b/okta.properties index 4e8c8c35..9540f1d1 100644 --- a/okta.properties +++ b/okta.properties @@ -4,3 +4,5 @@ issuer=https://this.does.not.exist.com/oauth2/default clientId=test-client-id signInRedirectUri=com.okta.sample.android:/login +emailRedirectHost=example.domain.com +emailRedirectPrefix=/example/path/prefix/