Migrate to OAuth2

- implement OAuth2 for both Spreadsheets & Fusion Tables
- switch cookie name from authsub_token to oauth2_token so there's no
confusion
- implementation is otherwise unchanged - in particular still uses old
Fusion Tables SQL API
This commit is contained in:
Tom Morris 2013-02-10 14:17:28 -05:00
parent d7081415d4
commit bf2f775c0b
6 changed files with 102 additions and 62 deletions

View File

@ -31,6 +31,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
/*
* Controller for GData extension.
*
* This is run in the Butterfly (ie Refine) server context using the Rhino
* Javascript interpreter.
*/
var html = "text/html"; var html = "text/html";
var encoding = "UTF-8"; var encoding = "UTF-8";
var version = "0.2"; var version = "0.2";
@ -101,21 +108,13 @@ function process(path, request, response) {
} else if (path == "authorized") { } else if (path == "authorized") {
var context = {}; var context = {};
context.winname = request.getParameter("winname"); context.winname = request.getParameter("winname");
context.callback = request.getParameter("callback"); context.callback = request.getParameter("cb");
(function() { (function() {
var queryString = request.getQueryString(); var token = Packages.com.google.refine.extension.gdata.GDataExtension.getTokenFromCode(module,request);
if (queryString != null) { if (token) {
var AuthSubUtil = Packages.com.google.gdata.client.http.AuthSubUtil; Packages.com.google.refine.extension.gdata.TokenCookie.setToken(request, response, token);
return;
// FIXME(SM): can we safely assume UTF-8 encoding here?
var onetimeUseToken = AuthSubUtil.getTokenFromReply(
Packages.java.net.URLDecoder.decode(queryString, "UTF-8"));
if (onetimeUseToken) {
var sessionToken = AuthSubUtil.exchangeForSessionToken(onetimeUseToken, null);
Packages.com.google.refine.extension.gdata.TokenCookie.setToken(request, response, sessionToken);
return;
}
} }
Packages.com.google.refine.extension.gdata.TokenCookie.deleteToken(request, response); Packages.com.google.refine.extension.gdata.TokenCookie.deleteToken(request, response);
})(); })();

View File

@ -34,7 +34,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
var GdataExtension = {}; var GdataExtension = {};
GdataExtension.isAuthorized = function() { GdataExtension.isAuthorized = function() {
return $.cookie('authsub_token') !== null; return $.cookie('oauth2_token') !== null;
}; };
GdataExtension.showAuthorizationDialog = function(onAuthorized, onNotAuthorized) { GdataExtension.showAuthorizationDialog = function(onAuthorized, onNotAuthorized) {
@ -57,6 +57,6 @@ GdataExtension.showAuthorizationDialog = function(onAuthorized, onNotAuthorized)
}; };
window[callbackName] = callback; window[callbackName] = callback;
var url = ModuleWirings['gdata'] + "authorize?winname=" + escape(windowName) + "&callback=" + escape(callbackName); var url = ModuleWirings['gdata'] + "authorize?winname=" + escape(windowName) + "&cb=" + escape(callbackName);
var win = window.open(url, "openrefinegdataauth", "resizable=1,width=800,height=600"); var win = window.open(url, "openrefinegdataauth", "resizable=1,width=800,height=600");
}; };

View File

