package com.leanengine.server.auth;
import com.leanengine.server.LeanEngineSettings;
import com.leanengine.server.LeanException;
import com.leanengine.server.appengine.ServerUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.UUID;
public class FacebookLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String errorCode = request.getParameter("error");
String authorizationCode = request.getParameter("code");
// checking the stage of a Facebook OAuth flow
if (errorCode == null && authorizationCode == null) { // first part of Facebook OAuth flow
String type = request.getParameter("type");
String display = request.getParameter("display");
Scheme scheme;
String facebookAntiCSRF;
// checking if request is coming from mobile client
boolean isMobile = type != null && type.equals("mobile");
String redirectUrl;
if (request.getParameter("onlogin") != null) {
redirectUrl = request.getParameter("onlogin");
} else {
redirectUrl = "/";
}
String errorUrl;
if (request.getParameter("onerror") != null) {
errorUrl = request.getParameter("onerror");
} else {
errorUrl = "/loginerror";
}
if (isMobile) {
// login via mobile device - this means redirecting to 'leanengine://hostname/?access_token=..' URLs
scheme = new MobileScheme(request.getServerName());
// we add 'mob:' in front of state parameter to indicate that request comes from mobile device
facebookAntiCSRF = "mob:" + UUID.randomUUID().toString();
} else {
// login via web interface
String hostname = request.getServerName();
if (request.getLocalPort() != 80 && request.getLocalPort() != 0) {
hostname = hostname + ":" + request.getLocalPort();
}
scheme = new WebScheme(request.getScheme(), hostname);
facebookAntiCSRF = "web:" + UUID.randomUUID().toString() + ":" + redirectUrl + ":" + errorUrl;
}
// Facebook login is not enabled in settings
if (!LeanEngineSettings.isFacebookLoginEnabled()) {
response.sendRedirect(
scheme.getErrorUrl(new LeanException(LeanException.Error.FacebookAuthNotEnabled), errorUrl));
return;
}
// development server mocks Facebook logins
if (ServerUtils.isDevServer()) {
String mockEmail = request.getParameter("email");
String action = request.getParameter("action");
if (mockEmail == null) {
FacebookMockLogin.showForm(request, response, isMobile);
} else {
if ("Log Out".equals(action)) {
response.sendRedirect(scheme.getErrorUrl(
new LeanException(LeanException.Error.FacebookAuthError, " User cancelled login."), errorUrl));
} else {
FacebookMockLogin.login(request, response, mockEmail, scheme, redirectUrl, errorUrl);
}
}
return;
}
// 'state' parameter is passed around the Facebook OAuth redirects and ends up at our final auth page
// Primarily it's used for preventing CSRF attacks, but we also use it to signal the login type to the final auth page
HttpSession session = request.getSession(true);
session.setAttribute("antiCSRF", facebookAntiCSRF);
// get Facebook OAuth Login URL
String loginUrl = null;
try {
loginUrl = isMobile ?
FacebookAuth.getLoginUrlMobile(request.getScheme() + "://" + request.getServerName(),
facebookAntiCSRF, request.getRequestURI(), display) :
FacebookAuth.getLoginUrlWeb(request.getScheme() + "://" + request.getServerName(),
facebookAntiCSRF, request.getRequestURI());
} catch (LeanException e) {
response.sendRedirect(scheme.getErrorUrl(e, errorUrl));
return;
}
response.sendRedirect(loginUrl);
} else { // second part of Facebook OAuth flow
// user is trying to login
// reset the existing auth data if it exists
AuthService.resetCurrentAuthData();
HttpSession session = request.getSession(true);
session.removeAttribute("lean_token");
// include the port number - usually needed for Dev server
String hostname = request.getServerName();
if (request.getLocalPort() != 80 && request.getLocalPort() != 0) {
hostname = hostname + ":" + request.getLocalPort();
}
// get the 'state' parameter containing CSRF code
String state = request.getParameter("state");
// 'state' must be equal to 'antiCSRF' attribute saved to web session
if (state == null || !state.equals(session.getAttribute("antiCSRF"))) {
// oauth error - redirect back to client with error
response.sendRedirect(new WebScheme(request.getScheme(),
hostname).getErrorUrl(new LeanException(LeanException.Error.FacebookAuthMissingCRSF), "/"));
return;
}
String redirectUrl = null;
String errorUrl = null;
// 'state' parameter has format "login_type:CSRFtoken:redirect_url:error_url"
// extract the login type from 'state' parameter
Scheme scheme;
if (state.startsWith("mob:")) {
scheme = new MobileScheme(request.getServerName());
} else {
scheme = new WebScheme(request.getScheme(), hostname);
// extract the redirect URL
String[] stateItems = state.split(":");
redirectUrl = (stateItems.length == 4) ? stateItems[2] : null;
errorUrl = (stateItems.length == 4) ? stateItems[3] : null;
}
// error url might not have been supplied if this second part of the flow was invoked directly
errorUrl = (errorUrl == null || errorUrl.isEmpty()) ? "/loginerror" : errorUrl;
// did Facebook OAuth return error?
if (errorCode != null) {
// oauth error - redirect back to client with error
response.sendRedirect(scheme.getErrorUrl(
new LeanException(LeanException.Error.FacebookAuthError, " OAuth error: " + errorCode), errorUrl));
} else { // no error
String currentUrl = request.getRequestURL().toString();
try {
// authenticate with Facebook Graph OAuth API
// this makes a direct connection from server to 'https://graph.facebook.com/oauth/access_token'
AuthToken lean_token = FacebookAuth.authenticateWithOAuthGraphAPI(currentUrl, authorizationCode);
// save token in session
session.setAttribute("lean_token", lean_token.token);
//send lean_token back to browser
response.sendRedirect(scheme.getUrl(lean_token.token, redirectUrl));
} catch (LeanException le) {
response.sendRedirect(scheme.getErrorUrl(le, errorUrl));
}
}
}
}
}