Skip to content

feat(telemetry): add A1-A5/TM/JT/OT/BN per-user feature flag markers (W-23762053) - #2980

Merged
wmathurin merged 17 commits into
forcedotcom:devfrom
wmathurin:auth-type-token-format-flags
Aug 11, 2026
Merged

feat(telemetry): add A1-A5/TM/JT/OT/BN per-user feature flag markers (W-23762053)#2980
wmathurin merged 17 commits into
forcedotcom:devfrom
wmathurin:auth-type-token-format-flags

Conversation

@wmathurin

@wmathurin wmathurin commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add 9 new per-user feature flag markers: A1–A5 (auth-flow type), TM (token migration), JT/OT (token format), BN (beacon child app)
  • A-markers use the existing transient-global → per-user two-phase pattern: registered globally in certAuthOrLogin()/loadLoginPageInCustomTab(), promoted in onAuthFlowSuccess() and global cleared
  • A5 (native login) registered globally in SalesforceSDKManager.useNativeLogin(); A-marker per-user promotion for the native path happens in AuthenticationUtilities.kt
  • BW and A-markers are orthogonal: BW captures that a browser (Custom Tab) was used; the A-marker captures the OAuth grant × hybrid dimension independently. Custom Tab always uses web-server grant → A1 (non-hybrid) or A2 (hybrid); user-agent flow tests must use forceAdvancedAuthentication = false to get A3/A4
  • TM registered in AuthenticationUtilities.kt (tokenMigration == true branch); cleared on full login in onAuthFlowSuccess()
  • Production fix: LoginActivity.onAuthFlowSuccess and AuthenticationUtilities.kt now unconditionally clear TM from the global features set after every auth completion, preventing it from bleeding into other users' UA strings
  • JT/OT and BN written on both full login and migration paths
  • Update AuthFlowTesterPageObject.kt validateUserAgent to assert all new flags (all new params default to non-asserting so existing call sites compile unchanged)
  • 8 new unit tests in SalesforceSDKManagerTests.kt; extensive multi-user and migration UI test fixes

GUS story

W-23762053

Key behavioral rules

Flag When written Global phase? Refresh path touches it? Cleared by full re-login?
A1–A5 Every fresh login Yes (transient) No Yes — re-promoted from global on next login
TM Migration only No No Yes — explicitly unregistered
JT / OT Full login and migration No No Yes — re-written with new token format
BN Full login and migration No No Yes — re-written from credentials

Test plan

  • ./gradlew :libs:SalesforceSDK:testDebugUnitTest — BUILD SUCCESSFUL
  • ./gradlew :native:NativeSampleApps:AuthFlowTester:assembleDebugAndroidTest — BUILD SUCCESSFUL
  • AuthFlowTester UI tests: all tests pass locally (verified 2026-08-09)

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
1 Warning
⚠️ libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/LoginActivity.kt#L847 - Switch statement on an int with known associated constant missing case BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE

Generated by 🚫 Danger

…all sites (W-23762053)

Adds AppConfig.isBeacon (derived from app name prefix), then threads
isJwt and isBeacon into loginAndValidate, restartAndValidateUser, and
migrateAndValidate in AuthFlowTest so every login assertion also
validates JT/OT and BN. Migration tests pass expectedAMarker matching
the original login's A-marker (preserved unchanged during migration).
… auth surface (W-23762053)

registerAuthTypeFeatureGlobal() was called at LoginActivity.onCreate() but not
when Login Options disabled web server flow and reloadWebView() ran on resume.
The stale A2 global persisted into the completed login. Adding the call in the
loginDevMenuReload block ensures the global reflects the effective flow type
(A3/A4 for user agent) by the time auth completes.
…ath (W-23762053)

NativeLoginManager.suspendFinishAuthFlow calls onAuthFlowComplete directly,
bypassing LoginActivity.onAuthFlowSuccess where A-marker promotion normally
happens. Added equivalent promotion block inside onAuthFlowComplete for the
nativeLogin path: promotes active A-marker global per-user (A5 in the
happy path), clears all A-marker globals, writes JT/OT/BN, and clears TM.
…gin Options (W-23762053)

loginAndValidate only called disableWebServerFlow/disableHybridAuthToken/enableDPoP
when the flag was false/true respectively, leaving stale toggle state from a prior
login in the same test run. Multi-user tests where User A disables hybrid and
User B re-enables it (default) would see User B inherit A's A1 instead of A2.
Explicitly set all three toggles to the desired state on every Login Options visit.
…er state (W-23762053)

Three issues found running the expanded test list:

1. Login Options toggles left stale: loginAndValidate only disabled
   web-server/hybrid toggles when requested off, never re-enabled them
   for the next user. Multi-user tests where User A disables hybrid and
   User B uses the default hybrid=true inherited the wrong toggle state.
   Fix: explicitly set all three toggles to the requested value on every
   Login Options visit.

2. ensureRegularAuthServer fast-path skipped stale SDK flag: the fast
   path (expectCustomTab=true, alreadyOnRegularAuth=true) assumed the
   SDK forceAdvancedAuthentication flag matched the caller's intent, but
   a prior loginAndValidate call with forceAdvancedAuthentication=false
   left it cleared. Added an SDK-state check to the fast-path guard and
   replaced the conditional setForcedAdvancedAuthEnabled(false) with an
   unconditional setForcedAdvancedAuthEnabled(forceAdvancedAuthentication)
   in the general path.

3. testMultiUser_A4OT_BN_vs_A2JT used impossible combinations: BN
   (beacon child key) is only returned by web-server flow's token
   endpoint, not user-agent flow's URL-fragment redirect. Redesigned to
   use web-server hybrid beacon (A2+OT+BN) vs web-server non-hybrid
   ECA opaque (A1+OT, no BN).

