Mark M. | has entered the room |
Mark M. | turned on guest access |
Tad | has entered the room |
Tad |
Hey Mark...
|
Mark M. |
hello, Tad!
|
Mark M. |
how can I help you today?
|
Tad |
Thanks for responding yesterday to my Stack Overflow post...
|
Tad |
A couple of follow-up questions I had
|
Tad |
I'm running down the notion you had about TOS violation - that was a good point.
|
Tad |
In terms of security though, can you elaborate a bit on what caused you to think there may be an issue?
|
Jan 22 | 9:00 AM |
Tad |
I'm not storing any credentials - I go through the flow I've seen that is supposed to be what I thought was best practice:
|
Mark M. |
unless you (and the users) are explicitly expecting other apps to be able to use your DocumentsProviders, I wouldn't go the route that you're going
|
Tad |
I spawn off an intent that causes chrome or webview to come up and require the user to log in to their DB account, then I get an access token back and I use that for all access.
|
Mark M. |
I don't know enough about the cloud service URLs to know whether leaking that information to other apps might represent a security issue or not
|
Tad |
I guess I don't see any harm in other apps using the DocumentsProvider I wrote - as long as I am not violating any TOS as you suggest, they will have to have also retrieved an access token.
|
Mark M. |
that's your call
|
Tad |
IOW, the provider won't work without a token.
|
Tad |
But I am curious why you think this is still a bad idea.
|
Tad |
Is it not due to a programmatic thing, but more of a "that's not how Android community expects it" kind of thing?
|
Mark M. |
IMHO, adding some device-wide stuff (DocumentsProvider) for what ostensibly is an in-app thing (browsing media) is not a good idea
|
Tad |
OK - I hear you. I'm curious WHY you think it is a bad idea...confusing for users? Not the right "culture"?
|
Mark M. |
it opens you up to risks
|
Tad |
Please elaborate
|
Tad |
Risks of....?
|
Mark M. |
other apps using the data that you are publishing
|
Mark M. |
unless that's part of your business model, of course
|
Mark M. |
but I get the sense that you're deeming that to be a side effect
|
Mark M. |
in which case, I'd go a different route
|
Jan 22 | 9:05 AM |
Mark M. |
you're not going to be focusing on the security or privacy aspects, because you're going to be focused on your own app and its use of the provider
|
Mark M. |
and that's risky
|
Mark M. |
case in point: the Android Scripting Environment
|
Tad |
The DP requires an access token to work. It also requires that this token be saved in Settings (which maybe you also think is a bad idea?). For every call that requires a token, it checks the settings (to see if it got updated) and retrieves the token. Thus, if a user of this DP doesn't know about that token or doesn't know the settings, etc. it simply won't work.
|
Mark M. |
waaaaaaaaaaay back in the early days of Android, ASE was created to allow users to write scripts in various languages (Perl, Python, PHP, etc.)
|
Mark M. |
but the ASE devs wanted those scripts to be able to perform some Android-y things, like get at location data
|
Mark M. |
so, they wrote an embedded Web service, exposing a bunch of Android APIs via Web service APIs, to localhost on a particular port
|
Mark M. |
and as part of script execution, they would inject a global variable (or the equivalent) that served as a language-specific client to that Web service
|
Mark M. |
as a result, scripts could call functions on some "Android" object and be able to access that API
|
Mark M. |
it was a slick solution to enabling basic Android access to languages that lacked any direct support for it
|
Mark M. |
it also was a privacy issue, because *any* app on the device could hit that Web service
|
Mark M. |
the ASE devs didn't think about that, because their focus was on their script engines accessing the Web service
|
Tad |
I see.
|
Mark M. |
when ASE was eventually rebooted as Scripting Layer for Android (SL4A), they revised the Web service to include a unique path segment
|
Mark M. |
which they passed into their script engines, so they could form valid paths, but other apps could not
|
Jan 22 | 9:10 AM |
Mark M. |
I am not saying that what you're doing is identical, but there are similarities: offering a system-wide API for what really should be an in-app thing
|
Mark M. |
this all comes back to one decision: "I decided to use the Android File Chooser"
|
Mark M. |
using the Storage Access Framework as a client is perfectly fine
|
Mark M. |
developing DocumentsProvider implementations is fine... if your objective is to offer a system-wide source of documents
|
Tad |
I get what you are saying.
|
Mark M. |
I just don't like seeing that sort of system-wide thing being done as a side-effect, for ASE-style "oops" scenarios
|
Mark M. |
there are a bazillion file picker libraries and a bazillion gallery libraries for Android
|
Mark M. |
even if none of them are sufficiently pluggable for other data sources, it suggests that writing such code is not that difficult
|
Tad |
The flip side to that is making the users (elderly people in my case) potentially go through multiple chooser experiences to be able to get the content they want...that's what I was trying to avoid.
|
Tad |
But I hear you on taking care on the unintended side-effects of opening up access to this content to other apps.
|
Mark M. |
you can have one chooser, but IMHO it needs to be your own, not a system one
|
Tad |
Right. I'm big on not re-inventing the wheel :)
|
Mark M. |
sure, but there are bazillion existing wheels
|
Mark M. |
personally, I think having a provide DocumentsProvider would be a nice addition to the SDK
|
Jan 22 | 9:15 AM |
Mark M. |
er, sorry, a private DocumentsProvider
|
Mark M. |
but, unless they snuck that in when I wasn't looking, there's no option for that
|
Mark M. |
you could try to play some games with enabling/disabling your DocumentsProvider on the fly
|
Mark M. |
I don't know how well that would work, but it might limit the side-effect visibility, if you really wanted to stick with your current approach
|
Mark M. |
but another benefit to having your own UI is that you get past the problem that triggered your SO question
|
Mark M. |
you won't be stuck with the whole DocumentsContract interface between your content consumers (Glide, ExoPlayer) and the content sources (DropBox, et. al.)
|
Tad |
OK - thanks for the insight. I'll muse on that a bit. I actually think from a security standpoint the app is technically pretty tight (I don't surface the access token anywhere, it is encrypted in the DB, I don't publish how it is used in the DP, etc.), so I don't think the token itself can get hi-jacked, but certainly retrieving the documents could be re-used by other apps, which is exactly your point.
|
Tad |
Yep - I agree with your latter points.
|
Tad |
I was thinking about that last night.
|
Mark M. |
to put it another way: your SO question looks like yak shaving
|
Tad |
"yak shaving"....(!) That's a new one. What does that mean?
|
Mark M. | |
Mark M. |
ah, here's the link I was looking for: https://seths.blog/2005/03/dont_shave_that/
|
Tad |
I just looked it up.
|
Tad |
Funny.
|
Jan 22 | 9:20 AM |
Tad |
Hey let me ask you something else - I did manage to find a couple of posts from you a few years back about using a ParcelFileDescriptor for streaming, but it still looked to me like it was being used in a larger context to stream down locally and hand back a handle to a file stored locally.
|
Tad |
Is it possible to use a PFD to a dynamic real-time stream to it's caller?
|
Mark M. |
if you don't need rewind, you can use a ParcelFileDescriptor pipe
|
Tad |
Or is a ParcelFileDescriptor ("File") always intended to ultimately represent a file stored locally?
|
Tad |
Is there a good example of doing that?
|
Mark M. |
I have some pipe examples in *The Busy Coder's Guide to Android App Development*
|
Tad |
ok I'll check those out.
|
Mark M. |
the problem is that you don't wind up with a seekable stream on the client side
|
Mark M. |
ParcelFileDescriptors backed by files can be rewound
|
Tad |
Right, I remember that discussion in the post I read.
|
Mark M. |
ParcelFileDescriptors backed by pipes cannot
|
Mark M. |
in your case, while Glide might not need to rewind, I bet ExoPlayer does
|
Tad |
The other thing that was confusing to me - is I don't understand how these objects get used by the recipients. So for example, if I were to implement a pipe and return that from my DocumentsProvider the way I return one now based on a physical file, could Glide and/or ExoPlayer just "use it"?
|
Tad |
Somewhere, it seems that it has to still resolve to a URI (since that's what those products expect).
|
Mark M. |
I haven't tried pipes with DocumentsProvider specifically, only ContentProvider (on which DocumentsProvider is based)
|
Tad |
But I don't see where/how this is happening (but it clearly works with a ParcelFileDesciptor.open(File) now, which is what I'm doing in my DropBoxProvider.
|
Tad |
At least - I have tested this with Glide, not yet ExoPlayer
|
Mark M. |
you'd just replace that with one end of the pipe, where the other end is used by a background thread of yours
|
Mark M. |
the client is oblivious to it... until they try to use Java stream APIs like mark() and reset() that won't work with the pipie
|
Mark M. |
er, pipe
|
Jan 22 | 9:25 AM |
Mark M. | |
Mark M. |
and specifically https://github.com/commonsguy/cw-omnibus/blob/v...
|
Mark M. |
so, in openFile(), instead of using ParcelFileDescriptor.open(), I use ParcelFileDescriptor.createPipe()
|
Mark M. |
that returns a two-element ParcelFileDescriptor array
|
Tad |
I understand conceptually what you are saying, but what I'm still missing is what is expected "underneath". I.e. for example the Glide API uses a .load() method that expects a URI. When that URI is content://, my provider gets called, and I return a ParcelFileDescriptor. When it is based on a physical file, this somehow under the covers gets converted back to the URI that Glide needs. This will "just work" if it is a pipe?
|
Mark M. |
the 0th element is what you return, representing the client's side of the pipe
|
Mark M. |
yes
|
Mark M. |
other than the seekable-stream issue
|
Mark M. |
Glide uses openInputStream() on a ContentResolver
|
Mark M. |
(at least, I assume that's what they're using)
|
Tad |
Any idea about ExoPlayer?
|
Mark M. |
presumably the same, though there are APIs on ContentResolver to get the ParcelFileDescriptor directly
|
Mark M. |
but whether that ParcelFileDescriptor represents a file, pipe, or something else is not something the client knows about
|
Mark M. |
and, if they all worked the same, I'd be much happier
|
Jan 22 | 9:35 AM |
Tad |
This conversation has me wondering about another security issue. What's the standard access control over persisted data in my app, i.e. I have a local DB that the app is using (just saved in the default location). My understanding is that other apps cannot see it, is that right?
|
Mark M. |
the default location for databases is on internal storage, which is only visible to your app, plus apps running with root privileges
|
Mark M. |
the latter scenario is uncommon, but it's why people root their devices
|
Tad |
So what is the best practice for protecting sensitive data -i.e. back to my access token. I took the approach of never saving user credentials, but I do save that access token associated with a specific user's Dropbox account (this is the practice the Dropbox dev site suggests).
|
Tad |
I actually am saving this token now in Shared Preferences, but it is encrypted. So it is useless as is in Shared Prefs.
|
Mark M. |
somewhere on internal storage (database, SharedPreferences, or other sort of file) should be OK
|
Tad |
What I store locally in the DB is the key to unencrypt.
|
Tad |
Is that overkill?
|
Tad |
Is it ok to simply store the token in Shared Prefs as-is?
|
Mark M. |
a hardcoded or persisted key is overkill
|
Mark M. |
that's not especially difficult for an attacker to get past
|
Tad |
ok
|
Jan 22 | 9:40 AM |
Mark M. |
if the encryption were based on a user-supplied passphrase, or used hardware key storage (e.g., KeyStore), then there may be incremental value
|
Mark M. |
still probably overkill, though, just for an API key
|
Tad |
yeah, ok.
|
Tad |
Do you have a favorite open source File Chooser that I could look into if I decide to abandon the SAF approach?
|
Mark M. |
no, because I haven't yet needed one
|
Mark M. | |
Mark M. |
that's the list I point people to, though
|
Tad |
ok I'll check it out.
|
Tad |
Btw - are you aware of good forums (in addition to SO) for ExoPlayer discussion?
|
Jan 22 | 9:45 AM |
Mark M. |
no, sorry, I haven't had to use ExoPlayer either, so I haven't needed to hunt around for help
|
Tad |
OK - well, thanks for your time this morning (7am my time - I'm three cups of coffee into the day :) )!
|
Mark M. |
you're welcome, and enjoy the caffeine! :-)
|
Jan 22 | 10:00 AM |
Mark M. |
OK, that's a wrap for today's chat
|
Mark M. |
the transcript will be up on https://commonsware.com/office-hours/ shortly
|
Mark M. |
the next chat is tomorrow at 4pm US Eastern / 1pm US Pacific
|
Mark M. |
have a pleasant day!
|
Tad | has left the room |
Mark M. | turned off guest access |