Skip to content
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

Feat/enhanced change listener #47

Merged

Conversation

davidgarciaanton
Copy link
Contributor

As EncryptionProtocol is not accessible and there may be the requirement to respond only to specific keys changed, and the regular SharedPreferences.OnPreferenceChangeListener callback isn't enough as we get the stored version of the key, witch we cannot compare against constants.

I've created a specific OnSecurePreferenceChangeListener that gives a facility to at least compare the changed key against some arbitrary string.

I don't know in terms of security of implementation if this is the best way to achieve this. Please let me know if you think of another way more secure

@patrickfav
Copy link
Owner

Thanks for the PR. I will review it in the next days!

Copy link
Owner

@patrickfav patrickfav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All in all looks good, I think the code can be a bit simplified and made a little easier to understand for the caller.

Important: Please add unit/integration test.

}

private final OnSecurePreferenceChangeListener.DerivedKeyComparison keyComparison;
private final WeakReference<OnSecurePreferenceChangeListener> wrappedRef;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using WeakReference here might lead to problems. This may be not the behaviour you want and it is quite difficult to understand from the outside why this may not work for a developer. I usually refrain from using Weak/Soft references as they not very clear to most devs how they work (and behave differently on Android ie. more aggresivly). I would make it the callers responsibility to unregister his listeners, as it is usually the case in Android.
As reference why this might be an issue: https://android.jlelse.eu/android-weakreferences-how-not-to-5522bb325935

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know and I myself did suffer some issues at this respect letting Kotlin's lambda to implement directly a OnSharedPreferencesChangeListener which was translated to an anonymous class instantiation, thus being garbage collected (I didn't know at the time how kotlin translated that piece of code, I learnt it by force).

I decided to go with the WeakReference approach just based on the same principle than the SharedPreferences, because well the system indeed treats SharedPreferences instances as singletons, then adding listeners and failing to unregister could cause potential leak.

For me is a design choice, if you prefer I can remove the WeakReferences, but here I like them indeed, for same reason than a user should know well to not register twice a listener, they should know well to keep a reference to the listener (I could add this fact to the javadoc). It's also true that armadillo isn't enforcing any singleton and will be the user of the library who decides how he/she manages that.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair argument, however I prefer the easier to understand, explicit way with a little more boilerplate, ie. please change it to register/unregister with a strong reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've just updated to avoid usage of WeakReferences on listeners

/**
* Allows client side {@link OnSecurePreferenceChangeListener} to check if the changed key is the key of interest.
*/
interface DerivedKeyComparison {
Copy link
Owner

@patrickfav patrickfav Jul 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of providing the caller with a generic doesMyKeyMatchWithTheDerivedKey() it may be easier to understand for the callback to provide a checker which has the derived key baked in. Therefore the onSecurePreferenceChanged() would not require to pass the String derivedKeyChanged argument and the caller would just check with e.g. comparison.matchesDerivedKey(String rawContentKey)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Here's the funny story...

I begun doing it that way indeed, but then I though that in case of positive match I could use the sharedPreferences and key parameters, to get the changed value.

I then realised that this didn't work because the sharedPreferences object was the original one, and requesting a preference with the key parameter would provide the encrypted value.

And then I probably left that as it was. I think that it might be nice to have both of the proposals. So let's see what do you think about having the method signature

void onSecurePreferenceChanged(@NonNull SharedPreferences sharedPreferences, @NonNull DerivedKeyComparison comparison)

Where the instance of sharedPreferences would be the SecureSharedPreferences instead of the underlying SharedPreferencesImpl so that client code could then get the changed value.

I didn't do that at the time, worried about security or something similar. If we're not interesting on allowing such behaviour, then I may suggest to change the signature of the method to just receive the DerivedKeyComparison parameter as right now is the only usable parameter

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the improved version. What we should change is that the SharedPreferences sharedPreferences should be the Armadillo instance not the inner wrapped version. A) a lib user has no business changing the raw encrypted data B) the raw encrypted shared pref is practically useless for the user, with the Armadillo wrapper she would be able to retrieve the enecrypted data with the content-keys she knows.

public interface OnSecurePreferenceChangeListener {

/**
* Variation of the regular {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged(SharedPreferences, String)} that
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a simple code example in javadoc on how to use it.

<pre> .... </pre>

@davidgarciaanton
Copy link
Contributor Author

I'll try to give it another round when't I find a moment to it. Thanks for taking your time to review!

@davidgarciaanton
Copy link
Contributor Author

Hi @patrickfav I've updated the PR with some of the corrections you requested, and added some Unit and Integration tests.

Let me know if I missed something.

Copy link
Owner

@patrickfav patrickfav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very sorry for leaving you hanging this long, had a lot on my mind lately - be sure that I fully appreciate your contribution and hope we can merge it soon.

Basically LGTM only change request worth mentioning is the weak reference, other minor stuff see comments

/**
* Allows client side {@link OnSecurePreferenceChangeListener} to check if the changed key is the key of interest.
*/
interface DerivedKeyComparison {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the improved version. What we should change is that the SharedPreferences sharedPreferences should be the Armadillo instance not the inner wrapped version. A) a lib user has no business changing the raw encrypted data B) the raw encrypted shared pref is practically useless for the user, with the Armadillo wrapper she would be able to retrieve the enecrypted data with the content-keys she knows.

if (sharedPreferences != null && key != null) {
wrapped.onSecurePreferenceChanged(keyComparison, sharedPreferences, key);
if (key != null) {
wrapped.onSecurePreferenceChanged(securedPrefs, new KeyComparisonImpl(encryptionProtocol, key));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above. I think it would be better to pass to:

wrapped.onSecurePreferenceChanged(this, new KeyComparisonImpl(encryptionProtocol, key));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I'm understanding correctly. I'm just passing the securedPrefs instance, with is actually initialised with the library SecureSharedPreferences instance, so that as you say in your comment the user doesn't have access to the raw encrypted data, but to the wrapper that will allow to get decrypted info.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I m not sure it is of much use to get the encrypted data, now would it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, but actually the user will be able to access the decrypted data through armadillo's SecureSharedPreferences instance.

What is exactly the change you're proposing here?

}

private final OnSecurePreferenceChangeListener.DerivedKeyComparison keyComparison;
private final WeakReference<OnSecurePreferenceChangeListener> wrappedRef;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair argument, however I prefer the easier to understand, explicit way with a little more boilerplate, ie. please change it to register/unregister with a strong reference.

@patrickfav patrickfav merged commit e580dfc into patrickfav:master Sep 8, 2019
@patrickfav
Copy link
Owner

LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants