package org.fluxtream.connectors.bodymedia; import java.io.IOException; import java.net.URLDecoder; import java.security.NoSuchAlgorithmException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.fluxtream.core.Configuration; import org.fluxtream.core.auth.AuthHelper; import org.fluxtream.core.connectors.Connector; import org.fluxtream.connectors.controllers.ControllerSupport; import org.fluxtream.core.connectors.updaters.UpdateFailedException; import org.fluxtream.core.connectors.updaters.UpdateInfo; import org.fluxtream.core.domain.ApiKey; import org.fluxtream.core.domain.Guest; import org.fluxtream.core.domain.Notification; import org.fluxtream.core.services.ConnectorUpdateService; import org.fluxtream.core.services.GuestService; import org.fluxtream.core.services.NotificationsService; import oauth.signpost.OAuthConsumer; import oauth.signpost.OAuthProvider; import oauth.signpost.basic.DefaultOAuthConsumer; import oauth.signpost.commonshttp.CommonsHttpOAuthProvider; import oauth.signpost.exception.OAuthCommunicationException; import oauth.signpost.exception.OAuthExpectationFailedException; import oauth.signpost.exception.OAuthMessageSignerException; import oauth.signpost.exception.OAuthNotAuthorizedException; import oauth.signpost.http.HttpParameters; import org.apache.http.client.HttpClient; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping(value = "/bodymedia") public class BodymediaController { @Autowired GuestService guestService; @Autowired Configuration env; @Autowired NotificationsService notificationsService; @Autowired ConnectorUpdateService connectorUpdateService; static final Logger logger = Logger.getLogger(BodymediaController.class); private static final String BODYMEDIA_OAUTH_CONSUMER = "bodymediaOAuthConsumer"; private static final String BODYMEDIA_OAUTH_PROVIDER = "bodymediaOAuthProvider"; @RequestMapping(value = "/token") public String getToken(HttpServletRequest request) throws IOException, ServletException, OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException { String oauthCallback = ControllerSupport.getLocationBase(request, env) + "bodymedia/upgradeToken"; if (request.getParameter("guestId") != null) oauthCallback += "?guestId=" + request.getParameter("guestId"); if (request.getParameter("apiKeyId") != null) oauthCallback += "?apiKeyId=" + request.getParameter("apiKeyId"); String apiKey = env.get("bodymediaConsumerKey"); OAuthConsumer consumer = new DefaultOAuthConsumer( apiKey, env.get("bodymediaConsumerSecret")); HttpParameters additionalParameter = new HttpParameters(); additionalParameter.put("api_key", apiKey); consumer.setAdditionalParameters(additionalParameter); HttpClient httpClient = env.getHttpClient(); OAuthProvider provider = new CommonsHttpOAuthProvider( "https://api.bodymedia.com/oauth/request_token?api_key="+apiKey, "https://api.bodymedia.com/oauth/access_token?api_key="+apiKey, "https://api.bodymedia.com/oauth/authorize?api_key="+apiKey, httpClient); request.getSession().setAttribute(BODYMEDIA_OAUTH_CONSUMER, consumer); request.getSession().setAttribute(BODYMEDIA_OAUTH_PROVIDER, provider); String approvalPageUrl = null; try { approvalPageUrl = provider.retrieveRequestToken(consumer, oauthCallback); } catch (Throwable t) { logger.error("Couldn't retrieve BodyMedia request token."); t.printStackTrace(); notificationsService.addNamedNotification(AuthHelper.getGuestId(), Notification.Type.ERROR, connector().statusNotificationName(), "Oops. There was an error with the BodyMedia API. " + "Hang tight, we are working on it."); // TODO: Should we record permanent failure since an existing connector won't work again until // it is reauthenticated? We would need to get hold of the apiKey and do: // guestService.setApiKeyStatus(apiKey.getId(), ApiKey.Status.STATUS_PERMANENT_FAILURE, null); return "redirect:/app/"; } System.out.println("the token secret is: " + consumer.getTokenSecret()); approvalPageUrl+="&oauth_api=" + apiKey; approvalPageUrl = URLDecoder.decode(approvalPageUrl, "UTF-8"); return "redirect:" + approvalPageUrl; } @RequestMapping(value = "/upgradeToken") public String upgradeToken(HttpServletRequest request) throws NoSuchAlgorithmException, IOException, OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException { OAuthConsumer consumer = (OAuthConsumer) request.getSession() .getAttribute(BODYMEDIA_OAUTH_CONSUMER); OAuthProvider provider = (OAuthProvider) request.getSession() .getAttribute(BODYMEDIA_OAUTH_PROVIDER); String verifier = request.getParameter("oauth_verifier"); provider.retrieveAccessToken(consumer, verifier); Guest guest = AuthHelper.getGuest(); ApiKey apiKey; if (request.getParameter("apiKeyId")!=null) { long apiKeyId = Long.valueOf(request.getParameter("apiKeyId")); apiKey = guestService.getApiKey(apiKeyId); } else apiKey = guestService.createApiKey(guest.getId(), connector()); guestService.setApiKeyAttribute(apiKey, "api_key", env.get("bodymediaConsumerKey")); guestService.setApiKeyAttribute(apiKey, "accessToken", consumer.getToken()); guestService.setApiKeyAttribute(apiKey, "tokenSecret", consumer.getTokenSecret()); guestService.setApiKeyAttribute(apiKey, "tokenExpiration", provider.getResponseParameters().get("xoauth_token_expiration_time").first()); // Store the OAuth server keys used for the token upgrade, which are the ones currently in oauth.properties // into ApiKeyAttributes. The values in oauth.properties may change over time, so we need to preserve // the values used for the token creation with the ApiKey itself. guestService.setApiKeyAttribute(apiKey, "bodymediaConsumerKey", env.get("bodymediaConsumerKey")); guestService.setApiKeyAttribute(apiKey, "bodymediaConsumerSecret", env.get("bodymediaConsumerSecret")); // If bodymediaRateDelayMs is specified in oauth.properties store it with the attributes // This should be 1000/Calls per second for the associated key. The default is 2 calls per second, // which is bodymediaRateDelayMs=500 String bodymediaRateDelayMs = env.get("bodymediaRateDelayMs"); if(bodymediaRateDelayMs==null) { bodymediaRateDelayMs="500"; } guestService.setApiKeyAttribute(apiKey, "bodymediaRateDelayMs", bodymediaRateDelayMs); request.getSession().removeAttribute(BODYMEDIA_OAUTH_CONSUMER); request.getSession().removeAttribute(BODYMEDIA_OAUTH_PROVIDER); if (request.getParameter("apiKeyId")!=null) return "redirect:/app/tokenRenewed/" + connector().getName(); else return "redirect:/app/from/" + connector().getName(); } private Connector connector() { return Connector.getConnector("bodymedia"); } public void replaceToken(UpdateInfo updateInfo) throws OAuthExpectationFailedException, OAuthMessageSignerException, OAuthCommunicationException, OAuthNotAuthorizedException, UpdateFailedException { // Check to see if we are running on a mirrored test instance // and should therefore refrain from swapping tokens lest we // invalidate an existing token instance String disableTokenSwap = env.get("disableTokenSwap"); if(disableTokenSwap!=null && disableTokenSwap.equals("true")) { String msg = "**** Skipping refreshToken for bodymedia connector instance because disableTokenSwap is set on this server"; ; StringBuilder sb2 = new StringBuilder("module=BodymediaController component=BodymediaController action=replaceToken apiKeyId=" + updateInfo.apiKey.getId()) .append(" message=\"").append(msg).append("\""); logger.info(sb2.toString()); System.out.println(msg); // Notify the user that the tokens need to be manually renewed notificationsService.addNamedNotification(updateInfo.getGuestId(), Notification.Type.WARNING, connector().statusNotificationName(), "Heads Up. This server cannot automatically refresh your authentication tokens.<br>" + "Please head to <a href=\"javascript:App.manageConnectors()\">Manage Connectors</a>,<br>" + "scroll to the BodyMedia connector, and renew your tokens (look for the <i class=\"icon-resize-small icon-large\"></i> icon)"); // Record permanent failure since this connector won't work again until // it is reauthenticated guestService.setApiKeyStatus(updateInfo.apiKey.getId(), ApiKey.Status.STATUS_PERMANENT_FAILURE, null, ApiKey.PermanentFailReason.NEEDS_REAUTH); throw new UpdateFailedException("requires token reauthorization",true, ApiKey.PermanentFailReason.NEEDS_REAUTH); } // We're not on a mirrored test server. Try to swap the expired // access token for a fresh one. // First, retrieve the OAuth server keys used when this key was created. These are automatically stored // in the ApiKeyAttribute table at the time of creation based on the values present in // oauth.properties. String bodymediaConsumerKey = guestService.getApiKeyAttribute(updateInfo.apiKey, "bodymediaConsumerKey"); String bodymediaConsumerSecret = guestService.getApiKeyAttribute(updateInfo.apiKey, "bodymediaConsumerSecret"); OAuthConsumer consumer = new DefaultOAuthConsumer( bodymediaConsumerKey, bodymediaConsumerSecret); String accessToken = guestService.getApiKeyAttribute(updateInfo.apiKey, "accessToken"); consumer.setTokenWithSecret(accessToken, guestService.getApiKeyAttribute(updateInfo.apiKey, "tokenSecret")); HttpParameters additionalParameter = new HttpParameters(); additionalParameter.put("api_key", bodymediaConsumerKey); additionalParameter.put("oauth_token", accessToken); consumer.setAdditionalParameters(additionalParameter); HttpClient httpClient = env.getHttpClient(); OAuthProvider provider = new CommonsHttpOAuthProvider( "https://api.bodymedia.com/oauth/request_token?api_key="+bodymediaConsumerKey, "https://api.bodymedia.com/oauth/access_token?api_key="+bodymediaConsumerKey, "https://api.bodymedia.com/oauth/authorize?api_key="+bodymediaConsumerKey, httpClient); try { provider.retrieveAccessToken(consumer, null); guestService.setApiKeyAttribute(updateInfo.apiKey, "accessToken", consumer.getToken()); guestService.setApiKeyAttribute(updateInfo.apiKey, "tokenSecret", consumer.getTokenSecret()); guestService.setApiKeyAttribute(updateInfo.apiKey, "tokenExpiration", provider.getResponseParameters().get("xoauth_token_expiration_time").first()); // Record this connector as having status up guestService.setApiKeyStatus(updateInfo.apiKey.getId(), ApiKey.Status.STATUS_UP, null, null); // Schedule an update for this connector connectorUpdateService.updateConnector(updateInfo.apiKey, false); } catch (Throwable t) { // Notify the user that the tokens need to be manually renewed notificationsService.addNamedNotification(updateInfo.getGuestId(), Notification.Type.WARNING, connector().statusNotificationName(), "Heads Up. We failed in our attempt to automatically refresh your authentication tokens.<br>" + "Please head to <a href=\"javascript:App.manageConnectors()\">Manage Connectors</a>,<br>" + "scroll to the BodyMedia connector, and renew your tokens (look for the <i class=\"icon-resize-small icon-large\"></i> icon)"); // Record permanent failure since this connector won't work again until // it is reauthenticated guestService.setApiKeyStatus(updateInfo.apiKey.getId(), ApiKey.Status.STATUS_PERMANENT_FAILURE, null, ApiKey.PermanentFailReason.NEEDS_REAUTH); throw new UpdateFailedException("refresh token attempt failed", t, true, ApiKey.PermanentFailReason.NEEDS_REAUTH); } } }