Skip to content
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

@W-16171409: [Android] Add QR Code Login Support in MSDK #2594

Open
wants to merge 14 commits into
base: dev
Choose a base branch
from

Conversation

JohnsonEricAtSalesforce
Copy link
Contributor

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce commented Jul 22, 2024

🎸 Ready For Review! 🥁

This adds support for log in via log in QR codes generated with the UI Bridge API to MSDK. This completes a very detailed spike previously completed by @wmathurin and directly inherits from that codebase.

Before reviewing, I highly recommend reading this document and references in detail:
👉🏻 https://salesforce.quip.com/JXmvAwirhR3V

There's actually very little if any logic change from the spike code. What I did try to apply was my novice's eye to the topic so we can name and document the code such that it'll be a bit more approachable for those uninitiated to the UI Bridge API, how that is delivered via the QR code and the many touch points that has in MSDK's log in logic. I did the same in the companion pull request that introduces commented out content to our template guiding a developer through enabling this in a new app.

The companion template update is in forcedotcom/SalesforceMobileSDK-Templates#417

@JohnsonEricAtSalesforce
Copy link
Contributor Author

JohnsonEricAtSalesforce commented Jul 22, 2024

It's worth noting I did intend to include commits from the spike performed by @wmathurin. I rebased his branch on dev before adding my own commits. Plus, a squash will make this very tidy on dev when we're done while preserving his history here.

