Skip to content

Commit f775dd1

Browse files
authored
Provide interface for Signed Variables (#457)
* Add varsJson and varsSignature * Changes in naming and javadoc. Add unit tests. * fixes
1 parent c8aa0d0 commit f775dd1

File tree

12 files changed

+217
-9
lines changed

12 files changed

+217
-9
lines changed

AndroidSDKCore/src/main/java/com/leanplum/Leanplum.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import android.location.Location;
2626
import android.text.TextUtils;
2727

28+
import androidx.annotation.Nullable;
2829
import com.leanplum.ActionContext.ContextualValues;
2930
import com.leanplum.callbacks.ActionCallback;
3031
import com.leanplum.callbacks.MessageDisplayedCallback;
@@ -551,7 +552,9 @@ static synchronized void start(final Context context, final String userId,
551552
new HashMap<>(),
552553
new HashMap<>(),
553554
new ArrayList<>(),
554-
new HashMap<>());
555+
new HashMap<>(),
556+
"",
557+
"");
555558
LeanplumInbox.getInstance().update(new HashMap<>(), 0, false);
556559
return;
557560
}
@@ -956,13 +959,23 @@ private static void applyContentInResponse(JSONObject response, boolean alwaysAp
956959
response.optJSONArray(Constants.Keys.VARIANTS));
957960
Map<String, Object> variantDebugInfo = JsonConverter.mapFromJsonOrDefault(
958961
response.optJSONObject(Constants.Keys.VARIANT_DEBUG_INFO));
962+
JSONObject varsJsonObj = response.optJSONObject(Constants.Keys.VARS);
963+
String varsJson = (varsJsonObj != null) ? varsJsonObj.toString() : null;
964+
String varsSignature = response.optString(Constants.Keys.VARS_SIGNATURE);
959965

960966
if (alwaysApply
961967
|| !values.equals(VarCache.getDiffs())
962968
|| !messages.equals(VarCache.getMessageDiffs())
963969
|| !variants.equals(VarCache.variants())
964970
|| !regions.equals(VarCache.regions())) {
965-
VarCache.applyVariableDiffs(values, messages, regions, variants, variantDebugInfo);
971+
VarCache.applyVariableDiffs(
972+
values,
973+
messages,
974+
regions,
975+
variants,
976+
variantDebugInfo,
977+
varsJson,
978+
varsSignature);
966979
}
967980
}
968981

@@ -2157,6 +2170,18 @@ public static List<Map<String, Object>> variants() {
21572170
return variants;
21582171
}
21592172

2173+
/**
2174+
* Returns the last received signed variables. If signature was not provided from server the
2175+
* result of this method will be null.
2176+
*
2177+
* @return {@link SecuredVars} instance containing variable's JSON and signature. If signature
2178+
* wasn't downloaded from server it will return null.
2179+
*/
2180+
@Nullable
2181+
public static SecuredVars securedVars() {
2182+
return VarCache.getSecuredVars();
2183+
}
2184+
21602185
/**
21612186
* Returns metadata for all active in-app messages. Recommended only for debugging purposes and
21622187
* advanced use cases.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.leanplum;
2+
3+
import androidx.annotation.NonNull;
4+
5+
/**
6+
* Represents the variables in JSON format, cryptographically signed from Leanplum server.
7+
*/
8+
public class SecuredVars {
9+
private String json;
10+
private String signature;
11+
12+
public SecuredVars(@NonNull String json, @NonNull String signature) {
13+
this.json = json;
14+
this.signature = signature;
15+
}
16+
17+
/**
18+
* Get JSON of the variables.
19+
*
20+
* @return The JSON representation of the variables as received from Leanplum server.
21+
*/
22+
@NonNull
23+
public String getJson() {
24+
return json;
25+
}
26+
27+
/**
28+
* Get the cryptographic signature of the variables.
29+
*
30+
* @return The signature of the variables.
31+
*/
32+
@NonNull
33+
public String getSignature() {
34+
return signature;
35+
}
36+
}

