package org.fluxtream.connectors.up; import net.coobird.thumbnailator.Thumbnailator; import net.sf.json.JSONObject; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.fluxtream.connectors.controllers.ControllerSupport; import org.fluxtream.core.Configuration; import org.fluxtream.core.auth.AuthHelper; import org.fluxtream.core.connectors.Connector; import org.fluxtream.core.domain.ApiKey; import org.fluxtream.core.domain.Guest; import org.fluxtream.core.domain.Notification; import org.fluxtream.core.services.GuestService; import org.fluxtream.core.services.NotificationsService; import org.fluxtream.core.utils.HttpUtils; import org.joda.time.DateTimeConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; /** * User: candide * Date: 26/01/14 * Time: 09:56 */ @Controller @RequestMapping(value = "/up") public class JawboneUpController { @Autowired Configuration env; @Autowired NotificationsService notificationsService; @Autowired GuestService guestService; @Autowired JawboneUpUpdater updater; @RequestMapping(value = "/token") public String getToken(HttpServletRequest request) throws IOException, ServletException { String redirectUri = getRedirectUri(); // Check that the redirectUri is going to work final String validRedirectUrl = env.get("jawboneUp.validRedirectURL"); if (!validRedirectUrl.startsWith(ControllerSupport.getLocationBase(request, env))) { final long guestId = AuthHelper.getGuestId(); final String validRedirectBase = getBaseURL(validRedirectUrl); notificationsService.addNamedNotification(guestId, Notification.Type.WARNING, Connector.getConnector("up").statusNotificationName(), "Adding a Jawbone UP connector only works when logged in through " + validRedirectBase + ". You are logged in through " + ControllerSupport.getLocationBase(request, env) + ".<br>Please re-login via the supported URL or inform your Fluxtream administrator " + "that the jawboneUp.validRedirectURL setting does not match your needs."); return "redirect:/app"; } // Here we know that the redirectUri will work String approvalPageUrl = String.format("https://jawbone.com/auth/oauth2/auth?" + "redirect_uri=%s&" + "response_type=code&client_id=%s&" + "scope=basic_read meal_read location_read move_read sleep_read", redirectUri, env.get("jawboneUp.client.id")); final String apiKeyIdParameter = request.getParameter("apiKeyId"); if (apiKeyIdParameter !=null && !StringUtils.isEmpty(apiKeyIdParameter)) approvalPageUrl += "&state=" + apiKeyIdParameter; return "redirect:" + approvalPageUrl; } public static String getBaseURL(String url) { try { URI uri = new URI(url); StringBuilder rootURI = new StringBuilder(uri.getScheme()).append("://").append(uri.getHost()); if(uri.getPort()!=-1) { rootURI.append(":" + uri.getPort()); } return (rootURI.toString()); } catch (URISyntaxException e) { return null; } } private String getRedirectUri() { // TODO: This should be checked against the jawboneUp.validRedirectURL property to make // sure that it will work. UP only accepts the specific redirect URI's which matches the one // configured for this key. return env.get("homeBaseUrl") + "up/swapToken"; } @RequestMapping(value = "/swapToken") public String swapToken(HttpServletRequest request) throws Exception { final String errorMessage = request.getParameter("error"); final Guest guest = AuthHelper.getGuest(); Connector connector = Connector.getConnector("up"); if (errorMessage!=null) { notificationsService.addNamedNotification(guest.getId(), Notification.Type.ERROR, connector.statusNotificationName(), "There was an error while setting you up with the Jawbone UP service: " + errorMessage); return "redirect:/app"; } final String code = request.getParameter("code"); Map<String,String> parameters = new HashMap<String,String>(); parameters.put("grant_type", "authorization_code"); parameters.put("code", code); parameters.put("client_id", env.get("jawboneUp.client.id")); parameters.put("client_secret", env.get("jawboneUp.client.secret")); final String json = HttpUtils.fetch("https://jawbone.com/auth/oauth2/token", parameters); JSONObject token = JSONObject.fromObject(json); if (token.has("error")) { String errorCode = token.getString("error"); notificationsService.addNamedNotification(guest.getId(), Notification.Type.ERROR, connector.statusNotificationName(), errorCode); // NOTE: In the future if we implement renew for the UP connector // we will potentially need to mark the connector as permanently failed. // The way to do this is to get hold of the existing apiKey and do: // guestService.setApiKeyStatus(apiKey.getId(), ApiKey.Status.STATUS_PERMANENT_FAILURE, null); return "redirect:/app"; } final String refresh_token = token.getString("refresh_token"); // Create the entry for this new apiKey in the apiKey table and populate // ApiKeyAttributes with all of the keys fro oauth.properties needed for // subsequent update of this connector instance. ApiKey apiKey; final String stateParameter = request.getParameter("state"); if (stateParameter !=null&&!StringUtils.isEmpty(stateParameter)) { long apiKeyId = Long.valueOf(stateParameter); apiKey = guestService.getApiKey(apiKeyId); } else { apiKey = guestService.createApiKey(guest.getId(), Connector.getConnector("up")); } guestService.populateApiKey(apiKey.getId()); guestService.setApiKeyAttribute(apiKey, "accessToken", token.getString("access_token")); int expiresIn = token.getInt("expires_in"); guestService.setApiKeyAttribute(apiKey, "tokenExpires", String.valueOf(new BigInteger(String.valueOf(System.currentTimeMillis())). add(new BigInteger(String.valueOf(expiresIn*1000))))); guestService.setApiKeyAttribute(apiKey, "refreshToken", refresh_token); // Record that this connector is now up guestService.setApiKeyStatus(apiKey.getId(), ApiKey.Status.STATUS_UP, null, null); if (stateParameter !=null&&!StringUtils.isEmpty(stateParameter)) { updater.refreshToken(apiKey); return "redirect:/app/tokenRenewed/up"; } else return "redirect:/app/from/up"; } @RequestMapping(value="/img/{guestId}/{apiKeyId}/**") public void getSnapshotImage(@PathVariable("guestId") long guestId, @PathVariable("apiKeyId") long apiKeyId, HttpServletRequest request, HttpServletResponse response) throws IOException { final String widthParameter = request.getParameter("w"); Integer width = null; if (widthParameter!=null) width = Integer.valueOf(widthParameter); final String requestURI = request.getRequestURI(); final String prefix = new StringBuilder("/up/img/").append(guestId).append("/").append(apiKeyId).append("/").toString(); final String snapshotImagePath = requestURI.substring(prefix.length()); final String devKvsLocation = env.get("btdatastore.db.location"); File f = new File(new StringBuilder(devKvsLocation).append(File.separator) .append(guestId) .append(File.separator) .append(Connector.getConnector("up").prettyName()) .append(File.separator) .append(apiKeyId) .append(File.separator) .append(snapshotImagePath).toString()); if (width!=null) Thumbnailator.createThumbnail(new FileInputStream(f), response.getOutputStream(), width, Integer.MAX_VALUE); else IOUtils.copy(new FileInputStream(f), response.getOutputStream()); } }