mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 03:18:06 +00:00
Updated third party SIP account login to allow for SIP identity with a different domain for proxy + bearer auth
This commit is contained in:
parent
655cc8c291
commit
ecad8fbdce
8 changed files with 113 additions and 36 deletions
|
|
@ -38,6 +38,7 @@ import org.linphone.databinding.AssistantThirdPartySipAccountLoginFragmentBindin
|
|||
import org.linphone.ui.GenericActivity
|
||||
import org.linphone.ui.GenericFragment
|
||||
import org.linphone.ui.assistant.viewmodel.ThirdPartySipAccountLoginViewModel
|
||||
import org.linphone.ui.main.sso.fragment.SingleSignOnFragmentDirections
|
||||
import org.linphone.utils.PhoneNumberUtils
|
||||
|
||||
@UiThread
|
||||
|
|
@ -119,6 +120,22 @@ class ThirdPartySipAccountLoginFragment : GenericFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
coreContext.bearerAuthenticationRequestedEvent.observe(viewLifecycleOwner) {
|
||||
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.postOnCoreThread {
|
||||
val dialPlan = PhoneNumberUtils.getDeviceDialPlan(requireContext())
|
||||
if (dialPlan != null) {
|
||||
|
|
|
|||
|
|
@ -124,9 +124,6 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : GenericViewMo
|
|||
loginEnabled.addSource(username) {
|
||||
loginEnabled.value = isLoginButtonEnabled()
|
||||
}
|
||||
loginEnabled.addSource(password) {
|
||||
loginEnabled.value = isLoginButtonEnabled()
|
||||
}
|
||||
loginEnabled.addSource(domain) {
|
||||
loginEnabled.value = isLoginButtonEnabled()
|
||||
}
|
||||
|
|
@ -143,8 +140,31 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : GenericViewMo
|
|||
coreContext.postOnCoreThread { core ->
|
||||
core.loadConfigFromXml(corePreferences.thirdPartyDefaultValuesPath)
|
||||
|
||||
val user = username.value.orEmpty().trim()
|
||||
// Remove sip: in front of domain, just in case...
|
||||
val domainValue = domain.value.orEmpty().trim()
|
||||
val domain = if (domainValue.startsWith("sip:")) {
|
||||
domainValue.substring("sip:".length)
|
||||
} else {
|
||||
domainValue
|
||||
}
|
||||
|
||||
// Allow to enter SIP identity instead of simply username
|
||||
// in case identity domain doesn't match proxy domain
|
||||
val user = username.value.orEmpty().trim()
|
||||
val identity = if (user.startsWith("sip:")) {
|
||||
if (user.contains("@")) {
|
||||
user
|
||||
} else {
|
||||
"$user@$domain"
|
||||
}
|
||||
} else {
|
||||
if (user.contains("@")) {
|
||||
"sip:$user"
|
||||
} else {
|
||||
"sip:$user@$domain"
|
||||
}
|
||||
}
|
||||
val identityAddress = Factory.instance().createAddress(identity)
|
||||
|
||||
newlyCreatedAuthInfo = Factory.instance().createAuthInfo(
|
||||
user,
|
||||
|
|
@ -152,19 +172,18 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : GenericViewMo
|
|||
password.value.orEmpty().trim(),
|
||||
null,
|
||||
null,
|
||||
domainValue
|
||||
null
|
||||
)
|
||||
core.addAuthInfo(newlyCreatedAuthInfo)
|
||||
|
||||
val accountParams = core.createAccountParams()
|
||||
|
||||
val identityAddress = Factory.instance().createAddress("sip:$user@$domainValue")
|
||||
if (displayName.value.orEmpty().isNotEmpty()) {
|
||||
identityAddress?.displayName = displayName.value.orEmpty().trim()
|
||||
}
|
||||
accountParams.identityAddress = identityAddress
|
||||
|
||||
val serverAddress = Factory.instance().createAddress("sip:$domainValue")
|
||||
val serverAddress = Factory.instance().createAddress("sip:$domain")
|
||||
serverAddress?.transport = when (transport.value.orEmpty().trim()) {
|
||||
TransportType.Tcp.name.uppercase(Locale.getDefault()) -> TransportType.Tcp
|
||||
TransportType.Tls.name.uppercase(Locale.getDefault()) -> TransportType.Tls
|
||||
|
|
@ -204,6 +223,7 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : GenericViewMo
|
|||
|
||||
@UiThread
|
||||
private fun isLoginButtonEnabled(): Boolean {
|
||||
return username.value.orEmpty().isNotEmpty() && password.value.orEmpty().isNotEmpty() && domain.value.orEmpty().isNotEmpty()
|
||||
// Password isn't mandatory as authentication could be Bearer
|
||||
return username.value.orEmpty().isNotEmpty() && domain.value.orEmpty().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import net.openid.appauth.AuthorizationException
|
||||
import net.openid.appauth.AuthorizationResponse
|
||||
import org.linphone.R
|
||||
|
|
@ -44,9 +44,7 @@ class SingleSignOnFragment : GenericMainFragment() {
|
|||
|
||||
private lateinit var binding: SingleSignOnFragmentBinding
|
||||
|
||||
private val viewModel: SingleSignOnViewModel by navGraphViewModels(
|
||||
R.id.main_nav_graph
|
||||
)
|
||||
private lateinit var viewModel: SingleSignOnViewModel
|
||||
|
||||
private val args: SingleSignOnFragmentArgs by navArgs()
|
||||
|
||||
|
|
@ -62,6 +60,8 @@ class SingleSignOnFragment : GenericMainFragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
|
||||
viewModel = ViewModelProvider(this)[SingleSignOnViewModel::class.java]
|
||||
binding.viewModel = viewModel
|
||||
observeToastEvents(viewModel)
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ class SingleSignOnViewModel : GenericViewModel() {
|
|||
"$singleSignOnUrl/.well-known/openid-configuration"
|
||||
}
|
||||
singleSignOn()
|
||||
return@RetrieveConfigurationCallback
|
||||
} else {
|
||||
onErrorEvent.postValue(Event("Failed to fetch configuration"))
|
||||
return@RetrieveConfigurationCallback
|
||||
|
|
@ -158,34 +159,48 @@ class SingleSignOnViewModel : GenericViewModel() {
|
|||
authService = AuthorizationService(coreContext.context)
|
||||
}
|
||||
|
||||
val authStateJsonFile = File(
|
||||
coreContext.context.filesDir.absolutePath,
|
||||
"auth_state.json"
|
||||
)
|
||||
Log.i("$TAG Starting refresh token request")
|
||||
authService.performTokenRequest(
|
||||
authState.createTokenRefreshRequest()
|
||||
) { resp, ex ->
|
||||
if (resp != null) {
|
||||
Log.i("$TAG Token refresh succeeded!")
|
||||
try {
|
||||
authService.performTokenRequest(
|
||||
authState.createTokenRefreshRequest()
|
||||
) { resp, ex ->
|
||||
if (resp != null) {
|
||||
Log.i("$TAG Token refresh succeeded!")
|
||||
|
||||
if (::authState.isInitialized) {
|
||||
Log.i("$TAG Updating AuthState object after refresh token response")
|
||||
authState.update(resp, ex)
|
||||
storeAuthStateAsJsonFile()
|
||||
}
|
||||
updateTokenInfo()
|
||||
} else {
|
||||
Log.e(
|
||||
"$TAG Failed to perform token refresh [$ex], destroying auth_state.json file"
|
||||
)
|
||||
onErrorEvent.postValue(Event(ex?.errorDescription.orEmpty()))
|
||||
|
||||
val file = File(coreContext.context.filesDir.absolutePath, "auth_state.json")
|
||||
viewModelScope.launch {
|
||||
FileUtils.deleteFile(file.absolutePath)
|
||||
Log.w(
|
||||
"$TAG Previous auth_state.json file deleted, starting single sign on process from scratch"
|
||||
if (::authState.isInitialized) {
|
||||
Log.i("$TAG Updating AuthState object after refresh token response")
|
||||
authState.update(resp, ex)
|
||||
storeAuthStateAsJsonFile()
|
||||
}
|
||||
updateTokenInfo()
|
||||
} else {
|
||||
Log.e(
|
||||
"$TAG Failed to perform token refresh [$ex], destroying auth_state.json file"
|
||||
)
|
||||
singleSignOn()
|
||||
onErrorEvent.postValue(Event(ex?.errorDescription.orEmpty()))
|
||||
|
||||
viewModelScope.launch {
|
||||
FileUtils.deleteFile(authStateJsonFile.absolutePath)
|
||||
Log.w(
|
||||
"$TAG Previous auth_state.json file deleted, starting single sign on process from scratch"
|
||||
)
|
||||
singleSignOn()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ise: IllegalStateException) {
|
||||
Log.e("$TAG Illegal state exception, clearing auth state and trying again: $ise")
|
||||
viewModelScope.launch {
|
||||
FileUtils.deleteFile(authStateJsonFile.absolutePath)
|
||||
authState = getAuthState()
|
||||
performRefreshToken()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("$TAG Failed to perform token request: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@
|
|||
android:layout_marginTop="38dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@{@string/username + `*`, default=`Username*`}"
|
||||
android:text="@string/assistant_third_party_sip_account_username_or_identity"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="@id/username"/>
|
||||
|
||||
|
|
|
|||
|
|
@ -168,4 +168,27 @@
|
|||
app:destination="@id/profileModeFragment" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/singleSignOnFragment"
|
||||
android:name="org.linphone.ui.main.sso.fragment.SingleSignOnFragment"
|
||||
android:label="SingleSignOnFragment"
|
||||
tools:layout="@layout/single_sign_on_fragment">
|
||||
<argument
|
||||
android:name="serverUrl"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="username"
|
||||
app:argType="string"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
</fragment>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_global_singleSignOnFragment"
|
||||
app:destination="@id/singleSignOnFragment"
|
||||
app:launchSingleTop="true"
|
||||
app:enterAnim="@anim/slide_in"
|
||||
app:popExitAnim="@anim/slide_out" />
|
||||
|
||||
</navigation>
|
||||
|
|
@ -137,6 +137,7 @@
|
|||
<string name="assistant_third_party_sip_account_warning_explanation">Certaines fonctionalités telles que les conversations de groupe, les vidéo-conférences, etc… nécessitent un compte &appName;.\n\nCes fonctionalités seront masquées si vous utilisez un compte SIP tiers.\n\nPour les activer dans un projet commercial, merci de nous contacter.</string>
|
||||
<string name="assistant_third_party_sip_account_create_linphone_account">Je préfère créer un compte</string>
|
||||
<string name="assistant_third_party_sip_account_warning_ok">J\'ai compris</string>
|
||||
<string name="assistant_third_party_sip_account_username_or_identity">Nom d\'utilisateur ou identité SIP*</string>
|
||||
<string name="assistant_account_secure_mode_title">Choisissez votre mode</string>
|
||||
<string name="assistant_account_secure_mode_subtitle">Vous pourrez le changer plus tard</string>
|
||||
<string name="assistant_secure_mode_finish_account_login">Continuer</string>
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@
|
|||
<string name="assistant_third_party_sip_account_warning_explanation">Some features require a &appName; account, such as group messaging, video conferences…\n\nThese features are hidden when you register with a third party SIP account.\n\nTo enable it in a commercial project, please contact us.</string>
|
||||
<string name="assistant_third_party_sip_account_create_linphone_account">I prefer to create an account</string>
|
||||
<string name="assistant_third_party_sip_account_warning_ok">I understand</string>
|
||||
<string name="assistant_third_party_sip_account_username_or_identity">Username or SIP identity*</string>
|
||||
<string name="assistant_account_secure_mode_title">Personalize your profile mode</string>
|
||||
<string name="assistant_account_secure_mode_subtitle">You may change that mode later</string>
|
||||
<string name="assistant_secure_mode_finish_account_login">Continue</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue