Skip to content

Commit 1962712

Browse files
authored
Merge pull request #2776 from wmathurin/zero_scopes
Saving scope in user account
2 parents 81858bd + 0eb56b8 commit 1962712

File tree

6 files changed

+120
-2
lines changed

6 files changed

+120
-2
lines changed

libs/SalesforceSDK/src/com/salesforce/androidsdk/accounts/UserAccount.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public class UserAccount {
9696
public static final String TOKEN_FORMAT = "tokenFormat";
9797
public static final String BEACON_CHILD_CONSUMER_KEY = "beacon_child_consumer_key";
9898
public static final String BEACON_CHILD_CONSUMER_SECRET = "beacon_child_consumer_secret";
99+
public static final String SCOPE = "scope";
99100

100101
private static final String TAG = "UserAccount";
101102
private static final String FORWARD_SLASH = "/";
@@ -142,6 +143,7 @@ public class UserAccount {
142143
private Map<String, String> additionalOauthValues;
143144
private String beaconChildConsumerKey;
144145
private String beaconChildConsumerSecret;
146+
private String scope;
145147

146148
/**
147149
* Parameterized constructor.
@@ -182,6 +184,7 @@ public class UserAccount {
182184
* @param beaconChildConsumerKey beacon child consumer key
183185
* @param beaconChildConsumerSecret beacon child consumer secret
184186
* @param apiInstanceServer API instance server
187+
* @param scope Scope
185188
*/
186189
UserAccount(String authToken, String refreshToken,
187190
String loginServer, String idUrl, String instanceServer,
@@ -193,7 +196,7 @@ public class UserAccount {
193196
String contentDomain, String contentSid, String csrfToken, Boolean nativeLogin,
194197
String language, String locale, String cookieClientSrc, String cookieSidClient,
195198
String sidCookieName, String clientId, String parentSid, String tokenFormat,
196-
String beaconChildConsumerKey, String beaconChildConsumerSecret, String apiInstanceServer) {
199+
String beaconChildConsumerKey, String beaconChildConsumerSecret, String apiInstanceServer, String scope) {
197200
this.authToken = authToken;
198201
this.refreshToken = refreshToken;
199202
this.loginServer = loginServer;
@@ -231,6 +234,7 @@ public class UserAccount {
231234
this.tokenFormat = tokenFormat;
232235
this.beaconChildConsumerKey = beaconChildConsumerKey;
233236
this.beaconChildConsumerSecret = beaconChildConsumerSecret;
237+
this.scope = scope;
234238
SalesforceSDKManager.getInstance().registerUsedAppFeature(Features.FEATURE_USER_AUTH);
235239
}
236240

@@ -281,6 +285,7 @@ public class UserAccount {
281285
tokenFormat = object.optString(TOKEN_FORMAT, null);
282286
beaconChildConsumerKey = object.optString(BEACON_CHILD_CONSUMER_KEY, null);
283287
beaconChildConsumerSecret = object.optString(BEACON_CHILD_CONSUMER_SECRET, null);
288+
scope = object.optString(SCOPE, null);
284289
additionalOauthValues = MapUtil.addJSONObjectToMap(object, additionalOauthKeys, additionalOauthValues);
285290
}
286291
}
@@ -337,6 +342,7 @@ public UserAccount(JSONObject object) {
337342
tokenFormat = bundle.getString(TOKEN_FORMAT);
338343
beaconChildConsumerKey = bundle.getString(BEACON_CHILD_CONSUMER_KEY);
339344
beaconChildConsumerSecret = bundle.getString(BEACON_CHILD_CONSUMER_SECRET);
345+
scope = bundle.getString(SCOPE);
340346
additionalOauthValues = MapUtil.addBundleToMap(bundle, additionalOauthKeys, additionalOauthValues);
341347
}
342348
}
@@ -667,7 +673,49 @@ public String getTokenFormat() {
667673
}
668674

669675
/**
670-
* Returns the beacon child consumer key .
676+
* Returns the OAuth scopes returned by the token endpoint.
677+
*
678+
* @return scope string.
679+
*/
680+
public String getScope() {
681+
return scope;
682+
}
683+
684+
/**
685+
* Parses the space-delimited scope string into its individual components.
686+
*
687+
* @return Array of scope strings (empty if scope is null/empty).
688+
*/
689+
public String[] parseScopes() {
690+
if (TextUtils.isEmpty(scope)) {
691+
return new String[0];
692+
}
693+
final String trimmed = scope.trim();
694+
if (trimmed.isEmpty()) {
695+
return new String[0];
696+
}
697+
return trimmed.split("\\s+");
698+
}
699+
700+
/**
701+
* Checks whether the provided scope exists in this account's scope list.
702+
*
703+
* @param scopeToCheck Scope name to check.
704+
* @return True if present, false otherwise.
705+
*/
706+
public boolean hasScope(String scopeToCheck) {
707+
if (TextUtils.isEmpty(scopeToCheck)) {
708+
return false;
709+
}
710+
for (final String s : parseScopes()) {
711+
if (scopeToCheck.equals(s)) {
712+
return true;
713+
}
714+
}
715+
return false;
716+
}
717+
/**
718+
* Returns the beacon child consumer key.
671719
*
672720
* @return beacon child consumer key.
673721
*/
@@ -956,6 +1004,7 @@ JSONObject toJson(List<String> additionalOauthKeys) {
9561004
object.put(TOKEN_FORMAT, tokenFormat);
9571005
object.put(BEACON_CHILD_CONSUMER_KEY, beaconChildConsumerKey);
9581006
object.put(BEACON_CHILD_CONSUMER_SECRET, beaconChildConsumerSecret);
1007+
object.put(SCOPE, scope);
9591008
object = MapUtil.addMapToJSONObject(additionalOauthValues, additionalOauthKeys, object);
9601009
} catch (JSONException e) {
9611010
SalesforceSDKLogger.e(TAG, "Unable to convert to JSON", e);
@@ -1016,6 +1065,7 @@ Bundle toBundle(List<String> additionalOauthKeys) {
10161065
object.putString(TOKEN_FORMAT, tokenFormat);
10171066
object.putString(BEACON_CHILD_CONSUMER_KEY, beaconChildConsumerKey);
10181067
object.putString(BEACON_CHILD_CONSUMER_SECRET, beaconChildConsumerSecret);
1068+
object.putString(SCOPE, scope);
10191069
object = MapUtil.addMapToBundle(additionalOauthValues, additionalOauthKeys, object);
10201070
return object;
10211071
}

libs/SalesforceSDK/src/com/salesforce/androidsdk/accounts/UserAccountBuilder.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class UserAccountBuilder private constructor() {
7171
private var allowUnset = true
7272
private var beaconChildConsumerKey: String? = null
7373
private var beaconChildConsumerSecret: String? = null
74+
private var scope: String? = null
7475

7576
/**
7677
* Set fields from token end point response
@@ -106,6 +107,7 @@ class UserAccountBuilder private constructor() {
106107
.tokenFormat(tr.tokenFormat)
107108
.beaconChildConsumerKey(tr.beaconChildConsumerKey)
108109
.beaconChildConsumerSecret(tr.beaconChildConsumerSecret)
110+
.scope(tr.scope)
109111
}
110112

111113
/**
@@ -173,6 +175,7 @@ class UserAccountBuilder private constructor() {
173175
.tokenFormat(userAccount.tokenFormat)
174176
.beaconChildConsumerKey(userAccount.beaconChildConsumerKey)
175177
.beaconChildConsumerSecret(userAccount.beaconChildConsumerSecret)
178+
.scope(userAccount.scope)
176179
}
177180

178181
/**
@@ -572,6 +575,16 @@ class UserAccountBuilder private constructor() {
572575
return if (!allowUnset && beaconChildConsumerSecret == null) this else apply { this.beaconChildConsumerSecret = beaconChildConsumerSecret }
573576
}
574577

578+
/**
579+
* Sets scope returned by token endpoint.
580+
*
581+
* @param scope OAuth scopes string.
582+
* @return Instance of this class.
583+
*/
584+
fun scope(scope: String?): UserAccountBuilder {
585+
return if (!allowUnset && scope == null) this else apply { this.scope = scope }
586+
}
587+
575588
/**
576589
* Builds and returns a UserAccount object.
577590
*
@@ -616,6 +629,7 @@ class UserAccountBuilder private constructor() {
616629
beaconChildConsumerKey,
617630
beaconChildConsumerSecret,
618631
apiInstanceServer,
632+
scope,
619633
)
620634
}
621635

libs/SalesforceSDK/src/com/salesforce/androidsdk/accounts/UserAccountManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ public Bundle updateAccount(Account account, UserAccount userAccount) {
539539
final String tokenFormat = decryptUserData(account, AuthenticatorService.KEY_TOKEN_FORMAT, encryptionKey);
540540
final String beaconChildConsumerKey = decryptUserData(account, AuthenticatorService.KEY_BEACON_CHILD_CONSUMER_KEY, encryptionKey);
541541
final String beaconChildConsumerSecret = decryptUserData(account, AuthenticatorService.KEY_BEACON_CHILD_CONSUMER_SECRET, encryptionKey);
542+
final String scope = decryptUserData(account, AuthenticatorService.KEY_SCOPE, encryptionKey);
542543

543544
Map<String, String> additionalOauthValues = null;
544545
List<String> additionalOauthKeys = SalesforceSDKManager.getInstance().getAdditionalOauthKeys();
@@ -593,6 +594,7 @@ public Bundle updateAccount(Account account, UserAccount userAccount) {
593594
.tokenFormat(tokenFormat)
594595
.beaconChildConsumerKey(beaconChildConsumerKey)
595596
.beaconChildConsumerSecret(beaconChildConsumerSecret)
597+
.scope(scope)
596598
.additionalOauthValues(additionalOauthValues)
597599
.build();
598600
}
@@ -742,6 +744,7 @@ private Bundle buildAuthBundle(UserAccount userAccount) {
742744
extras.putString(AuthenticatorService.KEY_TOKEN_FORMAT, SalesforceSDKManager.encrypt(userAccount.getTokenFormat(), encryptionKey));
743745
extras.putString(AuthenticatorService.KEY_BEACON_CHILD_CONSUMER_KEY, SalesforceSDKManager.encrypt(userAccount.getBeaconChildConsumerKey(), encryptionKey));
744746
extras.putString(AuthenticatorService.KEY_BEACON_CHILD_CONSUMER_SECRET, SalesforceSDKManager.encrypt(userAccount.getBeaconChildConsumerSecret(), encryptionKey));
747+
extras.putString(AuthenticatorService.KEY_SCOPE, SalesforceSDKManager.encrypt(userAccount.getScope(), encryptionKey));
745748

746749
final List<String> additionalOauthKeys = SalesforceSDKManager.getInstance().getAdditionalOauthKeys();
747750
if (additionalOauthKeys != null && !additionalOauthKeys.isEmpty()) {

libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/AuthenticatorService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public class AuthenticatorService extends Service {
8989
public static final String KEY_TOKEN_FORMAT = "tokenFormat";
9090
public static final String KEY_BEACON_CHILD_CONSUMER_KEY = "beacon_child_consumer_key";
9191
public static final String KEY_BEACON_CHILD_CONSUMER_SECRET = "beacon_child_consumer_secret";
92+
public static final String KEY_SCOPE = "scope";
9293

9394
private static final String TAG = "AuthenticatorService";
9495

libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/OAuth2.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,7 @@ public static class TokenEndpointResponse {
842842
public String tokenFormat;
843843
public String beaconChildConsumerKey;
844844
public String beaconChildConsumerSecret;
845+
public String scope;
845846

846847
/**
847848
* Parameterized constructor built from params during user agent login flow.
@@ -882,6 +883,7 @@ public TokenEndpointResponse(Map<String, String> callbackUrlParams, List<String>
882883
sidCookieName = callbackUrlParams.get(SID_COOKIE_NAME);
883884
parentSid = callbackUrlParams.get(PARENT_SID);
884885
tokenFormat = callbackUrlParams.get(TOKEN_FORMAT);
886+
scope = callbackUrlParams.get(SCOPE);
885887

886888
// NB: beacon apps not supported with user agent flow so no beacon child fields expected
887889

@@ -959,6 +961,7 @@ public TokenEndpointResponse(Response response, List<String> additionalOauthKeys
959961
if (parsedResponse.has(BEACON_CHILD_CONSUMER_SECRET)) {
960962
beaconChildConsumerSecret = parsedResponse.getString(BEACON_CHILD_CONSUMER_SECRET);
961963
}
964+
scope = parsedResponse.optString(SCOPE);
962965

963966
} catch (Exception e) {
964967
SalesforceSDKLogger.w(TAG, "Could not parse token endpoint response", e);

libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/accounts/UserAccountTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,14 @@ public class UserAccountTest {
104104
public static final String TEST_TOKEN_FORMAT = "test-token-format";
105105
public static final String TEST_BEACON_CHILD_CONSUMER_KEY = "test-beacon-child-consumer-key";
106106
public static final String TEST_BEACON_CHILD_CONSUMER_SECRET = "test-beacon-child-consumer-secret";
107+
public static final String TEST_SCOPE = "api web openid refresh_token";
107108

108109
// other user
109110
public static final String TEST_ORG_ID_2 = "test_org_id_2";
110111
public static final String TEST_USER_ID_2 = "test_user_id_2";
111112
public static final String TEST_ACCOUNT_NAME_2 = "test_username_2 (https://cs1.salesforce.com) (SalesforceSDKTest)";
112113
public static final String TEST_USERNAME_2 = "test_username_2";
114+
public static final String TEST_SCOPE_2 = "api web refresh_token sfap_api";
113115

114116

115117
private EventsListenerQueue eq;
@@ -125,6 +127,43 @@ public void testConvertAccountToBundle() {
125127
BundleTestHelper.checkSameBundle("UserAccount bundles do not match", expected, actual);
126128
}
127129

130+
@Test
131+
public void testParseScopes() {
132+
UserAccount account = createTestAccount();
133+
String[] scopes = account.parseScopes();
134+
// Our TEST_SCOPE is "api web openid refresh_token"
135+
Assert.assertArrayEquals(new String[]{"api", "web", "openid", "refresh_token"}, scopes);
136+
137+
// Empty / null handling
138+
UserAccount emptyScope = UserAccountBuilder.getInstance()
139+
.populateFromUserAccount(account)
140+
.scope(null)
141+
.build();
142+
Assert.assertArrayEquals(new String[]{}, emptyScope.parseScopes());
143+
144+
UserAccount blankScope = UserAccountBuilder.getInstance()
145+
.populateFromUserAccount(account)
146+
.scope(" ")
147+
.build();
148+
Assert.assertArrayEquals(new String[]{}, blankScope.parseScopes());
149+
}
150+
151+
@Test
152+
public void testHasScope() {
153+
UserAccount account = createTestAccount();
154+
Assert.assertTrue(account.hasScope("api"));
155+
Assert.assertTrue(account.hasScope("web"));
156+
Assert.assertTrue(account.hasScope("openid"));
157+
Assert.assertTrue(account.hasScope("refresh_token"));
158+
Assert.assertFalse(account.hasScope("unknown"));
159+
160+
UserAccount emptyScope = UserAccountBuilder.getInstance()
161+
.populateFromUserAccount(account)
162+
.scope("")
163+
.build();
164+
Assert.assertFalse(emptyScope.hasScope("api"));
165+
}
166+
128167
/**
129168
* Tests user account to json conversion.
130169
*/
@@ -203,6 +242,7 @@ public void testPopulateFromUserAccount() {
203242
.orgId(TEST_ORG_ID_2)
204243
.username(TEST_USERNAME_2)
205244
.accountName(TEST_ACCOUNT_NAME_2)
245+
.scope(TEST_SCOPE_2)
206246
.build();
207247
checkOtherTestAccount(otherUserAccount);
208248
}
@@ -403,6 +443,7 @@ private JSONObject createTestAccountJSON() throws JSONException{
403443
object.put(UserAccount.SID_COOKIE_NAME, TEST_SID_COOKIE_NAME);
404444
object.put(UserAccount.PARENT_SID, TEST_PARENT_SID);
405445
object.put(UserAccount.TOKEN_FORMAT, TEST_TOKEN_FORMAT);
446+
object.put(UserAccount.SCOPE, TEST_SCOPE);
406447
object.put(UserAccount.BEACON_CHILD_CONSUMER_KEY, TEST_BEACON_CHILD_CONSUMER_KEY);
407448
object.put(UserAccount.BEACON_CHILD_CONSUMER_SECRET, TEST_BEACON_CHILD_CONSUMER_SECRET);
408449
object = MapUtil.addMapToJSONObject(createAdditionalOauthValues(), createAdditionalOauthKeys(), object);
@@ -452,6 +493,7 @@ private Bundle createTestAccountBundle() {
452493
object.putString(UserAccount.TOKEN_FORMAT, TEST_TOKEN_FORMAT);
453494
object.putString(UserAccount.BEACON_CHILD_CONSUMER_KEY, TEST_BEACON_CHILD_CONSUMER_KEY);
454495
object.putString(UserAccount.BEACON_CHILD_CONSUMER_SECRET, TEST_BEACON_CHILD_CONSUMER_SECRET);
496+
object.putString(UserAccount.SCOPE, TEST_SCOPE);
455497
object = MapUtil.addMapToBundle(createAdditionalOauthValues(), createAdditionalOauthKeys(), object);
456498
return object;
457499
}
@@ -497,6 +539,7 @@ public static UserAccount createTestAccount() {
497539
.tokenFormat(TEST_TOKEN_FORMAT)
498540
.beaconChildConsumerKey(TEST_BEACON_CHILD_CONSUMER_KEY)
499541
.beaconChildConsumerSecret(TEST_BEACON_CHILD_CONSUMER_SECRET)
542+
.scope(TEST_SCOPE)
500543
.additionalOauthValues(createAdditionalOauthValues())
501544
.build();
502545
}
@@ -511,6 +554,7 @@ public static UserAccount createOtherTestAccount() {
511554
.orgId(TEST_ORG_ID_2)
512555
.username(TEST_USERNAME_2)
513556
.accountName(TEST_ACCOUNT_NAME_2)
557+
.scope(TEST_SCOPE_2)
514558
.build();
515559
}
516560

@@ -567,6 +611,7 @@ void checkTestAccount(UserAccount account, boolean expectBeaconChildFields) {
567611
Assert.assertNull("Beacon child consumer key should be null", account.getBeaconChildConsumerKey());
568612
Assert.assertNull("Beacon child consumer secret should be null", account.getBeaconChildConsumerSecret());
569613
}
614+
Assert.assertEquals("Scope should match", TEST_SCOPE, account.getScope());
570615
Assert.assertEquals("Additional OAuth values should match", createAdditionalOauthValues(), account.getAdditionalOauthValues());
571616
}
572617

@@ -609,6 +654,7 @@ void checkOtherTestAccount(UserAccount account) {
609654
Assert.assertEquals("Token format should match", TEST_TOKEN_FORMAT, account.getTokenFormat());
610655
Assert.assertEquals("Beacon child consumer key should match", TEST_BEACON_CHILD_CONSUMER_KEY, account.getBeaconChildConsumerKey());
611656
Assert.assertEquals("Beacon child consumer secret should match", TEST_BEACON_CHILD_CONSUMER_SECRET, account.getBeaconChildConsumerSecret());
657+
Assert.assertEquals("Scope should match", TEST_SCOPE_2, account.getScope());
612658
Assert.assertEquals("Additional OAuth values should match", createAdditionalOauthValues(), account.getAdditionalOauthValues());
613659
}
614660

@@ -669,6 +715,7 @@ private Map<String, String> createTokenEndpointParams() {
669715
params.put("sidCookieName", TEST_SID_COOKIE_NAME);
670716
params.put("parent_sid", TEST_PARENT_SID);
671717
params.put("token_format", TEST_TOKEN_FORMAT);
718+
params.put("scope", TEST_SCOPE);
672719

673720
return params;
674721
}

0 commit comments

Comments
 (0)