// Created by Sumit Shah on 7/02/09. // Copyright (c) 2010 Yahoo! Inc. All rights reserved. // // The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license. package com.yahoo.yos; import net.oauth.OAuth; import net.oauth.OAuthAccessor; import net.oauth.OAuthConsumer; import net.oauth.OAuthException; import net.oauth.OAuthMessage; import net.oauth.OAuthProblemException; import net.oauth.OAuthServiceProvider; import net.oauth.ParameterStyle; import net.oauth.client.OAuthClient; import net.oauth.http.HttpClient; import net.oauth.server.OAuthServlet; import net.oauth.signature.OAuthSignatureMethod; import org.json.JSONException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URISyntaxException; import java.util.List; import java.util.Map; import java.util.Properties; /** * This is a servlet filter that assists in OAuth validation and authorization as well * as some boilerplate when dealing with Yahoo! Open APIs. Access and request tokens * are stored in cookies in a similar manner to the PHP SDK. * * @author Sam Pullara * @author Sumit Shah */ public class YahooFilter implements Filter { private final static Logger logger = LoggerFactory.getLogger(YahooFilter.class); private static enum SESSION_TYPE { YAHOO_YAP_SESSION_TYPE, YAHOO_OAUTH_AT_SESSION_TYPE, YAHOO_OAUTH_RT_SESSION_TYPE, } private Properties oauthConfig; private boolean redirect = false; private OAuthClient client; private OAuthServiceProvider provider; private OAuthConsumer consumer; private String callbackUrl; /** * Set the 'oauth' init parameter for this filter to change from the default * oauth.properties resource for configuration. The file should define: * <p/> * yos.consumerKey=... * yos.consumerSecret=... * oauth.requesttoken.url=https://api.login.yahoo.com/oauth/v2/get_request_token * oauth.requestauth.url=https://api.login.yahoo.com/oauth/v2/request_auth * oauth.accesstoken.url=https://api.login.yahoo.com/oauth/v2/get_token * oauth.callback.url=http://myapplication:8080/ * <p/> * To access production OAuth services from Yahoo. This filter relies on session * information stored by cookies to operate. * * @param filterConfig see upstream docs * @throws ServletException */ public void init(FilterConfig filterConfig) throws ServletException { String filename = filterConfig.getInitParameter("oauth"); if (filename == null) { filename = "oauth.properties"; } logger.debug("oauth properties file: {}", filename); oauthConfig = new Properties(); try { oauthConfig.load(getClass().getResourceAsStream("/" + filename)); } catch (IOException e) { throw new ServletException("Could not load oauth properties from resource: " + filename, e); } String redirectString = filterConfig.getInitParameter("redirect"); // defaults to redirect if null, zero-length redirect = redirectString == null || redirectString.trim().length() <= 0 || "true".equalsIgnoreCase(redirectString.trim()); logger.debug("redirect if access token not found: {}", redirect); String oauthConnectionClass = filterConfig.getInitParameter("oauthConnectionClass"); if (oauthConnectionClass == null) { oauthConnectionClass = "net.oauth.client.URLConnectionClient"; } logger.debug("oauth client connection class: {}", oauthConnectionClass); try { client = new OAuthClient((HttpClient)Class.forName(oauthConnectionClass).newInstance()); } catch (Exception cce) { throw new ServletException("unable to create OAuthClient from: " + oauthConnectionClass, cce); } provider = new OAuthServiceProvider( oauthConfig.getProperty("oauth.requesttoken.url", "https://api.login.yahoo.com/oauth/v2/get_request_token"), oauthConfig.getProperty("oauth.requestauth.url", "https://api.login.yahoo.com/oauth/v2/request_auth"), oauthConfig.getProperty("oauth.accesstoken.url", "https://api.login.yahoo.com/oauth/v2/get_token") ); consumer = new OAuthConsumer(oauthConfig.getProperty("oauth.callback.url"), oauthConfig.getProperty("yos.consumerKey"), oauthConfig.getProperty("yos.consumerSecret"), provider); consumer.setProperty("oauth_signature_method", oauthConfig.getProperty("yos.oauth_signature_method", "HMAC-SHA1")); callbackUrl = oauthConfig.getProperty("oauth.callback.url", ""); } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String yap_appid = getParam(request, "yap_appid"); SESSION_TYPE sessionType; if ("POST".equals(request.getMethod()) && yap_appid != null && yap_appid.length() > 0) { sessionType = SESSION_TYPE.YAHOO_YAP_SESSION_TYPE; } else if (cookieExists(request.getCookies(), "yosdk_at")) { sessionType = SESSION_TYPE.YAHOO_OAUTH_AT_SESSION_TYPE; } else if (cookieExists(request.getCookies(), "yosdk_rt")) { sessionType = SESSION_TYPE.YAHOO_OAUTH_RT_SESSION_TYPE; } else { sessionType = null; } if (logger.isDebugEnabled()) { logger.debug("sessionType: {}", sessionType); } OAuthAccessor accessor = new OAuthAccessor(consumer); if (sessionType == null) { if (redirect) { if (logger.isDebugEnabled()) { logger.debug("redirecting user to yahoo acquire access token"); } redirectForAuthorization(accessor, request, response); return; } else { if (logger.isDebugEnabled()) { logger.debug("inserting YahooSession suitable for 2-legged oauth calls into request attribute"); } String appId = oauthConfig.getProperty("yos.appid"); request.setAttribute("yahooSession", new YahooSession(client, consumer, null, appId)); } } else if (sessionType == SESSION_TYPE.YAHOO_YAP_SESSION_TYPE) { if (logger.isDebugEnabled()) { logger.debug("inserting YahooSession suitable for 2-legged oauth calls into request attribute"); } if (consumer.consumerKey == null || !consumer.consumerKey.equals(getParam(request, "yap_consumer_key"))) { logger.error("Consumer key from YAP does not match config."); clearSession(request, response); if (redirect) { redirectForAuthorization(accessor, request, response); return; } } try { OAuthSignatureMethod method = OAuthSignatureMethod.newMethod("HMAC-SHA1", accessor); OAuthMessage msg = OAuthServlet.getMessage(request, null); method.validate(msg); } catch (OAuthProblemException ex) { logger.error("Signature from YAP failed.", ex); clearSession(request, response); if (redirect) { redirectForAuthorization(accessor, request, response); return; } } catch (Exception ex) { throw new ServletException(ex); } AccessToken at = new AccessToken(); at.setKey(getParam(request, "yap_viewer_access_token")); at.setSecret(getParam(request, "yap_viewer_access_token_secret")); at.setGuid(getParam(request, "yap_viewer_guid")); at.setOwner(getParam(request, "yap_owner_guid")); at.setTokenExpires(-1); String appId = getParam(request, "yap_appid"); YahooSession yahooSession = new YahooSession(client, consumer, at, appId); request.setAttribute("yahooSession", yahooSession); } else if (sessionType == SESSION_TYPE.YAHOO_OAUTH_AT_SESSION_TYPE) { long now = System.currentTimeMillis() / 1000; try { AccessToken accessToken = new AccessToken(cookie(request.getCookies(), "yosdk_at")); if (consumer.consumerKey == null || !consumer.consumerKey.equals(accessToken.getConsumer())) { logger.error("Consumer key for token does not match the defined Consumer Key. The Consumer Key has probably changed since the user last authorized the application."); clearSession(request, response); if (redirect) { redirectForAuthorization(accessor, request, response); return; } } if (accessToken.getTokenExpires() >= 0 && logger.isDebugEnabled()) { logger.debug("AT Expires in: {}", (accessToken.getTokenExpires() - now)); } if (accessToken.getTokenExpires() >= 0 && (accessToken.getTokenExpires() - now) < 30) { try { accessTokenExpired(accessor, request, response, accessToken, filterChain); } catch (OAuthException ex) { if (ex instanceof OAuthProblemException) { OAuthProblemException oape = (OAuthProblemException) ex; String s = oape.getProblem() + oape.getParameters(); throw new ServletException(s, ex); } throw new ServletException(ex); } catch (URISyntaxException ex) { throw new ServletException(ex); } return; } else { String appId = oauthConfig.getProperty("yos.appid"); YahooSession yahooSession = new YahooSession(client, consumer, accessToken, appId); request.setAttribute("yahooSession", yahooSession); } } catch (JSONException e) { throw new ServletException(e); } } else if (sessionType == SESSION_TYPE.YAHOO_OAUTH_RT_SESSION_TYPE) { try { RequestToken rt = new RequestToken(cookie(request.getCookies(), "yosdk_rt")); accessor.tokenSecret = rt.getSecret(); String verifier = getParam(request, "oauth_verifier"); if (logger.isDebugEnabled()) { logger.debug("got oauth_verifier {}", verifier); } try { if(logger.isDebugEnabled()) { logger.error("request token found, fetching access token for user"); } AccessToken at = fetchAccessToken(accessor, rt, verifier); Cookie yosdk_at = at.getCookie(); Cookie yosdk_rt = new Cookie("yosdk_rt", ""); yosdk_at.setMaxAge(30 * 24 * 60 * 60); yosdk_rt.setMaxAge(0); response.addCookie(yosdk_at); response.addCookie(yosdk_rt); String appId = oauthConfig.getProperty("yos.appid"); YahooSession yahooSession = new YahooSession(client, consumer, at, appId); request.setAttribute("yahooSession", yahooSession); } catch (URISyntaxException ex) { throw new ServletException(ex); } catch (OAuthException ex) { clearSession(request, response); if (redirect) { redirectForAuthorization(accessor, request, response); return; } else { throw new ServletException(ex); } } } catch (JSONException e) { throw new ServletException(e); } } filterChain.doFilter(servletRequest, servletResponse); } private String getParam(HttpServletRequest request, String key) { String param = request.getParameter(key); if (param == null) { return request.getHeader("x-" + key.replace("_", "-")); } return param; } private AccessToken fetchAccessToken(OAuthAccessor accessor, RequestToken requestToken, String verifier) throws IOException, URISyntaxException, OAuthException { List<OAuth.Parameter> params; if (requestToken.getSessionHandle() != null) { params = OAuth.newList("oauth_token", requestToken.getKey(), "oauth_session_handle", requestToken.getSessionHandle()); } else { params = OAuth.newList("oauth_token", requestToken.getKey()); } // Add the verifier which is required for OAuth1.0a if (verifier != null) { params.addAll(OAuth.newList("oauth_verifier", verifier)); } OAuthMessage getTokenMsg = new OAuthMessage("GET", provider.accessTokenURL, params); getTokenMsg.addRequiredParameters(accessor); OAuthMessage msg = client.invoke(getTokenMsg, ParameterStyle.QUERY_STRING); Map<String, String> map = OAuth.newMap(msg.getParameters()); AccessToken at = new AccessToken(); at.setKey(map.get("oauth_token")); at.setSecret(map.get("oauth_token_secret")); at.setGuid(map.get("xoauth_yahoo_guid")); at.setConsumer(accessor.consumer.consumerKey); at.setSessionHandle(map.get("oauth_session_handle")); long now = System.currentTimeMillis() / 1000; if (map.containsKey("oauth_expires_in")) { at.setTokenExpires(now + Long.parseLong(msg.getParameter("oauth_expires_in"))); } else { at.setTokenExpires(-1); } if (map.containsKey("oauth_authorization_expires_in")) { at.setHandleExpires(now + Long.parseLong(map.get("oauth_authorization_expires_in"))); } else { at.setHandleExpires(-1); } if (logger.isDebugEnabled()) { logger.debug("setting access token expires in: {}", at.getTokenExpires()); logger.debug("setting access token handle expires in: {}", at.getHandleExpires()); } return at; } private void accessTokenExpired(OAuthAccessor accessor, HttpServletRequest request, HttpServletResponse response, AccessToken accessToken, FilterChain filterChain) throws IOException, ServletException, JSONException, OAuthException, URISyntaxException { if(logger.isDebugEnabled()) { logger.debug("access token expired, attempting to renew"); } long now = System.currentTimeMillis() / 1000; if (accessToken.getHandleExpires() == -1 || (now < accessToken.getHandleExpires())) { RequestToken requestToken = new RequestToken(); requestToken.setKey(accessToken.getKey()); requestToken.setSessionHandle(accessToken.getSessionHandle()); accessor.tokenSecret = accessToken.getSecret(); AccessToken at = fetchAccessToken(accessor, requestToken, null); Cookie yosdk_at = at.getCookie(); yosdk_at.setMaxAge(30 * 24 * 60 * 60); response.addCookie(yosdk_at); String appId = oauthConfig.getProperty("yos.appid"); YahooSession yahooSession = new YahooSession(client, consumer, at, appId); request.setAttribute("yahooSession", yahooSession); filterChain.doFilter(request, response); } else { Cookie at = new Cookie("yosdk_at", ""); at.setMaxAge(0); at.setMaxAge(0); response.addCookie(at); request.setAttribute("yahooSession", null); request.setAttribute("yahooRedirect", null); filterChain.doFilter(request, response); if (redirect) { redirectForAuthorization(accessor, request, response); } } } private void redirectForAuthorization(OAuthAccessor accessor, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { // get the request token List<OAuth.Parameter> callback = OAuth.newList(OAuth.OAUTH_CALLBACK, callbackUrl); //client.getRequestToken(accessor, null, callback); OAuthMessage message = client.getRequestTokenResponse(accessor, null, callback); } catch (URISyntaxException ex) { throw new ServletException(ex); } catch (OAuthException ex) { throw new ServletException(ex); } if (accessor.requestToken != null) { try { RequestToken rt = new RequestToken(); rt.setKey(accessor.requestToken); rt.setSecret(accessor.tokenSecret); Cookie yosdk_rt = rt.getCookie(); yosdk_rt.setMaxAge(600); response.addCookie(yosdk_rt); } catch (JSONException ex) { throw new ServletException(ex); } } else { throw new ServletException("Failed to create request token"); } String redirectUrl = OAuth.addParameters(provider.userAuthorizationURL, "oauth_token", accessor.requestToken, "oauth_callback", callbackUrl); request.setAttribute("yahooRedirect", redirectUrl); response.sendRedirect(redirectUrl); } public void clearSession(HttpServletRequest req, HttpServletResponse res) { if(logger.isDebugEnabled()) { logger.debug("clear session requested"); } Cookie at = new Cookie("yosdk_at", ""); at.setMaxAge(0); Cookie rt = new Cookie("yosdk_rt", ""); rt.setMaxAge(0); res.addCookie(at); res.addCookie(rt); req.setAttribute("yahooSession", null); req.setAttribute("yahooRedirect", null); } private boolean cookieExists(Cookie[] cookies, String cookieName) { if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName() != null && cookie.getName().equals(cookieName)) { return true; } } } return false; } private Cookie cookie(Cookie[] cookies, String cookieName) { if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName() != null && cookie.getName().equals(cookieName)) { return cookie; } } } return null; } public void destroy() { //empty } }