package com.leanengine.server.auth; import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.appengine.api.urlfetch.URLFetchService; import com.google.appengine.api.urlfetch.URLFetchServiceFactory; import com.leanengine.server.JsonUtils; import com.leanengine.server.LeanEngineSettings; import com.leanengine.server.LeanException; import com.leanengine.server.appengine.AccountUtils; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonProcessingException; import org.codehaus.jackson.map.ObjectMapper; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.logging.Logger; public class FacebookAuth { private static final Logger log = Logger.getLogger(FacebookAuth.class.getName()); public static String getLoginUrlMobile(String serverName, String state, String facebookLoginPath, String display) throws LeanException { if (LeanEngineSettings.getFacebookAppID() == null) { throw new LeanException(LeanException.Error.FacebookAuthMissingAppId); } String redirectUrl = serverName + facebookLoginPath; StringBuilder stringBuilder = new StringBuilder(); stringBuilder .append("https://m.facebook.com/dialog/oauth?") .append("client_id=").append(LeanEngineSettings.getFacebookAppID()).append("&") .append("redirect_uri=").append(redirectUrl).append("&"); if (display != null) stringBuilder.append("display=").append(display).append("&"); stringBuilder.append("scope=offline_access&") .append("response_type=code&") .append("state=") .append(state); return stringBuilder.toString(); } public static String getLoginUrlWeb(String serverName, String state, String facebookLoginPath) throws LeanException { if (LeanEngineSettings.getFacebookAppID() == null) { throw new LeanException(LeanException.Error.FacebookAuthMissingAppId); } String redirectUrl = serverName + facebookLoginPath; return "https://www.facebook.com/dialog/oauth?" + "client_id=" + LeanEngineSettings.getFacebookAppID() + "&" + "redirect_uri=" + redirectUrl + "&" + "scope=offline_access&" + "response_type=code&" + "state=" + state; } public static String getGraphAuthUrl(String redirectUrl, String code) throws LeanException { if (LeanEngineSettings.getFacebookAppID() == null) { throw new LeanException(LeanException.Error.FacebookAuthMissingAppId); } if (LeanEngineSettings.getFacebookAppID() == null) { throw new LeanException(LeanException.Error.FacebookAuthMissingAppSecret); } return "https://graph.facebook.com/oauth/access_token?" + "client_id=" + LeanEngineSettings.getFacebookAppID() + "&" + "redirect_uri=" + redirectUrl + "&" + "client_secret=" + LeanEngineSettings.getFacebookAppSecret() + "&" + "code=" + code; } public static JsonNode fetchUserDataFromFacebook(String access_token) throws LeanException { String url = "https://graph.facebook.com/me?access_token=" + access_token; URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService(); HTTPResponse fetchResponse; try { fetchResponse = fetchService.fetch(new URL(url)); if (fetchResponse.getResponseCode() == 200) { return JsonUtils.getObjectMapper().readTree(new String(fetchResponse.getContent(), "UTF-8")); } else { throw new LeanException(LeanException.Error.FacebookAuthError); } } catch (MalformedURLException ex) { //todo This should not happen - log it } catch (JsonProcessingException ex) { throw new LeanException(LeanException.Error.FacebookAuthParseError, ex); } catch (IOException e) { throw new LeanException(LeanException.Error.FacebookAuthConnectError, e); } return null; } public static AuthToken authenticateWithOAuthGraphAPI(String currentUrl, String code) throws LeanException { try { URL facebookGraphUrl = new URL(FacebookAuth.getGraphAuthUrl(currentUrl, code)); log.info("facebookGraphUrl="+facebookGraphUrl); URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService(); HTTPResponse fetchResponse = fetchService.fetch(facebookGraphUrl); String responseContent = new String(fetchResponse.getContent(), Charset.forName("UTF-8")); if (fetchResponse.getResponseCode() == 400) { // error: facebook server error replied with 400 throw new LeanException(LeanException.Error.FacebookAuthResponseError," \n\n"+responseContent); } String fbAccessToken = null, expires = null; String[] splitResponse = responseContent.split("&"); for (String split : splitResponse) { String[] parts = split.split("="); if (parts.length != 2) break; fbAccessToken = parts[0].equals("access_token") ? parts[1] : fbAccessToken; expires = parts[0].equals("expires") ? parts[1] : expires; } // check if we got required parameters if (fbAccessToken == null) { //error: wrong parameters: facebook should return 'access_token' parameter throw new LeanException(LeanException.Error.FacebookAuthMissingParam, " 'access_token' not available."); } // All is good - check the user JsonNode userData = FacebookAuth.fetchUserDataFromFacebook(fbAccessToken); String providerID = userData.get("id").getTextValue(); if (providerID == null || providerID.length() == 0) { //Facebook returned user data but ID field is missing throw new LeanException(LeanException.Error.FacebookAuthMissingParam, " Missing ID field in user data. Content: " + responseContent); } LeanAccount account = AccountUtils.findAccountByProvider(providerID, "fb-oauth"); if (account == null) { //todo this is one-to-one mapping between Account and User //change this in the future // account does not yet exist - create it account = parseAccountFB(userData); AccountUtils.saveAccount(account); } // create our own authentication token return AuthService.createAuthToken(account.id); } catch (MalformedURLException e) { throw new LeanException(LeanException.Error.FacebookAuthNoConnection, e); } catch (IOException e) { throw new LeanException(LeanException.Error.FacebookAuthNoConnection, e); } } /** * Creates LeanAccount form data returned by Facebook authentication service * * @param userData JSON data as provided by Facebook OAuth * @return */ public static LeanAccount parseAccountFB(JsonNode userData) { Map<String, Object> props = new HashMap<String, Object>(userData.size()); Iterator<String> fields = userData.getFieldNames(); while (fields.hasNext()) { String field = fields.next(); // field 'id' is not treated as part of provider properties if (field.equals("id")) continue; props.put(field, userData.get(field).getValueAsText()); } return new LeanAccount( 0, userData.get("name").getTextValue(), userData.get("id").getTextValue(), "fb-oauth", props); } }