Skip to content

Commit 2437420

Browse files
@W-20161958: [MSDK 13.1][Android] Cannot login GUS using Welcome endpoint
1 parent 318e75c commit 2437420

2 files changed

Lines changed: 108 additions & 73 deletions

File tree

libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/LoginActivity.kt

Lines changed: 99 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ import com.salesforce.androidsdk.util.SalesforceSDKLogger.e
147147
import com.salesforce.androidsdk.util.SalesforceSDKLogger.w
148148
import com.salesforce.androidsdk.util.UriFragmentParser
149149
import kotlinx.coroutines.CoroutineScope
150-
import kotlinx.coroutines.Dispatchers
151150
import kotlinx.coroutines.Dispatchers.Default
152151
import kotlinx.coroutines.Dispatchers.IO
152+
import kotlinx.coroutines.Dispatchers.Main
153153
import kotlinx.coroutines.launch
154154
import org.json.JSONObject
155155
import java.lang.String.format
@@ -175,6 +175,8 @@ import java.security.cert.X509Certificate
175175
* them.
176176
*/
177177
open class LoginActivity : FragmentActivity() {
178+
private var customTabLauncher: ActivityResultLauncher<Intent>? = null
179+
178180
// View Model
179181
@VisibleForTesting(otherwise = PROTECTED)
180182
open val viewModel: LoginViewModel
@@ -226,7 +228,7 @@ open class LoginActivity : FragmentActivity() {
226228
SalesforceSDKManager.getInstance().setViewNavigationVisibility(this)
227229
}
228230

229-
applyIntent(intent)
231+
applyIntent()
230232

231233
// Don't let sharedBrowserSession org setting stop a new user from logging in.
232234
if (intent.extras?.getBoolean(NEW_USER) == true) {
@@ -272,7 +274,7 @@ open class LoginActivity : FragmentActivity() {
272274
onBackPressedDispatcher.addCallback { handleBackBehavior() }
273275
}
274276

275-
val customTabLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
277+
customTabLauncher = registerForActivityResult(
276278
ActivityResultContracts.StartActivityForResult()
277279
) { result: ActivityResult ->
278280
// Check if the user backed out of the custom tab.
@@ -287,48 +289,6 @@ open class LoginActivity : FragmentActivity() {
287289
}
288290
}
289291

290-
// Take action on selected server change.
291-
viewModel.selectedServer.observe(this) { selectedServer ->
292-
293-
// Guard against observing a selected server already provided by the intent data, such as a Salesforce Welcome Discovery mobile URL.
294-
val selectedServerUri = selectedServer.toUri()
295-
if (intent.data?.host == selectedServerUri.host) {
296-
return@observe
297-
}
298-
299-
// Use the URL to switch between default or Salesforce Welcome Discovery log in, if applicable.
300-
if (switchDefaultOrSalesforceWelcomeDiscoveryLogin(selectedServerUri)) {
301-
return@observe
302-
}
303-
304-
if (viewModel.singleServerCustomTabActivity) {
305-
// Skip fetching authorization and show custom tab immediately.
306-
viewModel.reloadWebView()
307-
viewModel.loginUrl.value?.let { url ->
308-
loadLoginPageInCustomTab(url, customTabLauncher)
309-
}
310-
} else {
311-
with(SalesforceSDKManager.getInstance()) {
312-
// Fetch well known config and load in custom tab if required.
313-
fetchAuthenticationConfiguration {
314-
/* Browser-based authentication is applicable when not authenticating with a front-door bridge URL */
315-
if (isBrowserLoginEnabled && !viewModel.isUsingFrontDoorBridge) {
316-
if (useWebServerAuthentication) {
317-
viewModel.loginUrl.value?.let { url -> loadLoginPageInCustomTab(url, customTabLauncher) }
318-
} else {
319-
/* Reload the webview now that isBrowserLoginEnabled has been set
320-
to true so that we generate an authorization URL with PKCE values. */
321-
lifecycleScope.launch(Dispatchers.Main) {
322-
viewModel.reloadWebView()
323-
viewModel.loginUrl.value?.let { url -> loadLoginPageInCustomTab(url, customTabLauncher) }
324-
}
325-
}
326-
}
327-
}
328-
}
329-
}
330-
}
331-
332292
// Support magic links
333293
if (viewModel.jwt != null) {
334294
swapJWTForAccessToken()
@@ -359,7 +319,9 @@ open class LoginActivity : FragmentActivity() {
359319
return
360320
}
361321

362-
applyIntent(intent)
322+
// Store the new intent and apply it to the activity.
323+
setIntent(intent)
324+
applyIntent()
363325
}
364326

365327
private fun clearWebView(showServerPicker: Boolean = true) {
@@ -925,6 +887,9 @@ open class LoginActivity : FragmentActivity() {
925887
// endregion
926888
// region Salesforce Welcome Login Private Implementation
927889

890+
/** The previously observed pending login server for use in switching between default and Salesforce Welcome Discovery log in */
891+
private var previousPendingLoginServer: String? = null
892+
928893
/**
929894
* If the intent is for Salesforce Welcome Discovery, apply it to the activity.
930895
* @param intent The intent
@@ -985,51 +950,51 @@ open class LoginActivity : FragmentActivity() {
985950
.build()
986951

987952
/**
988-
* Determines if the provided proposed selected server URL is a switch from
953+
* Determines if the provided pending login server URL is a switch from
989954
* Salesforce Welcome Discovery back to default log in.
990-
* @param proposedSelectedServerUrl The proposed selected server URL
991-
* @return Boolean true if the provided proposed selected server URL is a
955+
* @param pendingLoginServerUri The pending login server URL
956+
* @return Boolean true if the provided pending login server URL is a
992957
* switch from Salesforce Welcome Discovery back to the default log in,
993958
* false otherwise.
994959
* */
995960
private fun isSwitchFromSalesforceWelcomeDiscoveryToDefaultLogin(
996-
proposedSelectedServerUrl: Uri
997-
) = viewModel.loginUrl.value?.toUri()?.let { loginUrl ->
998-
isSalesforceWelcomeDiscoveryMobileUrl(this, loginUrl) && !(isSalesforceWelcomeDiscoveryMobileUrl(this, proposedSelectedServerUrl))
961+
pendingLoginServerUri: Uri
962+
) = previousPendingLoginServer?.toUri()?.let { previousPendingLoginServerUri ->
963+
isSalesforceWelcomeDiscoveryUrlPath(previousPendingLoginServerUri) && !(isSalesforceWelcomeDiscoveryMobileUrl(this, pendingLoginServerUri))
999964
} ?: false
1000965

1001966
/**
1002967
* Switches between default or Salesforce Welcome Discovery log in as needed
1003-
* using the provided proposed selected server URL.
1004-
* @param uri The proposed selected server URL
968+
* using the provided pending login server URL.
969+
* @param pendingLoginServerUri The pending login server URL
1005970
* @return Boolean true if a switch between default or Salesforce Welcome
1006971
* Discovery log is made, false otherwise.
1007972
*/
1008-
private fun switchDefaultOrSalesforceWelcomeDiscoveryLogin(uri: Uri) =
973+
private fun switchDefaultOrSalesforceWelcomeDiscoveryLogin(pendingLoginServerUri: Uri) =
1009974

1010-
// If the selected server has changed to a new Salesforce Welcome Discovery URL and host.
1011-
if (isSalesforceWelcomeDiscoveryUrlPath(uri)) {
975+
// If the pending login server is a change to a new Salesforce Welcome Discovery URL and host.
976+
if (isSalesforceWelcomeDiscoveryUrlPath(pendingLoginServerUri)) {
1012977

1013978
// Navigate to Salesforce Welcome Discovery.
1014979
startActivity(
1015980
Intent(
1016981
this,
1017-
LoginActivity::class.java
982+
SalesforceSDKManager.getInstance().loginActivityClass
1018983
).apply {
1019-
data = generateSalesforceWelcomeDiscoveryMobileUrl(uri)
984+
data = generateSalesforceWelcomeDiscoveryMobileUrl(pendingLoginServerUri)
1020985
flags = FLAG_ACTIVITY_SINGLE_TOP
1021986
})
1022987
true
1023988
}
1024989

1025-
// If the new selected server isn't a Salesforce Welcome Discovery URL but the previous was...
1026-
else if (isSwitchFromSalesforceWelcomeDiscoveryToDefaultLogin(uri)) {
990+
// If the pending login server isn't a Salesforce Welcome Discovery URL but the previous was...
991+
else if (isSwitchFromSalesforceWelcomeDiscoveryToDefaultLogin(pendingLoginServerUri)) {
1027992

1028-
// Navigate to login.
993+
// Navigate to default login.
1029994
startActivity(
1030995
Intent(
1031996
this,
1032-
LoginActivity::class.java
997+
SalesforceSDKManager.getInstance().loginActivityClass
1033998
).apply {
1034999
flags = FLAG_ACTIVITY_SINGLE_TOP
10351000
})
@@ -1051,6 +1016,8 @@ open class LoginActivity : FragmentActivity() {
10511016
uri: Uri
10521017
): Boolean {
10531018
return if (isSalesforceWelcomeDiscoveryMobileCallbackUrl(uri)) {
1019+
// Stop observing the pending login server since the newly started default login will do so.
1020+
viewModel.pendingServer.removeObservers(this)
10541021
startDefaultLoginWithHintAndHost(
10551022
context = this,
10561023
loginHint = uri.getQueryParameter(SALESFORCE_WELCOME_DISCOVERY_MOBILE_CALLBACK_URL_QUERY_PARAMETER_KEY_LOGIN_HINT) ?: return false,
@@ -1075,17 +1042,81 @@ open class LoginActivity : FragmentActivity() {
10751042
// endregion
10761043

10771044
/**
1078-
* Applies a new intent to the activity, for instance when the activity is
1079-
* created or receives a new intent.
1080-
* @param intent The new intent
1045+
* (Re-)applies the intent to the activity, for instance when the activity
1046+
* is created or receives a new intent.
10811047
*/
1082-
private fun applyIntent(intent: Intent) {
1048+
private fun applyIntent() {
10831049

10841050
// If the intent is for Salesforce Welcome Discovery, apply it to the activity.
10851051
applySalesforceWelcomeDiscoveryIntent(intent)
10861052

10871053
// If the intent is for log in using a UI Bridge API front door URL, apply it to the activity.
10881054
applyUiBridgeApiFrontDoorUrl(intent)
1055+
1056+
// Apply the pending login server, if applicable.
1057+
applyPendingServer()
1058+
}
1059+
1060+
/**
1061+
* Applies the pending login server. The authentication configuration will
1062+
* be fetched for the pending server and any applicable tasks will be
1063+
* performed such as browser based authentication. The pending login
1064+
* server will be set as the selected login server once the authentication
1065+
* configuration has been fetched.
1066+
*/
1067+
private fun applyPendingServer() {
1068+
1069+
// Reset the observer to consume the current and future values.
1070+
viewModel.pendingServer.removeObservers(this)
1071+
viewModel.pendingServer.observe(this) { pendingServer ->
1072+
1073+
// Guard against observing a pending login server already provided by the intent data, such as a Salesforce Welcome Discovery mobile URL.
1074+
val pendingServerUri = pendingServer.toUri()
1075+
if (intent.data?.host == pendingServerUri.host) {
1076+
previousPendingLoginServer = pendingServer
1077+
return@observe
1078+
}
1079+
1080+
// Use the URL to switch between default or Salesforce Welcome Discovery log in, if applicable.
1081+
if (switchDefaultOrSalesforceWelcomeDiscoveryLogin(pendingServerUri)) {
1082+
previousPendingLoginServer = pendingServer
1083+
return@observe
1084+
}
1085+
1086+
// Recall this pending login server for reference by future updates.
1087+
previousPendingLoginServer = pendingServer
1088+
1089+
if (viewModel.singleServerCustomTabActivity) {
1090+
// Skip fetching authorization and show custom tab immediately.
1091+
viewModel.selectedServer.value = pendingServer
1092+
viewModel.reloadWebView()
1093+
viewModel.loginUrl.value?.let { url ->
1094+
customTabLauncher?.let { loadLoginPageInCustomTab(url, it) }
1095+
}
1096+
} else {
1097+
with(SalesforceSDKManager.getInstance()) {
1098+
// Fetch the authentication configuration and load the login page in a custom tab if required.
1099+
fetchAuthenticationConfiguration {
1100+
lifecycleScope.launch(Main) {
1101+
viewModel.selectedServer.value = pendingServer
1102+
/* Browser-based authentication is applicable when not authenticating with a front-door bridge URL */
1103+
if (isBrowserLoginEnabled && !viewModel.isUsingFrontDoorBridge) {
1104+
if (useWebServerAuthentication) {
1105+
viewModel.loginUrl.value?.let { url ->
1106+
customTabLauncher?.let { loadLoginPageInCustomTab(url, it) }
1107+
}
1108+
} else {
1109+
/* Reload the webview now that isBrowserLoginEnabled has been set
1110+
to true so that we generate an authorization URL with PKCE values. */
1111+
viewModel.reloadWebView()
1112+
viewModel.loginUrl.value?.let { url -> customTabLauncher?.let { loadLoginPageInCustomTab(url, it) } }
1113+
}
1114+
}
1115+
}
1116+
}
1117+
}
1118+
}
1119+
}
10891120
}
10901121

10911122
/**
@@ -1511,7 +1542,7 @@ open class LoginActivity : FragmentActivity() {
15111542
loginHint: String,
15121543
loginHost: String,
15131544
) {
1514-
Intent(context, LoginActivity::class.java).apply {
1545+
Intent(context, SalesforceSDKManager.getInstance().loginActivityClass).apply {
15151546
putExtra(EXTRA_KEY_LOGIN_HINT, loginHint)
15161547
putExtra(EXTRA_KEY_LOGIN_HOST, loginHost)
15171548
flags = FLAG_ACTIVITY_SINGLE_TOP

libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/LoginViewModel.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,15 @@ open class LoginViewModel(val bootConfig: BootConfig) : ViewModel() {
107107
// endregion
108108

109109
// Public LiveData
110+
/** The login server that has been selected by the login server manager, has an authentication configuration and is ready for use */
110111
val selectedServer = MediatorLiveData<String>()
111112
val loginUrl = MediatorLiveData<String>()
112113
var showServerPicker = mutableStateOf(false)
113114
var loading = mutableStateOf(false)
114115

115116
// Internal LiveData
117+
/** The login server that is pending authentication configuration before becoming the selected login server */
118+
internal val pendingServer = MediatorLiveData<String>()
116119
internal val authFinished = mutableStateOf(false)
117120
internal val isIDPLoginFlowEnabled = derivedStateOf {
118121
SalesforceSDKManager.getInstance().isIDPLoginFlowEnabled
@@ -207,20 +210,21 @@ open class LoginViewModel(val bootConfig: BootConfig) : ViewModel() {
207210

208211

209212
init {
210-
// Update selectedServer when the LoginServerManager value changes
211-
selectedServer.addSource(SalesforceSDKManager.getInstance().loginServerManager.selectedServer) { newServer ->
213+
// When the login server manager selects a login server, first fetch its authentication configuration by setting the pending login server. Second, the selected login server will be set afterwards.
214+
pendingServer.addSource(SalesforceSDKManager.getInstance().loginServerManager.selectedServer) { newServer ->
212215
val trimmedServer = newServer.url.run { trim { it <= ' ' } }
213-
if (selectedServer.value == trimmedServer) {
216+
if (pendingServer.value == trimmedServer) {
214217
reloadWebView()
215218
} else {
216-
selectedServer.value = trimmedServer
219+
pendingServer.value = trimmedServer
217220
}
218221
}
219222

220223
// Update loginUrl when selectedServer updates so webview automatically reloads
221224
loginUrl.addSource(selectedServer) { newServer ->
222225
val isNewServer = loginUrl.value?.startsWith(newServer) != true
223-
if (isNewServer && !isUsingFrontDoorBridge) {
226+
// Note the web view does not reload when browser-based authentication or a UI Bridge API front door bridge URL are active.
227+
if (isNewServer && !SalesforceSDKManager.getInstance().isBrowserLoginEnabled && !isUsingFrontDoorBridge) {
224228
loginUrl.value = getAuthorizationUrl(newServer)
225229
}
226230
}

0 commit comments

Comments
 (0)