/*--------------------------------------------------------------------------+ | 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); } }