Loading app/src/main/kotlin/at/bitfire/davdroid/authorization/IdentityProvider.kt +24 −0 Original line number Diff line number Diff line Loading @@ -125,6 +125,30 @@ enum class IdentityProvider( } } fun getClientId(context: Context): String { return if (this == MURENA) { MurenaServerConfig.getClientId(context) } else { clientId } } fun getRedirectUri(context: Context): Uri { return if (this == MURENA) { retrieveUri(MurenaServerConfig.getRedirectUri(context)) ?: redirectUri } else { redirectUri } } fun getLogoutRedirectUri(context: Context): Uri? { return if (this == MURENA) { retrieveUri(MurenaServerConfig.getLogoutRedirectUri(context)) ?: logoutRedirectUri } else { logoutRedirectUri } } private fun getDiscoveryEndpoint(context: Context): Uri? { return if (this == MURENA) { retrieveUri(MurenaServerConfig.getDiscoveryUrl(context)) Loading app/src/main/kotlin/at/bitfire/davdroid/settings/Settings.kt +4 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,10 @@ object Settings { const val PROXY_HOST = "proxy_host" // String const val PROXY_PORT = "proxy_port" // Integer const val MURENA_CLIENT_ID_OVERRIDE = "murena_client_id_override" const val MURENA_BASE_URL_PRODUCTION_OVERRIDE = "murena_base_url_production_override" const val MURENA_DISCOVERY_END_POINT_PRODUCTION_OVERRIDE = "murena_discovery_end_point_production_override" /** * Whether to ignore VPNs at internet connection detection, true by default because VPN connections * seem to include "VALIDATED" by default even without actual internet connection Loading app/src/main/kotlin/at/bitfire/davdroid/syncadapter/AccountUtils.kt +4 −5 Original line number Diff line number Diff line Loading @@ -8,10 +8,10 @@ import android.accounts.Account import android.accounts.AccountManager import android.content.Context import android.os.Bundle import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.util.MurenaServerConfig import at.bitfire.davdroid.util.setAndVerifyUserData import com.owncloud.android.lib.common.accounts.AccountUtils.Constants Loading Loading @@ -173,10 +173,9 @@ object AccountUtils { accountManager.getUserData(account, Constants.KEY_OC_BASE_URL) ?: return false val baseUrl = extractBaseUrl(urlData) val murenaBaseUrls = setOf( extractBaseUrl(BuildConfig.MURENA_BASE_URL_PRODUCTION), extractBaseUrl(BuildConfig.MURENA_BASE_URL_STAGING), ) val murenaBaseUrls = MurenaServerConfig.getBaseUrls(context) .map(::extractBaseUrl) .toSet() // User can have their own Murena account set up with custom Nextcloud instance, // so a check for base URL is necessary. Loading app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorFragment.kt +76 −0 Original line number Diff line number Diff line Loading @@ -38,15 +38,28 @@ import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.FragmentEeloAuthenticatorBinding import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.murenasso.MurenaSsoMigrationPreferences import at.bitfire.davdroid.settings.Settings import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.ui.ShowUrlActivity import at.bitfire.davdroid.ui.account.SettingsActivity import at.bitfire.davdroid.util.MurenaServerConfig import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent import java.net.URI class EeloAuthenticatorFragment : Fragment() { @EntryPoint @InstallIn(SingletonComponent::class) interface EeloAuthenticatorFragmentEntryPoint { fun settingsManager(): SettingsManager } private val model by viewModels<EeloAuthenticatorModel>() private val loginModel by activityViewModels<LoginModel>() Loading @@ -59,6 +72,17 @@ class EeloAuthenticatorFragment : Fragment() { private lateinit var serverUrlEditText: TextInputEditText private lateinit var passwordEditText: TextInputEditText private lateinit var passwordHolder: View private lateinit var murenaIdpOverrideContainer: View private lateinit var murenaClientIdEditText: TextInputEditText private lateinit var murenaBaseUrlEditText: TextInputEditText private lateinit var murenaDiscoveryEndpointEditText: TextInputEditText private val settings by lazy { EntryPointAccessors.fromApplication( requireContext(), EeloAuthenticatorFragmentEntryPoint::class.java ).settingsManager() } private val isReAuthenticating by lazy { requireActivity().intent.getBooleanExtra(SettingsActivity.EXTRA_IS_RE_AUTHENTICATING, false) Loading Loading @@ -88,17 +112,24 @@ class EeloAuthenticatorFragment : Fragment() { serverUrlEditText = v.root.findViewById(R.id.urlpwd_server_uri) passwordEditText = v.root.findViewById(R.id.urlpwd_password) passwordHolder = v.root.findViewById(R.id.password_holder) murenaIdpOverrideContainer = v.root.findViewById(R.id.murena_idp_override_container) murenaClientIdEditText = v.root.findViewById(R.id.murena_client_id_override) murenaBaseUrlEditText = v.root.findViewById(R.id.murena_base_url_production_override) murenaDiscoveryEndpointEditText = v.root.findViewById(R.id.murena_discovery_end_point_production_override) passwordHolder.isVisible = !EeloAuthenticatorModel.ENABLE_OIDC_SUPPORT serverToggleButton.setOnClickListener { expandCollapse() } v.root.findViewById<View>(R.id.sign_in).setOnClickListener { login() } v.root.findViewById<View>(R.id.murena_idp_reset_button).setOnClickListener { resetMurenaOverrides() } val tfaButton = v.root.findViewById<View>(R.id.twofa_info_button) tfaButton.setOnClickListener { show2FAInfoDialog() } tfaButton.isVisible = !EeloAuthenticatorModel.ENABLE_OIDC_SUPPORT loadMurenaOverrides() userIdEditText.doOnTextChanged { text, _, _, _ -> val domain = computeDomain(text) if (domain.isEmpty()) { Loading @@ -119,10 +150,13 @@ class EeloAuthenticatorFragment : Fragment() { serverToggleButton.setCompoundDrawablesWithIntrinsicBounds(null, null , ContextCompat.getDrawable(requireContext(), R.drawable.ic_expand_less), null) serverUrlEditTextLayout.visibility = View.VISIBLE serverUrlEditText.isEnabled = true murenaIdpOverrideContainer.visibility = View.GONE passwordHolder.visibility = View.VISIBLE } else { serverToggleButton.setCompoundDrawablesWithIntrinsicBounds(null, null , ContextCompat.getDrawable(requireContext(), R.drawable.ic_expand_more), null) serverUrlEditTextLayout.visibility = View.GONE serverUrlEditText.isEnabled = false murenaIdpOverrideContainer.visibility = View.VISIBLE } return v.root } Loading Loading @@ -229,6 +263,9 @@ class EeloAuthenticatorFragment : Fragment() { private fun login() { handleNoNetworkAvailable() if (!persistMurenaOverrides()) { return } val handleOpenIdAuth = EeloAuthenticatorModel.ENABLE_OIDC_SUPPORT && !toggleButtonState val userId = userIdEditText.text.toString() Loading Loading @@ -328,6 +365,7 @@ class EeloAuthenticatorFragment : Fragment() { serverToggleButton.setCompoundDrawablesWithIntrinsicBounds(null, null , ContextCompat.getDrawable(requireContext(), R.drawable.ic_expand_less), null) serverUrlEditTextLayout.visibility = View.VISIBLE serverUrlEditText.isEnabled = true murenaIdpOverrideContainer.visibility = View.GONE toggleButtonState = true passwordHolder.visibility = View.VISIBLE Loading @@ -335,6 +373,7 @@ class EeloAuthenticatorFragment : Fragment() { serverToggleButton.setCompoundDrawablesWithIntrinsicBounds(null, null , ContextCompat.getDrawable(requireContext(), R.drawable.ic_expand_more), null) serverUrlEditTextLayout.visibility = View.GONE serverUrlEditText.isEnabled = false murenaIdpOverrideContainer.visibility = View.VISIBLE toggleButtonState = false if(!EeloAuthenticatorModel.ENABLE_OIDC_SUPPORT) { Loading @@ -358,4 +397,41 @@ class EeloAuthenticatorFragment : Fragment() { .show() } private fun loadMurenaOverrides() { murenaClientIdEditText.setText(settings.getString(Settings.MURENA_CLIENT_ID_OVERRIDE) ?: MurenaServerConfig.defaultClientId()) murenaBaseUrlEditText.setText(settings.getString(Settings.MURENA_BASE_URL_PRODUCTION_OVERRIDE) ?: MurenaServerConfig.defaultProductionBaseUrl()) murenaDiscoveryEndpointEditText.setText(settings.getString(Settings.MURENA_DISCOVERY_END_POINT_PRODUCTION_OVERRIDE) ?: MurenaServerConfig.defaultProductionDiscoveryUrl()) } private fun persistMurenaOverrides(): Boolean { val clientId = murenaClientIdEditText.text?.toString()?.trim().orEmpty() val baseUrl = murenaBaseUrlEditText.text?.toString()?.trim().orEmpty() val discoveryUrl = murenaDiscoveryEndpointEditText.text?.toString()?.trim().orEmpty() if (clientId.isBlank()) { Toast.makeText(context, R.string.app_settings_murena_client_id, Toast.LENGTH_LONG).show() return false } persistOverride(Settings.MURENA_CLIENT_ID_OVERRIDE, clientId, MurenaServerConfig.defaultClientId()) persistOverride(Settings.MURENA_BASE_URL_PRODUCTION_OVERRIDE, baseUrl, MurenaServerConfig.defaultProductionBaseUrl()) persistOverride(Settings.MURENA_DISCOVERY_END_POINT_PRODUCTION_OVERRIDE, discoveryUrl, MurenaServerConfig.defaultProductionDiscoveryUrl()) return true } private fun persistOverride(key: String, value: String, defaultValue: String) { if (value == defaultValue) { settings.remove(key) } else { settings.putString(key, value) } } private fun resetMurenaOverrides() { settings.remove(Settings.MURENA_CLIENT_ID_OVERRIDE) settings.remove(Settings.MURENA_BASE_URL_PRODUCTION_OVERRIDE) settings.remove(Settings.MURENA_DISCOVERY_END_POINT_PRODUCTION_OVERRIDE) loadMurenaOverrides() } } app/src/main/kotlin/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationViewModel.kt +3 −2 Original line number Diff line number Diff line Loading @@ -95,14 +95,15 @@ class OpenIdAuthenticationViewModel @Inject constructor( intent: Intent ) { authState = AuthState(serviceConfiguration) val context = getApplication<Application>() val loginHint = intent.getStringExtra(LoginActivity.USERNAME_HINT) val authRequest = AuthorizationRequest.Builder( serviceConfiguration, identityProvider!!.clientId, identityProvider!!.getClientId(context), ResponseTypeValues.CODE, identityProvider!!.redirectUri identityProvider!!.getRedirectUri(context) ) .setScope(identityProvider!!.scope) .setLoginHint(sanitizeHint(loginHint)) Loading Loading
app/src/main/kotlin/at/bitfire/davdroid/authorization/IdentityProvider.kt +24 −0 Original line number Diff line number Diff line Loading @@ -125,6 +125,30 @@ enum class IdentityProvider( } } fun getClientId(context: Context): String { return if (this == MURENA) { MurenaServerConfig.getClientId(context) } else { clientId } } fun getRedirectUri(context: Context): Uri { return if (this == MURENA) { retrieveUri(MurenaServerConfig.getRedirectUri(context)) ?: redirectUri } else { redirectUri } } fun getLogoutRedirectUri(context: Context): Uri? { return if (this == MURENA) { retrieveUri(MurenaServerConfig.getLogoutRedirectUri(context)) ?: logoutRedirectUri } else { logoutRedirectUri } } private fun getDiscoveryEndpoint(context: Context): Uri? { return if (this == MURENA) { retrieveUri(MurenaServerConfig.getDiscoveryUrl(context)) Loading
app/src/main/kotlin/at/bitfire/davdroid/settings/Settings.kt +4 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,10 @@ object Settings { const val PROXY_HOST = "proxy_host" // String const val PROXY_PORT = "proxy_port" // Integer const val MURENA_CLIENT_ID_OVERRIDE = "murena_client_id_override" const val MURENA_BASE_URL_PRODUCTION_OVERRIDE = "murena_base_url_production_override" const val MURENA_DISCOVERY_END_POINT_PRODUCTION_OVERRIDE = "murena_discovery_end_point_production_override" /** * Whether to ignore VPNs at internet connection detection, true by default because VPN connections * seem to include "VALIDATED" by default even without actual internet connection Loading
app/src/main/kotlin/at/bitfire/davdroid/syncadapter/AccountUtils.kt +4 −5 Original line number Diff line number Diff line Loading @@ -8,10 +8,10 @@ import android.accounts.Account import android.accounts.AccountManager import android.content.Context import android.os.Bundle import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.util.MurenaServerConfig import at.bitfire.davdroid.util.setAndVerifyUserData import com.owncloud.android.lib.common.accounts.AccountUtils.Constants Loading Loading @@ -173,10 +173,9 @@ object AccountUtils { accountManager.getUserData(account, Constants.KEY_OC_BASE_URL) ?: return false val baseUrl = extractBaseUrl(urlData) val murenaBaseUrls = setOf( extractBaseUrl(BuildConfig.MURENA_BASE_URL_PRODUCTION), extractBaseUrl(BuildConfig.MURENA_BASE_URL_STAGING), ) val murenaBaseUrls = MurenaServerConfig.getBaseUrls(context) .map(::extractBaseUrl) .toSet() // User can have their own Murena account set up with custom Nextcloud instance, // so a check for base URL is necessary. Loading
app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorFragment.kt +76 −0 Original line number Diff line number Diff line Loading @@ -38,15 +38,28 @@ import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.FragmentEeloAuthenticatorBinding import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.murenasso.MurenaSsoMigrationPreferences import at.bitfire.davdroid.settings.Settings import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.ui.ShowUrlActivity import at.bitfire.davdroid.ui.account.SettingsActivity import at.bitfire.davdroid.util.MurenaServerConfig import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent import java.net.URI class EeloAuthenticatorFragment : Fragment() { @EntryPoint @InstallIn(SingletonComponent::class) interface EeloAuthenticatorFragmentEntryPoint { fun settingsManager(): SettingsManager } private val model by viewModels<EeloAuthenticatorModel>() private val loginModel by activityViewModels<LoginModel>() Loading @@ -59,6 +72,17 @@ class EeloAuthenticatorFragment : Fragment() { private lateinit var serverUrlEditText: TextInputEditText private lateinit var passwordEditText: TextInputEditText private lateinit var passwordHolder: View private lateinit var murenaIdpOverrideContainer: View private lateinit var murenaClientIdEditText: TextInputEditText private lateinit var murenaBaseUrlEditText: TextInputEditText private lateinit var murenaDiscoveryEndpointEditText: TextInputEditText private val settings by lazy { EntryPointAccessors.fromApplication( requireContext(), EeloAuthenticatorFragmentEntryPoint::class.java ).settingsManager() } private val isReAuthenticating by lazy { requireActivity().intent.getBooleanExtra(SettingsActivity.EXTRA_IS_RE_AUTHENTICATING, false) Loading Loading @@ -88,17 +112,24 @@ class EeloAuthenticatorFragment : Fragment() { serverUrlEditText = v.root.findViewById(R.id.urlpwd_server_uri) passwordEditText = v.root.findViewById(R.id.urlpwd_password) passwordHolder = v.root.findViewById(R.id.password_holder) murenaIdpOverrideContainer = v.root.findViewById(R.id.murena_idp_override_container) murenaClientIdEditText = v.root.findViewById(R.id.murena_client_id_override) murenaBaseUrlEditText = v.root.findViewById(R.id.murena_base_url_production_override) murenaDiscoveryEndpointEditText = v.root.findViewById(R.id.murena_discovery_end_point_production_override) passwordHolder.isVisible = !EeloAuthenticatorModel.ENABLE_OIDC_SUPPORT serverToggleButton.setOnClickListener { expandCollapse() } v.root.findViewById<View>(R.id.sign_in).setOnClickListener { login() } v.root.findViewById<View>(R.id.murena_idp_reset_button).setOnClickListener { resetMurenaOverrides() } val tfaButton = v.root.findViewById<View>(R.id.twofa_info_button) tfaButton.setOnClickListener { show2FAInfoDialog() } tfaButton.isVisible = !EeloAuthenticatorModel.ENABLE_OIDC_SUPPORT loadMurenaOverrides() userIdEditText.doOnTextChanged { text, _, _, _ -> val domain = computeDomain(text) if (domain.isEmpty()) { Loading @@ -119,10 +150,13 @@ class EeloAuthenticatorFragment : Fragment() { serverToggleButton.setCompoundDrawablesWithIntrinsicBounds(null, null , ContextCompat.getDrawable(requireContext(), R.drawable.ic_expand_less), null) serverUrlEditTextLayout.visibility = View.VISIBLE serverUrlEditText.isEnabled = true murenaIdpOverrideContainer.visibility = View.GONE passwordHolder.visibility = View.VISIBLE } else { serverToggleButton.setCompoundDrawablesWithIntrinsicBounds(null, null , ContextCompat.getDrawable(requireContext(), R.drawable.ic_expand_more), null) serverUrlEditTextLayout.visibility = View.GONE serverUrlEditText.isEnabled = false murenaIdpOverrideContainer.visibility = View.VISIBLE } return v.root } Loading Loading @@ -229,6 +263,9 @@ class EeloAuthenticatorFragment : Fragment() { private fun login() { handleNoNetworkAvailable() if (!persistMurenaOverrides()) { return } val handleOpenIdAuth = EeloAuthenticatorModel.ENABLE_OIDC_SUPPORT && !toggleButtonState val userId = userIdEditText.text.toString() Loading Loading @@ -328,6 +365,7 @@ class EeloAuthenticatorFragment : Fragment() { serverToggleButton.setCompoundDrawablesWithIntrinsicBounds(null, null , ContextCompat.getDrawable(requireContext(), R.drawable.ic_expand_less), null) serverUrlEditTextLayout.visibility = View.VISIBLE serverUrlEditText.isEnabled = true murenaIdpOverrideContainer.visibility = View.GONE toggleButtonState = true passwordHolder.visibility = View.VISIBLE Loading @@ -335,6 +373,7 @@ class EeloAuthenticatorFragment : Fragment() { serverToggleButton.setCompoundDrawablesWithIntrinsicBounds(null, null , ContextCompat.getDrawable(requireContext(), R.drawable.ic_expand_more), null) serverUrlEditTextLayout.visibility = View.GONE serverUrlEditText.isEnabled = false murenaIdpOverrideContainer.visibility = View.VISIBLE toggleButtonState = false if(!EeloAuthenticatorModel.ENABLE_OIDC_SUPPORT) { Loading @@ -358,4 +397,41 @@ class EeloAuthenticatorFragment : Fragment() { .show() } private fun loadMurenaOverrides() { murenaClientIdEditText.setText(settings.getString(Settings.MURENA_CLIENT_ID_OVERRIDE) ?: MurenaServerConfig.defaultClientId()) murenaBaseUrlEditText.setText(settings.getString(Settings.MURENA_BASE_URL_PRODUCTION_OVERRIDE) ?: MurenaServerConfig.defaultProductionBaseUrl()) murenaDiscoveryEndpointEditText.setText(settings.getString(Settings.MURENA_DISCOVERY_END_POINT_PRODUCTION_OVERRIDE) ?: MurenaServerConfig.defaultProductionDiscoveryUrl()) } private fun persistMurenaOverrides(): Boolean { val clientId = murenaClientIdEditText.text?.toString()?.trim().orEmpty() val baseUrl = murenaBaseUrlEditText.text?.toString()?.trim().orEmpty() val discoveryUrl = murenaDiscoveryEndpointEditText.text?.toString()?.trim().orEmpty() if (clientId.isBlank()) { Toast.makeText(context, R.string.app_settings_murena_client_id, Toast.LENGTH_LONG).show() return false } persistOverride(Settings.MURENA_CLIENT_ID_OVERRIDE, clientId, MurenaServerConfig.defaultClientId()) persistOverride(Settings.MURENA_BASE_URL_PRODUCTION_OVERRIDE, baseUrl, MurenaServerConfig.defaultProductionBaseUrl()) persistOverride(Settings.MURENA_DISCOVERY_END_POINT_PRODUCTION_OVERRIDE, discoveryUrl, MurenaServerConfig.defaultProductionDiscoveryUrl()) return true } private fun persistOverride(key: String, value: String, defaultValue: String) { if (value == defaultValue) { settings.remove(key) } else { settings.putString(key, value) } } private fun resetMurenaOverrides() { settings.remove(Settings.MURENA_CLIENT_ID_OVERRIDE) settings.remove(Settings.MURENA_BASE_URL_PRODUCTION_OVERRIDE) settings.remove(Settings.MURENA_DISCOVERY_END_POINT_PRODUCTION_OVERRIDE) loadMurenaOverrides() } }
app/src/main/kotlin/at/bitfire/davdroid/ui/setup/OpenIdAuthenticationViewModel.kt +3 −2 Original line number Diff line number Diff line Loading @@ -95,14 +95,15 @@ class OpenIdAuthenticationViewModel @Inject constructor( intent: Intent ) { authState = AuthState(serviceConfiguration) val context = getApplication<Application>() val loginHint = intent.getStringExtra(LoginActivity.USERNAME_HINT) val authRequest = AuthorizationRequest.Builder( serviceConfiguration, identityProvider!!.clientId, identityProvider!!.getClientId(context), ResponseTypeValues.CODE, identityProvider!!.redirectUri identityProvider!!.getRedirectUri(context) ) .setScope(identityProvider!!.scope) .setLoginHint(sanitizeHint(loginHint)) Loading