Skip to content

Commit 4f1e711

Browse files
committed
Share OAuth now provides a return web script to receive verification codes from an OAuth service, responsible for exchanging the request token and verifier for a permanent access token and then persisting this before redirecting the user back to the original application.
git-svn-id: https://share-extras.googlecode.com/svn/trunk/Share OAuth@1000 a3f5c567-fd0f-3a89-9b71-a290c5a5f590
1 parent 08aaf25 commit 4f1e711

File tree

5 files changed

+437
-3
lines changed

5 files changed

+437
-3
lines changed

build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
jar.name=share-oauth-2.1.1.jar
1+
jar.name=share-oauth-2.2-dev.jar
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<webscript>
2+
<shortname>Twitter OAuth return</shortname>
3+
<description>Receive a verification code back from the Twitter OAuth service, swap it for an access token and store this</description>
4+
<url>/extras/oauth/auth-return</url>
5+
</webscript>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
3+
4+
<beans>
5+
6+
<bean id="webscript.org.sharextras.slingshot.oauth-return.get" class="org.sharextras.webscripts.OAuthReturn" parent="webscript">
7+
<property name="scriptRemote">
8+
<ref bean="webscripts.script.remote" />
9+
</property>
10+
<property name="connectorService">
11+
<ref bean="connector.service" />
12+
</property>
13+
</bean>
14+
15+
</beans>
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
package org.sharextras.webscripts;
2+
3+
import java.io.IOException;
4+
import java.nio.charset.Charset;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
import org.apache.commons.httpclient.HttpClient;
9+
import org.apache.commons.httpclient.HttpException;
10+
import org.apache.commons.httpclient.HttpMethod;
11+
import org.apache.commons.httpclient.methods.PostMethod;
12+
import org.json.JSONException;
13+
import org.json.JSONObject;
14+
import org.json.JSONStringer;
15+
import org.json.JSONWriter;
16+
import org.sharextras.webscripts.connector.HttpOAuthConnector;
17+
import org.springframework.extensions.webscripts.AbstractWebScript;
18+
import org.springframework.extensions.webscripts.Format;
19+
import org.springframework.extensions.webscripts.ScriptRemote;
20+
import org.springframework.extensions.webscripts.ScriptRemoteConnector;
21+
import org.springframework.extensions.webscripts.Status;
22+
import org.springframework.extensions.webscripts.WebScriptException;
23+
import org.springframework.extensions.webscripts.WebScriptRequest;
24+
import org.springframework.extensions.webscripts.WebScriptResponse;
25+
import org.springframework.extensions.webscripts.connector.ConnectorService;
26+
import org.springframework.extensions.webscripts.connector.Response;
27+
28+
/**
29+
* Landing page web script for returning from a 3rd party OAuth 1.0a authorization page.
30+
*
31+
* <p>The script receives a verifier code from the 3rd party and is responsible for
32+
* exchanging this (plus the temporary request token) for a permanent access token, and
33+
* then persisting this into the repository and redirecting the user to their original
34+
* page.</p>
35+
*
36+
* @author Will Abson
37+
*/
38+
public class OAuthReturn extends AbstractWebScript
39+
{
40+
public static final String USER_TOKEN_URL = "/extras/slingshot/tokenstore/usertoken";
41+
public static final String PREFS_BASE = "org.alfresco.share.oauth.";
42+
public static final String PREF_DATA = "data";
43+
44+
/* URL fragments */
45+
public static final String URL_PROXY_SERVLET = "/proxy";
46+
public static final String URL_OAUTH_ACCESSTOKEN_DEFAULT = "/oauth/access_token";
47+
48+
/* URL Parameter names */
49+
public static final String PARAM_OAUTH_VERIFIER = "oauth_verifier";
50+
public static final String PARAM_CONNECTOR_ID = "cid";
51+
public static final String PARAM_ENDPOINT_ID = "eid";
52+
public static final String PARAM_PROVIDER_ID = "pid";
53+
public static final String PARAM_REDIRECT_PAGE = "rp";
54+
55+
/* Connector property names */
56+
public static final String PROP_ACCESS_TOKEN_PATH = "access-token-path";
57+
58+
ScriptRemote scriptRemote;
59+
ConnectorService connectorService;
60+
String accessTokenUrl;
61+
62+
public OAuthReturn()
63+
{
64+
}
65+
66+
@Override
67+
public void execute(WebScriptRequest req, WebScriptResponse resp) throws IOException
68+
{
69+
String verifier = req.getParameter(PARAM_OAUTH_VERIFIER),
70+
connectorId = req.getParameter(PARAM_CONNECTOR_ID),
71+
endpointName = req.getParameter(PARAM_ENDPOINT_ID),
72+
providerName = req.getParameter(PARAM_PROVIDER_ID);
73+
74+
if (verifier == null || verifier.length() == 0)
75+
{
76+
throw new WebScriptException("No OAuth verifier was found");
77+
}
78+
if (endpointName == null || endpointName.length() == 0)
79+
{
80+
throw new WebScriptException("No connector name was specified");
81+
}
82+
if (providerName == null || providerName.length() == 0)
83+
{
84+
throw new WebScriptException("No provider name was specified");
85+
}
86+
87+
String pn = PREFS_BASE + providerName;
88+
89+
Map<String, Object> scriptParams = this.getContainer().getScriptParameters();
90+
scriptRemote = (ScriptRemote) scriptParams.get("remote");
91+
ScriptRemoteConnector alfrescoConnector = scriptRemote.connect(), oauthConnector = null;
92+
if (connectorId != null && connectorId.length() > 0)
93+
{
94+
oauthConnector = scriptRemote.connect(connectorId);
95+
}
96+
97+
String authToken = "", authTokenSecret = "";
98+
99+
// Load the current auth data
100+
Response authDataResp = alfrescoConnector.get(USER_TOKEN_URL + "?filter=" + pn + "." + PREF_DATA);
101+
if (authDataResp.getStatus().getCode() == Status.STATUS_OK)
102+
{
103+
String authData = authDataResp.getResponse();
104+
JSONObject authObj = null;
105+
Map<String, String> authParams = null;
106+
try
107+
{
108+
if (authData.length() > 0)
109+
{
110+
authObj = new JSONObject(authData);
111+
for (String k : pn.split("\\."))
112+
{
113+
if (authObj != null)
114+
{
115+
try
116+
{
117+
authObj = authObj.getJSONObject(k);
118+
}
119+
catch (JSONException e)
120+
{
121+
authObj = null;
122+
}
123+
}
124+
}
125+
if (authObj != null && authObj.length() > 0)
126+
{
127+
String data = authObj.optString("data", "");
128+
if (data.length() > 0)
129+
{
130+
Map<String, String> dataMap = this.unpackData(data);
131+
// Unpack the existing parameters
132+
authToken = dataMap.get(HttpOAuthConnector.OAUTH_TOKEN);
133+
authTokenSecret = dataMap.get(HttpOAuthConnector.OAUTH_TOKEN_SECRET);
134+
}
135+
else
136+
{
137+
throw new WebScriptException(Status.STATUS_NOT_FOUND, "No OAuth data could be found for provider " + providerName);
138+
}
139+
}
140+
else
141+
{
142+
throw new WebScriptException(Status.STATUS_NOT_FOUND, "No OAuth data could be found for provider " + providerName);
143+
}
144+
145+
if (authToken.length() == 0)
146+
{
147+
throw new WebScriptException(Status.STATUS_NOT_FOUND, "Request token could not be found");
148+
}
149+
if (authTokenSecret.length() == 0)
150+
{
151+
throw new WebScriptException(Status.STATUS_NOT_FOUND, "Request token secret could not be found");
152+
}
153+
154+
authParams = requestAccessToken(endpointName, authToken, authTokenSecret, verifier, req, oauthConnector);
155+
}
156+
else
157+
{
158+
throw new WebScriptException("Empty response received from OAuth data JSON");
159+
}
160+
}
161+
catch (JSONException e)
162+
{
163+
throw new WebScriptException("Could not decode OAuth data JSON response", e);
164+
}
165+
/*
166+
catch (ConnectorServiceException e)
167+
{
168+
throw new WebScriptException("Could not locate OAuth connector", e);
169+
}*/
170+
171+
if (authParams.size() == 0)
172+
{
173+
throw new WebScriptException("No data was returned when requesting the access token");
174+
}
175+
if (authParams.get(HttpOAuthConnector.OAUTH_TOKEN) == null)
176+
{
177+
throw new WebScriptException("No token was returned when requesting the access token");
178+
}
179+
if (authParams.get(HttpOAuthConnector.OAUTH_TOKEN_SECRET) == null)
180+
{
181+
throw new WebScriptException("No token secret was returned when requesting the access token");
182+
}
183+
184+
// Persist the data
185+
Response writeAccessTokenResponse = this.storeAccessTokenData(authParams, pn);
186+
if (writeAccessTokenResponse.getStatus().getCode() == Status.STATUS_OK)
187+
{
188+
String redirectPage = req.getParameter(PARAM_REDIRECT_PAGE).indexOf('/') == 0 ? req.getParameter(PARAM_REDIRECT_PAGE) : "/" + req.getParameter(PARAM_REDIRECT_PAGE),
189+
redirectLocation = req.getServerPath() + req.getContextPath() + (redirectPage != null ? redirectPage : "");
190+
resp.addHeader(WebScriptResponse.HEADER_LOCATION, redirectLocation);
191+
resp.setStatus(Status.STATUS_MOVED_TEMPORARILY);
192+
}
193+
else
194+
{
195+
throw new WebScriptException("A problem occurred while persisting the OAuth token data");
196+
}
197+
198+
}
199+
else
200+
{
201+
// TODO if resp is 401 then redirect to original page
202+
throw new WebScriptException(authDataResp.getStatus().getCode(), "A problem occurred while loading the OAuth token data (code " + authDataResp.getStatus().getCode() + ")");
203+
}
204+
}
205+
206+
/**
207+
* Unpack OAuth data received in the body of a response
208+
*
209+
* @param body
210+
* @return
211+
*/
212+
private Map<String, String> unpackData(String body)
213+
{
214+
String[] pairs = body.split("&");
215+
Map<String, String> m = new HashMap<String, String>(pairs.length);
216+
String[] pair;
217+
for (int i = 0; i < pairs.length; i++)
218+
{
219+
pair = pairs[i].split("=");
220+
if (pair.length == 2)
221+
{
222+
m.put(pair[0], pair[1]);
223+
}
224+
}
225+
return m;
226+
}
227+
228+
private Map<String, String> requestAccessToken(
229+
String endpointName, String authToken,
230+
String authTokenSecret, String verifier,
231+
WebScriptRequest req,
232+
ScriptRemoteConnector oauthConnector) throws HttpException, IOException
233+
{
234+
Map<String, String> authParams;
235+
// Add the verifier
236+
//Connector oauthConnector = connectorService.getConnector(connectorName);
237+
HttpClient client = new HttpClient();
238+
239+
// TODO Parameterise the access token path
240+
String postUri = req.getServerPath() + req.getContextPath() + URL_PROXY_SERVLET + "/" + endpointName + getAccessTokenUrl(oauthConnector);
241+
HttpMethod method = new PostMethod(postUri);
242+
method.addRequestHeader(HttpOAuthConnector.HEADER_OAUTH_DATA, HttpOAuthConnector.OAUTH_TOKEN + "=\"" + authToken + "\"," +
243+
HttpOAuthConnector.OAUTH_TOKEN_SECRET + "=\"" + authTokenSecret + "\"," + PARAM_OAUTH_VERIFIER + "=\"" + verifier + "\"");
244+
int statusCode = client.executeMethod(method);
245+
if (statusCode == Status.STATUS_OK)
246+
{
247+
// do something with the input stream, which contains the new parameters in the body
248+
byte[] responseBody = method.getResponseBody();
249+
String tokenResp = new String(responseBody, Charset.forName("UTF-8"));
250+
authParams = this.unpackData(tokenResp);
251+
return authParams;
252+
}
253+
else
254+
{
255+
throw new WebScriptException(statusCode, "A problem occurred while requesting the access token");
256+
}
257+
}
258+
259+
/**
260+
* Store access token data back into the repository
261+
*
262+
* @param authParams
263+
* @param base
264+
* @return
265+
*/
266+
private Response storeAccessTokenData(Map<String, String> authParams, String base)
267+
{
268+
ScriptRemoteConnector connector = scriptRemote.connect();
269+
String[] baseParts = base.split("\\.");
270+
try
271+
{
272+
StringBuffer newdata = new StringBuffer();
273+
JSONWriter currJSON = new JSONStringer().object();
274+
for (String k : baseParts)
275+
{
276+
currJSON.key(k).object();
277+
}
278+
// add each auth parameter to currJSON
279+
for (Map.Entry<String, String> p : authParams.entrySet())
280+
{
281+
newdata.append(newdata.length() > 0 ? "&" : "");
282+
newdata.append(p.getKey() + "=" + p.getValue());
283+
}
284+
currJSON.key(PREF_DATA).value(newdata.toString());
285+
for (int i = 0; i < baseParts.length; i++)
286+
{
287+
currJSON.endObject();
288+
}
289+
currJSON.endObject();
290+
String postBody = currJSON.toString();
291+
292+
return connector.post(USER_TOKEN_URL, postBody, Format.JSON.mimetype());
293+
}
294+
catch (JSONException e)
295+
{
296+
throw new WebScriptException("Could not encode OAuth data in JSON format", e);
297+
}
298+
}
299+
300+
public ScriptRemote getScriptRemote()
301+
{
302+
return scriptRemote;
303+
}
304+
305+
public void setScriptRemote(ScriptRemote scriptRemote)
306+
{
307+
this.scriptRemote = scriptRemote;
308+
}
309+
310+
public ConnectorService getConnectorService()
311+
{
312+
return connectorService;
313+
}
314+
315+
public void setConnectorService(ConnectorService connectorService)
316+
{
317+
this.connectorService = connectorService;
318+
}
319+
320+
public String getAccessTokenUrl()
321+
{
322+
return accessTokenUrl != null ? accessTokenUrl : URL_OAUTH_ACCESSTOKEN_DEFAULT;
323+
}
324+
325+
public String getAccessTokenUrl(ScriptRemoteConnector c)
326+
{
327+
if (c != null)
328+
{
329+
String tokenPath = c.getDescriptor().getStringProperty(PROP_ACCESS_TOKEN_PATH);
330+
return tokenPath != null ? tokenPath : getAccessTokenUrl();
331+
}
332+
else
333+
{
334+
return getAccessTokenUrl();
335+
}
336+
}
337+
338+
public void setAccessTokenUrl(String accessTokenUrl)
339+
{
340+
this.accessTokenUrl = accessTokenUrl;
341+
}
342+
343+
}

0 commit comments

Comments
 (0)