Skip to content

Conversation

JohnsonEricAtSalesforce
Copy link
Contributor

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce commented Sep 10, 2025

🎸 Ready For Final Review 🥁

This is a counterpart to the same set of fixes and tests for MSDK iOS. The only significant difference is that this is all AI generated. The tools did a really nice job taking a prompt from the iOS update and reflecting that in the Android logic, though I did massage a few naming conventions to better fit the Android module's naming. This also brings the structure of QR Code Login much more in-line with the code review conversation we've had on the iOS side, which has yielded a lot of benefit compared to the earlier structure that was still in use on the Android side.

I'll add some detail highlights in the diff.

This has all been tested with this code cherry picked into the latest Salesforce app build and MSDK branch against two Salesforce app test orgs. One of them has advanced authentication enabled and the other does not. Everything works now in those tests, especially where advanced authentication had some troubles. The new validation alerts and the server matching are also working just like iOS does.

The existing tests have been updated and pass. Code coverage is likely still low while I await generated tests on the new classes.

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce force-pushed the feature/w-19461869_m1-msdk13.1-enable-flexible-server-matching-for-qr-code-login-android branch from 4af6091 to a92e929 Compare September 10, 2025 02:46
Copy link

github-actions bot commented Sep 10, 2025

1 Warning
⚠️ libs/SalesforceSDK/src/com/salesforce/androidsdk/config/LoginServerManager.java#L348 - Use of this function is discouraged because resource reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resources by identifier (e.g. R.foo.bar) than by name (e.g. getIdentifier("bar", "foo", null)).

Generated by 🚫 Danger

…de Login (Android) (Correct `LoginActivityTests`)
…de Login (Android) (`FrontdoorBridgeLoginOverrideTest` And `FrontdoorBridgeLoginOverride`)
@JohnsonEricAtSalesforce JohnsonEricAtSalesforce marked this pull request as ready for review September 10, 2025 03:39
…de Login (Android) (Correct String Resources)
Copy link

1 Warning
⚠️ Big PR, try to keep changes smaller if you can.

Generated by 🚫 Danger

