diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bd21fcd50..5ed8e2571 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -114,21 +114,6 @@
android:launchMode="singleTask"
android:resizeableActivity="true" />
-
-
-
-
-
-
-
-
-
-
-
>> by lazy {
+ MutableLiveData>>()
+ }
+
+ val digestAuthenticationRequestedEvent: MutableLiveData> by lazy {
+ MutableLiveData>()
+ }
+
val greenToastToShowEvent: MutableLiveData>> by lazy {
MutableLiveData>>()
}
@@ -210,6 +221,57 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
}
}
}
+
+ @WorkerThread
+ override fun onAuthenticationRequested(core: Core, authInfo: AuthInfo, method: AuthMethod) {
+ if (authInfo.username == null || authInfo.domain == null || authInfo.realm == null) {
+ Log.e(
+ "$TAG Authentication request but either username [${authInfo.username}], domain [${authInfo.domain}] or realm [${authInfo.realm}] is null!"
+ )
+ return
+ }
+
+ when (method) {
+ AuthMethod.Bearer -> {
+ val serverUrl = authInfo.authorizationServer
+ val username = authInfo.username
+ if (!serverUrl.isNullOrEmpty()) {
+ Log.i(
+ "$TAG Authentication requested method is Bearer, starting Single Sign On activity with server URL [$serverUrl] and username [$username]"
+ )
+ bearerAuthInfoPendingPasswordUpdate = authInfo
+ bearerAuthenticationRequestedEvent.postValue(
+ Event(Pair(serverUrl, username))
+ )
+ } else {
+ Log.e(
+ "$TAG Authentication requested method is Bearer but no authorization server was found in auth info!"
+ )
+ }
+ }
+ AuthMethod.HttpDigest -> {
+ val accountFound = core.accountList.find {
+ it.params.identityAddress?.username == authInfo.username && it.params.identityAddress?.domain == authInfo.domain
+ }
+ if (accountFound == null) {
+ Log.w(
+ "$TAG Failed to find account matching auth info, aborting auth dialog"
+ )
+ return
+ }
+
+ val identity = "${authInfo.username}@${authInfo.domain}"
+ Log.i(
+ "$TAG Authentication requested method is HttpDigest, showing dialog asking user for password for identity [$identity]"
+ )
+ digestAuthInfoPendingPasswordUpdate = authInfo
+ digestAuthenticationRequestedEvent.postValue(Event(identity))
+ }
+ AuthMethod.Tls -> {
+ Log.w("$TAG Authentication requested method is TLS, not doing anything...")
+ }
+ }
+ }
}
private val loggingServiceListener = object : LoggingServiceListenerStub() {
@@ -392,6 +454,22 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
}
}
+ @WorkerThread
+ fun updateAuthInfo(password: String) {
+ val authInfo = digestAuthInfoPendingPasswordUpdate
+ if (authInfo != null) {
+ Log.i(
+ "$TAG Updating password for username [${authInfo.username}] using auth info [$authInfo]"
+ )
+ authInfo.password = password
+ core.addAuthInfo(authInfo)
+ digestAuthInfoPendingPasswordUpdate = null
+ core.refreshRegisters()
+ } else {
+ Log.e("$TAG No pending auth info for digest authentication!")
+ }
+ }
+
@WorkerThread
fun isAddressMyself(address: Address): Boolean {
val found = core.accountList.find {
diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt
index 0eea7aaeb..961498c60 100644
--- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt
+++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt
@@ -53,6 +53,7 @@ import org.linphone.ui.GenericActivity
import org.linphone.ui.assistant.AssistantActivity
import org.linphone.ui.main.chat.fragment.ConversationsListFragmentDirections
import org.linphone.ui.main.fragment.AuthRequestedDialogModel
+import org.linphone.ui.main.sso.fragment.SingleSignOnFragmentDirections
import org.linphone.ui.main.viewmodel.MainViewModel
import org.linphone.ui.main.viewmodel.SharedMainViewModel
import org.linphone.ui.welcome.WelcomeActivity
@@ -164,12 +165,6 @@ class MainActivity : GenericActivity() {
}
}
- viewModel.authenticationRequestedEvent.observe(this) {
- it.consume { identity ->
- showAuthenticationRequestedDialog(identity)
- }
- }
-
binding.root.doOnAttach {
Log.i("$TAG Report UI has been fully drawn (TTFD)")
try {
@@ -179,6 +174,28 @@ class MainActivity : GenericActivity() {
}
}
+ coreContext.bearerAuthenticationRequestedEvent.observe(this) {
+ it.consume { pair ->
+ val serverUrl = pair.first
+ val username = pair.second
+
+ Log.i(
+ "$TAG Navigating to Single Sign On Fragment with server URL [$serverUrl] and username [$username]"
+ )
+ val action = SingleSignOnFragmentDirections.actionGlobalSingleSignOnFragment(
+ serverUrl,
+ username
+ )
+ findNavController().navigate(action)
+ }
+ }
+
+ coreContext.digestAuthenticationRequestedEvent.observe(this) {
+ it.consume { identity ->
+ showAuthenticationRequestedDialog(identity)
+ }
+ }
+
coreContext.greenToastToShowEvent.observe(this) {
it.consume { pair ->
val message = pair.first
@@ -586,7 +603,9 @@ class MainActivity : GenericActivity() {
model.confirmEvent.observe(this) {
it.consume { password ->
- viewModel.updateAuthInfo(password)
+ coreContext.postOnCoreThread {
+ coreContext.updateAuthInfo(password)
+ }
dialog.dismiss()
}
}
diff --git a/app/src/main/java/org/linphone/ui/main/recordings/RecordingsFragment.kt b/app/src/main/java/org/linphone/ui/main/recordings/RecordingsFragment.kt
index d4c97ed2c..aabcd5e4d 100644
--- a/app/src/main/java/org/linphone/ui/main/recordings/RecordingsFragment.kt
+++ b/app/src/main/java/org/linphone/ui/main/recordings/RecordingsFragment.kt
@@ -30,6 +30,7 @@ import org.linphone.ui.main.fragment.GenericFragment
@UiThread
class RecordingsFragment : GenericFragment() {
private lateinit var binding: RecordingsFragmentBinding
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
diff --git a/app/src/main/java/org/linphone/ui/assistant/SingleSignOnActivity.kt b/app/src/main/java/org/linphone/ui/main/sso/fragment/SingleSignOnFragment.kt
similarity index 52%
rename from app/src/main/java/org/linphone/ui/assistant/SingleSignOnActivity.kt
rename to app/src/main/java/org/linphone/ui/main/sso/fragment/SingleSignOnFragment.kt
index 39a5e3c70..95f0354af 100644
--- a/app/src/main/java/org/linphone/ui/assistant/SingleSignOnActivity.kt
+++ b/app/src/main/java/org/linphone/ui/main/sso/fragment/SingleSignOnFragment.kt
@@ -17,83 +17,80 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.linphone.ui.assistant
+package org.linphone.ui.main.sso.fragment
import android.content.Intent
import android.os.Bundle
-import androidx.annotation.UiThread
-import androidx.databinding.DataBindingUtil
-import androidx.lifecycle.ViewModelProvider
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.navigation.fragment.navArgs
+import androidx.navigation.navGraphViewModels
import net.openid.appauth.AuthorizationException
import net.openid.appauth.AuthorizationResponse
import org.linphone.R
import org.linphone.core.tools.Log
-import org.linphone.databinding.AssistantSingleSignOnActivityBinding
+import org.linphone.databinding.SingleSignOnFragmentBinding
import org.linphone.ui.GenericActivity
-import org.linphone.ui.assistant.viewmodel.SingleSignOnViewModel
+import org.linphone.ui.main.fragment.GenericFragment
+import org.linphone.ui.main.sso.viewmodel.SingleSignOnViewModel
-@UiThread
-class SingleSignOnActivity : GenericActivity() {
+class SingleSignOnFragment : GenericFragment() {
companion object {
- private const val TAG = "[Single Sign On Activity]"
+ private const val TAG = "[Single Sign On Fragment]"
private const val ACTIVITY_RESULT_ID = 666
}
- private lateinit var binding: AssistantSingleSignOnActivityBinding
+ private lateinit var binding: SingleSignOnFragmentBinding
- private lateinit var viewModel: SingleSignOnViewModel
+ private val viewModel: SingleSignOnViewModel by navGraphViewModels(
+ R.id.main_nav_graph
+ )
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
+ private val args: SingleSignOnFragmentArgs by navArgs()
- binding = DataBindingUtil.setContentView(this, R.layout.assistant_single_sign_on_activity)
- binding.lifecycleOwner = this
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = SingleSignOnFragmentBinding.inflate(layoutInflater)
+ return binding.root
+ }
- viewModel = ViewModelProvider(this)[SingleSignOnViewModel::class.java]
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
- setUpToastsArea(binding.toastsArea)
-
- if (intent != null) {
- Log.i(
- "$TAG Handling intent action [${intent.action}], type [${intent.type}] and data [${intent.data}]"
- )
- val uri = intent.data?.toString() ?: ""
- if (uri.startsWith("linphone-sso:")) {
- val ssoUrl = uri.replace("linphone-sso:", "https:")
- Log.i("$TAG Setting SSO URL [$ssoUrl]")
- viewModel.singleSignOnUrl.value = ssoUrl
- }
- }
-
- viewModel.singleSignOnUrl.observe(this) { url ->
- Log.i("$TAG SSO URL found [$url], setting it up")
- viewModel.setUp()
- }
-
- viewModel.singleSignOnProcessCompletedEvent.observe(this) {
+ viewModel.singleSignOnProcessCompletedEvent.observe(viewLifecycleOwner) {
it.consume {
- Log.i("$TAG Process complete, leaving assistant")
- finish()
+ Log.i("$TAG Process complete, going back")
+ goBack()
}
}
- viewModel.startAuthIntentEvent.observe(this) {
+ viewModel.startAuthIntentEvent.observe(viewLifecycleOwner) {
it.consume { intent ->
Log.i("$TAG Starting auth intent activity")
startActivityForResult(intent, ACTIVITY_RESULT_ID)
}
}
- viewModel.onErrorEvent.observe(this) {
+ viewModel.onErrorEvent.observe(viewLifecycleOwner) {
it.consume { errorMessage ->
- showRedToast(
+ (requireActivity() as GenericActivity).showRedToast(
errorMessage,
R.drawable.warning_circle
)
}
}
+
+ val serverUrl = args.serverUrl
+ val username = args.username
+ Log.i("$TAG Found server URL [$serverUrl] and username [$username] in args")
+ viewModel.setUp(serverUrl, username.orEmpty())
}
@Deprecated("Deprecated in Java")
diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/SingleSignOnViewModel.kt b/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt
similarity index 84%
rename from app/src/main/java/org/linphone/ui/assistant/viewmodel/SingleSignOnViewModel.kt
rename to app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt
index 043aabe89..b838bff0f 100644
--- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/SingleSignOnViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt
@@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.linphone.ui.assistant.viewmodel
+package org.linphone.ui.main.sso.viewmodel
import android.content.Intent
import android.net.Uri
@@ -35,6 +35,7 @@ import net.openid.appauth.AuthorizationService
import net.openid.appauth.AuthorizationServiceConfiguration
import net.openid.appauth.ResponseTypeValues
import org.linphone.LinphoneApplication.Companion.coreContext
+import org.linphone.core.Factory
import org.linphone.core.tools.Log
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
@@ -50,7 +51,9 @@ class SingleSignOnViewModel : ViewModel() {
val singleSignOnProcessCompletedEvent = MutableLiveData>()
- val singleSignOnUrl = MutableLiveData()
+ private var singleSignOnUrl = ""
+
+ private var username: String = ""
val startAuthIntentEvent: MutableLiveData> by lazy {
MutableLiveData>()
@@ -60,15 +63,18 @@ class SingleSignOnViewModel : ViewModel() {
MutableLiveData>()
}
- private var preFilledUser: String = ""
-
private lateinit var authState: AuthState
private lateinit var authService: AuthorizationService
@UiThread
- fun setUp() {
+ fun setUp(ssoUrl: String, user: String = "") {
viewModelScope.launch {
- Log.i("$TAG Setting up SSO environment, redirect URI is [$REDIRECT_URI]")
+ singleSignOnUrl = ssoUrl
+ username = user
+
+ Log.i(
+ "$TAG Setting up SSO environment for username [$username] and URL [$singleSignOnUrl], redirect URI is [$REDIRECT_URI]"
+ )
authState = getAuthState()
updateTokenInfo()
}
@@ -94,7 +100,7 @@ class SingleSignOnViewModel : ViewModel() {
private fun singleSignOn() {
Log.i("$TAG Fetch from issuer")
AuthorizationServiceConfiguration.fetchFromUrl(
- Uri.parse(singleSignOnUrl.value),
+ Uri.parse(singleSignOnUrl),
AuthorizationServiceConfiguration.RetrieveConfigurationCallback { serviceConfiguration, ex ->
if (ex != null) {
Log.e("$TAG Failed to fetch configuration")
@@ -120,8 +126,8 @@ class SingleSignOnViewModel : ViewModel() {
Uri.parse(REDIRECT_URI) // the redirect URI to which the auth response is sent
)
- if (preFilledUser.isNotEmpty()) {
- authRequestBuilder.setLoginHint(preFilledUser)
+ if (username.isNotEmpty()) {
+ authRequestBuilder.setLoginHint(username)
}
val authRequest = authRequestBuilder.build()
@@ -187,7 +193,7 @@ class SingleSignOnViewModel : ViewModel() {
storeAuthStateAsJsonFile()
}
- useToken()
+ storeTokensInAuthInfo()
} else {
Log.e("$TAG Failed to perform token request [$ex]")
onErrorEvent.postValue(Event(ex?.errorDescription.orEmpty()))
@@ -214,9 +220,7 @@ class SingleSignOnViewModel : ViewModel() {
return@AuthStateAction
}
- Log.i("$$TAG Access & id tokens are now available")
- Log.d("$TAG Access token [$accessToken], id token [$idToken]")
-
+ Log.i("$TAG Access & id tokens are now available")
storeAuthStateAsJsonFile()
}
)*/
@@ -288,7 +292,7 @@ class SingleSignOnViewModel : ViewModel() {
}
val time = TimestampUtils.toString(expiration, timestampInSecs = false)
Log.i("$TAG Access token expires [$date] [$time]")
- singleSignOnProcessCompletedEvent.postValue(Event(true))
+ storeTokensInAuthInfo()
}
} else {
Log.w("$TAG Access token expiration info not available")
@@ -307,4 +311,35 @@ class SingleSignOnViewModel : ViewModel() {
singleSignOn()
}
}
+
+ @UiThread
+ private fun storeTokensInAuthInfo() {
+ coreContext.postOnCoreThread { core ->
+ val expire = authState.accessTokenExpirationTime
+ if (expire == null) {
+ Log.e("$TAG Access token expiration time is null!")
+ onErrorEvent.postValue(Event("Invalid access token expiration time"))
+ } else {
+ val accessToken =
+ Factory.instance().createBearerToken(authState.accessToken, expire)
+ val refreshToken =
+ Factory.instance().createBearerToken(authState.refreshToken, expire)
+
+ val authInfo = coreContext.bearerAuthInfoPendingPasswordUpdate
+ if (authInfo == null) {
+ Log.e("$TAG No pending auth info in CoreContext!")
+ return@postOnCoreThread
+ }
+ authInfo.accessToken = accessToken
+ authInfo.refreshToken = refreshToken
+ core.addAuthInfo(authInfo)
+
+ Log.i(
+ "$TAG Auth info for username [$username] filled with access token [${authState.accessToken}], refresh token [${authState.refreshToken}] and expire [$expire], refreshing REGISTERs"
+ )
+ core.refreshRegisters()
+ singleSignOnProcessCompletedEvent.postValue(Event(true))
+ }
+ }
+ }
}
diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt
index e0d2ba9b7..b422d49b9 100644
--- a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt
@@ -34,8 +34,6 @@ import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.core.Account
-import org.linphone.core.AuthInfo
-import org.linphone.core.AuthMethod
import org.linphone.core.Call
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
@@ -93,10 +91,6 @@ class MainViewModel @UiThread constructor() : ViewModel() {
MutableLiveData>()
}
- val authenticationRequestedEvent: MutableLiveData> by lazy {
- MutableLiveData>()
- }
-
var accountsFound = -1
var mainIntentHandled = false
@@ -109,8 +103,6 @@ class MainViewModel @UiThread constructor() : ViewModel() {
private var firstAccountRegistered: Boolean = false
- private var authInfoPendingPasswordUpdate: AuthInfo? = null
-
private val coreListener = object : CoreListenerStub() {
@WorkerThread
override fun onLastCallEnded(core: Core) {
@@ -237,27 +229,6 @@ class MainViewModel @UiThread constructor() : ViewModel() {
core.defaultAccount = core.accountList.firstOrNull()
}
}
-
- @WorkerThread
- override fun onAuthenticationRequested(core: Core, authInfo: AuthInfo, method: AuthMethod) {
- if (authInfo.username == null || authInfo.domain == null || authInfo.realm == null) {
- return
- }
-
- Log.w(
- "$TAG Authentication requested for account [${authInfo.username}@${authInfo.domain}] with realm [${authInfo.realm}] using method [$method]"
- )
- val accountFound = core.accountList.find {
- it.params.identityAddress?.username == authInfo.username && it.params.identityAddress?.domain == authInfo.domain
- }
- if (accountFound == null) {
- Log.w("$TAG Failed to find account matching auth info, aborting auth dialog")
- return
- }
- val identity = "${authInfo.username}@${authInfo.domain}"
- authInfoPendingPasswordUpdate = authInfo
- authenticationRequestedEvent.postValue(Event(identity))
- }
}
init {
@@ -340,22 +311,6 @@ class MainViewModel @UiThread constructor() : ViewModel() {
}
}
- @UiThread
- fun updateAuthInfo(password: String) {
- coreContext.postOnCoreThread { core ->
- val authInfo = authInfoPendingPasswordUpdate
- if (authInfo != null) {
- Log.i(
- "$TAG Updating password for username [${authInfo.username}] using auth info [$authInfo]"
- )
- authInfo.password = password
- core.addAuthInfo(authInfo)
- authInfoPendingPasswordUpdate = null
- core.refreshRegisters()
- }
- }
- }
-
@WorkerThread
private fun updateCallAlert() {
val core = coreContext.core
diff --git a/app/src/main/res/layout/account_profile_fragment.xml b/app/src/main/res/layout/account_profile_fragment.xml
index e00d1c463..c26fc120d 100644
--- a/app/src/main/res/layout/account_profile_fragment.xml
+++ b/app/src/main/res/layout/account_profile_fragment.xml
@@ -250,7 +250,7 @@
android:textColor="@color/gray_main2_600"
android:maxLines="1"
android:background="@drawable/edit_text_background"
- android:inputType="text|textPersonName"
+ android:inputType="text|textPersonName|textCapSentences"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_max="@dimen/text_input_max_width"
app:layout_constraintTop_toBottomOf="@id/display_name_label"
@@ -403,6 +403,7 @@
android:layout_marginEnd="16dp"
android:contentDescription="@null"
android:src="@drawable/shape_squircle_white_background"
+ android:visibility="@{viewModel.showModeSelection ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/connection_background"
@@ -416,6 +417,7 @@
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.isCurrentlySelectedModeSecure ? @string/manage_account_e2e_encrypted_mode_default_title : @string/manage_account_e2e_encrypted_mode_interoperable_title, default=@string/manage_account_e2e_encrypted_mode_default_title}"
+ android:visibility="@{viewModel.showModeSelection ? View.VISIBLE : View.GONE}"
app:layout_constraintTop_toTopOf="@id/mode_background"
app:layout_constraintStart_toStartOf="@id/mode_background"
app:layout_constraintBottom_toBottomOf="@id/mode_background"/>
@@ -436,6 +438,7 @@
android:text="@string/manage_account_change_mode"
android:maxLines="1"
android:ellipsize="end"
+ android:visibility="@{viewModel.showModeSelection ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="@id/mode_background"
app:layout_constraintTop_toTopOf="@id/mode_background"
app:layout_constraintBottom_toBottomOf="@id/mode_background"/>
diff --git a/app/src/main/res/layout/assistant_single_sign_on_activity.xml b/app/src/main/res/layout/assistant_single_sign_on_activity.xml
index 7e00bbc82..3a61d8b79 100644
--- a/app/src/main/res/layout/assistant_single_sign_on_activity.xml
+++ b/app/src/main/res/layout/assistant_single_sign_on_activity.xml
@@ -6,7 +6,7 @@
+ type="org.linphone.ui.main.sso.viewmodel.SingleSignOnViewModel" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml
index b396b5ecc..07d72949d 100644
--- a/app/src/main/res/navigation/main_nav_graph.xml
+++ b/app/src/main/res/navigation/main_nav_graph.xml
@@ -437,4 +437,25 @@
android:label="SettingsAdvancedFragment"
tools:layout="@layout/settings_advanced_fragment"/>
+
+
+
+
+
+
+
\ No newline at end of file