Skip to content

Add support for ordered collections of strings. #92

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,76 @@ open class SecureStorageLogicTest : SecureStorageBaseTest() {
SecurePreferences.clearAllValues(context)
}

@Test
fun testStoreRetrieveAndRemoveStringSetValue() {
val KEY_STRING_SET = "KEY_STRING_SET"
val VALUE_STRING_SET = setOf(
"The wheels on the \uD83D\uDE8C go, Round and round, Round and round, Round and round.",
"The wheels on the \uD83D\uDE8C go Round and round, All through the town. The doors on",
"the \uD83D\uDE8C go, Open and shut ♫, Open and shut ♫, Open and shut."
)
val context = activityRule.activity.applicationContext

// Store a String Set value in SecureStorage
SecurePreferences.setValue(context, KEY_STRING_SET, VALUE_STRING_SET)

// Check if the value exists in SecureStorage
Assert.assertTrue(SecurePreferences.contains(context, KEY_STRING_SET))

// Retrieve the previously stored String Set value from the SecureStorage
val retrievedValue: MutableSet<String> = SecurePreferences.getStringSetValue(context, KEY_STRING_SET, setOf())

// Check if the retrievedValue is not null
Assert.assertEquals(VALUE_STRING_SET.size, retrievedValue.size)

// Check if the retrievedValue equals the pre-stored value
Assert.assertEquals(VALUE_STRING_SET, retrievedValue)

// Remove the String Set value from SecureStorage
SecurePreferences.removeValue(context, KEY_STRING_SET)

// Check if the String Set value has been removed from SecureStorage
Assert.assertFalse(SecurePreferences.contains(context, KEY_STRING_SET))

// Delete keys and clear SecureStorage
SecurePreferences.clearAllValues(context)
}

@Test
fun testStoreRetrieveAndRemoveStringListValue() {
val KEY_STRING_LIST = "KEY_STRING_LIST"
val VALUE_STRING_LIST = listOf(
"The wheels on the \uD83D\uDE8C go, Round and round, Round and round, Round and round.",
"The wheels on the \uD83D\uDE8C go Round and round, All through the town. The doors on",
"the \uD83D\uDE8C go, Open and shut ♫, Open and shut ♫, Open and shut."
)
val context = activityRule.activity.applicationContext

// Store a String Set value in SecureStorage
SecurePreferences.setValue(context, KEY_STRING_LIST, VALUE_STRING_LIST)

// Check if the value exists in SecureStorage
Assert.assertTrue(SecurePreferences.contains(context, KEY_STRING_LIST))

// Retrieve the previously stored String Set value from the SecureStorage
val retrievedValue = SecurePreferences.getStringListValue(context, KEY_STRING_LIST, listOf())

// Check if the retrievedValue is not null
Assert.assertEquals(VALUE_STRING_LIST.size, retrievedValue.size)

// Check if the retrievedValue equals the pre-stored value
Assert.assertEquals(VALUE_STRING_LIST, retrievedValue)

// Remove the String Set value from SecureStorage
SecurePreferences.removeValue(context, KEY_STRING_LIST)

// Check if the String Set value has been removed from SecureStorage
Assert.assertFalse(SecurePreferences.contains(context, KEY_STRING_LIST))

// Delete keys and clear SecureStorage
SecurePreferences.clearAllValues(context)
}