* @author bhariharan
*/
public class LoginServerManager {
public class LoginServerManager implements LoginServerManaging {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wmathurin - Here's a comparable interface for the protocol we added on iOS. It does really help the tests and logic objects remain consistent.

* For Salesforce Identity UI Bridge API support, an overriding front door
* bridge URL to use in place of the default initial URL.
*/
internal class FrontdoorBridgeLoginOverride(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wmathurin - This is nearly identical to the iOS version we created.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class encapsulates the otherwise separate front-door bridge properties that were attached to unrelated classes along with all the logic and tests that has accumulated around them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't love the very overly documented generated code :/

import com.salesforce.androidsdk.config.LoginServerManaging
import java.net.URL

internal data class FrontdoorBridgeUrlAppLoginServerMatch(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wmathurin - This is another bit of logic we can use as-is from the iOS code along with all its tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a class and not a function that returns a single boolean? It is instantiated just so appLoginServerMatch can be checked once.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable. @wmathurin - Thinking of the way FrontdoorBridgeLoginOverride grew over time from a single property on an existing class to an entire class of its own in your reviews from iOS, now we have this new FrontdoorBridgeUrlAppLoginServerMatch type which we broke out in turn. It has humble enough beginnings (for now) to make a stand-alone function as Brandon suggested. Do you see this type getting more complex over time? If not, we can do as Brandon suggested easily enough.

// Fetch well known config and load in custom tab if required.
fetchAuthenticationConfiguration {
if (isBrowserLoginEnabled) {
if (isBrowserLoginEnabled && viewModel.frontdoorBridgeLoginOverride == null /* Browser-based authentication is applicable when not authenticating with a front-door bridge URL */) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Android and just like iOS, this is the key fix to enable a front-door bridge URL generated by an advanced authentication app to bypass showing the unneeded native browser. The user does get logged in successfully when the front door bridge URL is loaded in the default web view.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if the app has QR Code login setup it disables Advanced Authentication?

Also, please move the comment from inside the if to a separate line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll get that comment moved. 👍🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frontdoorBridgeLoginOverride is only set during the lifetime of a front door bridge login event. At the end of the event, resetFrontDoorBridgeUrl is called and all properties related to front door use are cleared (conveniently this is a single object now based on Wolf's iOS reviews). After attempting the QR Code, the advanced authentication logic is as it was before.

For additional context, users/apps with advanced authentication can authenticate via a front door bridge URL without MSDK needing to show the browser. This new condition prevents MSDK from displaying the unneeded browser while behind it the user would already be authenticated successfully from the front door bridge URL.

Copy link

Job Summary for Gradle

Pull Request :: test-android
Gradle Root Project Requested Tasks Gradle Version Build Outcome Build Scan®
SalesforceMobileSDK-Android libs:SalesforceSDK:lint 8.14.2 Build Scan not published
SalesforceMobileSDK-Android libs:SalesforceSDK:assembleAndroidTest 8.14.2 Build Scan not published
SalesforceMobileSDK-Android libs:SalesforceSDK:convertCodeCoverage 8.14.2 Build Scan not published

Copy link
Contributor

@wmathurin wmathurin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

…de Login (Android) (Correct Assertions For `FrontdoorBridgeLoginOverrideTest` And `FrontdoorBridgeLoginOverride`)
@JohnsonEricAtSalesforce
Copy link
Contributor Author

Today's commit updates the newly generated tests to resolve issues the generator had when using "Mockk" plus resolves one string comparison issue with the My Domain soft-matching that the tests revealed 🥁 There's almost no functional change since yesterday's approval by @wmathurin, so this should be merge-ready.

val url = URL(selectedAppLoginHost)
results.add(url.host)
} catch (_: Exception) {
// If parsing fails, try to use as-is (might already be just a host)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When do you think it would not parse but should still be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of all the interesting things the AI tooling did that worked and didn't, this was the oddest choice - Or so I thought! 🥴. Nothing in my prompt or the existing logic suggested this, from what I saw. I was already planning on removing this before merging.

@Test
fun testEdgeCase_VeryLongCodeVerifier() {
// Arrange
val longCodeVerifier = "a".repeat(1000) // Very long code verifier
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the code verifier a base64-encoded 128-byte randomly generated key?
It should always be the same length. That test probably does not make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't make sense, agreed. MSDK only passes this value through and I don't see there's any length restriction that should have prompted the tool to create this test. I'll remove it.

Copy link
Contributor

@wmathurin wmathurin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - I went through the tests and found only one to be maybe unnecessary?

private static RuntimeConfig INSTANCE = null;

RuntimeConfig(Context ctx) {
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know you could could use the Kotlin style named parameter syntax in a Java file. Must be because it is an annotation?

Copy link
Contributor Author

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recall using named parameters with Java annotations all the way back to Java 5 and Hibernate ORM (2005), which used it a lot. That's an interesting place where named parameters snuck in early and I'd never thought about it 🙂

Comment on lines 93 to 94
val frontdoorBridgeUrlComponents = frontdoorBridgeUrl.toString()
val frontdoorBridgeUri = frontdoorBridgeUrlComponents.toUri()
Copy link
Contributor

@brandonpage brandonpage Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We take in frontdoorBridgeUrl as a Uri to make a frontdoorBridgeUrlComponents String... and then only use that to make frontdoorBridgeUri which is a Uri.

Am I missing something or is this completely pointless?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is indeed completely pointless and I hadn't gotten that far in reviewing the generated output. Thanks for finding it.

* allowing the addition and switching of app login servers. This is
* intended for test automation only
*/
addingAndSwitchingLoginServersPerMdm: Boolean = true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Something like mdmControlledServers might be better?

}

// Check if the front door URL host matches the app's selected login server
val addingAndSwitchingLoginServersAllowedResolved = if (!addingAndSwitchingLoginServersPerMdm) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Unnecessary !. Just flip the if and else blocks so it is easier for a human to read.

Comment on lines +68 to +73
/**
* The preference for allowing the addition and switching of app login
* servers when the MDM preference is ignored. This is intended for test
* automation only
*/
addingAndSwitchingLoginServerOverride: Boolean = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why (even for testing) would we ever want to ignore a hard requirement from an MDM?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is never set outside of tests why does it exists. Why test both true and false in tests if it can only ever be false in production?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is just there for testing, true. It's use is paired with addingAndSwitchingLoginServerOverride which lets the test choose the test value. An alternate approach could be to pop and interface on RuntimeConfig and mock it much as we did for LoginServerManager.

} catch (_: Exception) {
null
}
return !onlyShowAuthorizedServers && (mdmLoginServers?.isEmpty() != false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like neither of these values tell us if adding is allowed. Just if switching is allowed/possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OnlyShowAuthorizedHosts: If true, prevents users from modifying the list of hosts that the Salesforce mobile app can connect to. See doc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, OnlyShowAuthorizedHosts seems poorly named but fair enough. Maybe we should add a code comment stating that?

}
}

private val addingAndSwitchingLoginServersAllowed: Boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Why are these private vals at the bottom of the file?

@Suppress("MemberVisibilityCanBePrivate")
fun loginWithFrontdoorBridgeUrl(
frontdoorBridgeUrl: String,
frontdoorBridgeUrl: Uri,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks semantic versioning.

// Fetch well known config and load in custom tab if required.
fetchAuthenticationConfiguration {
if (isBrowserLoginEnabled) {
if (isBrowserLoginEnabled && viewModel.frontdoorBridgeLoginOverride == null /* Browser-based authentication is applicable when not authenticating with a front-door bridge URL */) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if the app has QR Code login setup it disables Advanced Authentication?

Also, please move the comment from inside the if to a separate line.

Comment on lines 1362 to 1356
val frontdoorBridgeUrl: String,
val frontdoorBridgeUrl: Uri,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, semver.

Comment on lines 231 to 236
if (!isUsingFrontDoorBridge) {
// The Web Server Flow code challenge makes the authorization url unique each time,
// which triggers recomposition. For User Agent Flow, change it to blank.
if (!SalesforceSDKManager.getInstance().useWebServerAuthentication) {
loginUrl.value = ABOUT_BLANK
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this! This is a bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That one was a hallucination in the tool 🤖 and I'll revert it.

frontdoorBridgeServer = with(URI(frontdoorBridgeUrl)) { "${scheme}://${host}" }
frontdoorBridgeCodeVerifier = pkceCodeVerifier
loginUrl.value = frontdoorBridgeUrl
val frontdoorBridgeLoginOverride = FrontdoorBridgeLoginOverride(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shadowed name. And why are there two? We set the class level one from the Activity. Why are we making one local to this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was also work-in-progress output of the tool that hadn't yet been reviewed 🤖 I'll optimize this ✅

Copy link
Contributor

@wmathurin wmathurin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the tests we we have a front door url and a bunch of login servers, and we want to see which one match, should we use a parametrized test class (like this one.
It would be a lot easier to read, and we could see right away if we are testing all the permutations we wish to test etc.

selectedAppLoginServer = "different.salesforce.com"
)

// Assert - Should match the first server ending with "my.salesforce.com"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait. if front door url is: https://company.my.salesforce.com
And login servers are login.salesforce.com, test.my.salesforce.com, custom.my.salesforce.com, then it should soft-match with login.salesforce.com.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wmathurin - This specific rule is where our notion of "matching" the front-door URL's host to another host needs work. The users I was given by the app cannot log in using the "soft-matched" login server. Only the exact My Domain host that's encoded in the front-door URL by the Identity API will work.

This may be the reason only the logic that creates the QR Code and obtains the UI Bridge front-door URL from the API might be able to know what the appropriate login server. The client code, like we attempted here in MSDK, may not be able to guess the answer. Even if it could, the server should likely own that decision for additional future-proofing in the code and avoiding redundant client implementation.

We should ask the identity team for more information on this.

…de Login (Android) (W.I.P.: Temporarily Disable Flapping Test)
@JohnsonEricAtSalesforce
Copy link
Contributor Author

I'll be doing additional review on this one this evening and tomorrow, in particular since we'll need to vet the soft-matching logic again. I'd recommend holding any additional detailed review until I certify that. Thanks!

…de Login (Android) (W.I.P.: Code Review Updates w/Remaining To-Dos And Disabling Test That Only Fail In CI)
…de Login (Android) (W.I.P.: Correct Tests In `FrontdoorBridgeUrlAppLoginServerMatchTest.kt` With URL Cleanup As Well)
@JohnsonEricAtSalesforce
Copy link
Contributor Author

This pull request was superseded by #2764 until the requirements that were being attempted here are fully defined and testable in a future release. The iOS version of this change was also reverted in forcedotcom/SalesforceMobileSDK-iOS#3917.

@JohnsonEricAtSalesforce
Copy link
Contributor Author

This change has been shelved indefinitely pending systems requirements analysis.

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce deleted the feature/w-19461869_m1-msdk13.1-enable-flexible-server-matching-for-qr-code-login-android branch September 15, 2025 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants