Skip to content

Commit daeb811

Browse files
@W-20161958: [MSDK 13.1][Android] Cannot login GUS using Welcome endpoint
1 parent 924877c commit daeb811

2 files changed

Lines changed: 109 additions & 74 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
@@ -145,9 +145,9 @@ import com.salesforce.androidsdk.util.SalesforceSDKLogger.e
145145
import com.salesforce.androidsdk.util.SalesforceSDKLogger.w
146146
import com.salesforce.androidsdk.util.UriFragmentParser
147147
import kotlinx.coroutines.CoroutineScope
148-
import kotlinx.coroutines.Dispatchers
149148
import kotlinx.coroutines.Dispatchers.Default
150149
import kotlinx.coroutines.Dispatchers.IO
150+
import kotlinx.coroutines.Dispatchers.Main
151151
import kotlinx.coroutines.launch
152152
import org.json.JSONObject
153153
import java.lang.String.format
@@ -173,6 +173,8 @@ import java.security.cert.X509Certificate
173173
* them.
174174
*/
175175
open class LoginActivity : FragmentActivity() {
176+
private var customTabLauncher: ActivityResultLauncher<Intent>? = null
177+
176178
// View Model
177179
@VisibleForTesting(otherwise = PROTECTED)
178180
open val viewModel: LoginViewModel
@@ -224,7 +226,7 @@ open class LoginActivity : FragmentActivity() {
224226
SalesforceSDKManager.getInstance().setViewNavigationVisibility(this)
225227
}
226228

227-
applyIntent(intent)
229+
applyIntent()
228230

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

273-
val customTabLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
275+
customTabLauncher = registerForActivityResult(
274276
ActivityResultContracts.StartActivityForResult()
275277
) { result: ActivityResult ->
276278
// Check if the user backed out of the custom tab.
@@ -285,48 +287,6 @@ open class LoginActivity : FragmentActivity() {
285287
}
286288
}
287289

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

371-
applyIntent(intent)
331+
// Store the new intent and apply it to the activity.
332+
setIntent(intent)
333+
applyIntent()
372334
}
373335

