Repository navigation
Native Login in SDK debug Login Options + AuthFlowTester Native Login UI tests - #3065
Conversation
…lid_request for the required-DPoP rejection
Generated by 🚫 Danger |
Generated by 🚫 Danger |
- Login Options (debug) gets Authentication Mode: Browser / Native at the top, with Client ID, Native Callback URL, Community Login URL, Use DPoP and Use Hybrid Authentication; browser-only rows are hidden in Native mode. - Native settings are persisted separately from the browser config (NativeLoginDebugSettings, SalesforceSDKManager.debugLoginSettings with apply(json) and reset()). - Built-in Compose DebugNativeLoginActivity wired to the Native Login API (loading, cancel, result/error detail, redacted diagnostics, browser fallback). - SalesforceSDKManager resolves the login activity per attempt: the app's nativeLoginActivity, else the debug form in Native mode, else the web login. - Removes LoginOptionsExtension and AuthFlowTester's AuthModeStore, LoginRouterActivity and NativeLoginActivity.
| // Welcome Discovery simulation editor: visible only when the launcher Activity flagged | ||
| // the process as UI-testing (and only in debug builds, enforced by the setter). | ||
| // Mirrors iOS' IS_UI_TESTING gate in LoginOptionsViewController.swift. | ||
| if (sdkManager?.isDebugBuild == true && sdkManager.isUiTesting) { |
There was a problem hiding this comment.
| This method should only be accessed from tests or within private scope |
…als rejection and RestExplorer Native Login
JohnsonEricAtSalesforce
left a comment
There was a problem hiding this comment.
Verdict: Request changes.
Reviewed head: 083fe6f508bd5ec71a3c57cd3ad82f6ea33ebb1e.
Requested Changes
-
[P2] Do not publish an authentication error from a cancelled job (
DebugNativeLoginActivity.kt:141). Reproduce:- Start Native Login for client A and hold its response. Make cancellation cleanup wait on a gate, matching the SDK noncancellable cleanup path.
- Change the native configuration to client B and close Login Options. The old job is cancelled and the form initially becomes Idle.
- Release client A's cancellation-cleanup gate.
Expected: The cancelled attempt publishes no result; client B's form stays Idle and usable.
Actual:
runCatchingcatchesCancellationException, substitutesUnknownError, and writesFailed(UnknownError)from the cancelled job after cleanup. The local probe reproduced Idle changing to Failed without a new login attempt.Rethrow cancellation and prevent obsolete attempts from publishing UI state. Add a ViewModel regression test with delayed cancellation cleanup. The previous stale-account-completion defect is addressed; this is a narrower error-state defect in the replacement implementation.
-
[P2] Skip only an absent native fixture (
NativeLoginTests.kt:97).hasNativeLoginConfigcatchesnativeLoginSetupfailures and converts them to false. A presentnativeLoginblock with an unknown app/host or no users therefore skips every test in the documented NativeLoginTests-only invocation. The separate validation class is not selected by that command. Check absence for the assumption, then let malformed or dangling configuration fail with its diagnostic. -
[P2] Assert that in-flight cancellation preserves the session (
NativeLoginTests.kt:199). This branch submits the same user as the existing account, then checks only that one account remains. If cancellation fails and reauthentication replaces that account, the count is still one and the test passes. The access-token comparison above covers only idle cancellation. Hold the response until cancellation, then verify the original credentials and feature markers remain unchanged and no late completion occurs. -
[P3] Correct the password recreation expectation (
README.md:125). The README says the password does not survive Activity recreation, butDebugNativeLoginViewModelretains it in memory across recreation; the shipped test never enters or checks a password. The local SDK-form smoke test also confirmed that it remains after recreation. Document clearing across process death, or implement and test clearing across Activity recreation if that is the intended behavior.
Open Questions
-
Will the headless native fixtures be provisioned for CI so the four-case matrix actually runs? The previous run skipped all nine native tests and the validator; the latest SDK/UI jobs are still running, so updated matrix results are not yet available.
-
Is the iOS twin implementation available on another branch or PR? Its tracking record is identified, but implementation searches still did not locate code to compare, so behavioral parity remains unverified.
Validation: SDK and AuthFlowTester test APKs build at the updated head. Focused SDK suites pass 39/39 (settings 12, Authentication Mode UI 4, existing Login Options 23); the SDK code in those runs is identical to this final head. A credential-free SDK-form/recreation smoke test also passes at this head. A separate production-ViewModel probe reproduces the cancellation-state defect above. All 19 current Kotlin files received IDE analysis, with the final changed test file reinspected after the last commit; no IDE errors were reported. The live native-login matrix was not independently rerun because available local fixtures lack the headless ECAs. Latest CI is still in progress; prior-head results are not validation of this redesign.
This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.
…e SDK debug Login Options
…x tests, update AuthFlowTester README Native Login tests use the eca_jwt_headless and eca_jwt_dpop_headless apps and the community_auth host from the existing collections instead of a nativeLogin block. They skip when neither app is configured and fail with a clear message when only one is, or when community_auth is missing.
|
Follow-up at Resolved: original item 2 (fixture skipping) is fixed; the five configuration-rule tests pass locally. The iOS implementation is identified in iOS PR 4192, and both platforms now use the same named-fixture convention. Still requested:
Validation remains open for current-head CI: confirm all four native matrix cases execute rather than skip, and resolve or explain the earlier Browser-mode UI-test failure. That four-test UI class passes locally. The review remains Changes requested. This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce. |
…the Native Login fixtures
…te; hold the token response in the cancel test - DebugNativeLoginViewModel rethrows CancellationException, and only the latest attempt can update the form - DebugNativeLoginViewModelTest: config change with delayed cancellation cleanup, late completion after cancel, retry after cancel - testNativeLogin_CancelDuringLogin_NoNewAccount holds the token response until after Cancel, then checks tokens, token type and user agent are unchanged - README: the password is retained across Activity recreation and cleared on process death
|
Thanks @JohnsonEricAtSalesforce — all four items are addressed in
Open questions: (1) CI provisioning of the headless fixtures is still to be arranged with the Local results (API 36.1 emulator): NativeLoginTests 9/9, DebugNativeLoginViewModelTest 3/3, NativeLoginConfigRulesTest 7/7, Community+DPoP regression 33/34 (the one failure, |
|
Thanks, Wolf. I independently verified the new ViewModel tests (3/3) and fixture-rule tests (7/7) at Two requests remain:
This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce. |
…enforced-ECA rejection test The enforced ECA redirects to the callback with error=invalid_request (missing required dpop_jkt for code binding); the SDK shows it in a toast and reloads the login page, so Chrome never renders the error text.
…light cancel A transport failure or non-200 token response now fails the cancel test, so an unchanged session can only come from the cancel.
… a community), like iOS
…me process) and L5 assertions
|
Thanks @JohnsonEricAtSalesforce. Both remaining items are addressed, along with the doc fixes:
Results on API 36.1:
In the combined run, the instrumentation process was killed ( |
|
Thanks, Wolf. Rechecked at One request remains — [P2] Native cold-restart coverage (
A bounded option is a host-driven test with two instrumentation invocations: seed the session and finish the first invocation without logout/reset; force-stop the app and verify the old process is gone; then start a fresh verification invocation against the installed app, without reinstalling or resetting data. Assert the restored session and refresh/binding, then clean up. This can be limited to the native Bearer/DPoP cases without rewriting the browser harness. This is a coverage request, not a demonstrated persistence bug. The review remains Changes requested pending that check. This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce. |
|
Android emulator: Full AuthFlowTester UI suite run locally: all tests pass against the test1 org (new NativeLoginTests plus the existing browser, Community, DPoP, RTR and restart suites). |
|
Thanks @JohnsonEricAtSalesforce, agreed on the analysis. We'd like to leave the true cold-restart check for follow-up work rather than add it in this PR.
We will track the cold-restart harness as a separate follow-up. Please let us know if you'd rather keep this P2 open as a blocker. |
JohnsonEricAtSalesforce
left a comment
There was a problem hiding this comment.
Approved at 36446b9b1. The cancellation-state, fixture-validation, session-preservation and documentation fixes are addressed.
We accept Wolf’s proposal to defer genuine cold-restart coverage to shared test-harness follow-up work. The current tests and documentation correctly describe Activity relaunch within the same process.
This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.
|
CI: the Community + Native Login Firebase shard (PR and nightly) now has a 45m timeout (was 15m), since it now carries both suites. |
Summary
Native Login becomes an SDK debug feature: any debug-build app (RestExplorer, AuthFlowTester, templates) can log in natively from the developer support Login Options screen, without app code. AuthFlowTester only consumes it and adds the Native Login UI tests (W-24600769).
SDK changes (debug only, needs human review)
LoginOptionsActivityshows Authentication Mode: Browser / Native at the top (when developer support is enabled, which is the default in debug builds, and only when the app has nonativeLoginActivity). Native mode shows Client ID, Native Callback URL, Community Login URL, JSON import, Use DPoP and Use Hybrid Authentication. Browser-only rows are hidden in Native mode.NativeLoginDebugSettings(own SharedPreferences) stores the mode and the native config apart from the browser config, so Browser mode and browser fallback keep the boot/override config and callback URL. Native DPoP/hybrid choices apply to Native Login only.SalesforceSDKManager.debugLoginSettingswithmode,config,apply(json)andreset(). JSON keys match iOS:consumerKey,redirectUri,communityUrl,useDPoP,useHybridFlow,authenticationMode(browser|native). Invalid JSON or an incomplete native config is rejected and nothing is saved.DebugNativeLoginActivity(not exported) is wired to the existing Native Login API: loading state, cancel (no account is created, and a cancelled or replaced attempt that completes late cannot update the form), result and OAuth error detail, a redacted diagnostics trace, Login Options link and browser fallback.loginActivityClassis resolved per login attempt: the app'snativeLoginActivity(frominitNative) always wins; otherwiseDebugNativeLoginActivityin Native mode; otherwise the web login activity. Closing Login Options after a mode/config change restarts authentication (loginDevMenuReload).NativeLoginDiagnostics(internal, recorded when developer support is enabled) records each authorize/token exchange: stage, HTTP status, OAutherror/error_description, and whether DPoP/nonce headers were present. No tokens, codes, proofs, nonces or passwords are recorded.NativeLoginManagertakes optionaluseDPoP/useHybridAuthenticationoverrides (internal), used by the debug config.AuthenticationUtilities.onAuthFlowComplete, usingLoginActivity.selectLMarker): a community URL givesL5, a My Domain URLL4. Before, Native Login sessions had noLmarker.debugLoginSettings,AuthenticationMode,NativeLoginDebugConfig,DebugNativeLoginActivity) is debug tooling.AuthFlowTester
initNativewithout a native login activity. Launch extrasEXTRA_AUTHENTICATION_MODE/EXTRA_NATIVE_LOGIN_CONFIGmap ontodebugLoginSettings; a value the settings reject is logged and ignored, as on iOS.NativeLoginTestsdrive the SDK Login Options and debug form: theeca_jwt_headless/eca_jwt_dpop_headlessx DPoP on/off matrix, cancel during login (idle, and with a successful (HTTP 200) token response held until after Cancel; a failed token request fails the test: tokens, token type and user agent markers stay unchanged), browser fallback, same-user DPoP->Bearer re-auth, Activity recreation, multi-user isolation and config validation. Successful sessions are checked forNL,A5,L5(andDPfor DPoP).testECAJwtHeadless_DPoPOff_Relaunch_BearerandtestECAJwtDPoPHeadless_DPoPOn_Relaunch_DPoPrelaunch the app and check that the session is restored without the form, with unchanged tokens and markers, a working REST call, and revoke+refresh (DPoP binding kept). This is an Activity relaunch in the same process, not a cold restart: the instrumentation runs in the app's process, sorestartApp()cannot kill it. The same applies to every Android restart test; the README and therestartApp()KDoc say so.eca_jwt_dpop_headlesswith client DPoP off must reportInvalidCredentialswith authorize HTTP 400invalid_request(missingdpop_jkt) and no account created.apps(eca_jwt_headless,eca_jwt_dpop_headless) andloginHosts(community_auth) ofui_test_config.json. The tests skip only when neither app is configured. They fail with a clear message when only one is, whencommunity_author its user is missing, or when an app has an emptyconsumerKeyor a non-https://redirectUri.Screenshots
eca_jwt_dpop_headlesswithout DPoPTests
Run on an API 36.1 emulator.
Full AuthFlowTester UI suite: passes locally on an Android emulator against the test1 org (new
NativeLoginTestsplus the existing browser, Community, DPoP, RTR and restart suites).SDK androidTests:
AuthenticationUtilitiesTestandBrowserLoginTelemetryTestpass (81/81), including three new L-marker tests (Native Login on a community registers onlyL5, on My DomainL4, and a web login leaves the L markers toLoginActivity).DebugNativeLoginViewModelTestpasses (3/3: config change with delayed cancellation cleanup, late completion after cancel, retry after cancel); all three fail without the ViewModel fix.NativeLoginDebugSettingsTest,LoginOptionsAuthenticationModeTestandLoginOptionsActivityTestpass (39/39). These classes also pass:SalesforceSDKManagerTest(8),LoginViewModelTest(93),BiometricAuthenticationManagerTest(21) andLoginActivityScenarioTest(22). Some mockk-based classes (SalesforceSDKManagerTests,LoginViewModelMockTest,NativeLoginManagerTest,LoginActivityCancellationTest,LoginActivityTest) crash with SIGABRT when the mockk JVMTI agent loads on this emulator image. That is an environment problem, so they need a run on CI or another image.AuthFlowTester
NativeLoginTests: 11/11 pass, plusUITestConfigNativeLoginTest(1/1) andNativeLoginConfigRulesTest(7/7). IntestECAJwtDPoPHeadless_DPoPOff_RejectedNoAccount, the SDK returnsInvalidCredentialsand the diagnostic readsstage=authorize status=400 error=invalid_request dpop=false ... description="missing required dpop_jkt for code binding". No account is created.Regression:
DPoPLoginTests18/18 andNegativeLoginTests3/3.testLogin_DPoP_ECA_Without_DPoP_Failschecks themissing required dpop_jkt for code bindingerror in the toast the SDK shows before it reloads the login page.CommunityLoginTests16/16 in the run before the L5 change. In the combined run (NativeLoginTests,DPoPLoginTests,NegativeLoginTests), 30 of 32 tests ran and passed. The instrumentation process for the other two was killed (remove task) before any test code ran. Both passed when rerun.Manual check in RestExplorer (debug): Developer Support > Login Options shows Authentication Mode. Native mode shows the client ID, callback, community, DPoP and hybrid fields and JSON import. Pressing Back restarts authentication in
DebugNativeLoginActivity.Screenshots: the AuthFlowTester README screenshots contain no credentials. The Login Options screenshot uses placeholder values, and identifying values are redacted.
For reviewers
Lmarker now registered by Native Login in all builds (telemetry only).debugLoginSettings,AuthenticationMode,NativeLoginDebugConfigandDebugNativeLoginActivity.LoginOptionsActivitystrings. They are not insf__strings.xml.