@Test
fun testStoreRetrieveAndRemoveBooleanValue() {
val KEY_BOOLEAN = "KEY_BOOLEAN"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@
import android.content.SharedPreferences;
import android.text.TextUtils;

import java.util.HashSet;
import java.util.Set;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import static android.content.Context.MODE_PRIVATE;
import static de.adorsys.android.securestoragelibrary.SecureStorageException.ExceptionType.CRYPTO_EXCEPTION;

Expand Down Expand Up @@ -65,7 +68,7 @@ public static void setValue(@NonNull Context context,
}

/**
* Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
* Takes plain boolean value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
Expand All @@ -78,7 +81,7 @@ public static void setValue(@NonNull Context context,
}

/**
* Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
* Takes plain float value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
Expand All @@ -91,7 +94,7 @@ public static void setValue(@NonNull Context context,
}

/**
* Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
* Takes plain long value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
Expand All @@ -104,7 +107,7 @@ public static void setValue(@NonNull Context context,
}

/**
* Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
* Takes plain int value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
Expand All @@ -117,21 +120,29 @@ public static void setValue(@NonNull Context context,
}

/**
* Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
* Takes plain Set&lt;String&gt; value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
* @param value Plain Set(type: String) value that will be encrypted and stored in the SecureStorage
* @param value Plain Set&lt;String&gt; value that will be encrypted and stored in the SecureStorage
*/
public static void setValue(@NonNull Context context,
@NonNull String key,
@NonNull Set<String> value) throws SecureStorageException {
setValue(context, key + KEY_SET_COUNT_POSTFIX, String.valueOf(value.size()));
setCollectionValue(context, key, value);
}

int i = 0;
for (String s : value) {
setValue(context, key + "_" + (i++), s);
}
/**
* Takes plain List&lt;String&gt; value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
* @param value Plain List&lt;String&gt; value that will be encrypted and stored in the SecureStorage
*/
public static void setValue(@NonNull Context context,
@NonNull String key,
@NonNull List<String> value) throws SecureStorageException {
setCollectionValue(context, key, value);
}

/**
Expand Down Expand Up @@ -216,29 +227,33 @@ public static int getIntValue(@NonNull Context context,
}

/**
* Gets encrypted int value for given key from the SecureStorage on the Android Device, decrypts it and returns it
* Gets encrypted Set&lt;String&gt; value for given key from the SecureStorage on the Android Device, decrypts it and returns it
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
* @param defValue Default Set(type: String) value that will be returned if the value with given key doesn't exist or an exception is thrown
* @return Decrypted Set(type: String) value associated with given key from SecureStorage
* @return Decrypted Set&lt;String&gt; value associated with given key from SecureStorage
*/
@NonNull
public static Set<String> getStringSetValue(@NonNull Context context,
@NonNull String key,
@NonNull Set<String> defValue) {
int size = getIntValue(context, key + KEY_SET_COUNT_POSTFIX, -1);

if (size == -1) {
return defValue;
}

Set<String> res = new HashSet<>(size);
for (int i = 0; i < size; i++) {
res.add(getStringValue(context, key + "_" + i, ""));
}
return getStringCollectionValue(context, key, defValue, new LinkedHashSet<>());
}

return res;
/**
* Gets encrypted List&lt;String&gt; value for given key from the SecureStorage on the Android Device, decrypts it and returns it
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
* @param defValue Default List&lt;String&gt; value that will be returned if the value with given key doesn't exist or an exception is thrown
* @return Decrypted List&lt;String&gt; value associated with given key from SecureStorage
*/
@NonNull
public static List<String> getStringListValue(@NonNull Context context,
@NonNull String key,
@NonNull List<String> defValue) {
return getStringCollectionValue(context, key, defValue, new ArrayList<>());
}

/**
Expand All @@ -254,7 +269,8 @@ public static boolean contains(@NonNull Context context,
SharedPreferences preferences = applicationContext
.getSharedPreferences(KEY_SHARED_PREFERENCES_NAME, MODE_PRIVATE);
try {
return preferences.contains(key) && KeystoreTool.keyPairExists();
return (preferences.contains(key) || containsCollection(context, key))
&& KeystoreTool.keyPairExists();
} catch (SecureStorageException e) {
return false;
}
Expand All @@ -270,6 +286,7 @@ public static void removeValue(@NonNull Context context,
@NonNull String key) {
Context applicationContext = context.getApplicationContext();
removeSecureValue(applicationContext, key);
removeSecureCollectionValue(applicationContext, key);
}

/**
Expand Down Expand Up @@ -322,6 +339,24 @@ private static void setSecureValue(@NonNull Context context,
preferences.edit().putString(key, value).apply();
}

/**
* Takes plain Collection&lt;String&gt; value, encrypts it and stores it encrypted in the SecureStorage on the Android Device
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
* @param value Plain Collection&lt;String&gt; value that will be encrypted and stored in the SecureStorage
*/
private static void setCollectionValue(@NonNull Context context,
@NonNull String key,
@NonNull Collection<String> value) throws SecureStorageException {
setValue(context, key + KEY_SET_COUNT_POSTFIX, String.valueOf(value.size()));

int i = 0;
for (String s : value) {
setValue(context, key + "_" + (i++), s);
}
}

@Nullable
private static String getSecureValue(@NonNull Context context,
@NonNull String key) {
Expand All @@ -330,13 +365,78 @@ private static String getSecureValue(@NonNull Context context,
return preferences.getString(key, null);
}

/**
* Gets encrypted Collection&lt;String&gt; value for given key from the SecureStorage on the Android Device, decrypts it and returns it
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
* @param defValue Default Set(type: String) value that will be returned if the value with given key doesn't exist or an exception is thrown
* @return Decrypted Collection&lt;String&gt; value associated with given key from SecureStorage
*/
@NonNull
private static <T extends Collection<String>> T getStringCollectionValue(@NonNull Context context,
@NonNull String key,
@NonNull T defValue,
@NonNull T retValue) {
int size = getIntValue(context, key + KEY_SET_COUNT_POSTFIX, -1);

if (size == -1) {
return defValue;
}

for (int i = 0; i < size; i++) {
retValue.add(getStringValue(context, key + "_" + i, ""));
}

return retValue;
}

private static void removeSecureValue(@NonNull Context context,
@NonNull String key) {
SharedPreferences preferences = context
.getSharedPreferences(KEY_SHARED_PREFERENCES_NAME, MODE_PRIVATE);
preferences.edit().remove(key).apply();
}

private static void removeSecureCollectionValue(@NonNull Context context,
@NonNull String key) {
int size = getIntValue(context, key + KEY_SET_COUNT_POSTFIX, -1);

if (size != -1) {
for (int i = 0; i < size; i++) {
removeSecureValue(context, key + "_" + i);
}
removeSecureValue(context,key + KEY_SET_COUNT_POSTFIX);
}
}

/**
* Checks if SecureStorage contains a String Set value for the given key. Since sets are stored
* under multiple keys, this verifies that each expected key is present but does not verify the
* value types of those keys.
*
* @param context Context is used internally
* @param key Key used to identify the stored value in SecureStorage
* @return True if the keys for the set value exist in SecureStorage, otherwise false
*/
private static boolean containsCollection(@NonNull Context context,
@NonNull String key) {
Context applicationContext = context.getApplicationContext();
SharedPreferences preferences = applicationContext
.getSharedPreferences(KEY_SHARED_PREFERENCES_NAME, MODE_PRIVATE);

int size = getIntValue(context, key + KEY_SET_COUNT_POSTFIX, -1);

if (size == -1) {
return false;
}

for (int i = 0; i < size; i++) {
if (!preferences.contains(key + "_" + i)) return false;
}
return true;
}

private static void clearAllSecureValues(@NonNull Context context) {
SharedPreferences preferences = context
.getSharedPreferences(KEY_SHARED_PREFERENCES_NAME, MODE_PRIVATE);
Expand Down