/* * Copyright 2012 George Armhold * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * under the License. */ package com.example.justaddwater.facebook; import com.example.justaddwater.model.AuthenticationType; import com.example.justaddwater.model.DAO; import com.example.justaddwater.model.User; import com.example.justaddwater.web.app.AccountPage; import com.example.justaddwater.web.app.LoginPage; import com.example.justaddwater.web.app.LoginUtil; import com.example.justaddwater.web.app.MySession; import com.visural.common.IOUtil; import com.visural.common.StringUtil; import net.ftlines.blog.cdidemo.web.UserAction; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.request.Url; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.util.string.Strings; import org.json.JSONObject; import org.slf4j.Logger; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.servlet.http.HttpServletRequest; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Date; /** * Most of this code is derived from the following blog post by Richard Nichols: * * http://www.richardnichols.net/2010/06/implementing-facebook-oauth-2-0-authentication-in-java * * Here is the basic flow: * * 1. create "login with facebook" link somewhere in your app using getFacebookLoginUrl() * 2. facebook will authenticate user, and send them back to this page (via pathToFBOAuthPage()) * 3. we pull the "code" param out of the request, and send this Facebook for verification via getAuthURL() * 4. parse the response for the access_token * 5. use the access_token to retrieve the user's email (etc) via getEmailFromFacebookGraphAPI() * 6. set user's email in MySession; user is now logged in * * @author George Armhold armhold@gmail.com */ public class FacebookOAuthPage extends WebPage { @Inject Logger log; @Inject DAO dao; @Inject EntityManager em; @Inject LoginUtil loginUtil; @Inject MySession session; @Inject UserAction action; // get these from your FB Dev App private static final String FACEBOOK_APP_ID = "your-facebook-app-id"; private static final String FACEBOOK_APP_SECRET = "your-facebook-app-secret"; // set this to the list of extended permissions you want private static final String[] perms = new String[] { /* "publish_stream", */ "email" }; /** * @return the (Facebook) URL where we send the user for authentication */ public static String getFacebookLoginUrl() { return "https://graph.facebook.com/oauth/authorize?client_id=" + FACEBOOK_APP_ID + "&display=page&redirect_uri=" + pathToFBOAuthPage() + "&scope=" + StringUtil.delimitObjectsToString(",", perms); } private String getAuthURL(String authCode) { return "https://graph.facebook.com/oauth/access_token?client_id=" + FACEBOOK_APP_ID + "&redirect_uri=" + pathToFBOAuthPage() + "&client_secret=" + FACEBOOK_APP_SECRET + "&code=" + authCode; } /** * @return the URL where the user should be directed after successfully authenticating to Facebook, i.e. this page */ public static String pathToFBOAuthPage() { RequestCycle rc = RequestCycle.get(); return rc.getUrlRenderer().renderFullUrl(Url.parse(rc.urlFor(FacebookOAuthPage.class, null).toString())); } public FacebookOAuthPage() { log.info("reached FacebookOAuthPage from facebook login"); HttpServletRequest req = (HttpServletRequest) getRequest().getContainerRequest(); String code = req.getParameter("code"); if (! Strings.isEmpty(code)) { String authURL = getAuthURL(code); try { String result = readFromURL(authURL); String accessToken = null; Integer expires = null; String[] pairs = result.split("&"); for (String pair : pairs) { String[] kv = pair.split("="); if (kv.length != 2) { throw new RuntimeException("Unexpected auth response"); } else { if (kv[0].equals("access_token")) { accessToken = kv[1]; } if (kv[0].equals("expires")) { expires = Integer.valueOf(kv[1]); } } } if (accessToken != null && expires != null) { logUserIn(accessToken, expires); setResponsePage(AccountPage.class); } else { throw new RuntimeException("Access token and expires not found"); } } catch (IOException e) { throw new RuntimeException(e); } } else { setResponsePage(LoginPage.class); } } private String readFromURL(String authURL) throws IOException { URL url = new URL(authURL); ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream is = url.openStream(); int r; while ((r = is.read()) != -1) { baos.write(r); } return new String(baos.toByteArray()); } /** * set username in user session to value returned from the Facebook graph api- * this marks the user as being logged in. */ private void logUserIn(String accessToken, int expires) { try { String email = getEmailFromFacebookGraphAPI(accessToken, expires); User user = findOrCreateFacebookUser(email); session.setUsername(user.getEmail()); log.info("logged on via facebook as: " + session.getUsername()); } catch (Throwable ex) { throw new RuntimeException("failed login", ex); } } private String getEmailFromFacebookGraphAPI(String accessToken, int expires) { try { JSONObject resp = new JSONObject(IOUtil.urlToString(new URL("https://graph.facebook.com/me?access_token=" + accessToken))); // other objects available here are: "id", "first_name", "last_name", etc. return resp.getString("email"); } catch (Exception e) { throw new RuntimeException("erorr getting email address from Facebook", e); } } private User findOrCreateFacebookUser(String email) { User user = dao.findUserByEmail(email); if (user == null) { user = new User(); user.setEmail(email); user.setAuthenticationType(AuthenticationType.facebook); user.setAccountCreationDate(new Date()); em.persist(user); action.apply(); log.info("created new facebook user account: " + email); } else if (user.getAuthenticationType() != AuthenticationType.facebook) { throw new RuntimeException("bad login- account has a local password, not a facebook login"); } return user; } }