AndroidSDKCore/src/main/java/com/leanplum/internal/Constants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public static class Defaults {
7373
public static final String ITEM_KEY = "__leanplum_unsynced_%d";
7474
public static final String UUID_KEY = "__leanplum_uuid";
7575
public static final String VARIABLES_KEY = "__leanplum_variables";
76+
public static final String VARIABLES_JSON_KEY = "__leanplum_variables_json";
77+
public static final String VARIABLES_SIGN_KEY = "__leanplum_variables_signature";
7678
public static final String ATTRIBUTES_KEY = "__leanplum_attributes";
7779
public static final String TOKEN_KEY = "__leanplum_token";
7880
public static final String MESSAGES_KEY = "__leanplum_messages";
@@ -210,6 +212,7 @@ public static class Keys {
210212
public static final String TOKEN = "token";
211213
public static final String VARIANTS = "variants";
212214
public static final String VARS = "vars";
215+
public static final String VARS_SIGNATURE = "varsSignature";
213216
public static final String VARS_FROM_CODE = "varsFromCode";
214217
public static final String NOTIFICATION_CHANNELS = "notificationChannels";
215218
public static final String DEFAULT_NOTIFICATION_CHANNEL = "defaultNotificationChannel";

AndroidSDKCore/src/main/java/com/leanplum/internal/Socket.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ static void handleApplyVarsEvent(JSONArray args) {
300300
return;
301301
}
302302
VarCache.applyVariableDiffs(
303-
JsonConverter.mapFromJson(object), null, null, null, null);
303+
JsonConverter.mapFromJson(object), null, null, null, null, null, null);
304304
} catch (JSONException e) {
305305
Log.e("Couldn't applyVars for preview.", e);
306306
} catch (Throwable e) {

AndroidSDKCore/src/main/java/com/leanplum/internal/VarCache.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
import android.content.Context;
2525
import android.content.SharedPreferences;
2626

27+
import android.text.TextUtils;
28+
import androidx.annotation.Nullable;
2729
import com.leanplum.ActionContext;
2830
import com.leanplum.CacheUpdateBlock;
31+
import com.leanplum.SecuredVars;
2932
import com.leanplum.Leanplum;
3033
import com.leanplum.LocationManager;
3134
import com.leanplum.Var;
@@ -60,6 +63,8 @@ public class VarCache {
6063
private static final Map<String, Var<?>> vars = new ConcurrentHashMap<>();
6164
private static final Map<String, Object> fileAttributes = new HashMap<>();
6265
private static final Map<String, StreamProvider> fileStreams = new HashMap<>();
66+
private static volatile String varsJson;
67+
private static volatile String varsSignature;
6368

6469
/**
6570
* The default values set by the client. This is not thread-safe so traversals should be
@@ -391,7 +396,9 @@ public static void loadDiffs() {
391396
new HashMap<String, Object>(),
392397
new HashMap<String, Object>(),
393398
new ArrayList<Map<String, Object>>(),
394-
new HashMap<String, Object>());
399+
new HashMap<String, Object>(),
400+
"",
401+
"");
395402
return;
396403
}
397404
try {
@@ -404,12 +411,16 @@ public static void loadDiffs() {
404411
String regions = aesContext.decodePreference(defaults, Constants.Defaults.REGIONS_KEY, "{}");
405412
String variants = aesContext.decodePreference(defaults, Constants.Keys.VARIANTS, "[]");
406413
String variantDebugInfo = aesContext.decodePreference(defaults, Constants.Keys.VARIANT_DEBUG_INFO, "{}");
414+
String varsJson = aesContext.decodePreference(defaults, Constants.Defaults.VARIABLES_JSON_KEY, "{}");
415+
String varsSignature = aesContext.decodePreference(defaults, Constants.Defaults.VARIABLES_SIGN_KEY, null);
407416
applyVariableDiffs(
408417
JsonConverter.fromJson(variables),
409418
JsonConverter.fromJson(messages),
410419
JsonConverter.fromJson(regions),
411420
JsonConverter.<Map<String, Object>>listFromJson(new JSONArray(variants)),
412-
JsonConverter.fromJson(variantDebugInfo));
421+
JsonConverter.fromJson(variantDebugInfo),
422+
varsJson,
423+
varsSignature);
413424
String deviceId = aesContext.decodePreference(defaults, Constants.Params.DEVICE_ID, null);
414425
if (deviceId != null) {
415426
if (Util.isValidDeviceId(deviceId)) {
@@ -475,6 +486,9 @@ public static void saveDiffs() {
475486
aesContext.encrypt(JsonConverter.toJson(variantDebugInfo)));
476487
}
477488

489+
editor.putString(Constants.Defaults.VARIABLES_JSON_KEY, aesContext.encrypt(varsJson));
490+
editor.putString(Constants.Defaults.VARIABLES_SIGN_KEY, aesContext.encrypt(varsSignature));
491+
478492
editor.putString(Constants.Params.DEVICE_ID, aesContext.encrypt(APIConfig.getInstance().deviceId()));
479493
editor.putString(Constants.Params.USER_ID, aesContext.encrypt(APIConfig.getInstance().userId()));
480494
editor.putString(Constants.Keys.LOGGING_ENABLED,
@@ -529,7 +543,9 @@ public static void applyVariableDiffs(
529543
Map<String, Object> messages,
530544
Map<String, Object> regions,
531545
List<Map<String, Object>> variants,
532-
Map<String, Object> variantDebugInfo) {
546+
Map<String, Object> variantDebugInfo,
547+
String varsJson,
548+
String varsSignature) {
533549
if (diffs != null) {
534550
VarCache.diffs = diffs;
535551
computeMergedDictionary();
@@ -600,6 +616,11 @@ public static void applyVariableDiffs(
600616
VarCache.setVariantDebugInfo(variantDebugInfo);
601617
}
602618

619+
if (varsJson != null) {
620+
VarCache.varsJson = varsJson;
621+
VarCache.varsSignature = varsSignature;
622+
}
623+
603624
contentVersion++;
604625

605626
if (!silent) {
@@ -872,10 +893,20 @@ public static void saveUserAttributes() {
872893
SharedPreferencesUtil.commitChanges(editor);
873894
}
874895

896+
@Nullable
897+
public static SecuredVars getSecuredVars() {
898+
if (TextUtils.isEmpty(varsJson) || TextUtils.isEmpty(varsSignature)) {
899+
return null;
900+
}
901+
return new SecuredVars(varsJson, varsSignature);
902+
}
903+
875904
public static void clearUserContent() {
876905
vars.clear();
877906
variants = new ArrayList<>();
878907
variantDebugInfo.clear();
908+
varsJson = null;
909+
varsSignature = null;
879910

880911
diffs.clear();
881912
messageDiffs.clear();
@@ -913,5 +944,7 @@ public static void reset() {
913944
silent = false;
914945
contentVersion = 0;
915946
userAttributes = null;
947+
varsJson = null;
948+
varsSignature = null;
916949
}
917950
}

AndroidSDKPush/src/main/java/com/leanplum/LeanplumPushService.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,15 +192,29 @@ public void response(JSONObject response) {
192192
response.optJSONObject(Constants.Keys.REGIONS));
193193
List<Map<String, Object>> variants = JsonConverter.listFromJson(
194194
response.optJSONArray(Constants.Keys.VARIANTS));
195+
196+
JSONObject varsJsonObj = response.optJSONObject(Constants.Keys.VARS);
197+
String varsJson = (varsJsonObj != null) ? varsJsonObj.toString() : null;
198+
String varsSignature = response.optString(Constants.Keys.VARS_SIGNATURE);
199+
195200
if (!Constants.canDownloadContentMidSessionInProduction ||
196201
VarCache.getDiffs().equals(values)) {
197202
values = null;
203+
varsJson = null;
204+
varsSignature = null;
198205
}
199206
if (VarCache.getMessageDiffs().equals(messages)) {
200207
messages = null;
201208
}
202209
if (values != null || messages != null) {
203-
VarCache.applyVariableDiffs(values, messages, regions, variants, null);
210+
VarCache.applyVariableDiffs(
211+
values,
212+
messages,
213+
regions,
214+
variants,
215+
null,
216+
varsJson,
217+
varsSignature);
204218
}
205219
}
206220
onComplete.variablesChanged();

AndroidSDKTests/src/test/java/com/leanplum/LeanplumActionContextTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ public void testPrefetchingChainedMessage() {
308308
// Add chained message to VarCache.
309309
VarCache.applyVariableDiffs(null, new HashMap<>(
310310
ImmutableMap.<String, Object>of(Long.toString(chainedMessageId),
311-
ImmutableMap.<String, Object>of())), null, null, null);
311+
ImmutableMap.<String, Object>of())), null, null, null, null, null);
312312
// Since it now exists locally, we should return false for forceContentUpdate.
313313
Assert.assertFalse(ActionContext.shouldForceContentUpdateForChainedMessage(
314314
JsonConverter.fromJson(jsonData)));

AndroidSDKTests/src/test/java/com/leanplum/LeanplumTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ public void testMetadata() {
528528
}});
529529
}};
530530

531-
VarCache.applyVariableDiffs(null, messages, null, variants, null);
531+
VarCache.applyVariableDiffs(null, messages, null, variants, null, null, null);
532532
assertEquals(variants, Leanplum.variants());
533533
assertEquals(messages, Leanplum.messageMetadata());
534534
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.leanplum;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNull;
5+
import static org.junit.Assert.assertTrue;
6+
7+
import com.leanplum.__setup.AbstractTest;
8+
import com.leanplum._whitebox.utilities.ResponseHelper;
9+
import com.leanplum.internal.VarCache;
10+
import org.junit.Test;
11+
12+
public class SecuredVarsTest extends AbstractTest {
13+
14+
@Test
15+
public void testVarsAndSignature() {
16+
ResponseHelper.seedResponse("/responses/secured_vars_response.json");
17+
18+
Leanplum.start(mContext);
19+
assertTrue(Leanplum.hasStarted());
20+
21+
SecuredVars securedVars = VarCache.getSecuredVars();
22+
23+
assertTrue(securedVars.getJson().contains("intVariable"));
24+
assertTrue(securedVars.getJson().contains("stringVariable"));
25+
26+
assertEquals(securedVars.getSignature(), "sign_of_vars");
27+
}
28+
29+
@Test
30+
public void testVarsNoSignature() {
31+
ResponseHelper.seedResponse("/responses/secured_vars_no_sign_response.json");
32+
33+
Leanplum.start(mContext);
34+
assertTrue(Leanplum.hasStarted());
35+
36+
SecuredVars securedVars = VarCache.getSecuredVars();
37+
assertNull(securedVars);
38+
}
39+
40+
@Test
41+
public void testEmptyVarsNoSignature() {
42+
ResponseHelper.seedResponse("/responses/secured_vars_empty_response.json");
43+
44+
Leanplum.start(mContext);
45+
assertTrue(Leanplum.hasStarted());
46+
47+
SecuredVars securedVars = VarCache.getSecuredVars();
48+
assertNull(securedVars);
49+
}
50+
51+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"response": [
3+
{
4+
"regions": {},
5+
"fileAttributes": {},
6+
"success": true,
7+
"messages": {},
8+
"vars": {},
9+
"variants": [],
10+
"token": "c2q5to0cTdiZWE2bdkKUQsTs0cQRVkfHkSGoPeqrQWc"
11+
}
12+
]
13+
}

0 commit comments

Comments
 (0)