From 5eb7fd92c2dac6ab5f172fb5c27c66f1c3009207 Mon Sep 17 00:00:00 2001 From: Amanpal Singh <87360222+aman-alfresco@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:40:48 +0530 Subject: [PATCH] added auth0 authentication --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 12 +- .../content/app/activity/LoginActivity.kt | 6 +- .../content/app/fragment/SettingsFragment.kt | 4 +- .../alfresco/auth/activity/LoginActivity.kt | 8 +- .../alfresco/auth/activity/LoginViewModel.kt | 137 +++++----- .../alfresco/auth/activity/LogoutActivity.kt | 19 +- .../fragments/AdvancedSettingsFragment.kt | 45 +--- .../res/layout/fragment_auth_settings.xml | 253 ++++++++---------- gradle/libs.versions.toml | 13 +- 10 files changed, 219 insertions(+), 280 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 26b1be9d..8fcf95c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,7 +17,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - manifestPlaceholders = [auth0Domain: "@string/com_auth0_domain", auth0Scheme: "@string/com_auth0_scheme"] +// manifestPlaceholders = [auth0Domain: "@string/com_auth0_domain", auth0Scheme: "@string/com_auth0_scheme"] } signingConfigs { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d03953be..5eb3f07e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -77,17 +77,19 @@ android:scheme="androidacsapp" /> - + android:host="*.auth0.com" + android:scheme="demo" + android:path="/android/com.alfresco.content.app.debug/callback"/> + - + () { LoginViewModel.Step.InputIdentityServer -> InputIdentityFragment.with(this).display() LoginViewModel.Step.InputAppServer -> InputServerFragment.with(this).display() LoginViewModel.Step.EnterBasicCredentials -> BasicAuthFragment.with(this).display() - LoginViewModel.Step.EnterPkceCredentials, LoginViewModel.Step.EnterAuth0Credentials -> viewModel.ssoLogin() + LoginViewModel.Step.EnterPkceCredentials -> { + println("LoginActivity.onMoveToStep EnterPkceCredentials") + viewModel.ssoLogin() + } + LoginViewModel.Step.Cancelled -> finish() } } @@ -186,4 +190,4 @@ abstract class LoginActivity : AuthenticationActivity() { current = queue.poll() } } -} +} \ No newline at end of file diff --git a/auth/src/main/kotlin/com/alfresco/auth/activity/LoginViewModel.kt b/auth/src/main/kotlin/com/alfresco/auth/activity/LoginViewModel.kt index 243cdf0e..20c0aef5 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/activity/LoginViewModel.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/activity/LoginViewModel.kt @@ -11,19 +11,19 @@ import androidx.lifecycle.viewModelScope import com.alfresco.android.aims.R import com.alfresco.auth.AuthConfig import com.alfresco.auth.AuthType -import com.alfresco.auth.config.defaultAuth0Config -import com.alfresco.auth.config.defaultKeycloakConfig +import com.alfresco.auth.AuthTypeProvider +import com.alfresco.auth.config.defaultConfig import com.alfresco.auth.data.LiveEvent import com.alfresco.auth.data.MutableLiveEvent +import com.alfresco.auth.data.OAuth2Data import com.alfresco.auth.ui.AuthenticationViewModel -import com.alfresco.content.data.OptionsModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class LoginViewModel( private val applicationContext: Context, - val authType: AuthType?, + authType: AuthType?, authState: String?, authConfig: AuthConfig?, endpoint: String?, @@ -59,9 +59,7 @@ class LoginViewModel( val canonicalApplicationUrl: String get() { return previousAppEndpoint - ?: if (authType == AuthType.PKCE) { - discoveryService.contentServiceUrl(applicationUrl.value!!).toString() - } else discoveryService.oidcUrl(applicationUrl.value!!).toString() + ?: discoveryService.contentServiceUrl(applicationUrl.value!!, authConfig.authType).toString() } // Used for display purposes @@ -98,11 +96,14 @@ class LoginViewModel( } fun startEditing() { - authConfigEditor = AuthConfigEditor(context) + authConfigEditor = AuthConfigEditor() authConfigEditor.reset(authConfig) } fun connect() { + + println("connect to server") + isLoading.value = true try { @@ -112,33 +113,68 @@ class LoginViewModel( } } - private fun onAuthType(authType: AuthType) { + private fun onAuthType(authType: AuthType, oAuth2Data: OAuth2Data?) { + + println("LoginViewModel.onAuthType 1 ==== $oAuth2Data") + + + if (oAuth2Data != null && oAuth2Data.secret.isNotEmpty()) { + + println("LoginViewModel.onAuthType 2 ==== ${Uri.parse(oAuth2Data.host).host}") + + val host = Uri.parse(oAuth2Data.host).host ?: "" + + var additionalParams = mutableMapOf() + + if (oAuth2Data.audience.isNotEmpty()) { + + val key = OAuth2Data::audience.name + val value = oAuth2Data.audience + + additionalParams[key] = value + + } + + authConfig = AuthConfig( + https = authConfig.https, + port = "", + contentServicePath = "", + realm = "", + clientId = oAuth2Data.clientId, + redirectUrl = "demo://$host/android/com.alfresco.content.app.debug/callback", + host = host, + secret = oAuth2Data.secret, + scope = oAuth2Data.scope, + authType = AuthTypeProvider.NEW_IDP, + additionalParams = additionalParams + ) + } + when (authType) { AuthType.PKCE -> { viewModelScope.launch { - val isContentServicesInstalled = - withContext(Dispatchers.IO) { - discoveryService.isContentServiceInstalled(identityUrl.value ?: "") + + when (authConfig.authType) { + AuthTypeProvider.NEW_IDP -> { + applicationUrl.value = identityUrl.value + moveToStep(Step.EnterPkceCredentials) } - if (isContentServicesInstalled) { - applicationUrl.value = identityUrl.value - moveToStep(Step.EnterPkceCredentials) - } else { - moveToStep(Step.InputAppServer) + else -> { + val isContentServicesInstalled = + withContext(Dispatchers.IO) { + discoveryService.isContentServiceInstalled(identityUrl.value ?: "") + } + if (isContentServicesInstalled) { + applicationUrl.value = identityUrl.value + moveToStep(Step.EnterPkceCredentials) + } else { + moveToStep(Step.InputAppServer) + } + } } - } - } - AuthType.OIDC -> { - viewModelScope.launch { - val isOIDCInstalled = withContext(Dispatchers.IO) { - discoveryService.isOIDCInstalled(identityUrl.value ?: "") - } - if (isOIDCInstalled) { - applicationUrl.value = identityUrl.value - moveToStep(Step.EnterAuth0Credentials) - } + } } @@ -156,6 +192,7 @@ class LoginViewModel( isLoading.value = true val endpoint = requireNotNull(identityUrl.value) + println("LoginViewModel.ssoLogin entered ==== $endpoint") pkceLogin(endpoint, authConfig, previousAuthState) } @@ -188,7 +225,7 @@ class LoginViewModel( val sharedPrefs = applicationContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE) val configJson = sharedPrefs.getString(SHARED_PREFS_CONFIG_KEY, null) ?: "" - authConfig = AuthConfig.jsonDeserialize(configJson) ?: AuthConfig.defaultKeycloakConfig + authConfig = AuthConfig.jsonDeserialize(configJson) ?: AuthConfig.defaultConfig } fun saveConfigChanges() { @@ -225,7 +262,7 @@ class LoginViewModel( applicationUrl.value = identityUrl.value } - Step.EnterPkceCredentials, Step.EnterAuth0Credentials -> { + Step.EnterPkceCredentials -> { } Step.Cancelled -> { @@ -240,7 +277,6 @@ class LoginViewModel( InputAppServer, EnterBasicCredentials, EnterPkceCredentials, - EnterAuth0Credentials, Cancelled, } @@ -310,36 +346,20 @@ class LoginViewModel( } } - class AuthConfigEditor(val context: Context) { + class AuthConfigEditor { private lateinit var source: AuthConfig private val _changed = MediatorLiveData() - val authTypeValue = MutableLiveData() - val authTypeName = MutableLiveData() val https = MutableLiveData() val port = MutableLiveData() val contentServicePath = MutableLiveData() val realm = MutableLiveData() val clientId = MutableLiveData() private var redirectUrl: String = "" - private var scheme: String = "" val changed: LiveData get() = _changed - val listAuthType = listOf( - OptionsModel( - id = AuthType.PKCE.value, - name = context.getString(R.string.text_auth_keycloak), - ), - OptionsModel( - id = AuthType.OIDC.value, - name = context.getString(R.string.text_auth_oidc_auth0), - ), - ) - init { - _changed.addSource(authTypeValue, this::onChange) - _changed.addSource(authTypeName, this::onChange) _changed.addSource(https, this::onChange) _changed.addSource(port, this::onChange) _changed.addSource(contentServicePath, this::onChange) @@ -359,15 +379,6 @@ class LoginViewModel( port.value = if (https.value == true) DEFAULT_HTTPS_PORT else DEFAULT_HTTP_PORT } - fun onAuthChange(authName: String, authValue: String) { - authTypeName.value = authName - authTypeValue.value = authValue - when (authValue.lowercase()) { - AuthType.PKCE.value.lowercase() -> load(AuthConfig.defaultKeycloakConfig) - AuthType.OIDC.value.lowercase() -> load(AuthConfig.defaultAuth0Config) - } - } - private fun onChange( @Suppress("UNUSED_PARAMETER") value: Boolean, ) { @@ -391,22 +402,16 @@ class LoginViewModel( fun resetToDefaultConfig() { // Source is not changed as resetting to default does not commit changes - when (authTypeValue.value?.lowercase()) { - AuthType.PKCE.value.lowercase() -> load(AuthConfig.defaultKeycloakConfig) - AuthType.OIDC.value.lowercase() -> load(AuthConfig.defaultAuth0Config) - } + load(AuthConfig.defaultConfig) } private fun load(config: AuthConfig) { - authTypeName.value = listAuthType.find { it.id == config.authType }?.name ?: "" - authTypeValue.value = config.authType https.value = config.https port.value = config.port contentServicePath.value = config.contentServicePath realm.value = config.realm clientId.value = config.clientId redirectUrl = config.redirectUrl - scheme = config.scheme onChange() } @@ -418,8 +423,6 @@ class LoginViewModel( realm = realm.value ?: "", clientId = clientId.value ?: "", redirectUrl = redirectUrl, - scheme = scheme, - authType = authTypeValue.value ?: "", ) } @@ -428,4 +431,4 @@ class LoginViewModel( private const val DEFAULT_HTTPS_PORT = "443" } } -} +} \ No newline at end of file diff --git a/auth/src/main/kotlin/com/alfresco/auth/activity/LogoutActivity.kt b/auth/src/main/kotlin/com/alfresco/auth/activity/LogoutActivity.kt index c63e338a..d4a87d47 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/activity/LogoutActivity.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/activity/LogoutActivity.kt @@ -9,11 +9,9 @@ import com.alfresco.auth.ui.EndSessionViewModel import com.alfresco.common.getViewModel import org.json.JSONException -class LogoutViewModel(context: Context, authType: AuthType?, authState: String, authConfig: AuthConfig, serverURL: String, clientId: String) : - EndSessionViewModel(context, authType, authState, authConfig, serverURL, clientId) { +class LogoutViewModel(context: Context, authType: AuthType?, authState: String, authConfig: AuthConfig) : + EndSessionViewModel(context, authType, authState, authConfig) { companion object { - const val EXTRA_HOST_NAME = "host_name" - const val EXTRA_CLIENT_ID = "client_id" const val EXTRA_AUTH_TYPE = "authType" const val EXTRA_AUTH_STATE = "authState" const val EXTRA_AUTH_CONFIG = "authConfig" @@ -26,8 +24,6 @@ class LogoutViewModel(context: Context, authType: AuthType?, authState: String, val stateString = bundle.getString(EXTRA_AUTH_STATE) val configString = bundle.getString(EXTRA_AUTH_CONFIG) - val hostName = bundle.getString(EXTRA_HOST_NAME) - val clientId = bundle.getString(EXTRA_CLIENT_ID) val authType = bundle.getString(EXTRA_AUTH_TYPE)?.let { AuthType.fromValue(it) } val config = @@ -38,16 +34,13 @@ class LogoutViewModel(context: Context, authType: AuthType?, authState: String, null } } catch (ex: JSONException) { - ex.printStackTrace() - null - } + null + } requireNotNull(stateString) - requireNotNull(hostName) - requireNotNull(clientId) requireNotNull(config) - return LogoutViewModel(context, authType, stateString, config, hostName, clientId) + return LogoutViewModel(context, authType, stateString, config) } } } @@ -58,4 +51,4 @@ class LogoutActivity : EndSessionActivity() { LogoutViewModel.with(applicationContext, intent.extras) } } -} +} \ No newline at end of file diff --git a/auth/src/main/kotlin/com/alfresco/auth/fragments/AdvancedSettingsFragment.kt b/auth/src/main/kotlin/com/alfresco/auth/fragments/AdvancedSettingsFragment.kt index f5cd2861..84df7044 100644 --- a/auth/src/main/kotlin/com/alfresco/auth/fragments/AdvancedSettingsFragment.kt +++ b/auth/src/main/kotlin/com/alfresco/auth/fragments/AdvancedSettingsFragment.kt @@ -17,16 +17,11 @@ import com.alfresco.android.aims.databinding.FragmentAuthSettingsBinding import com.alfresco.auth.activity.LoginViewModel import com.alfresco.auth.ui.observe import com.alfresco.common.FragmentBuilder -import com.alfresco.content.component.ComponentBuilder -import com.alfresco.content.component.ComponentData -import com.alfresco.content.component.ComponentType -import com.alfresco.content.setSafeOnClickListener import com.google.android.material.snackbar.Snackbar class AdvancedSettingsFragment : DialogFragment() { private val viewModel: LoginViewModel by activityViewModels() private val rootView: View get() = requireView() - private lateinit var binding: FragmentAuthSettingsBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -39,24 +34,13 @@ class AdvancedSettingsFragment : DialogFragment() { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - binding = DataBindingUtil.inflate(inflater, R.layout.fragment_auth_settings, container, false) + val binding = DataBindingUtil.inflate(inflater, R.layout.fragment_auth_settings, container, false) binding.viewModel = viewModel binding.lifecycleOwner = this viewModel.startEditing() return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.tieAuthType.inputType = 0 - binding.tieAuthType.setOnFocusChangeListener { textView, hasFocus -> - if (hasFocus) textView.performClick() - } - binding.tieAuthType.setSafeOnClickListener { - openAuthSelection() - } - } - override fun onStart() { super.onStart() @@ -89,35 +73,10 @@ class AdvancedSettingsFragment : DialogFragment() { viewModel.authConfigEditor.resetToDefaultConfig() true } - else -> super.onOptionsItemSelected(item) } } - private fun openAuthSelection() { - val authName = viewModel.authConfigEditor.authTypeName.value ?: "" - val authValue = viewModel.authConfigEditor.authTypeValue.value ?: "" - - val componentData = ComponentData.with( - outcomes = viewModel.authConfigEditor.listAuthType, - name = authName, - query = authValue, - title = binding.tilAuthType.hint.toString(), - selector = ComponentType.DROPDOWN_RADIO.value, - ) - ComponentBuilder(requireContext(), componentData) - .onApply { name, query, _ -> - viewModel.authConfigEditor.onAuthChange(name, query) - } - .onReset { name, query, _ -> - viewModel.authConfigEditor.onAuthChange(name, query) - } - .onCancel { - viewModel.authConfigEditor.onAuthChange(authName, authValue) - } - .show() - } - class Builder(parent: FragmentActivity) : FragmentBuilder(parent) { override val fragmentTag = TAG @@ -134,4 +93,4 @@ class AdvancedSettingsFragment : DialogFragment() { fun with(activity: FragmentActivity): Builder = Builder(activity) } -} +} \ No newline at end of file diff --git a/auth/src/main/res/layout/fragment_auth_settings.xml b/auth/src/main/res/layout/fragment_auth_settings.xml index fa3120f6..2a2c52ab 100644 --- a/auth/src/main/res/layout/fragment_auth_settings.xml +++ b/auth/src/main/res/layout/fragment_auth_settings.xml @@ -2,9 +2,6 @@ - - - @@ -12,10 +9,10 @@ + android:fillViewport="true"> + + + + + + + + + + + + + android:hint="@string/auth_settings_transport_port"> + android:text="@={viewModel.authConfigEditor.port}" /> + - + + + + + + android:hint="@string/auth_settings_service_path"> - - - - - - - - - - - - - - - + android:inputType="text|textNoSuggestions" + android:maxLines="1" + android:text="@={viewModel.authConfigEditor.contentServicePath}" /> + - + - - - - - - + android:layout_gravity="bottom" + android:layout_marginBottom="@dimen/auth_settings_header_title_offset" + android:text="@string/auth_settings_auth_section" /> + - - + - - - - - - - - - - - + + + + + - - - - - + android:inputType="text|textNoSuggestions" + android:maxLines="1" + android:text="@={viewModel.authConfigEditor.clientId}" /> + - + - + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a86bec7..e0c338e3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -131,4 +131,15 @@ ui-compose-viewbinding = "androidx.compose.ui:ui-viewbinding:1.7.3" navigation-compose = "androidx.navigation:navigation-compose:2.8.2" compose-runtime = "androidx.compose.runtime:runtime:1.7.3" androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "ui-tooling" } -constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } \ No newline at end of file +constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } + + +dokka = "org.jetbrains.dokka:dokka-gradle-plugin:1.9.20" +kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } +appauth = "net.openid:appauth:0.11.1" +jwtdecode = "com.auth0.android:jwtdecode:2.0.2" +kotlin-serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3" + +androidx-lifecycle-viewmodelKtx = "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6" +androidx-browser = "androidx.browser:browser:1.8.0" +okhttp = "com.squareup.okhttp3:okhttp:4.12.0" \ No newline at end of file