Skip to content

Commit 1734129

Browse files
committed
Encrypt current user data when storing into SharedPreferences
Refs SkygearIO/features#250.
2 parents bf99d29 + 5d35822 commit 1734129

File tree

12 files changed

+507
-17
lines changed

12 files changed

+507
-17
lines changed

skygear/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ android {
4242
useLibrary 'org.apache.http.legacy'
4343

4444
defaultConfig {
45-
minSdkVersion 16
45+
minSdkVersion 18
4646
targetSdkVersion 26
4747
versionCode 1
4848
versionName skygearVersion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package io.skygear.skygear;
2+
3+
import android.annotation.SuppressLint;
4+
import android.content.Context;
5+
import android.content.SharedPreferences;
6+
import android.support.test.InstrumentationRegistry;
7+
import android.support.test.runner.AndroidJUnit4;
8+
9+
import org.json.JSONObject;
10+
import org.junit.After;
11+
import org.junit.AfterClass;
12+
import org.junit.Before;
13+
import org.junit.BeforeClass;
14+
import org.junit.Test;
15+
import org.junit.runner.RunWith;
16+
17+
import java.util.HashMap;
18+
import java.util.Map;
19+
20+
import static junit.framework.Assert.assertEquals;
21+
import static junit.framework.Assert.assertNotNull;
22+
import static junit.framework.Assert.assertNull;
23+
24+
@RunWith(AndroidJUnit4.class)
25+
@SuppressLint("CommitPrefEdits")
26+
public class SecurePersistentStoreUnitTest {
27+
static Context instrumentationContext;
28+
29+
private static void clearSharedPreferences(Context context) {
30+
SharedPreferences pref = context.getSharedPreferences(
31+
PersistentStore.SKYGEAR_PREF_SPACE,
32+
Context.MODE_PRIVATE
33+
);
34+
SharedPreferences.Editor edit = pref.edit();
35+
edit.clear();
36+
edit.commit();
37+
}
38+
39+
@BeforeClass
40+
public static void setUpClass() throws Exception {
41+
instrumentationContext = InstrumentationRegistry.getContext().getApplicationContext();
42+
clearSharedPreferences(instrumentationContext);
43+
}
44+
45+
@AfterClass
46+
public static void tearDownClass() throws Exception {
47+
clearSharedPreferences(instrumentationContext);
48+
instrumentationContext = null;
49+
}
50+
51+
@Before
52+
public void setUp() throws Exception {
53+
clearSharedPreferences(instrumentationContext);
54+
}
55+
56+
@After
57+
public void tearDown() throws Exception {
58+
clearSharedPreferences(instrumentationContext);
59+
}
60+
61+
@Test
62+
public void testPersistentStoreRestoreUserFromEmptyState() throws Exception {
63+
SecurePersistentStore persistentStore = new SecurePersistentStore(instrumentationContext);
64+
assertNull(persistentStore.currentUser);
65+
}
66+
67+
@Test
68+
public void testPersistentStoreSaveAndRestoreUser() throws Exception {
69+
SecurePersistentStore persistentStore = new SecurePersistentStore(instrumentationContext);
70+
71+
// Save user
72+
Map profile = new HashMap<>();
73+
profile.put("username", "user_12345");
74+
profile.put("email", "user12345@skygear.dev");
75+
76+
persistentStore.currentUser = new Record("user", "12345", profile);
77+
persistentStore.accessToken = "token_12345";
78+
persistentStore.save();
79+
80+
// Restore current user
81+
SecurePersistentStore newPersistentStore = new SecurePersistentStore(instrumentationContext);
82+
Record currentUser = newPersistentStore.currentUser;
83+
84+
assertEquals("user", currentUser.type);
85+
assertEquals("12345", currentUser.id);
86+
assertEquals("user_12345", currentUser.get("username"));
87+
assertEquals("user12345@skygear.dev", currentUser.get("email"));
88+
89+
assertEquals("token_12345", persistentStore.accessToken);
90+
91+
// Current user information should be store in encrypted fields
92+
SharedPreferences pref = instrumentationContext.getSharedPreferences(
93+
SecurePersistentStore.SKYGEAR_PREF_SPACE,
94+
Context.MODE_PRIVATE
95+
);
96+
97+
String currentUserString = pref.getString(PersistentStore.CURRENT_USER_KEY, null);
98+
String currentUserEncryptedString = pref.getString(
99+
SecurePersistentStore.CURRENT_USER_ENCRYPTED_KEY, null);
100+
String currentUserEncryptedKey = pref.getString(
101+
SecurePersistentStore.CURRENT_USER_ENCRYPTED_KEY_KEY, null);
102+
assertNull(currentUserString);
103+
assertNotNull(currentUserEncryptedString);
104+
assertNotNull(currentUserEncryptedKey);
105+
106+
String accessToken = pref.getString(PersistentStore.ACCESS_TOKEN_KEY, null);
107+
String accessTokenEncrypted = pref.getString
108+
(SecurePersistentStore.ACCESS_TOKEN_ENCRYPTED_KEY, null);
109+
String accessTokenEncryptedKey = pref.getString(
110+
SecurePersistentStore.ACCESS_TOKEN_ENCRYPTED_KEY_KEY, null);
111+
assertNull(accessToken);
112+
assertNotNull(accessTokenEncrypted);
113+
assertNotNull(accessTokenEncryptedKey);
114+
}
115+
}

skygear/src/main/java/io/skygear/skygear/Configuration.java

+34-2
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,27 @@ public final class Configuration {
5252
*/
5353
final boolean pubsubConnectAutomatically;
5454

55+
56+
/**
57+
* Boolean indicating whether encrypt current user data saved in SharedPreferences.
58+
*/
59+
final boolean encryptCurrentUserData;
60+
61+
5562
private Configuration(
5663
String endpoint,
5764
String apiKey,
5865
String gcmSenderId,
5966
boolean pubsubHandlerExecutionInBackground,
60-
boolean pubsubConnectAutomatically
67+
boolean pubsubConnectAutomatically,
68+
boolean encryptCurrentUserData
6169
) {
6270
this.endpoint = endpoint;
6371
this.apiKey = apiKey;
6472
this.gcmSenderId = gcmSenderId;
6573
this.pubsubHandlerExecutionInBackground = pubsubHandlerExecutionInBackground;
6674
this.pubsubConnectAutomatically = pubsubConnectAutomatically;
75+
this.encryptCurrentUserData = encryptCurrentUserData;
6776
}
6877

6978
/**
@@ -111,6 +120,15 @@ public boolean isPubsubConnectAutomatically() {
111120
return pubsubConnectAutomatically;
112121
}
113122

123+
/**
124+
* Encrypt current user data boolean.
125+
*
126+
* @return the boolean
127+
*/
128+
public boolean encryptCurrentUserData() {
129+
return encryptCurrentUserData;
130+
}
131+
114132
/**
115133
* Creates an instance of default configuration.
116134
*
@@ -148,6 +166,7 @@ public static final class Builder {
148166
private String gcmSenderId;
149167
private boolean pubsubHandlerExecutionInBackground;
150168
private boolean pubsubConnectAutomatically;
169+
private boolean encryptCurrentUserData;
151170

152171
/**
153172
* Creates an instance of Builder.
@@ -212,6 +231,18 @@ public Builder pubsubConnectAutomatically(boolean automatic) {
212231
return this;
213232
}
214233

234+
/**
235+
* Sets whether encrypt current user data saved in SharedPreferences.
236+
*
237+
* @param enabled the boolean indicating whether encryption Enabled
238+
* automatically.
239+
* @return the builder
240+
*/
241+
public Builder encryptCurrentUserData(boolean enabled) {
242+
this.encryptCurrentUserData = enabled;
243+
return this;
244+
}
245+
215246
/**
216247
* Build a configuration.
217248
*
@@ -231,7 +262,8 @@ public Configuration build() {
231262
this.apiKey,
232263
this.gcmSenderId,
233264
this.pubsubHandlerExecutionInBackground,
234-
this.pubsubConnectAutomatically
265+
this.pubsubConnectAutomatically,
266+
this.encryptCurrentUserData
235267
);
236268
}
237269
}

skygear/src/main/java/io/skygear/skygear/Container.java

+15-7
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public final class Container {
3131
private static final String TAG = "Skygear SDK";
3232
private static Container sharedInstance;
3333

34-
final PersistentStore persistentStore;
34+
PersistentStore persistentStore;
3535
final Context context;
3636
final RequestManager requestManager;
3737
Configuration config;
@@ -61,18 +61,13 @@ public Container(Context context, Configuration config) {
6161
this.context = context.getApplicationContext();
6262
this.config = config;
6363
this.requestManager = new RequestManager(this.context, this.config);
64-
this.persistentStore = new PersistentStore(this.context);
6564

6665
this.auth = new AuthContainer(this);
6766
this.pubsub = new PubsubContainer(this);
6867
this.push = new PushContainer(this);
6968
this.publicDatabase = Database.Factory.publicDatabase(this);
7069
this.privateDatabase = Database.Factory.privateDatabase(this);
71-
this.requestManager.accessToken = this.persistentStore.accessToken;
72-
73-
if (this.persistentStore.defaultAccessControl != null) {
74-
AccessControl.defaultAccessControl = this.persistentStore.defaultAccessControl;
75-
}
70+
this.configPersistentStore(config);
7671
}
7772

7873
/**
@@ -151,6 +146,7 @@ public void configure(Configuration config) {
151146
}
152147

153148
this.config = config;
149+
this.configPersistentStore(config);
154150
this.requestManager.configure(config);
155151
this.pubsub.configure(config);
156152
}
@@ -263,4 +259,16 @@ public final void onFailure(Error error) {
263259
}
264260
});
265261
}
262+
263+
private void configPersistentStore(Configuration config) {
264+
if (config != null && config.encryptCurrentUserData) {
265+
this.persistentStore = new SecurePersistentStore(this.context);
266+
} else {
267+
this.persistentStore = new PersistentStore(this.context);
268+
}
269+
this.requestManager.accessToken = this.persistentStore.accessToken;
270+
if (this.persistentStore.defaultAccessControl != null) {
271+
AccessControl.defaultAccessControl = this.persistentStore.defaultAccessControl;
272+
}
273+
}
266274
}

skygear/src/main/java/io/skygear/skygear/PersistentStore.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class PersistentStore {
4242
static final String DEVICE_ID_KEY = "device_id";
4343
static final String DEVICE_TOKEN_KEY = "device_token";
4444

45-
private final Context context;
45+
final Context context;
4646
/**
4747
* The Current user.
4848
*/
@@ -109,7 +109,7 @@ void save() {
109109
prefEditor.apply();
110110
}
111111

112-
private void restoreAuthUser(SharedPreferences pref) {
112+
void restoreAuthUser(SharedPreferences pref) {
113113
String currentUserString = pref.getString(CURRENT_USER_KEY, null);
114114

115115
if (currentUserString == null) {
@@ -126,7 +126,7 @@ private void restoreAuthUser(SharedPreferences pref) {
126126
}
127127
}
128128

129-
private void saveAuthUser(SharedPreferences.Editor prefEditor) {
129+
void saveAuthUser(SharedPreferences.Editor prefEditor) {
130130
if (this.currentUser != null) {
131131
prefEditor.putString(CURRENT_USER_KEY,
132132
RecordSerializer.serialize(this.currentUser).toString()
@@ -136,11 +136,11 @@ private void saveAuthUser(SharedPreferences.Editor prefEditor) {
136136
}
137137
}
138138

139-
private void restoreAccessToken(SharedPreferences pref) {
139+
void restoreAccessToken(SharedPreferences pref) {
140140
this.accessToken = pref.getString(ACCESS_TOKEN_KEY, null);
141141
}
142142

143-
private void saveAccessToken(SharedPreferences.Editor prefEditor) {
143+
void saveAccessToken(SharedPreferences.Editor prefEditor) {
144144
if (this.accessToken != null) {
145145
prefEditor.putString(ACCESS_TOKEN_KEY, this.accessToken);
146146
} else {

0 commit comments

Comments
 (0)