Skip to content

Commit 347388f

Browse files
authored
Merge pull request forcedotcom#2808 from brandonpage/dynamic_oauth_config
Add per login host oauth configuration.
2 parents 4cdc3d0 + 612c8e3 commit 347388f

File tree

19 files changed

+1725
-160
lines changed

19 files changed

+1725
-160
lines changed

libs/SalesforceSDK/AndroidManifest.xml

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,6 @@
6969
android:theme="@style/SalesforceSDK"
7070
android:exported="true" />
7171

72-
<!-- Dev info activity -->
73-
<activity android:name="com.salesforce.androidsdk.ui.DevInfoActivity"
74-
android:theme="@style/SalesforceSDK"
75-
android:exported="false" />
76-
77-
<!-- Login Options activity -->
78-
<activity android:name="com.salesforce.androidsdk.ui.LoginOptionsActivity"
79-
android:theme="@style/SalesforceSDK"
80-
android:exported="false" />
81-
82-
<!-- Test Authentication Activity For Automated Testing. This activity is only functional for debug builds of the app using Salesforce Mobile SDK -->
83-
<activity
84-
android:name="com.salesforce.androidsdk.util.test.TestAuthenticationActivity"
85-
android:excludeFromRecents="true"
86-
android:exported="true"
87-
android:theme="@style/SalesforceSDK" />
88-
8972
<!-- Receiver in SP app for IDP-SP login flows -->
9073
<receiver android:name="com.salesforce.androidsdk.auth.idp.SPReceiver"
9174
android:exported="true"

libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.kt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ import com.salesforce.androidsdk.app.SalesforceSDKManager.Theme.SYSTEM_DEFAULT
9292
import com.salesforce.androidsdk.auth.AuthenticatorService.KEY_INSTANCE_URL
9393
import com.salesforce.androidsdk.auth.HttpAccess
9494
import com.salesforce.androidsdk.auth.HttpAccess.DEFAULT
95-
import com.salesforce.androidsdk.auth.JwtAccessToken
9695
import com.salesforce.androidsdk.auth.NativeLoginManager
9796
import com.salesforce.androidsdk.auth.OAuth2.LogoutReason
9897
import com.salesforce.androidsdk.auth.OAuth2.LogoutReason.UNKNOWN
@@ -107,6 +106,7 @@ import com.salesforce.androidsdk.config.LoginServerManager
107106
import com.salesforce.androidsdk.config.LoginServerManager.PRODUCTION_LOGIN_URL
108107
import com.salesforce.androidsdk.config.LoginServerManager.SANDBOX_LOGIN_URL
109108
import com.salesforce.androidsdk.config.LoginServerManager.WELCOME_LOGIN_URL
109+
import com.salesforce.androidsdk.config.OAuthConfig
110110
import com.salesforce.androidsdk.config.RuntimeConfig.ConfigKey.IDPAppPackageName
111111
import com.salesforce.androidsdk.config.RuntimeConfig.getRuntimeConfig
112112
import com.salesforce.androidsdk.developer.support.DevSupportInfo
@@ -149,11 +149,9 @@ import kotlinx.coroutines.Dispatchers.Main
149149
import kotlinx.coroutines.launch
150150
import kotlinx.coroutines.withTimeoutOrNull
151151
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
152-
import org.json.JSONObject
152+
import org.jetbrains.annotations.Debug
153153
import java.lang.String.CASE_INSENSITIVE_ORDER
154154
import java.net.URI
155-
import java.text.SimpleDateFormat
156-
import java.util.Locale
157155
import java.util.Locale.US
158156
import java.util.SortedSet
159157
import java.util.UUID.randomUUID
@@ -200,7 +198,7 @@ open class SalesforceSDKManager protected constructor(
200198
*
201199
* @return The class for the main activity.
202200
*/
203-
val mainActivityClass: Class<out Activity>
201+
val mainActivityClass: Class<out Activity> = mainActivity
204202

205203
/**
206204
* Null or an authenticated Activity for private use when developer support
@@ -234,6 +232,16 @@ open class SalesforceSDKManager protected constructor(
234232
*/
235233
var loginViewModelFactory = LoginViewModel.Factory
236234

235+
/**
236+
* Asynchronously retrieves the app config for the specified login host. If not set or null is
237+
* returned the values found in the BootConfig file will be used for all servers.
238+
*/
239+
var appConfigForLoginHost: suspend (server: String) -> OAuthConfig? = {
240+
OAuthConfig(getBootConfig(appContext))
241+
}
242+
243+
internal var debugOverrideAppConfig: OAuthConfig? = null
244+
237245
/** The class for the account switcher activity */
238246
var accountSwitcherActivityClass = AccountSwitcherActivity::class.java
239247

@@ -374,7 +382,6 @@ open class SalesforceSDKManager protected constructor(
374382
@set:Synchronized
375383
var useWebServerAuthentication = true
376384

377-
378385
/**
379386
* Whether or not the app supports welcome discovery. This should only be
380387
* enabled if the connected app is supported.
@@ -432,6 +439,7 @@ open class SalesforceSDKManager protected constructor(
432439
return _lightColorScheme ?: sfLightColors().also { _lightColorScheme = it }
433440
}
434441

442+
@Suppress("unused")
435443
fun setLightColorScheme(value: ColorScheme) {
436444
_lightColorScheme = value
437445
}
@@ -447,6 +455,7 @@ open class SalesforceSDKManager protected constructor(
447455
return _darkColorScheme ?: sfDarkColors().also { _darkColorScheme = it }
448456
}
449457

458+
@Suppress("unused")
450459
fun setDarkColorScheme(value: ColorScheme) {
451460
_darkColorScheme = value
452461
}
@@ -531,7 +540,6 @@ open class SalesforceSDKManager protected constructor(
531540

532541
/** Initializer */
533542
init {
534-
mainActivityClass = mainActivity
535543
features = ConcurrentSkipListSet(CASE_INSENSITIVE_ORDER)
536544

537545
/*
@@ -615,7 +623,7 @@ open class SalesforceSDKManager protected constructor(
615623
communityUrl: String,
616624
reCaptchaSiteKeyId: String? = null,
617625
googleCloudProjectId: String? = null,
618-
isReCaptchaEnterprise: Boolean = false
626+
isReCaptchaEnterprise: Boolean = false,
619627
): NativeLoginManagerInterface {
620628
registerUsedAppFeature(FEATURE_NATIVE_LOGIN)
621629
nativeLoginManager = NativeLoginManager(

libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/AuthenticationUtilities.kt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ internal suspend fun onAuthFlowComplete(
111111
handleScreenLockPolicy: (userIdentity: OAuth2.IdServiceResponse?, account: UserAccount) -> Unit = ::handleScreenLockPolicy,
112112
handleBiometricAuthPolicy: (userIdentity: OAuth2.IdServiceResponse?, account: UserAccount) -> Unit = ::handleBiometricAuthPolicy,
113113
handleDuplicateUserAccount: (userAccountManager: UserAccountManager, account: UserAccount, userIdentity: OAuth2.IdServiceResponse?) -> Unit = ::handleDuplicateUserAccount,
114-
) {
114+
) {
115+
// Reset Dev Support LoginOptionsActivity override
116+
SalesforceSDKManager.getInstance().debugOverrideAppConfig = null
115117

116118
// Note: Can't use default parameter value for suspended function parameter fetchUserIdentity
117119
val actualFetchUserIdentity = fetchUserIdentity ?: ::fetchUserIdentity
@@ -140,14 +142,7 @@ internal suspend fun onAuthFlowComplete(
140142
w(TAG, "Missing refresh token scope.")
141143
}
142144

143-
// Check that the tokenResponse.scope contains the identity scope before calling the identity service
144-
val userIdentity = if (scopeParser.hasIdentityScope()) {
145-
actualFetchUserIdentity(tokenResponse)
146-
} else {
147-
w(TAG, "Missing identity scope, skipping identity service call.")
148-
null
149-
}
150-
145+
val userIdentity = actualFetchUserIdentity(tokenResponse)
151146
val mustBeManagedApp = userIdentity?.customPermissions?.optBoolean(MUST_BE_MANAGED_APP_PERM) ?: false
152147
if (mustBeManagedApp && !runtimeConfig.isManagedApp) {
153148
onAuthFlowError(
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2025-present, salesforce.com, inc.
3+
* All rights reserved.
4+
* Redistribution and use of this software in source and binary forms, with or
5+
* without modification, are permitted provided that the following conditions
6+
* are met:
7+
* - Redistributions of source code must retain the above copyright notice, this
8+
* list of conditions and the following disclaimer.
9+
* - Redistributions in binary form must reproduce the above copyright notice,
10+
* this list of conditions and the following disclaimer in the documentation
11+
* and/or other materials provided with the distribution.
12+
* - Neither the name of salesforce.com, inc. nor the names of its contributors
13+
* may be used to endorse or promote products derived from this software without
14+
* specific prior written permission of salesforce.com, inc.
15+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
19+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25+
* POSSIBILITY OF SUCH DAMAGE.
26+
*/
27+
package com.salesforce.androidsdk.config
28+
29+
data class OAuthConfig(
30+
val consumerKey: String,
31+
val redirectUri: String,
32+
val scopes: List<String>? = null,
33+
) {
34+
35+
internal constructor(bootConfig: BootConfig): this(
36+
bootConfig.remoteAccessConsumerKey,
37+
bootConfig.oauthRedirectURI,
38+
scopes = bootConfig.oauthScopes?.ifEmpty { null }?.toList(),
39+
)
40+
41+
// Used by LoginOptionsActivity
42+
internal constructor(consumerKey: String, redirectUri: String, scopes: String): this(
43+
consumerKey.trim(),
44+
redirectUri.trim(),
45+
scopes = with(scopes) {
46+
if (isNullOrBlank()) return@with null
47+
48+
return@with if (contains(",")) {
49+
split(",")
50+
} else {
51+
split(" ")
52+
}.map { it.trim() }
53+
}
54+
)
55+
56+
// Used by LoginOptionsActivity
57+
internal val scopesString
58+
get() = scopes?.joinToString(separator = " ") ?: ""
59+
}

libs/SalesforceSDK/src/com/salesforce/androidsdk/developer/support/DevSupportInfo.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ data class DevSupportInfo(
157157
"Consumer Key" to currentUser.clientId,
158158
"Scopes" to currentUser.scope,
159159
"Instance URL" to currentUser.instanceServer,
160-
"Token Format" to currentUser.tokenFormat,
160+
"Token Format" to (currentUser.tokenFormat?.ifBlank { "Opaque" } ?: "Opaque"),
161161
"Access Token Expiration" to accessTokenExpiration,
162-
"Beacon Child Consumer Key" to currentUser.beaconChildConsumerKey,
162+
"Beacon Child Consumer Key" to (currentUser.beaconChildConsumerKey ?: "None"),
163163
)
164164
}
165165

libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/LoginActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ open class LoginActivity : FragmentActivity() {
983983
) = salesforceWelcomeDiscoveryHostAndPathUrl.buildUpon()
984984
.appendQueryParameter(
985985
SALESFORCE_WELCOME_DISCOVERY_MOBILE_URL_QUERY_PARAMETER_KEY_CLIENT_ID,
986-
viewModel.bootConfig.remoteAccessConsumerKey
986+
viewModel.oAuthConfig.redirectUri,
987987
)
988988
.appendQueryParameter(
989989
SALESFORCE_WELCOME_DISCOVERY_MOBILE_URL_QUERY_PARAMETER_KEY_CLIENT_VERSION,
@@ -1138,7 +1138,7 @@ open class LoginActivity : FragmentActivity() {
11381138
}
11391139

11401140
val formattedUrl = request.url.toString().replace("///", "/").lowercase()
1141-
val callbackUrl = viewModel.bootConfig.oauthRedirectURI.replace("///", "/").lowercase()
1141+
val callbackUrl = viewModel.oAuthConfig.redirectUri.replace("///", "/").lowercase()
11421142
val authFlowFinished = formattedUrl.startsWith(callbackUrl)
11431143

11441144
if (authFlowFinished) {

0 commit comments

Comments
 (0)