...
The first step is to register our application with Dropbox and get our App key and App secret. Go to the Dropbox Developer Home and click on the App Console menu item on the left. Click the "Create an app" button and fill in the fields. We'll be creating a "Core" application. The Core API is what allows programatic access to the linked Dropbox account. You'll be asked whether you want full access to the user's Dropbox or just a single directory in the /Apps
directory. For purposes of this recipe, I chose the a single directory. At this point, you should have an App key and App secret from Dropbox. You'll put these in the Keys section of the meta directory of your ruleset:
...
The tokens are in the event attribute content and are URL encoded. You can see how decode_content()
works in another recipe. The callback is the event we want Dropbox to raise when it responds with the access token. The event is an oauth:response
event. We take pains using meta:host()
and meta:rid()
to ensure that this code is portable between KRE instances and rulesets. The form of the URL that calls Dropbox is described in their documentation. The rule postlude stores the request token and request token secret. Note that Dropbox returns them labeled as oauth_token
and oauth_token_secret
.
Getting the Access Token
Because we set the callback URL as an ESL that raises a oauth:response
event to the current ruleset, we need a rule that is selected on that event. The rule can use the access token endpoint to retrieve the token and its associated secret.
Code Block | ||||
---|---|---|---|---|
| ||||
select when oauth response
if(not authorized) then {
http:post(dropbox_base_url+"/oauth/access_token") with
body = {} and
headers = {"Authorization" : create_oauth_header_value(keys:dropbox('app_key'),
keys:dropbox('app_secret'),
ent:request_token,
ent:request_token_secret)
} and
autoraise = "access_token"
}
} |
Like the rule that gets the request token above, this rule POSTs to the Dropbox access endpoint and automatically raises an event to process the response. The primary difference is that we create the Authorization
header value with the request token and secret that we stored in the last section to generate a request with a correctly formatted Authorization
header. We give the automatically raised even the label access_token
so that we can select it in the processing rule.
Processing the access token response is similar to processing the request token response in that we want to decode the response and store the access token and secret in entity variables. We have to redirect the user's browser to the right URL. The right URL depends on what you're trying to do. If you were using KRL to augment another page, it would be that page's URL and you could simply use the Web action redirect()
. In this case, we're working with a CloudOS application running at SquareTag.com
, so we need to redirect to the the app created by the current ruleset. We use the recipe for redirecting without runtime support to accomplish that.
Code Block | ||||
---|---|---|---|---|
| ||||
rule process_access_token {
select when http post label "access_token"
pre {
tokens = decode_content(event:attr('content'));
url = "https://squaretag.com/app.html#!/app/#{meta:rid()}/show";
js = <<
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<META HTTP-EQUIV="Refresh" CONTENT="0;#{url}">
<meta name="robots" content="noindex"/>
<link rel="canonical" href="#{url}"/>
</head>
<body>
<p>
You are being redirected to <a href="#{url}">#{url}</a>
</p>
<script type="text/javascript">
window.location = #{url};
</script>
</body>
</html>
>>;
}
send_raw("text/html")
with content= js
always {
set ent:dropbox_uid tokens{'uid'};
set ent:access_token_secret tokens{'oauth_token_secret'};
set ent:access_token tokens{'oauth_token'};
}
} |
This rule looks long, but most of it is the redirect HTML stored in the variable named js
. The send_raw()
action and the rule postlude do the real work.
Are We Authorized?
If you've been paying attention, you'll notice that most of the preceding rules are conditional on whether or not the user has already authorized the app to use Dropbox. Obviously, we only want the user to see the authorization flow if the app isn't already authorized. We need to set a variable named authorized
in the global block of the ruleset that the rules can test. How this test works depends on the API you're working against. Dropbox provides an account information endpoint that we can call to check if we're authorized:
Code Block | ||||
---|---|---|---|---|
| ||||
GET https://api.dropbox.com/1/account/info |
We define a function that does a GET on the Dropbox API to help us accomplish this and other API calls:
Code Block | ||||
---|---|---|---|---|
| ||||
dropbox_core_api_call = function(method) {
http:get(dropbox_base_url+method,
{},
{"Authorization" : create_oauth_header_value(keys:dropbox('app_key'),
keys:dropbox('app_secret'),
ent:access_token,
ent:access_token_secret)
});
} |
This function does an HTTP GET against the Dropbox API for any endpoint that accepts GET requests using an Authorization
header built from the access token and secret.
We can use this function to define authorized as follows:
Code Block | ||||
---|---|---|---|---|
| ||||
account_info_result = dropbox_core_api_call('/account/info');
authorized = account_info_result{'status_code'} eq '200'; |
We make a call for account info and if we get a 200 status code, we assume that we're authorized since Dropbox uses other 4xx and 5xx status codes to indicate authorization and other problems.
Using the API
Of course, other rules would use the API to provide the needed user experience. This simple rule uses the account information and the metadata endpoints from the Dropbox API along with a CloudOS UI function to paint a simple information screen for the user that shows any files stored for this application at Dropbox:
Code Block | ||||
---|---|---|---|---|
| ||||
rule show_account_info {
select when web cloudAppSelected
pre {
account_info = account_info_result{'content'}.decode();
name = account_info{'display_name'};
uid = account_info{'uid'};
metadata_result = dropbox_core_api_call('/metadata/sandbox/?list=true');
metadata = metadata_result{'content'}.decode();
files = metadata{'contents'}.map(function(x){x{'path'}}).join('<br/>');
my_html = <<
<div style="margin: 0px 0px 20px 20px">
<p>Your Dropbox name is #{name} and your UID is #{uid}.</p>
<p>Files:<br/>#{files}</p>
</div>
>>;
}
if(authorized) then {
CloudRain:createLoadPanel("Dropbox Account Info", {}, my_html);
}
} |
The result from the HTTP GET always contains the entire HTTP result. The content of the response is stored as a JSON string in the content field of the result object, so we select that from any response and decode() it to get a KRL data structure.
Conclusion
This recipe has shown how to use the Dropbox API and OAuth 1.0a from within KRL. The OAuth interactions have been handled completely within KRL. This recipe could be easily adapted to any OAuth 1.0a protected resource as long as it supports PLAINTEXT signatures. OAuth 2 is considerably simpler and should be easily doable using the techniques outlined above.