-
Notifications
You must be signed in to change notification settings - Fork 52
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
Feat/enhanced change listener #47
Conversation
Thanks for the PR. I will review it in the next days! |
There was a problem hiding this 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.
armadillo/src/main/java/at/favre/lib/armadillo/OnSecurePreferenceChangeListener.java
Outdated
Show resolved
Hide resolved
armadillo/src/main/java/at/favre/lib/armadillo/SharedPreferenceChangeListenerWrapper.java
Outdated
Show resolved
Hide resolved
armadillo/src/main/java/at/favre/lib/armadillo/OnSecurePreferenceChangeListener.java
Outdated
Show resolved
Hide resolved
armadillo/src/main/java/at/favre/lib/armadillo/SharedPreferenceChangeListenerWrapper.java
Outdated
Show resolved
Hide resolved
armadillo/src/main/java/at/favre/lib/armadillo/SecureSharedPreferences.java
Show resolved
Hide resolved
} | ||
|
||
private final OnSecurePreferenceChangeListener.DerivedKeyComparison keyComparison; | ||
private final WeakReference<OnSecurePreferenceChangeListener> wrappedRef; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
armadillo/src/main/java/at/favre/lib/armadillo/OnSecurePreferenceChangeListener.java
Outdated
Show resolved
Hide resolved
armadillo/src/main/java/at/favre/lib/armadillo/OnSecurePreferenceChangeListener.java
Outdated
Show resolved
Hide resolved
/** | ||
* Allows client side {@link OnSecurePreferenceChangeListener} to check if the changed key is the key of interest. | ||
*/ | ||
interface DerivedKeyComparison { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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>
I'll try to give it another round when't I find a moment to it. Thanks for taking your time to review! |
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. |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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));
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
armadillo/src/main/java/at/favre/lib/armadillo/SecureSharedPreferences.java
Show resolved
Hide resolved
} | ||
|
||
private final OnSecurePreferenceChangeListener.DerivedKeyComparison keyComparison; | ||
private final WeakReference<OnSecurePreferenceChangeListener> wrappedRef; |
There was a problem hiding this comment.
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.
LGTM. |
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