374336
private fun clearWebView(showServerPicker: Boolean = true) {
@@ -936,6 +898,9 @@ open class LoginActivity : FragmentActivity() {
936898
// endregion
937899
// region Salesforce Welcome Login Private Implementation
938900

901+
/** The previously observed pending login server for use in switching between default and Salesforce Welcome Discovery log in */
902+
private var previousPendingLoginServer: String? = null
903+
939904
/**
940905
* If the intent is for Salesforce Welcome Discovery, apply it to the activity.
941906
* @param intent The intent
@@ -996,51 +961,51 @@ open class LoginActivity : FragmentActivity() {
996961
.build()
997962

998963
/**
999-
* Determines if the provided proposed selected server URL is a switch from
964+
* Determines if the provided pending login server URL is a switch from
1000965
* Salesforce Welcome Discovery back to default log in.
1001-
* @param proposedSelectedServerUrl The proposed selected server URL
1002-
* @return Boolean true if the provided proposed selected server URL is a
966+
* @param pendingLoginServerUri The pending login server URL
967+
* @return Boolean true if the provided pending login server URL is a
1003968
* switch from Salesforce Welcome Discovery back to the default log in,
1004969
* false otherwise.
1005970
* */
1006971
private fun isSwitchFromSalesforceWelcomeDiscoveryToDefaultLogin(
1007-
proposedSelectedServerUrl: Uri
1008-
) = viewModel.loginUrl.value?.toUri()?.let { loginUrl ->
1009-
isSalesforceWelcomeDiscoveryMobileUrl(loginUrl) && !(isSalesforceWelcomeDiscoveryMobileUrl(proposedSelectedServerUrl))
972+
pendingLoginServerUri: Uri
973+
) = previousPendingLoginServer?.toUri()?.let { previousPendingLoginServerUri ->
974+
isSalesforceWelcomeDiscoveryUrlPath(previousPendingLoginServerUri) && !(isSalesforceWelcomeDiscoveryMobileUrl(this, pendingLoginServerUri))
1010975
} ?: false
1011976

1012977
/**
1013978
* Switches between default or Salesforce Welcome Discovery log in as needed
1014-
* using the provided proposed selected server URL.
1015-
* @param uri The proposed selected server URL
979+
* using the provided pending login server URL.
980+
* @param pendingLoginServerUri The pending login server URL
1016981
* @return Boolean true if a switch between default or Salesforce Welcome
1017982
* Discovery log is made, false otherwise.
1018983
*/
1019-
private fun switchDefaultOrSalesforceWelcomeDiscoveryLogin(uri: Uri) =
984+
private fun switchDefaultOrSalesforceWelcomeDiscoveryLogin(pendingLoginServerUri: Uri) =
1020985

1021-
// If the selected server has changed to a new Salesforce Welcome Discovery URL and host.
1022-
if (isSalesforceWelcomeDiscoveryUrlPath(uri)) {
986+
// If the pending login server is a change to a new Salesforce Welcome Discovery URL and host.
987+
if (isSalesforceWelcomeDiscoveryUrlPath(pendingLoginServerUri)) {
1023988

1024989
// Navigate to Salesforce Welcome Discovery.
1025990
startActivity(
1026991
Intent(
1027992
this,
1028-
LoginActivity::class.java
993+
SalesforceSDKManager.getInstance().loginActivityClass
1029994
).apply {
1030-
data = generateSalesforceWelcomeDiscoveryMobileUrl(uri)
995+
data = generateSalesforceWelcomeDiscoveryMobileUrl(pendingLoginServerUri)
1031996
flags = FLAG_ACTIVITY_SINGLE_TOP
1032997
})
1033998
true
1034999
}
10351000

1036-
// If the new selected server isn't a Salesforce Welcome Discovery URL but the previous was...
1037-
else if (isSwitchFromSalesforceWelcomeDiscoveryToDefaultLogin(uri)) {
1001+
// If the pending login server isn't a Salesforce Welcome Discovery URL but the previous was...
1002+
else if (isSwitchFromSalesforceWelcomeDiscoveryToDefaultLogin(pendingLoginServerUri)) {
10381003

1039-
// Navigate to login.
1004+
// Navigate to default login.
10401005
startActivity(
10411006
Intent(
10421007
this,
1043-
LoginActivity::class.java
1008+
SalesforceSDKManager.getInstance().loginActivityClass
10441009
).apply {
10451010
flags = FLAG_ACTIVITY_SINGLE_TOP
10461011
})
@@ -1062,6 +1027,8 @@ open class LoginActivity : FragmentActivity() {
10621027
uri: Uri
10631028
): Boolean {
10641029
return if (isSalesforceWelcomeDiscoveryMobileCallbackUrl(uri)) {
1030+
// Stop observing the pending login server since the newly started default login will do so.
1031+
viewModel.pendingServer.removeObservers(this)
10651032
startDefaultLoginWithHintAndHost(
10661033
context = this,
10671034
loginHint = uri.getQueryParameter(SALESFORCE_WELCOME_DISCOVERY_MOBILE_CALLBACK_URL_QUERY_PARAMETER_KEY_LOGIN_HINT) ?: return false,
@@ -1086,17 +1053,81 @@ open class LoginActivity : FragmentActivity() {
10861053
// endregion
10871054

10881055
/**
1089-
* Applies a new intent to the activity, for instance when the activity is
1090-
* created or receives a new intent.
1091-
* @param intent The new intent
1056+
* (Re-)applies the intent to the activity, for instance when the activity
1057+
* is created or receives a new intent.
10921058
*/
1093-
private fun applyIntent(intent: Intent) {
1059+
private fun applyIntent() {
10941060

10951061
// If the intent is for Salesforce Welcome Discovery, apply it to the activity.
10961062
applySalesforceWelcomeDiscoveryIntent(intent)
10971063

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

11021133
/**
@@ -1547,7 +1578,7 @@ open class LoginActivity : FragmentActivity() {
15471578
loginHint: String,
15481579
loginHost: String,
15491580
) {
1550-
Intent(context, LoginActivity::class.java).apply {
1581+
Intent(context, SalesforceSDKManager.getInstance().loginActivityClass).apply {
15511582
putExtra(EXTRA_KEY_LOGIN_HINT, loginHint)
15521583
putExtra(EXTRA_KEY_LOGIN_HOST, loginHost)
15531584
flags = FLAG_ACTIVITY_SINGLE_TOP

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

Lines changed: 10 additions & 6 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,21 +210,22 @@ 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 <= ' ' } }
213216
if (selectedServer.value == trimmedServer) {
214-
reloadWebView()
217+
reloadWebView() // TODO: Review the need for this and its use prior to fetching the authentication configuration. ECJ20251201
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) {
224-
loginUrl.value = getAuthorizationUrl(newServer)
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) {
228+
loginUrl.value = if (sharedBrowserSession) getAuthorizationUrl(newServer) else getAuthorizationUrl(newServer) + PROMPT_LOGIN
225229
}
226230
}
227231
}

0 commit comments

Comments
 (0)