public override fun onSaveInstanceState(bundle: Bundle) {
super.onSaveInstanceState(bundle)
webviewHelper?.saveState(bundle)
public override fun onSaveInstanceState(outState: Bundle) {
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 an unrelated yet low-risk inspector cleanup.

* @return Boolean true if a log in attempt is possible using the provided QR
* code content, false otherwise
*/
fun loginFromQrCode(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All the new methods here should look very familiar to @wmathurin. I did apply quite a bit of comment and naming thought though to reflect my own journey in understanding how it all worked plus see if I could make the semantic match through all the reference documentation. There's some Kotlin style added as well for extra credit.

Copy link
Contributor

Choose a reason for hiding this comment

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

@JohnsonEricAtSalesforce @wmathurin Is there any kind of profile limitation to this login option? If not should we consider moving this to a standalone utility class that could be used by LoginActivity and native login consumers? Perhaps native login support is a new story to itself, just trying to be forward thinking so we don't have to deprecate and move APIs around if possible.

Copy link
Contributor

Choose a reason for hiding this comment

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

The bridge url is supposed to be loaded in a web view to get the tokens or the code - it is not a headless API. So it's another form of web view based login.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, but despite it being a web based login it is possible to do it without the customer ever seeing a webview. You can put a button on a native login screen that has the same code as the QrCodeEnabledLoginActivity from the template app and if the user is pre authorized (a requirement for headless flow) they don't have to accept connected app policy. This is obviously work well beyond the scope of this PR and would require a headless webview but it sounds possible, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

It's like the web view to get a code for the SP in the IDP-based login. You have to show it because user interaction might be needed.
But you are right that you could launch it from the native login screen.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, if the user is preauthorized, we could hide the web view. I guess we could let the mobile app know whether to show the web view or not by adding a field in the JSON we encode in the QR URL.

* @param frontdoorBridgeUrl The UI Bridge API front door bridge API
* @param pkceCodeVerifier The PKCE code verifier
*/
internal fun loginWithFrontdoorBridgeUrl(
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 - I made this internal. Was there any reason it wasn't before?

Copy link
Contributor

Choose a reason for hiding this comment

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

The packing of front door bridge URL and PKCE in a QR code will be up to the apex or java page.
Hopefully it will use the attributes names we recommend, if it does not, an app could parse them out itself and call this method directly?
I guess if we make the method internal, it will force the server page to use the attributes names in the JSON that we expect.

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's very helpful, since I was also starting to wonder where a custom APEX with different structure would tie in. I'll make this public again!

@mobilesdk-bot
Copy link

mobilesdk-bot commented Jul 22, 2024

2 Warnings
⚠️ libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/LoginActivity.kt#L209 - Using setJavaScriptEnabled can introduce XSS vulnerabilities into your application, review carefully
⚠️ libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/OAuthWebviewHelper.kt#L215 - Using setJavaScriptEnabled can introduce XSS vulnerabilities into your application, review carefully

Tests results for SalesforceSDK

Generated by 🚫 Danger

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce force-pushed the feature/w-16171409_android-add-qr-code-login-support-in-msdk branch from e0c01af to f9ea0e5 Compare July 22, 2024 22:19
@JohnsonEricAtSalesforce
Copy link
Contributor Author

Hold merging this to dev and the 12.1.0 codebase until we confirm the iOS version will also make it in time for the release.

@@ -661,7 +661,7 @@ open class OAuthWebviewHelper : KeyChainAliasCallback {
)

else -> when {
instance.useWebServerAuthentication -> onWebServerFlowComplete(params["code"])
codeVerifier?.isNotEmpty() == true -> onWebServerFlowComplete(params["code"])
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bug and needs to be reverted as it will break User Agent flow login. codeVerifier is always created no matter which flow we are using.

Copy link
Contributor

Choose a reason for hiding this comment

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

You are right, I did not try user agent flow when doing the spike.
But we can't just revert.
With QR login, we will decide what flow to use based on what came through the JSON encoded in the QR URL.
If the JSON contains a code verifier, we use it and we should to a web server flow. And we should set a new flag saying that web server flow should be used.
That flag should be set to instance.useWebServerAuthentication by default.
We should use the new flag here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, @brandonpage (and @wmathurin)! I'll take a look.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay. So if I understand correctly the QR URL can/should override the app's setting for useWebServerAuthentication? Sounds like something that should be documented.

Also is User Agent Flow supported? The code above appears to require PKCE.

Copy link
Contributor

@wmathurin wmathurin Jul 23, 2024

Choose a reason for hiding this comment

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

We support both, if the QR encodes a code verifier we expect to the bridge URL to do a web server flow and the redirect to get us a code. If the QR comes with an empty/null code verifier, then the code verifier ends up not being defined and the code above will take the user agent branch. The bridge URL should do a user agent flow and the redirect get us the refresh tokens.

During the spike, I played with both, but the web server flow case meant a longer URL encoded in the QR which made it harder to read with the reader. I could not get it to work completely. See https://salesforce.quip.com/JXmvAwirhR3V#temp:C:IIO55768e89acdc49948a3be45bf

Copy link
Contributor

Choose a reason for hiding this comment

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

@JohnsonEricAtSalesforce Before closing the story, we need to make sure user agent and web server flow work with and without QR. During the spike, I did not test them all or get them all working.

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 & @brandonpage - I broke out W-16336715: [MSDK Android] Correct QR Code Log In When Using Web Server to track this in detail. I'll commit that here before we merge for 12.1 or whichever release QR code login lands in soon.

I also broke out W-16336722 to track the other bug in Quip regarding the main activity not being displayed.

Copy link
Contributor

@brandonpage brandonpage left a comment

Choose a reason for hiding this comment

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

Where the QR Code Login work should live isn't a blocker, but the User Agent Flow issue I mentioned is.

…ing login using front door URL and code verifier to override web server Vs. user agent flow)
// Determine if presence of override parameters require the user agent flow.
val overrideWithUserAgentFlow = isUsingFrontDoorBridgeUrl && frontDoorBridgeCodeVerifier == null
when {
instance.useWebServerAuthentication && !overrideWithUserAgentFlow ->
Copy link
Contributor Author

Choose a reason for hiding this comment

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

💤 [No after hours reply needed] @brandonpage & @wmathurin, this is my current thinking for storing the front door bridge URL and its optional accompanying code verifier as dedicated properties so we may then use them to determine the authentication flow between web server and user agent. That's for this thread from earlier:

#2594 (comment)

This looks a lot like what I have in place for iOS in SFOAuthCoordinator.

Just having an outside force overriding the app's chosen auth flow is complicated, so I want to internalize that from the app and try to keep our code simple. I was hoping just storing those and then clearly determining this one local variable would keep that readable. I'm open the suggestions, though.

On the iOS side, SFOAuthCoordinator is the WKNavigationDelegate. It would possibly be a lot easier to maintain if that was a separate class chosen just when a front door URL is in place. I'm thinking "one class per job" rather than a single class with an overly complicated control flow. I didn't do that yet, but perhaps when we convert that to Swift and here we eliminate OAuthWebViewHelper we could really simplify the code for this complicated set of logic flow.

@codecov-commenter
Copy link

codecov-commenter commented Sep 29, 2024

Codecov Report

Attention: Patch coverage is 16.98113% with 44 lines in your changes missing coverage. Please review.

Project coverage is 57.10%. Comparing base (301e0f4) to head (6f0974e).
Report is 72 commits behind head on dev.

Files with missing lines Patch % Lines
.../src/com/salesforce/androidsdk/ui/LoginActivity.kt 20.51% 27 Missing and 4 partials ⚠️
...com/salesforce/androidsdk/ui/OAuthWebviewHelper.kt 0.00% 13 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                dev    #2594      +/-   ##
============================================
+ Coverage     51.41%   57.10%   +5.69%     
- Complexity     1789     2424     +635     
============================================
  Files           146      187      +41     
  Lines         12097    15204    +3107     
  Branches       1712     2135     +423     
============================================
+ Hits           6220     8683    +2463     
- Misses         5178     5615     +437     
- Partials        699      906     +207     
Flag Coverage Δ
MobileSync 81.68% <ø> (?)
SalesforceHybrid 55.56% <ø> (ø)
SalesforceReact 52.36% <ø> (ø)
SalesforceSDK 42.24% <16.98%> (-0.81%) ⬇️
SmartStore 78.30% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
.../salesforce/androidsdk/app/SalesforceSDKManager.kt 53.35% <100.00%> (+0.08%) ⬆️
...com/salesforce/androidsdk/ui/OAuthWebviewHelper.kt 10.28% <0.00%> (-0.15%) ⬇️
.../src/com/salesforce/androidsdk/ui/LoginActivity.kt 24.53% <20.51%> (-1.43%) ⬇️

... and 49 files with indirect coverage changes

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.

5 participants