Introduction
A few of my applications generate files locally and although methods to transfer those files like Bluetooth/Wifi transfer , email and similar are included users are heavily using the Cloud so the option to write the files to the Cloud was due pronto. We can accomplish this using the RESTful API/services from the Cloud provider.
RESTFul services are becoming a predominant Web service nowadays due to its simpler style specially its stateless mode. Clients on different platforms can access resources via this architecture with very low complexity. Clients need to authenticate to the server and the OAuth is the method of choice since it is an open and straightforward standard with a decent level of security. In this article we will walk through an Android implementation
of uploading a file to dropbox using their RESTFul API which utilizes OAuth authentication.
Background
Most of the big players like Google, Facebook, Tweeter, etc. offer RESTful services with OAuth authentication.
The OAuth carries out the authentication with these few steps:
- Ask the server for permission to request the user to access his/her account. To carry out this the client (e.g. our code) needs a pair of key/secret_word credentials
which is available from the provider upon request.
- Upon success we then ask the user for permission this usually requires for him/her
to log in to the Dropbox account.
- Upon success a new set of key/secret_word credentials is provided and with this set handy all of the available APIs/methods can be accessed using
this pair to sign each request, in our case we can upload files, read user info , etc.
These RESTful APIs are nothing but URLs which are sent via a GET or POST http request. The OAUth authentication requires a call to these URLs with
a set of predefined args that include timestamps the key/secret_word set and the encryption type(SHA1, etc), this time stamp prevents from somebody else grabbing
the URL and trying to call it again since the time stamp will be different at a later time providing a good layer of security.
The syntax is straightforward:
<URL from the RESTful API>?<OAuth args ...>&<Dropbox args...>
For instance to start the Dropbox OAuth we need to call the URL below without any DropBpx args just the regular OAuth
https://api.dropbox.com/1/oauth/request_token?<OAuth args ..>
oauth_consumer_key=<key> provided by Dropbox
oauth_token=<secret_word> provided by Dropbox
oauth_nonce=<a number used once> a random string that is meant to uniquely identify each signed request.
oauth_timestamp=<when the request is sent>
oauth_signature_method=HMAC-SHA1 (it could be any other standard encryption used to generate the nonce)
oauth_version
The combination of nonce and timestamp takes the load off the server and allows the Service Provider to only keep nonce values for a limited time. The particular signature specification can be acquired and implemented internally in our code but there are quite a few pre-cooked libraries already so no need to reinvent the wheel here we will be using the Signpost library to help us with the OAuth authentication steps.
Finally RESTful providers like Dropbox also offer SDKs specific to the platforms (e.g. items for Android, iPhone, etc) but these are nothing but wrappers
of the basic RESTful APIs implemented for the specific platform.
1. Implementing the code
The audience here is supposed to be well versed in Java/Android already
so we will skip the basics and get to the meat of the topic. Our basic screen looks like this.
1.1 Prerequisites: Manifest and Jar libraries
The Manifest needs to include a section that allows our App access to the net. Also our application needs an intent-filter so that our browser fires up properly and a definition of our dummy schema/URL used when we come back to our application from the browser.
<activity android:name=".main"
....
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myrest" android:host="myStep.com" />
</intent-filter>
</activity>
...
<uses-permission android:name="android.permission.INTERNET">
</uses-permission>
We need to download the Signpost jar library and add it to our project ,we just drop it to the libs folder and add it
Also we need to keep a reference to the Dropbox RESTful APIs.
1.2 Getting the credentials to sign an API request
Call the server properly to start up the process, the calls to the SingPost methods are highlighted
private static CommonsHttpOAuthConsumer consumer;
private static CommonsHttpOAuthProvider provider ;
...
String DropB_key="<request this From DropBox>";
String DropB_secret="<request this From DropBox>";
String request_url = "https://api.dropbox.com/1/oauth/request_token";
String authorization_url = "https://www.dropbox.com/1/oauth/authorize";
String access_url ="https://api.dropbox.com/1/oauth/access_token";
String callBack_url ="myRest://myStep.com";
...
consumer = new CommonsHttpOAuthConsumer(
DropB_key, DropB_secret);
provider = new CommonsHttpOAuthProvider(
request_url, access_url, authorization_url);
try
{
String authURL = provider.retrieveRequestToken(
consumer, callBack_url);
Intent intent2 = new Intent(Intent.ACTION_VIEW);
intent2.setData( Uri.parse(authURL) );
startActivity(intent2);
}catch (Exception e)
{}
As you can see the highlighted Signpost library methods make it very straightforward to start out the authentication process taking care of all
of the gory details. When we call the user for authorization we will fire a browser to get back to our App the callBack_url is set to a dummy schema and URL that we can
catch on the onResume event and wrap up the process.
.. @Override
public void onResume()
{
super.onResume();
Uri uri = this.getIntent().getData();
...
if( uri != null)
{
...
if( uri.getHost().equals("myStep.com"))
{
String parms = uri.getEncodedQuery();
try {
String verifier = uri.getQueryParameter(OAuth.OAUTH_VERIFIER);
provider.retrieveAccessToken(consumer, verifier);
String ACCESS_KEY = consumer.getToken();
String ACCESS_SECRET = consumer.getTokenSecret();
Log.d("OAuth Dropbox", ACCESS_KEY);
Log.d("OAuth Dropbox", ACCESS_SECRET);
...
We have got the credentials to sign requests to the
available RESTful APIs, we are good to go now.
1.3 Request user info and upload a file to Dropbox
With the credentials acquired the rest is straightforward,
we get the proper API (a URL from Dropbox) and carry out either a GET or a POST as specified by the API, Dropbox returns either a few values formatted as URL arguments, or a JSON set.
Each request must be signed with those credentials
We ask Dropbox for current user info below:
...
consumer.setTokenWithSecret(ACCESS_KEY, ACCESS_SECRET);
String uRL_file_list_req="https://api.dropbox.com/1/account/info";
HttpClient httpclient = new DefaultHttpClient();
HttpGet request = new HttpGet(uRL_file_list_req );
consumer.sign(request);
ResponseHandler<String> handler = new BasicResponseHandler();
try {
result = httpclient.execute(request, handler);
} catch (ClientProtocolException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "Protocol failure uploading the file",
Toast.LENGTH_SHORT).show();
return;
} catch (IOException e) {
e.printStackTrace();
Log.e("******",Log.getStackTraceString(e));
Toast.makeText(getApplicationContext(), "IO failure uploading the file",
Toast.LENGTH_SHORT).show();
return;
}
JSONObject json_data = new JSONObject(result);
JSONArray nameArray = json_data.names();
JSONArray valArray = json_data.toJSONArray(nameArray);
...
We upload a text file with contents of the simple Android UI above, changing the proper MIME type and encoding the contents properly
is all it is needed to upload any other file type like an image.
String textStr = myContents.getText() + "";
String fileName = fName.getText() + "";
uRL_file_list_req="https://api-content.dropbox.com/1/files_put/" +
"sandbox/" + fileName ;
consumer.setTokenWithSecret(ACCESS_KEY, ACCESS_SECRET);
HttpPost request3 = new HttpPost(uRL_file_list_req );
request3.addHeader("Content-Type", "text/plain");
request3.setEntity(new StringEntity(textStr));
...
consumer.sign(request3);
result="";
try {
result = httpclient.execute(request3, handler);
} catch (ClientProtocolException e) {
e.printStackTrace();
Log.e("$$$$$$$",Log.getStackTraceString(e));
Toast.makeText(getApplicationContext(),
"Protocol failure uploading the file",
Toast.LENGTH_SHORT).show();
return;
} catch (IOException e)
{
e.printStackTrace();
Log.e("******",Log.getStackTraceString(e));
Toast.makeText(getApplicationContext(),
"IO failure uploading the file",
Toast.LENGTH_SHORT).show();
return;
}
json_data = new JSONObject(result);
nameArray = json_data.names();
valArray = json_data.toJSONArray(nameArray);
Toast.makeText(getApplicationContext(), "Succes!! - " +
valArray.getString(7) + " " + valArray.getString(10),
Toast.LENGTH_SHORT).show();
httpclient.getConnectionManager().shutdown();
...
2. Caveats
The only issue here is that the Dropbox documentation reference is a bit blurry, when you need to carry file transfer
operations the URL is of the form
https://api-content.dropbox.com/1/files/<root>/<path>
<root> is a string with a value of either dropbox or sandbox , when you request the initial credential pair from Dropbox it defaults
you in Folder mode with root as sandbox. Later on you can request to change the status to production so users can use your App.
It will create a folder under Apps with the name of your application.
<path> is the path to the actual file and it is relative to the App folder, e.g., to stat file
test.txt sitting in the Apps/myCoolApp/ folder the above should be
https://api-content.dropbox.com/1/files/sandbox/text.txt
3. Final Thoughts
Very straightforward as you can see just a matter of getting through the specification of the Dropbox API. Having the credential keys handy and stored
as a local preference in your app can allow further queries or file operations to Dropbox from your application without any other user interaction with Dropbox.
The included sample code has the little extra details to get this working on Android since we are calling the RESTful API and not the SDK it is easy to port over
to other platforms. Obviously the sample code is just a bare sample to get things going.