Skip to content

Commit

Permalink
add OAuth2 support for using Google Storage
Browse files Browse the repository at this point in the history
  • Loading branch information
rgerganov committed Jul 3, 2011
1 parent e912378 commit 02b8f3c
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/main/java/com/lz2zg/qsl/servlet/Initializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.lz2zg.qsl.model.QslCard;
import com.lz2zg.qsl.model.QslCardDao;
import com.lz2zg.qsl.model.QslCardDaoImpl;
Expand All @@ -18,6 +20,9 @@ public void contextInitialized(ServletContextEvent sce) {
QslCardDao dao = new QslCardDaoImpl(datastore);
ServletContext context = sce.getServletContext();
context.setAttribute("dao", dao);
MemcacheService memcacheService = MemcacheServiceFactory.getMemcacheService();
OAuth2ClientImpl oauth2client = new OAuth2ClientImpl(datastore, memcacheService);
context.setAttribute("oauth2client", oauth2client);
//populateTestData(dao);
}

Expand Down
34 changes: 34 additions & 0 deletions src/main/java/com/lz2zg/qsl/servlet/OAuth2CallbackServlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.lz2zg.qsl.servlet;

import java.io.IOException;
import java.util.logging.Logger;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class OAuth2CallbackServlet extends HttpServlet {

static final Logger log = Logger.getLogger(OAuth2CallbackServlet.class.getName());
private static final long serialVersionUID = 1L;
OAuth2Client oauth2client;

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
ServletContext context = config.getServletContext();
oauth2client = (OAuth2Client) context.getAttribute("oauth2client");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String authCode = req.getParameter("code");
log.info("authCode: " + authCode);
oauth2client.fetchAccessAndRefreshTokens(authCode);
resp.setContentType("text/html");
resp.getWriter().println("<html><body>Authorization in Google Storage successful</body></html>");
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/lz2zg/qsl/servlet/OAuth2Client.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.lz2zg.qsl.servlet;


public interface OAuth2Client {

String CLIENT_ID = "348460834037.apps.googleusercontent.com";

// this is useless until coupled with refresh token,
// so it safe to be hardcoded as a constant
String CLIENT_SECRET = "11DrV8S8kCglvYrxlG5Gt61Y";

String REDIRECT_URI = "http://localhost:8080/admin/oauth2callback";

String getAccessToken();

String getAccessToken(String refreshToken);

void fetchAccessAndRefreshTokens(String authCode);

String getRefreshToken();
}
129 changes: 129 additions & 0 deletions src/main/java/com/lz2zg/qsl/servlet/OAuth2ClientImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.lz2zg.qsl.servlet;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.MemcacheService;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class OAuth2ClientImpl implements OAuth2Client {

static final Logger log = Logger.getLogger(OAuth2ClientImpl.class.getName());
static final String OAUTH_URL = "https://accounts.google.com/o/oauth2/token";
final DatastoreService datastore;
final MemcacheService memcacheService;
final static String ENC = "UTF-8";
final static String ACCESS_TOKEN_KEY = "accessToken";

public OAuth2ClientImpl(DatastoreService datastore, MemcacheService memcacheService) {
this.datastore = datastore;
this.memcacheService = memcacheService;
}

@Override
public String getAccessToken() {
return (String) memcacheService.get(ACCESS_TOKEN_KEY);
}

@Override
public String getAccessToken(String refreshToken) {
try {
String data = String.format("client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token",
URLEncoder.encode(CLIENT_ID, ENC),
URLEncoder.encode(CLIENT_SECRET, ENC),
URLEncoder.encode(refreshToken, ENC));
String response = doPost(data);
log.info("Response after sending refreshToken: " + response);
String accessToken = parseStringField("access_token", response);
int expiresIn = parseIntField("expires_in", response);
memcacheService.put(ACCESS_TOKEN_KEY, accessToken, Expiration.byDeltaSeconds(expiresIn));
return accessToken;
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public void fetchAccessAndRefreshTokens(String authCode) {
try {
String data = String.format("code=%s&client_id=%s&client_secret=%s&redirect_uri=%s&grant_type=authorization_code",
URLEncoder.encode(authCode, ENC),
URLEncoder.encode(CLIENT_ID, ENC),
URLEncoder.encode(CLIENT_SECRET, ENC),
URLEncoder.encode(REDIRECT_URI, ENC)); // what is this for?
String response = doPost(data);
log.info("Response after sending authCode: " + response);
String accessToken = parseStringField("access_token", response);
String refreshToken = parseStringField("refresh_token", response);
int expiresIn = parseIntField("expires_in", response);
memcacheService.put(ACCESS_TOKEN_KEY, accessToken, Expiration.byDeltaSeconds(expiresIn));
saveRefreshToken(refreshToken);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

Integer parseIntField(String field, String text) {
Pattern p = Pattern.compile(String.format("\"%s\":(\\d+)", field));
Matcher m = p.matcher(text);
if (m.find()) {
return Integer.parseInt(m.group(1));
}
return null;
}

String parseStringField(String field, String text) {
Pattern p = Pattern.compile(String.format("\"%s\":\"(.+?)\"", field));
Matcher m = p.matcher(text);
if (m.find()) {
return m.group(1);
}
return null;
}

String doPost(String data) throws IOException {
URL url = new URL(OAUTH_URL);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
conn.getOutputStream().write(data.getBytes(ENC));
conn.getOutputStream().flush();
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = null;
String response = "";
while ((line = rd.readLine()) != null) {
response += line;
}
rd.close();
return response;
}

@Override
public String getRefreshToken() {
Key key = KeyFactory.createKey("refreshToken", "GoogleStorage");
try {
Entity entity = datastore.get(key);
return (String) entity.getProperty("value");
} catch (EntityNotFoundException e) {
return null;
}
}

void saveRefreshToken(String refreshToken) {
Key key = KeyFactory.createKey("refreshToken", "GoogleStorage");
Entity entity = new Entity(key);
entity.setProperty("value", refreshToken);
datastore.put(entity);
}
}
70 changes: 70 additions & 0 deletions src/main/java/com/lz2zg/qsl/servlet/OAuth2Filter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.lz2zg.qsl.servlet;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class OAuth2Filter implements Filter {

static final Logger log = Logger.getLogger(OAuth2ClientImpl.class.getName());
final static String GS_SCOPE = "https://www.googleapis.com/auth/devstorage.read_write";
OAuth2Client oauth2client;

@Override
public void init(FilterConfig config) throws ServletException {
ServletContext context = config.getServletContext();
oauth2client = (OAuth2Client) context.getAttribute("oauth2client");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (req.getRequestURL().toString().equals(OAuth2Client.REDIRECT_URI)) {
log.info("callback has been requested, pass forward");
chain.doFilter(request, response);
return;
}
String accessToken = oauth2client.getAccessToken();
if (accessToken != null) {
log.info("accessToken has been found");
req.setAttribute("accessToken", accessToken);
chain.doFilter(request, response);
} else {
String refreshToken = oauth2client.getRefreshToken();
if (refreshToken != null) {
log.info("getting accessToken using refreshToken");
accessToken = oauth2client.getAccessToken(refreshToken);
if (accessToken != null) {
req.setAttribute("accessToken", accessToken);
chain.doFilter(request, response);
} else {
resp.setStatus(401);
resp.setContentType("text/html");
resp.getWriter().println("<html><body>Not Authorized</body></html>");
}
} else {
log.info("refreshToken not found, redirect to get authCode");
resp.sendRedirect(String.format("https://accounts.google.com/o/oauth2/auth?client_id=%s&redirect_uri=%s&scope=%s&response_type=code",
OAuth2Client.CLIENT_ID,
URLEncoder.encode(OAuth2Client.REDIRECT_URI, "UTF-8"),
URLEncoder.encode(GS_SCOPE, "UTF-8")));
}
}
}

@Override
public void destroy() {
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/lz2zg/qsl/servlet/UploadServlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.lz2zg.qsl.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UploadServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String accessToken = (String) req.getAttribute("accessToken");
resp.setContentType("text/html");
resp.getWriter().println("Access token: " + accessToken);
}

}
3 changes: 3 additions & 0 deletions src/main/webapp/WEB-INF/appengine-web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>qsl-lz2zg</application>
<version>1</version>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties" />
</system-properties>
</appengine-web-app>
13 changes: 13 additions & 0 deletions src/main/webapp/WEB-INF/logging.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#

# Set the default logging level for all loggers to WARNING
.level = INFO
21 changes: 21 additions & 0 deletions src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@
<servlet-name>EditCardServlet</servlet-name>
<url-pattern>/admin/edit</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>OAuth2CallbackServlet</servlet-name>
<servlet-class>com.lz2zg.qsl.servlet.OAuth2CallbackServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>OAuth2CallbackServlet</servlet-name>
<url-pattern>/admin/oauth2callback</url-pattern>
</servlet-mapping>

<!--
<security-constraint>
<web-resource-collection>
Expand All @@ -78,6 +89,16 @@
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>

<filter>
<filter-name>OAuth2Filter</filter-name>
<filter-class>com.lz2zg.qsl.servlet.OAuth2Filter</filter-class>
</filter>

<filter-mapping>
<filter-name>OAuth2Filter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>

<welcome-file-list>
<welcome-file>main</welcome-file>
Expand Down

0 comments on commit 02b8f3c

Please sign in to comment.