@ -34,12 +34,18 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.google.gdata.client.http.AuthSubUtil; import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.refine.commands.Command; import com.google.refine.commands.Command;
public class DeAuthorizeCommand extends Command { public class DeAuthorizeCommand extends Command {
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
@Override @Override
public void doGet(HttpServletRequest request, HttpServletResponse response) public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
@ -50,7 +56,16 @@ public class DeAuthorizeCommand extends Command {
String sessionToken = TokenCookie.getToken(request); String sessionToken = TokenCookie.getToken(request);
if (sessionToken != null) { if (sessionToken != null) {
AuthSubUtil.revokeToken(sessionToken, null);
// No method to do this in Google's client lib, so roll our own
HttpRequestFactory factory = HTTP_TRANSPORT.createRequestFactory();
GenericUrl url = new GenericUrl("https://accounts.google.com/o/oauth2/revoke?token="+sessionToken);
HttpRequest rqst = factory.buildGetRequest(url);
HttpResponse resp = rqst.execute();
if (resp.getStatusCode() != 200) {
respond(response, String.valueOf(resp.getStatusCode()), resp.getStatusMessage());
}
TokenCookie.deleteToken(request, response); TokenCookie.deleteToken(request, response);
} }
respond(response, "200 OK", ""); respond(response, "200 OK", "");

View File

@ -30,7 +30,6 @@ package com.google.refine.extension.gdata;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,19 +38,12 @@ import java.util.Scanner;
import java.util.regex.MatchResult; import java.util.regex.MatchResult;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import com.google.gdata.client.GoogleService; import com.google.gdata.client.GoogleService;
import com.google.gdata.client.Service.GDataRequest; import com.google.gdata.client.Service.GDataRequest;
import com.google.gdata.client.Service.GDataRequest.RequestType; import com.google.gdata.client.Service.GDataRequest.RequestType;
import com.google.gdata.client.http.AuthSubUtil;
import com.google.gdata.util.ContentType; import com.google.gdata.util.ContentType;
import com.google.gdata.util.ServiceException; import com.google.gdata.util.ServiceException;
import com.google.refine.util.ParsingUtilities;
import edu.mit.simile.butterfly.ButterflyModule;
/** /**
* @author Tom Morris <tfmorris@gmail.com> * @author Tom Morris <tfmorris@gmail.com>
* @copyright 2010,2013 Thomas F. Morris * @copyright 2010,2013 Thomas F. Morris
@ -61,41 +53,22 @@ public class FusionTableHandler {
final static private String FUSION_TABLES_SERVICE_URL = final static private String FUSION_TABLES_SERVICE_URL =
"https://www.google.com/fusiontables/api/query"; "https://www.google.com/fusiontables/api/query";
// "https://www.googleapis.com/fusiontables/v1/query";
final static private Pattern CSV_VALUE_PATTERN = final static private Pattern CSV_VALUE_PATTERN =
Pattern.compile("([^,\\r\\n\"]*|\"(([^\"]*\"\")*[^\"]*)\")(,|\\r?\\n)"); Pattern.compile("([^,\\r\\n\"]*|\"(([^\"]*\"\")*[^\"]*)\")(,|\\r?\\n)");
static public String getAuthorizationUrl(ButterflyModule module, HttpServletRequest request)
throws MalformedURLException {
char[] mountPointChars = module.getMountPoint().getMountPoint().toCharArray();
StringBuffer sb = new StringBuffer();
sb.append(mountPointChars, 0, mountPointChars.length);
sb.append("authorized?winname=");
sb.append(ParsingUtilities.encode(request.getParameter("winname")));
sb.append("&callback=");
sb.append(ParsingUtilities.encode(request.getParameter("callback")));
URL thisUrl = new URL(request.getRequestURL().toString());
URL authorizedUrl = new URL(thisUrl, sb.toString());
return AuthSubUtil.getRequestUrl(
authorizedUrl.toExternalForm(), // execution continues at authorized on redirect
"https://www.google.com/fusiontables/api/query",
false,
true);
}
static public GDataRequest createFusionTablesPostRequest( static public GDataRequest createFusionTablesPostRequest(
GoogleService service, RequestType requestType, String query) GoogleService service, RequestType requestType, String query)
throws IOException, ServiceException { throws IOException, ServiceException {
URL url = new URL(FUSION_TABLES_SERVICE_URL); URL url = new URL(FUSION_TABLES_SERVICE_URL);
GDataRequest request = service.getRequestFactory().getRequest( GDataRequest request = service.getRequestFactory().getRequest(
requestType, url, new ContentType("application/x-www-form-urlencoded")); requestType, url, new ContentType("application/x-www-form-urlencoded"));
OutputStreamWriter writer = OutputStreamWriter writer =
new OutputStreamWriter(request.getRequestStream()); new OutputStreamWriter(request.getRequestStream());
writer.append("sql=" + URLEncoder.encode(query, "UTF-8")); writer.append("sql=" + URLEncoder.encode(query, "UTF-8") + "&alt=csv");
writer.flush(); writer.flush();
writer.close(); writer.close();
@ -106,7 +79,7 @@ public class FusionTableHandler {
GoogleService service, RequestType requestType, String query) GoogleService service, RequestType requestType, String query)
throws IOException, ServiceException { throws IOException, ServiceException {
URL url = new URL(FUSION_TABLES_SERVICE_URL + "?sql=" + URL url = new URL(FUSION_TABLES_SERVICE_URL + "?sql=" +
URLEncoder.encode(query, "UTF-8")); URLEncoder.encode(query, "UTF-8")+"&alt=csv");
return service.getRequestFactory().getRequest( return service.getRequestFactory().getRequest(
requestType, url, ContentType.TEXT_PLAIN); requestType, url, ContentType.TEXT_PLAIN);
} }

View File

@ -28,13 +28,22 @@
*/ */
package com.google.refine.extension.gdata; package com.google.refine.extension.gdata;
import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import com.google.api.client.auth.oauth2.AuthorizationCodeResponseUrl;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.gdata.client.docs.DocsService; import com.google.gdata.client.docs.DocsService;
import com.google.gdata.client.http.AuthSubUtil;
import com.google.gdata.client.spreadsheet.SpreadsheetService; import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.refine.util.ParsingUtilities; import com.google.refine.util.ParsingUtilities;
@ -48,28 +57,72 @@ import edu.mit.simile.butterfly.ButterflyModule;
*/ */
abstract public class GDataExtension { abstract public class GDataExtension {
static final String SERVICE_APP_NAME = "OpenRefine-GData-Extension"; static final String SERVICE_APP_NAME = "OpenRefine-GData-Extension";
static final String CLIENT_ID = "647865400439.apps.googleusercontent.com";
static final String CLIENT_SECRET = "0mW9OJji1yrgJk5AjJc5Pn6I"; // not really that secret, but the protocol accounts for that
/** Global instance of the HTTP transport. */
static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
/** Global instance of the JSON factory. */
static final JsonFactory JSON_FACTORY = new JacksonFactory();
static public String getAuthorizationUrl(ButterflyModule module, HttpServletRequest request) static public String getAuthorizationUrl(ButterflyModule module, HttpServletRequest request)
throws MalformedURLException { throws MalformedURLException {
char[] mountPointChars = module.getMountPoint().getMountPoint().toCharArray(); String authorizedUrl = makeRedirectUrl(module, request);
StringBuffer sb = new StringBuffer(); // New Oauth2
sb.append(mountPointChars, 0, mountPointChars.length); GoogleAuthorizationCodeRequestUrl url = new GoogleAuthorizationCodeRequestUrl(
CLIENT_ID,
authorizedUrl, // execution continues at authorized on redirect
Arrays.asList("https://www.googleapis.com/auth/fusiontables",
"https://docs.google.com/feeds", // create new spreadsheets
"https://spreadsheets.google.com/feeds"));
return url.toString();
}
private static String makeRedirectUrl(ButterflyModule module, HttpServletRequest request)
throws MalformedURLException {
StringBuffer sb = new StringBuffer(module.getMountPoint().getMountPoint());
sb.append("authorized?winname="); sb.append("authorized?winname=");
sb.append(ParsingUtilities.encode(request.getParameter("winname"))); sb.append(ParsingUtilities.encode(request.getParameter("winname")));
sb.append("&callback="); sb.append("&cb=");
sb.append(ParsingUtilities.encode(request.getParameter("callback"))); sb.append(ParsingUtilities.encode(request.getParameter("cb")));
URL thisUrl = new URL(request.getRequestURL().toString()); URL thisUrl = new URL(request.getRequestURL().toString());
URL authorizedUrl = new URL(thisUrl, sb.toString()); URL authorizedUrl = new URL(thisUrl, sb.toString());
return authorizedUrl.toExternalForm();
return AuthSubUtil.getRequestUrl(
authorizedUrl.toExternalForm(), // execution continues at authorized on redirect
"https://docs.google.com/feeds https://spreadsheets.google.com/feeds https://www.google.com/fusiontables/api/query",
false,
true);
} }
static public String getTokenFromCode(ButterflyModule module, HttpServletRequest request)
throws MalformedURLException {
String redirectUrl = makeRedirectUrl(module, request);
StringBuffer fullUrlBuf = request.getRequestURL();
if (request.getQueryString() != null) {
fullUrlBuf.append('?').append(request.getQueryString());
}
AuthorizationCodeResponseUrl authResponse =
new AuthorizationCodeResponseUrl(fullUrlBuf.toString());
// check for user-denied error
if (authResponse.getError() != null) {
// authorization denied...
} else {
// request access token using authResponse.getCode()...
String code = authResponse.getCode();
try {
GoogleTokenResponse response = new GoogleAuthorizationCodeTokenRequest(HTTP_TRANSPORT,
JSON_FACTORY, CLIENT_ID, CLIENT_SECRET, code, redirectUrl).execute();
String token = response.getAccessToken();
return token;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
static public DocsService getDocsService(String token) { static public DocsService getDocsService(String token) {
DocsService service = new DocsService(SERVICE_APP_NAME); DocsService service = new DocsService(SERVICE_APP_NAME);

View File

@ -8,7 +8,7 @@ import com.google.refine.util.CookiesUtilities;
public class TokenCookie { public class TokenCookie {
private static final String COOKIE_NAME = "authsub_token"; private static final String COOKIE_NAME = "oauth2_token";
private static int MAX_AGE = 30 * 24 * 60 * 60; // 30 days private static int MAX_AGE = 30 * 24 * 60 * 60; // 30 days
public static String getToken(HttpServletRequest request) { public static String getToken(HttpServletRequest request) {