-
Notifications
You must be signed in to change notification settings - Fork 393
Fix Login for Admin and Welcome Discovery incompatibility #2933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -79,6 +79,7 @@ import kotlinx.coroutines.launch | |||
| import kotlinx.coroutines.withContext | ||||
| import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||
| import java.net.URI | ||||
| import java.net.URLEncoder | ||||
| import kotlin.coroutines.CoroutineContext | ||||
|
|
||||
| /** | ||||
|
|
@@ -299,8 +300,69 @@ open class LoginViewModel( | |||
| loginUrl.addSource(selectedServer, LoginUrlSource()) | ||||
| } | ||||
|
|
||||
| /** | ||||
| * Creates a Salesforce Welcome Discovery mobile URL using the provided | ||||
| * Salesforce Welcome Discovery host and path URL. | ||||
| * @param salesforceWelcomeDiscoveryHostAndPathUrl The Salesforce Welcome | ||||
| * Discovery host and path URL | ||||
| * @return A Salesforce Welcome Discovery mobile URL with all required | ||||
| * parameters | ||||
| */ | ||||
| internal fun generateSalesforceWelcomeDiscoveryMobileUrl( | ||||
| salesforceWelcomeDiscoveryHostAndPathUrl: Uri | ||||
| ) = salesforceWelcomeDiscoveryHostAndPathUrl.buildUpon() | ||||
| .appendQueryParameter( | ||||
| LoginActivity.SALESFORCE_WELCOME_DISCOVERY_MOBILE_URL_QUERY_PARAMETER_KEY_CLIENT_ID, | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||
| oAuthConfig.consumerKey, | ||||
| ) | ||||
| .appendQueryParameter( | ||||
| LoginActivity.SALESFORCE_WELCOME_DISCOVERY_MOBILE_URL_QUERY_PARAMETER_KEY_CLIENT_VERSION, | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||
| URLEncoder.encode(SalesforceSDKManager.getInstance().appVersion, "utf8") | ||||
| ) | ||||
| .appendQueryParameter( | ||||
| LoginActivity.SALESFORCE_WELCOME_DISCOVERY_MOBILE_URL_QUERY_PARAMETER_KEY_CALLBACK_URL, | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||
| LoginActivity.SALESFORCE_WELCOME_DISCOVERY_MOBILE_CALLBACK_URL | ||||
| ) | ||||
| .build() | ||||
|
|
||||
| /** Reloads the WebView with a newly generated authorization URL. */ | ||||
| open fun reloadWebView() { | ||||
| // If the user-selected login server (per LoginServerManager) is Salesforce Welcome | ||||
| // Discovery, load the Phase 1 discovery mobile URL directly instead of regenerating a | ||||
| // Phase-2-style auth URL. We intentionally do NOT key off `selectedServer.value` here: | ||||
| // during Phase 2, the VM's selectedServer is the discovered My Domain, while the | ||||
| // LoginServerManager retains Welcome Discovery as the user's actual server selection. | ||||
| // This branch runs BEFORE the isUsingFrontDoorBridge short-circuit by design — Welcome | ||||
| // Discovery and frontdoor bridge are mutually exclusive in practice, but defense-in-depth | ||||
| // ordering ensures a stray frontdoor URL cannot suppress the discovery reload. | ||||
| SalesforceSDKManager.getInstance().loginServerManager.selectedLoginServer?.url | ||||
| ?.toUri()?.let { selectedLoginServerUri -> | ||||
| if (isSalesforceWelcomeDiscoveryUrlPath(selectedLoginServerUri)) { | ||||
| // Reset the top app bar background to White synchronously so the bar flips | ||||
| // immediately when the user reloads/re-selects Welcome Discovery from Phase 2. | ||||
| // dynamicBackgroundColor is otherwise only updated by AuthWebViewClient. | ||||
| // onPageFinished, which runs after the new page loads — the gap leaves the | ||||
| // Phase-2 color visible on the bar even though the WebView URL has changed. | ||||
| dynamicBackgroundColor.value = White | ||||
| // Set loginUrl first so LoginUrlSource's same-host short-circuit (host of new | ||||
| // selectedServer == host of current loginUrl) suppresses an auth-URL regen | ||||
| // when we reset selectedServer below. | ||||
| loginUrl.value = | ||||
| generateSalesforceWelcomeDiscoveryMobileUrl(selectedLoginServerUri).toString() | ||||
| // Reset VM-level selectedServer back to the Welcome URL so the top app bar | ||||
| // title (via defaultTitleText), menu gating in Compose (LoginView reads | ||||
| // viewModel.selectedServer to hide "Login for Admin" on Phase 1), and any | ||||
| // other observers of selectedServer realign with Phase 1. In Phase 2 this | ||||
| // value had been overwritten with the discovered My Domain by | ||||
| // applySalesforceWelcomeLoginHintAndHost → applyPendingServer. | ||||
| val welcomeUrl = selectedLoginServerUri.toString() | ||||
| if (selectedServer.value != welcomeUrl) { | ||||
| selectedServer.value = welcomeUrl | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ordering of |
||||
| } | ||||
| return | ||||
| } | ||||
| } | ||||
|
|
||||
| if (!isUsingFrontDoorBridge) { | ||||
| selectedServer.value?.let { server -> | ||||
| // The Web Server Flow code challenge makes the authorization url unique each time, | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -90,6 +90,7 @@ import androidx.compose.runtime.mutableStateOf | |
| import androidx.compose.runtime.remember | ||
| import androidx.compose.runtime.setValue | ||
| import androidx.compose.ui.Alignment | ||
| import androidx.core.net.toUri | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.draw.shadow | ||
| import androidx.compose.ui.graphics.Color | ||
|
|
@@ -147,10 +148,16 @@ fun LoginView() { | |
| val viewModel: LoginViewModel = | ||
| viewModel(factory = SalesforceSDKManager.getInstance().loginViewModelFactory) | ||
| val frontDoorBridgeUrl = viewModel.frontDoorBridgeUrl.observeAsState() | ||
| // Observe selectedServer and loginUrl AS COMPOSE STATE so that recomposition triggers when | ||
| // either changes — and so titleText is read inside Compose's snapshot system, not via the | ||
| // non-reactive `defaultTitleText` getter chain (which reads LiveData.getValue() directly and | ||
| // is invisible to Compose). | ||
| val selectedServer = viewModel.selectedServer.observeAsState() | ||
| val loginUrl = viewModel.loginUrl.observeAsState() | ||
| val titleText = if (frontDoorBridgeUrl.value != null) { | ||
| viewModel.frontdoorBridgeServer ?: "" | ||
| } else { | ||
| viewModel.titleText ?: viewModel.defaultTitleText | ||
| viewModel.titleText ?: if (loginUrl.value == LoginActivity.ABOUT_BLANK) "" else selectedServer.value ?: "" | ||
| } | ||
| val showDevSupport = with(SalesforceSDKManager.getInstance()) { | ||
| return@with if (isDebugBuild && isDevSupportEnabled()) { | ||
|
|
@@ -160,6 +167,8 @@ fun LoginView() { | |
| } | ||
| } | ||
|
|
||
| val isWelcomeDiscoveryServer = selectedServer.value | ||
| ?.let { LoginActivity.isSalesforceWelcomeDiscoveryUrlPath(it.toUri()) } == true | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| val topAppBar = viewModel.topAppBar ?: { | ||
| DefaultTopAppBar( | ||
| backgroundColor = viewModel.topBarColor ?: viewModel.dynamicBackgroundColor.value, | ||
|
|
@@ -172,7 +181,9 @@ fun LoginView() { | |
| shouldShowBackButton = viewModel.shouldShowBackButton, | ||
| showDevSupport = showDevSupport, | ||
| finish = { activity.handleBackBehavior() }, | ||
| onLoginForAdmins = { activity.launchLoginForAdminsAction() }, | ||
| onLoginForAdmins = if (isWelcomeDiscoveryServer) null else { | ||
| { activity.launchLoginForAdminsAction() } | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Compose-layer visibility gate (
isWelcomeDiscoveryServerinLoginView.kt) is keyed offviewModel.selectedServer, while this programmatic guard is keyed offLoginServerManager.selectedLoginServer. These are intentionally different state sources (documented well inreloadWebView), but they create a subtle window: in phase 2 the Compose layer correctly shows the LFA item (VM'sselectedServeris the My Domain), yet if this guard fires anyway (e.g. in a race or from a direct external call), the action silently no-ops while the user sees the item as active. A brief inline comment here noting the dual-source-of-truth intent would help future readers understand why the two guards differ rather than assuming one is stale.