4. testAdvancedAuthUser_HasBWFlag_RegularAuthUser_DoesNot used
   REGULAR_AUTH vs ADVANCED_AUTH users who may share orgId/userId (same
   Salesforce org accessed via two auth domains), collapsing per-user
   feature isolation. Redesigned to use REGULAR_AUTH for both users,
   distinguishing them by forced-advanced-auth vs user-agent flow.
   Also fixed the final validateUserAgent to expect A4 (user-agent
   hybrid) instead of A3 (non-hybrid).
testFlagDiversity_A1OtVsA2Jt: User A=A1+OT, User B=A2+JT; asserts
A-marker and token-format are isolated across user switches and
survive after User B logout.

testFlagDiversity_A1JtBnVsA2OtNoBn: User A=A1+JT+BN, User B=A2+OT
no BN; three flags differ simultaneously. Uses ECA_OPAQUE for User B
(not CA_OPAQUE) to force needsLoginOptions=true so the HybridAuthToken
toggle is explicitly re-enabled after User A disabled it.
…W-23762053)

Add two new rows to the MultiUserLoginTests table and a new "Multi-user
flag leakage detection tests" subsection explaining how per-user flag
storage works, what failure modes the tests catch, and the per-test
details including the ECA_OPAQUE-vs-CA_OPAQUE boot-config fast-path
note for testFlagDiversity_A1JtBnVsA2OtNoBn.
…sh helpers; fix admin A-marker registration

- adminLoginAndValidate(): resolve appConfig and pass isJwt/isBeacon to validateUser so JWT/Beacon apps no longer fail the OT/BN assertions
- assertRevokeAndRefreshWorks(): add isJwt param and thread it through to validateUserAgent; update JWT-config callers in DPoPLoginTests and RTRLoginTests
- switchToUserAndValidateUser(): add isJwt param and thread it through to validateUser; moved definition after assertRevokeAndRefreshWorks for logical grouping; update LoginWithRestartTests (ECA_OPAQUE→isJwt=false, ECA_JWT→isJwt=true) and DPoPLoginTests (ECA_JWT_DPOP→isJwt=true)
- LoginActivity.registerAuthTypeFeatureGlobal(): use webServer = viewModel.useWebServerFlow() || completedViaAdminCustomTab so the admin Custom Tab path always registers a web-server A-marker (A2) even when useWebServerAuthentication=false
…In (W-23762053)

Add com.android.chrome:id/skip_button, "Skip", and "Not now" to dismissOneFreControl
so the "Set Chrome as default" FRE page introduced in newer Chrome versions is dismissed
and the Custom Tab can render. Without this, testAdvancedAuth_WithRestart fails because
skipGoogleSignIn exhausts its 30s budget without clearing the FRE, leaving setUsername
with no username field to find.
…ns, test renames (W-23762053)

UserAgentFlow tests must set forceAdvancedAuthentication=false: Custom Tab always
uses web-server grant, making user-agent flow impossible with the default true.

Multi-user flag assertion fixes:
- testFirstDynamic_SecondStatic_DifferentApps: pass isJwt=true (User A = ECA_JWT)
- testDifferentApps_differentScopes: pass isBeacon=true for User A (BEACON_OPAQUE),
  isJwt=true for User B (ECA_JWT)
- testMultiUser_tokenMigration: pass isJwt=true, isBeacon=true (User A = BEACON_JWT)

Rename flag-diversity tests to descriptive flow names (already done in prior commit
for Android; README updated to match the renamed tests and add detail sections).
@github-actions

github-actions Bot commented Aug 9, 2026

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

Generated by 🚫 Danger

…sertions (W-23762053)

TM is per-user only but the global features set was never cleared of it, causing
TM to bleed into all users' UA strings. Fix: after writing TM per-user on migration
(AuthenticationUtilities) and after clearing TM per-user on full login (LoginActivity),
unconditionally remove TM from the global set — same pattern as iOS SFUserAccountManager.

Test fixes (switchToUserAndValidate missing isJwt/isBeacon/wasMigrated):
- testSameApp_ECA_DifferentScopes: add isJwt=true (both users are ECA_JWT)
- testSameApp_Beacon_DifferentScopes: add isBeacon=true (both users are BEACON_OPAQUE)
- testFirstStatic_SecondDynamic_DifferentApps: add isJwt=true,isBeacon=true for otherUser (BEACON_JWT)
- testMultiUser_tokenMigration_backgroundUser: add isJwt=true,wasMigrated=true for user post-migration (ECA_JWT)
…ion logins (W-23762053)

testMigrateCAUserAgent_To_ECAExtendedWebServer and _To_BeaconExtendedWebServer both
use useWebServerFlow=false to get A3 (user-agent non-hybrid), but without
forceAdvancedAuthentication=false the Custom Tab is opened and always uses the
web-server grant, producing A1 instead of A3.
Resolved conflict in LoginActivity.kt: added FEATURE_APP_ATTESTATION import
(from upstream) alongside the new FEATURE_AUTH_TYPE_* and FEATURE_BEACON
imports (from this branch), keeping all in alphabetical order.
Comment on lines +198 to +199
// Newer Chrome FRE "Set Chrome as default" page uses skip_button.
"com.android.chrome:id/skip_button",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great for local testing.

Resolve single conflict in LoginActivity.kt: keep registerAuthTypeFeatureGlobal()
alongside the @OptIn(ExperimentalInitialNavigationCanLeaveBrowser::class) annotation
added upstream before loadLoginPageInCustomTab.
@wmathurin
wmathurin merged commit c40e4ba into forcedotcom:dev Aug 11, 2026
4 of 5 checks passed
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.

2 participants