
com.threerings.facebook.servlet.Swizzler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ooo-facebook Show documentation
Show all versions of ooo-facebook Show documentation
Various Facebook helper bits used by OOO.
The newest version!
//
// $Id$
package com.threerings.facebook.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
import com.samskivert.servlet.util.CookieUtil;
import com.threerings.app.client.ServiceException;
import com.threerings.app.data.AppCodes;
import com.threerings.app.server.ServletAuthUtil;
import com.threerings.app.server.ServletLogic;
import com.threerings.servlet.util.Parameters;
import com.threerings.servlet.util.QueryBuilder;
import com.threerings.user.OOOSiteIdentifier;
import static com.threerings.user.ExternalAuther.FACEBOOK;
import com.threerings.facebook.SignedRequest;
import static com.threerings.facebook.Log.log;
/**
* Handles the fiddly business necessary to authenticate a Facebook app. This may involve
* redirecting the user out of Facebook to set a cookie and then back to the app.
*
* One special bit of handling is done to allow linking directly to a GWT page and
* preserving that page through the complicated series of redirects needed to properly
* authenticate on all browsers. You can direct the user to
* http://apps.facebook.com/yourapp/?token=SOMETOKEN
and we will make every effort
* to eventually load, inside the iframe, indexPath#SOMETOKEN
.
*/
public class Swizzler
{
/**
* Creates a swizzler for the given page request.
*
* @param indexUrl where to send properly authenticated users.
* @param allowGuests if false, require users to accept the app before being sent to indexUrl.
* @param nakedAppURL if parameters should be put into {@link AppCodes#PARAMETER_COOKIE} rather
* than being left on indexUrl.
*/
public Swizzler (HttpServletRequest req, HttpServletResponse rsp, FacebookConfig fbconf,
ServletLogic logic, String indexUrl, boolean allowGuests, boolean nakedAppURL)
throws IOException
{
_fbconf = fbconf;
_servletLogic = logic;
_req = req;
_rsp = rsp;
_indexPath = indexUrl;
_allowGuests = allowGuests;
_nakedAppURL = nakedAppURL;
_appURL = _fbconf.getFacebookAppURL(_req);
_params = new Parameters(_req);
_fbsig = new SignedRequest(_params, _fbconf.getFacebookSecret());
_swizzled = swizzle();
}
/**
* Returns true if this swizzle is on a facebook page.
*/
public boolean inFacebook ()
{
return _fbsig.isPresent();
}
/**
* Returns true if this user hasn't authorized our app.
*/
public boolean isGuest ()
{
return !_fbsig.isAuthorized();
}
/**
* Returns true if the user is already authenticated and they were redirected to the app, false
* if some part of the swizzling process was performed.
*/
public boolean isSwizzled()
{
return _swizzled;
}
protected boolean swizzle ()
throws IOException
{
if (inFacebook()) { // if we're in facebook then...
if (!isGuest()) { // ...if they've added our app, we're good
return doAddedAuth();
} else if (_allowGuests) { // ...if not, either let them in as a guest
return doGuestAuth();
} else { // ...or redirect them to the app login page
QueryBuilder qb = new QueryBuilder().
add("client_id", _fbconf.getFacebookAppId()).
add("redirect_uri", makeCleanQuery().toUrl(_appURL));
writeFrameRedirect(qb.toUrl("https://www.facebook.com/dialog/oauth"));
return false;
}
} else {
// if we're not in facebook, we must be swizzling
String authtok = _params.get("swizzle");
if (StringUtil.isBlank(authtok)) {
log.warning("We don't seem to be in Facebook or swizzling. WTF?",
"req", _req.getRequestURI());
_rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return false;
} else if (!_allowGuests && _servletLogic.getUser(authtok) == null) {
log.warning("Requested to swizzle with invalid auth token",
"req", _req.getRequestURI(), "authtok", authtok);
_rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return false;
} else {
// send these cookies out with our redirect and send them back to the app
addCookie(AppCodes.AUTH_COOKIE, authtok);
if (_nakedAppURL) {
QueryBuilder qb = makeCleanQuery().
add(AppCodes.PARAMETER_COOKIE_TIMESTAMP, System.currentTimeMillis());
addCookie(AppCodes.PARAMETER_COOKIE, qb.toString());
_rsp.sendRedirect(_appURL);
} else {
// we want to preserve all parameters except swizzle and fb params
_rsp.sendRedirect(makeCleanQuery().toUrl(_appURL));
}
return false;
}
}
}
protected boolean doGuestAuth ()
throws IOException
{
if (shouldSwizzleGuest()) {
// swizzle them a faked up session key that can be associated with a newly
// created account later
// concatenate some stuff together and MD5 hash it
String tok = new StringBuilder().
append(System.currentTimeMillis()).append(Math.random()).toString();
return swizzle(StringUtil.md5hex(tok));
}
return handleSwizzledUser();
}
protected boolean doAddedAuth ()
throws IOException
{
// if our external session is good to go, we're done
if (!_servletLogic.refreshExternalSession(FACEBOOK, _fbsig.getUserId(), _fbsig.getToken(),
_req, _rsp)) {
// otherwise we need to authenticate the user and swizzle authtok into a cookie
String authTok;
try {
authTok = _servletLogic.externalLogon(
FACEBOOK, _fbsig.getUserId(), _fbsig.getToken());
} catch (ServiceException se) {
log.warning("Failed to auth Facebook user", "uri", _req.getRequestURI(),
"cause", se.getMessage());
_rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return false;
}
ServletAuthUtil.addAuthCookie(_req, _rsp, authTok, -1);
return swizzle(authTok);
} else {
return handleSwizzledUser();
}
}
protected boolean handleSwizzledUser ()
throws IOException
{
if (_nakedAppURL) {
for (String name : _params.names()) {
if (!name.equals("signed_request")) {
// they've a valid auth token, but they have URL params we'd like to get rid of
return swizzle(CookieUtil.getCookieValue(_req, AppCodes.AUTH_COOKIE));
}
}
}
// They good! Send em to the index with the GWT history token if it exists
String url = _params.has("token") ? _indexPath + "#" + _params.get("token") : _indexPath;
_rsp.sendRedirect(url);
return true;
}
protected boolean shouldSwizzleGuest ()
{
if (_servletLogic.getUser(_req) != null) {
// we're dealing with a guest, and they've already been authenticated which is usually
// an indication that someone else was logged in on this browser, and then a different
// facebook user logged in.
return true;
} else if (StringUtil.isBlank(CookieUtil.getCookieValue(_req, AppCodes.AUTH_COOKIE))) {
return true; // they don't have an auth cookie, which they need
} else {
return false;
}
}
/**
* Frame redirect the client to the rebuilt url with authTok added as a swizzle param.
*/
protected boolean swizzle (String authTok)
throws IOException
{
// Kills HTTP Keep-Alive at this point, allowing Haproxy to see the next request and
// add its SERVERID cookie to the swizzle so that users remain on the server they're
// assigned to.
_rsp.addHeader("Connection", "close");
writeFrameRedirect(makeCleanQuery().add("swizzle", authTok).toUrl(_req.getRequestURI()));
return false;
}
public void writeFrameRedirect (String url)
throws IOException
{
// we're in an iframe so we have to send down some JavaScript that jimmies the URL
PrintWriter out = _rsp.getWriter();
out.println("");
out.close();
}
protected QueryBuilder makeCleanQuery ()
{
QueryBuilder qb = new QueryBuilder();
for (Tuple entry : _params.entries()) {
if (!entry.left.equals("swizzle") && !entry.left.equals("signed_request")) {
qb.add(entry.left, entry.right);
}
}
return qb;
}
protected void addCookie (String key, Object value)
{
Cookie cookie = new Cookie(key, String.valueOf(value));
cookie.setMaxAge(-1);
cookie.setPath("/");
if (!_req.getServerName().equals("localhost")) {
// we'll be requested as something like 'apps.threerings.net' and we want this cookie
// to be visible to billing.apps.threerings.net so we need to prefix domain with a .
cookie.setDomain("." + _req.getServerName());
}
_rsp.addCookie(cookie);
}
protected final FacebookConfig _fbconf;
protected final boolean _swizzled;
protected final boolean _nakedAppURL, _allowGuests;
protected final Parameters _params;
protected final SignedRequest _fbsig;
protected final HttpServletResponse _rsp;
protected final HttpServletRequest _req;
protected final String _appURL, _indexPath;
protected final ServletLogic _servletLogic;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy