-
Notifications
You must be signed in to change notification settings - Fork 393
Allowing no scopes to be provided #2777
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
Conversation
wmathurin
commented
Oct 2, 2025
- Checking the scope in the token endpoint response to make sure we got refresh_token
- Refactored onAuthFlowComplete() to make it testable and added tests for it
Checking the scope in the token endpoint response to make sure we got refresh_token (and id)
Throwing an error if it isn't. Refactored onAuthFlowComplete() to make it testable and added tests for it.
Generated by 🚫 Danger |
| } | ||
|
|
||
| @VisibleForTesting | ||
| internal suspend fun onAuthFlowComplete( |
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.
This is like the original onAuthFlowComplete except with pieces refactored out in private helper methods - which are not called directly but are called through lambda parameters.
| .clientId(consumerKey) | ||
| .nativeLogin(nativeLogin) | ||
| .build() | ||
| account.downloadProfilePhoto() |
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.
Moved this one into fun addAccount(account: UserAccount?, context: Context, isTestRun: Boolean, loginServerManager: LoginServerManager)
| val scopeParser = ScopeParser(tokenResponse.scope) | ||
|
|
||
| // Check that it has the refresh token scope, otherwise call onAuthFlowError | ||
| if (!scopeParser.hasRefreshTokenScope()) { |
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.
This check is new to this PR.
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.
Is not having the refresh token scope actually an invalid state/error? Do you still get the refresh scope if the policy is set to expire immediately?
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.
We never allowed it. We would add the refresh_token scope during login if it was not in bootconfig. So login would fail if the connected app did not have it configured.
I need to try and see what happens if the connected app does not have the scope and I don't check it and also what happens if it is set to expire immediately.
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.
@brandonpage
I just tried the following:
- an ECA that does not have refresh_token and a client app that does not specify scopes in bootconfig (and without the check above) => login works / token end point response does not include a refresh token
- an ECA with refresh token set to expire immediately => login works / token end point response includes a refresh token (but it will fail to refresh the access token when used)
I'm okay removing the check. We should include those scenarios in our test plan. Especially to make sure that having that null refresh token does not lead to some unwanted behaviors.
| } | ||
|
|
||
| @Test | ||
| fun testOnAuthFlowComplete_blockIntegrationUser_shouldCallError() = runTest { |
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.
Tests against the refactored onAuthFlowComplete().
| * @return Scope parameter. | ||
| * @return Scope parameter string (possibly empty). | ||
| */ | ||
| public static String computeScopeParameter(String[] scopes) { |
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.
This is an important change for this PR.
ScopeParser.computeScopeParameter(scopes) will leave an empty scopes list unchanged.
| * @return Scope parameter string (possibly empty). | ||
| */ | ||
| @JvmStatic | ||
| fun computeScopeParameter(scopes: Array<String>?): String { |
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.
This is the critical change of this PR.
brandonpage
left a comment
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.
Looks good! Really appreciate all of the tests and the effort put into making onAuthFlowComplete more testable!
| } | ||
| } | ||
| return false; | ||
| return new ScopeParser(scope).hasScope(scopeToCheck); |
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 syntax isn't as clean in Java, but I wonder if ScopeParser could just be a couple String[] extensions?
Edit: There are several functions in ScopeParser so I see its need to be a class.
| onAuthFlowComplete( | ||
| tokenResponse, | ||
| loginServer, | ||
| consumerKey, | ||
| onAuthFlowError, | ||
| onAuthFlowSuccess, | ||
| buildAccountName, | ||
| nativeLogin, | ||
| SalesforceSDKManager.getInstance().appContext, | ||
| SalesforceSDKManager.getInstance().userAccountManager, | ||
| blockIntegrationUser, | ||
| getRuntimeConfig(SalesforceSDKManager.getInstance().appContext), | ||
| ::updateLoggingPrefsHelper, | ||
| ::fetchUserIdentity, | ||
| ::startMainActivityHelper, | ||
| ::setAdministratorPreferences, | ||
| ::addAccountHelper, | ||
| ::handleScreenLockPolicy, | ||
| ::handleBiometricAuthPolicy, | ||
| ::handleDuplicateUserAccount | ||
| ) |
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.
Since the original onAuthFlowComplete is internal you could just update the original and add all of the new values with these as the default parameters.
| val scopeParser = ScopeParser(tokenResponse.scope) | ||
|
|
||
| // Check that it has the refresh token scope, otherwise call onAuthFlowError | ||
| if (!scopeParser.hasRefreshTokenScope()) { |
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.
Is not having the refresh token scope actually an invalid state/error? Do you still get the refresh scope if the policy is set to expire immediately?
| SalesforceSDKManager.getInstance().appContext.startActivity(Intent(SalesforceSDKManager.getInstance().appContext, SalesforceSDKManager.getInstance().mainActivityClass).apply { | ||
| setPackage(SalesforceSDKManager.getInstance().appContext.packageName) | ||
| flags = FLAG_ACTIVITY_NEW_TASK | ||
| }) |
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.
NIT: Why remove the with(SalesforceSDKManager.getInstance()) block?
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.
I can put it back - it was more readable.
| const val REFRESH_TOKEN = "refresh_token" | ||
| const val ID = "id" | ||
| private const val SINGLE_SPACE = " " | ||
| private const val EMPTY_STRING = "" |
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.
I don't dislike this, but I think we typically just use "" in code like how 0 isn't really a magic number. 🤷
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.
Agreed, I will get rid of it.
A quick test indicated that: - login with a CA/ECA that does not have refresh scope works but the token end point does not contain a refresh token - login with a CA/ECA that has a refresh token that expires immediately works also More testing is needed to make sure that expired or revoked access token is gracefully handled when we don't have a refresh token (and also when we have an expired refresh token).
|
The only test failure "LoginActivityTest.viewModelIsUsingFrontDoorBridge_DefaultValue_onCreateWithoutQrCodeLoginIntent" is unrelated. |