/*--------------------------------------------------------------------------+ | Copyright (c) 2006 Facebook, Inc. | | All rights reserved. | | | | Redistribution and use in source and binary forms, with or without | | modification, are permitted provided that the following conditions | | are met: | | | | 1. Redistributions of source code must retain the above copyright | | notice, this list of conditions and the following disclaimer. | | 2. Redistributions in binary form must reproduce the above copyright | | notice, this list of conditions and the following disclaimer in the | | documentation and/or other materials provided with the distribution. | | | | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | +---------------------------------------------------------------------------+ | For help with this library, contact api-help@facebook.com | +--------------------------------------------------------------------------*/ package com.moochspot.elements; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.Cookie; import com.moochspot.db.PersonDAO; import com.moochspot.model.Person; import com.moochspot.service.PersonService; import com.moochspot.util.Constants; import com.moochspot.util.FacebookApi; import com.moochspot.util.FacebookCredentials; import com.moochspot.util.FacebookFriend; import com.uwyn.rife.authentication.SessionManager; import com.uwyn.rife.authentication.SessionValidator; import com.uwyn.rife.authentication.credentials.RoleUser; import com.uwyn.rife.authentication.credentialsmanagers.DatabaseUsers; import com.uwyn.rife.authentication.credentialsmanagers.DatabaseUsersFactory; import com.uwyn.rife.authentication.credentialsmanagers.RoleUserAttributes; import com.uwyn.rife.authentication.credentialsmanagers.RoleUserIdentity; import com.uwyn.rife.authentication.elements.AuthenticatedDeployer; import com.uwyn.rife.authentication.elements.FactoryPropertyAuthenticated; import com.uwyn.rife.authentication.exceptions.SessionManagerException; import com.uwyn.rife.database.Datasource; import com.uwyn.rife.engine.exceptions.EngineException; /** * Authentication element for Facebook logins. Every page that requires a * Facebook login is in a subsite that is protected by this element. The * element checks for a valid Facebook session ID and uses it to derive * the user's identity. *

* See the * RIFE user's guide or the RIFE Wiki's * authentication system internals pages for more background about * RIFE's authentiation system. */ public class FacebookAuthenticated extends FactoryPropertyAuthenticated { private Logger _log = Logger.getLogger(getClass().getName()); private String _serverUrl = FacebookApi.DEFAULT_API_URL; private String _loginUrl = "http://api.facebook.com/login.php"; private SessionManager _sessionManager; /** * Authentication-related user information is kept in RIFE's normal user * tables, which are accessed with the built-in user database class. */ private DatabaseUsers _dbUsers; /* Service objects; these are dependency-injected at startup. */ private Datasource _datasource; private PersonDAO _personDao; private PersonService _personService; /** * RIFE calls this before each invocation of * {@link #processElement()}. */ public void initialize() { /* * Allow the server and login URLs to be overridden in the configuration * for developer setups. */ _serverUrl = getPropertyString("facebook_api_url", _serverUrl); _loginUrl = getPropertyString("facebook_login_url", _loginUrl); _sessionManager = ((AuthenticatedDeployer)getDeployer()) .getSessionValidator() .getSessionManager(); super.initialize(); } /** * Redirects the user to the Facebook login page. After login, we always * take the user back to their account home page. */ protected void redirectToFacebook() { String accountHomeUrl = getExitQueryUrl("accountHome").toString(); if (accountHomeUrl.startsWith("http://")) { accountHomeUrl = accountHomeUrl.substring(7); } else { accountHomeUrl = getServerRootUrl().substring(7) + accountHomeUrl; } try { redirect(_loginUrl + "?api_key=" + Constants.FACEBOOK_KEY + "&next=" + URLEncoder.encode(accountHomeUrl, "UTF-8")); } catch (UnsupportedEncodingException e) { _log.log(Level.SEVERE, "Can't do UTF-8 encoding!?!", e); print("Fatal error!"); } } /** * Tries to authenticate a user who doesn't have a valid session. This * method is called when the user has no "authid" cookie, or when the * cookie's value refers to a session that has expired. *

* If a user has no cookie, it might be because they've never visited the * site before. Since we don't have an explicit registration process * (Facebook gives us all the information we need to register a user) * the new user creation happens here too. */ public void processElement() throws EngineException { RoleUser user = null; SessionValidator validator = ((AuthenticatedDeployer)getDeployer()) .getSessionValidator(); /* * If the user was redirected here from a Facebook login, there * will be a valid "auth_token" parameter. */ setProhibitRawAccess(false); FacebookCredentials creds = new FacebookApi(_serverUrl, null) .fetchCredentials(getHttpServletRequest() .getParameter("auth_token")); if (creds == null || !creds.hasData()) { /* * There was no auth token, or it was invalid. Send the user to * the Facebook home page to log in. */ _log.fine("Redirecting to Facebook"); redirectToFacebook(); } else { _log.fine("Got Facebook credentials " + creds); FacebookApi api = new FacebookApi(_serverUrl, creds.getSessionKey()); FacebookFriend userInfo = api.getFriend(creds.getUid()); // Trouble contacting the API server? Fail gracefully. if (userInfo == null) { _log.info("Can't look up user information"); /* * This passes control to a globally defined error page. It * throws a runtime exception to avoid continuing to execute * the code here. */ exit("errorPage"); } user = new RoleUser(creds.getUid(), creds.getSessionKey(), "member"); RoleUserAttributes attrs = new RoleUserAttributes(); attrs.setPassword(creds.getSessionKey()); attrs.setRoles(new String[] { user.getRole() }); validator.setCredentialsManager(_dbUsers); validator.setSessionManager(_sessionManager); validator.setRememberManager(null); // Do we know about this person already? Person p = _personDao.getPersonByFacebookUid(creds.getUid()); if (p == null) { // Not in our database yet; create a new record. p = new Person(); p.setCreatedTime(new Date()); p.setPersonType(Person.PERSON_TYPE_FACEBOOK); p.setName(userInfo.name); _personService.createFacebookPerson(p, creds.getUid()); } p.setLastLogin(new Date()); // Update the user's information in case it changed on Facebook p.setName(userInfo.name); p.setFirstName(userInfo.firstName); _personDao.save(p); /* Use the person ID as the authentication ID too. */ attrs.setUserId(p.getPersonId()); try { /* * The user might have been previously known (added as a friend) * without having ever logged in. In that case he won't be in * the authentication database, so add him. * * Otherwise, update the authentication db with the user's * current session key so we can do API calls later. */ if (_dbUsers.containsUser(creds.getUid())) _dbUsers.updateUser(creds.getUid(), attrs); else _dbUsers.addUser(creds.getUid(), attrs); /* * Get a new authid cookie value from the session manager so * subsequent requests will be handled by childTriggered() * and we won't have to go through this code again. */ String sessionString = _sessionManager.startSession( attrs.getUserId(),getRemoteAddr(), false); Cookie cookie = new Cookie(getPropertyString("authvar_name"), sessionString); cookie.setPath("/"); _log.info("Logged in: " + p.getName() + " (" + creds.getUid() + ") with session " + creds.getSessionKey()); // This call actually hands control to childTriggered() setCookie(cookie); } catch (SessionManagerException e) { throw new EngineException(e); } continueToChild(creds.getUid()); } } /** * Continues to the child element after putting the user's identity on * the request so the child will see that this is an authenticated request. */ protected void continueToChild(String login) { setupIdentity(login); child(); } protected void setupIdentity(String login) { RoleUserAttributes attrs = _dbUsers.getAttributes(login); RoleUserIdentity identity = new RoleUserIdentity(login, attrs); setRequestAttribute(IDENTITY_ATTRIBUTE_NAME, identity); } /** * Handles a request that has an authid cookie. This checks to see if * the cookie is valid by calling a superclass method that talks to * the session manager. */ public boolean childTriggered(String name, String[] values) { if (getRequestAttribute(IDENTITY_ATTRIBUTE_NAME) == null) { RoleUserIdentity identity = getIdentity(name, values); if (null == identity) return false; setRequestAttribute(IDENTITY_ATTRIBUTE_NAME, identity); } return true; } public String name() { return "authid"; } /* * Setters for configuration injection. These are called by RIFE before * it calls our processElement() or childTriggered() methods. */ public void setServerUrl(String value) { _serverUrl = value; } public void setLoginUrl(String value) { _loginUrl = value; } public void setPersonDao(PersonDAO arg) { _personDao = arg; } public void setPersonService(PersonService arg) { _personService = arg; } public void setDatasource(Datasource ds) { _datasource = ds; _dbUsers = DatabaseUsersFactory.getInstance(_datasource); } }