/**********************************************************************************
* $URL: $
* $Id: $
***********************************************************************************
*
* Copyright (c) 2008 The Sakai Foundation.
*
* Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
*
* 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.
*
**********************************************************************************/
package org.sakaiproject.login.tool;
import java.io.IOException;
import java.io.PrintWriter;
import javax.security.auth.login.LoginException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.event.cover.UsageSessionService;
import org.sakaiproject.login.api.Login;
import org.sakaiproject.login.api.LoginCredentials;
import org.sakaiproject.login.api.LoginRenderContext;
import org.sakaiproject.login.api.LoginRenderEngine;
import org.sakaiproject.login.api.LoginService;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.api.Tool;
import org.sakaiproject.tool.cover.SessionManager;
import org.sakaiproject.util.ResourceLoader;
import org.sakaiproject.util.Web;
public class SkinnableLogin extends HttpServlet implements Login {
private static final long serialVersionUID = 1L;
/** Our log (commons). */
private static Log log = LogFactory.getLog(SkinnableLogin.class);
/** Session attribute used to store a message between steps. */
protected static final String ATTR_MSG = "notify";
/** Session attribute set and shared with ContainerLoginTool: URL for redirecting back here. */
public static final String ATTR_RETURN_URL = "sakai.login.return.url";
/** Session attribute set and shared with ContainerLoginTool: if set we have failed container and need to check internal. */
public static final String ATTR_CONTAINER_CHECKED = "sakai.login.container.checked";
/** Marker to indicate we are logging in the PDA Portal and should put out abbreviated HTML */
public static final String PDA_PORTAL_SUFFIX = "/pda/";
private static ResourceLoader rb = new ResourceLoader("auth");
private LoginService loginService;
private String loginContext;
public void init(ServletConfig config) throws ServletException
{
super.init(config);
loginContext = config.getInitParameter("login.context");
if (loginContext == null || loginContext.length() == 0)
{
loginContext = DEFAULT_LOGIN_CONTEXT;
}
loginService = org.sakaiproject.login.cover.LoginService.getInstance();
log.info("init()");
}
public void destroy()
{
log.info("destroy()");
super.destroy();
}
/**
* Access the Servlet's information display.
*
* @return servlet information.
*/
public String getServletInfo()
{
return "Sakai Login";
}
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
// get the session
Session session = SessionManager.getCurrentSession();
// get my tool registration
Tool tool = (Tool) req.getAttribute(Tool.TOOL);
// recognize what to do from the path
String option = req.getPathInfo();
// maybe we don't want to do the container this time
boolean skipContainer = false;
// if missing, set it to "/login"
if ((option == null) || ("/".equals(option)))
{
option = "/login";
}
// look for the extreme login (i.e. to skip container checks)
else if ("/xlogin".equals(option))
{
option = "/login";
skipContainer = true;
}
// get the parts (the first will be "", second will be "login" or "logout")
String[] parts = option.split("/");
if (parts[1].equals("logout"))
{
// get the session info complete needs, since the logout will invalidate and clear the session
String returnUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL);
// logout the user
UsageSessionService.logout();
complete(returnUrl, null, tool, res);
return;
}
// see if we need to check container
boolean checkContainer = ServerConfigurationService.getBoolean("container.login", false);
if (checkContainer && !skipContainer)
{
// if we have not checked the container yet, check it now
if (session.getAttribute(ATTR_CONTAINER_CHECKED) == null)
{
// save our return path
session.setAttribute(ATTR_RETURN_URL, Web.returnUrl(req, null));
String containerCheckPath = this.getServletConfig().getInitParameter("container");
String containerCheckUrl = Web.serverUrl(req) + containerCheckPath;
// support query parms in url for container auth
String queryString = req.getQueryString();
if (queryString != null) containerCheckUrl = containerCheckUrl + "?" + queryString;
res.sendRedirect(res.encodeRedirectURL(containerCheckUrl));
return;
}
}
// PDA or not?
String portalUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL);
boolean isPDA = false;
if ( portalUrl != null ) {
isPDA = (portalUrl.indexOf (PDA_PORTAL_SUFFIX) > 0);
}
log.debug("isPDA: " + isPDA);
// Present the xlogin template
LoginRenderContext rcontext = startPageContext("", req, res);
rcontext.put("isPDA", isPDA);
// Decide whether or not to put up the Cancel
String actualPortal = ServerConfigurationService.getPortalUrl();
if ( portalUrl != null && portalUrl.indexOf("/site/") < 1 && portalUrl.startsWith(actualPortal) ) {
rcontext.put("doCancel", Boolean.TRUE);
}
sendResponse(rcontext, res, "xlogin", null);
}
/**
* Respond to data posting requests.
*
* @param req
* The servlet request.
* @param res
* The servlet response.
* @throws ServletException.
* @throws IOException.
*/
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
// Present the xlogin template
LoginRenderContext rcontext = startPageContext(null, req, res);
// Get the Sakai session
Session session = SessionManager.getCurrentSession();
// Get my tool registration
Tool tool = (Tool) req.getAttribute(Tool.TOOL);
// Determine if the user canceled this request
String cancel = req.getParameter("cancel");
// cancel
if (cancel != null)
{
rcontext.put(ATTR_MSG, rb.getString("log.canceled"));
// get the session info complete needs, since the logout will invalidate and clear the session
String returnUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL);
// Trim off the force.login parameter from return URL if present
if ( returnUrl != null )
{
int where = returnUrl.indexOf("?force.login");
if ( where > 0 ) returnUrl = returnUrl.substring(0,where);
}
// TODO: send to the cancel URL, cleanup session
complete(returnUrl, session, tool, res);
}
// submit
else
{
LoginCredentials credentials = new LoginCredentials(req);
credentials.setSessionId(session.getId());
try {
loginService.authenticate(credentials);
String returnUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL);
complete(returnUrl, session, tool, res);
} catch (LoginException le) {
String message = le.getMessage();
log.debug("LoginException: " + message);
boolean showAdvice = false;
if (message.equals(EXCEPTION_INVALID_CREDENTIALS)) {
rcontext.put(ATTR_MSG, rb.getString("log.invalid.credentials"));
showAdvice = true;
} else if (message.equals(EXCEPTION_INVALID_WITH_PENALTY)) {
rcontext.put(ATTR_MSG, rb.getString("log.invalid.with.penalty"));
showAdvice = true;
} else if (message.equals(EXCEPTION_MISSING_CREDENTIALS))
rcontext.put(ATTR_MSG, rb.getString("log.tryagain"));
else
rcontext.put(ATTR_MSG, rb.getString("log.invalid"));
if (showAdvice) {
String loginAdvice = loginService.getLoginAdvice(credentials);
if (loginAdvice != null && !loginAdvice.equals("")) {
log.debug("Returning login advice");
rcontext.put("loginAdvice", loginAdvice);
}
}
// Decide whether or not to put up the Cancel
String portalUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL);
String actualPortal = ServerConfigurationService.getPortalUrl();
if ( portalUrl != null && portalUrl.indexOf("/site/") < 1 && portalUrl.startsWith(actualPortal) ) {
rcontext.put("doCancel", Boolean.TRUE);
}
sendResponse(rcontext, res, "xlogin", null);
}
}
}
public void sendResponse(LoginRenderContext rcontext, HttpServletResponse res,
String template, String contentType) throws IOException
{
// headers
if (contentType == null)
{
res.setContentType("text/html; charset=UTF-8");
}
else
{
res.setContentType(contentType);
}
res.addDateHeader("Expires", System.currentTimeMillis()
- (1000L * 60L * 60L * 24L * 365L));
res.addDateHeader("Last-Modified", System.currentTimeMillis());
res.addHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
res.addHeader("Pragma", "no-cache");
// get the writer
PrintWriter out = res.getWriter();
try
{
LoginRenderEngine rengine = rcontext.getRenderEngine();
rengine.render(template, rcontext, out);
}
catch (Exception e)
{
throw new RuntimeException("Failed to render template ", e);
}
}
public LoginRenderContext startPageContext(String skin, HttpServletRequest request, HttpServletResponse response)
{
LoginRenderEngine rengine = loginService.getRenderEngine(loginContext, request);
LoginRenderContext rcontext = rengine.newRenderContext(request);
if (skin == null || skin.trim().length() == 0)
{
skin = ServerConfigurationService.getString("skin.default");
}
String skinRepo = ServerConfigurationService.getString("skin.repo");
String uiService = ServerConfigurationService.getString("ui.service");
String eidWording = rb.getString("userid");
String pwWording = rb.getString("log.pass");
String loginRequired = rb.getString("log.logreq");
String loginWording = rb.getString("log.login");
String cancelWording = rb.getString("log.cancel");
rcontext.put("action", response.encodeURL(Web.returnUrl(request, null)));
rcontext.put("pageSkinRepo", skinRepo);
rcontext.put("pageSkin", skin);
rcontext.put("uiService", uiService);
rcontext.put("pageScriptPath", getScriptPath());
rcontext.put("loginEidWording", eidWording);
rcontext.put("loginPwWording", pwWording);
rcontext.put("loginRequired", loginRequired);
rcontext.put("loginWording", loginWording);
rcontext.put("cancelWording", cancelWording);
String eid = StringEscapeUtils.escapeHtml(request.getParameter("eid"));
String pw = StringEscapeUtils.escapeHtml(request.getParameter("pw"));
if (eid == null)
eid = "";
if (pw == null)
pw = "";
rcontext.put("eid", eid);
rcontext.put("password", pw);
return rcontext;
}
public String getLoginContext() {
return loginContext;
}
// Helper methods
/**
* Cleanup and redirect when we have a successful login / logout
*
* @param session
* @param tool
* @param res
* @throws IOException
*/
protected void complete(String returnUrl, Session session, Tool tool, HttpServletResponse res) throws IOException
{
// cleanup session
if (session != null)
{
session.removeAttribute(Tool.HELPER_MESSAGE);
session.removeAttribute(Tool.HELPER_DONE_URL);
session.removeAttribute(ATTR_MSG);
session.removeAttribute(ATTR_RETURN_URL);
session.removeAttribute(ATTR_CONTAINER_CHECKED);
}
// if we end up with nowhere to go, go to the portal
if (returnUrl == null)
{
returnUrl = ServerConfigurationService.getPortalUrl();
log.info("complete: nowhere set to go, going to portal");
}
// redirect to the done URL
res.sendRedirect(res.encodeRedirectURL(returnUrl));
}
protected String getScriptPath()
{
return "/library/js/";
}
}