/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 info.jtrac.wicket;
import info.jtrac.wicket.devmode.DebugHttpSessionStore;
import info.jtrac.Jtrac;
import info.jtrac.acegi.JtracCasProxyTicketValidator;
import info.jtrac.domain.Role;
import info.jtrac.domain.Space;
import info.jtrac.domain.User;
import info.jtrac.util.WebUtils;
import java.util.List;
import java.util.Locale;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.apache.wicket.Application;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.Request;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Response;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.Session;
import org.apache.wicket.authorization.Action;
import org.apache.wicket.authorization.IAuthorizationStrategy;
import org.apache.wicket.markup.html.pages.RedirectPage;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.request.target.coding.IndexedParamUrlCodingStrategy;
import org.apache.wicket.request.target.coding.QueryStringUrlCodingStrategy;
import org.apache.wicket.resource.loader.IStringResourceLoader;
import org.apache.wicket.session.ISessionStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
/**
* <p>
* Main Wicket application for jtrac.
* </p>
* <p>
* It holds singleton service layer instance pulled from Spring.
* </p>
*/
public class JtracApplication extends WebApplication {
/**
* Logger object
*/
private final static Logger logger = LoggerFactory.getLogger(JtracApplication.class);
private Jtrac jtrac;
private ApplicationContext applicationContext;
private JtracCasProxyTicketValidator jtracCasProxyTicketValidator;
public Jtrac getJtrac() {
return jtrac;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* This method will the login URL and is used only by CasLoginPage.
*
* @return Returns login URL.
*/
public String getCasLoginUrl() {
if (jtracCasProxyTicketValidator == null) {
return null;
}
return jtracCasProxyTicketValidator.getLoginUrl();
}
/**
* This method will the logout URL and is used only by logout link
* in HeaderPanel.
*
* @return Returns logout URL.
*/
public String getCasLogoutUrl() {
if (jtracCasProxyTicketValidator == null) {
return null;
}
return jtracCasProxyTicketValidator.getLogoutUrl();
}
/**
* This method will return the main JtracApplication object.
*
* @return Returns JtracApplication object.
*/
public static JtracApplication get() {
return (JtracApplication) Application.get();
}
/* (non-Javadoc)
* @see org.apache.wicket.protocol.http.WebApplication#init()
*/
@Override
public void init() {
super.init();
/*
* Get hold of spring managed service layer (see BasePage, BasePanel,
* etc. for how it is used).
*/
ServletContext sc = getServletContext();
applicationContext = WebApplicationContextUtils
.getWebApplicationContext(sc);
jtrac = (Jtrac) applicationContext.getBean("jtrac");
/*
* Check if acegi-cas authentication is being used, get reference to
* object to be used by Wicket authentication to redirect to right
* pages for login/logout.
*/
try {
jtracCasProxyTicketValidator = (JtracCasProxyTicketValidator)
applicationContext.getBean("casProxyTicketValidator");
logger.info("casProxyTicketValidator retrieved from application " +
"context: " + jtracCasProxyTicketValidator);
} catch (NoSuchBeanDefinitionException nsbde) {
logger.debug(nsbde.getMessage());
logger.info("casProxyTicketValidator not found in application " +
"context, CAS single-sign-on is not being used");
}
/*
* Delegate Wicket i18n support to spring i18n
*/
getResourceSettings().addStringResourceLoader(
new IStringResourceLoader() {
/* (non-Javadoc)
* @see org.apache.wicket.resource.loader.IStringResourceLoader#loadStringResource(java.lang.Class, java.lang.String, java.util.Locale, java.lang.String)
*/
public String loadStringResource(Class clazz, String key,
Locale locale, String style) {
try {
return applicationContext.getMessage(key, null,
locale == null ? Session.get().getLocale() : locale);
} catch (Exception e) {
/*
* For performance, Wicket expects null instead of
* throwing an exception and Wicket may try to
* re-resolve using prefixed variants of the key.
*/
return null;
}
}
/* (non-Javadoc)
* @see org.apache.wicket.resource.loader.IStringResourceLoader#loadStringResource(org.apache.wicket.Component, java.lang.String)
*/
public String loadStringResource(Component component,
String key) {
String value = loadStringResource(null, key,
component == null ? null : component.getLocale(), null);
if (logger.isDebugEnabled() && value == null) {
logger.debug("i18n failed for key: '" + key + "', component: " + component);
}
return value;
}
});
getSecuritySettings().setAuthorizationStrategy(
new IAuthorizationStrategy() {
/* (non-Javadoc)
* @see org.apache.wicket.authorization.IAuthorizationStrategy#isActionAuthorized(org.apache.wicket.Component, org.apache.wicket.authorization.Action)
*/
public boolean isActionAuthorized(Component c, Action a) {
return true;
}
/* (non-Javadoc)
* @see org.apache.wicket.authorization.IAuthorizationStrategy#isInstantiationAuthorized(java.lang.Class)
*/
public boolean isInstantiationAuthorized(Class clazz) {
if (BasePage.class.isAssignableFrom(clazz)) {
if (JtracSession.get().isAuthenticated()) {
return true;
}
if (jtracCasProxyTicketValidator != null) {
/*
* ============================================
* Attempt CAS authentication
* ============================================
*/
logger.debug("checking if context contains CAS authentication");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
logger.debug("security context contains CAS authentication, initializing session");
JtracSession.get().setUser((User) authentication.getPrincipal());
return true;
}
}
/*
* ================================================
* Attempt remember-me auto login
* ================================================
*/
if (attemptRememberMeAutoLogin()) {
return true;
}
/*
* =================================================
* Attempt guest access if there are "public" spaces
* =================================================
*/
List<Space> spaces = getJtrac().findSpacesWhereGuestAllowed();
if (spaces.size() > 0) {
logger.debug(spaces.size() +
" public space(s) available, initializing guest user");
User guestUser = new User();
guestUser.setLoginName("guest");
guestUser.setName("Guest");
for (Space space : spaces) {
guestUser.addSpaceWithRole(space, Role.ROLE_GUEST);
}
JtracSession.get().setUser(guestUser);
// and proceed
return true;
}
/*
* Not authenticated, go to login page.
*/
logger.debug("not authenticated, forcing login, " +
"page requested was " + clazz.getName());
if (jtracCasProxyTicketValidator != null) {
String serviceUrl = jtracCasProxyTicketValidator.getServiceProperties().getService();
String loginUrl = jtracCasProxyTicketValidator.getLoginUrl();
logger.debug("cas authentication: service URL: " + serviceUrl);
String redirectUrl = loginUrl + "?service=" + serviceUrl;
logger.debug("attempting to redirect to: " + redirectUrl);
throw new RestartResponseAtInterceptPageException(
new RedirectPage(redirectUrl));
} else {
throw new RestartResponseAtInterceptPageException(
LoginPage.class);
}
}
return true;
}
});
/*
* Friendly URLs for selected pages
*/
if (jtracCasProxyTicketValidator != null) {
mountBookmarkablePage("/login", CasLoginPage.class);
/*
* This matches the value set in:
* WEB-INF/applicationContext-acegi-cas.xml
*/
mountBookmarkablePage("/cas/error", CasLoginErrorPage.class);
} else {
mountBookmarkablePage("/login", LoginPage.class);
}
mountBookmarkablePage("/logout", LogoutPage.class);
mountBookmarkablePage("/svn", SvnStatsPage.class);
mountBookmarkablePage("/options", OptionsPage.class);
mountBookmarkablePage("/item/form", ItemFormPage.class);
/*
* Bookmarkable URL for search and search results
*/
mount(new QueryStringUrlCodingStrategy("/item/search",
ItemSearchFormPage.class));
mount(new QueryStringUrlCodingStrategy("/item/list", ItemListPage.class));
/*
* Bookmarkable URL for viewing items
*/
mount(new IndexedParamUrlCodingStrategy("/item", ItemViewPage.class));
}
/* (non-Javadoc)
* @see org.apache.wicket.protocol.http.WebApplication#newSession(org.apache.wicket.Request, org.apache.wicket.Response)
*/
@Override
public JtracSession newSession(Request request, Response response) {
return new JtracSession(request);
}
/* (non-Javadoc)
* @see org.apache.wicket.protocol.http.WebApplication#newSessionStore()
*/
@Override
protected ISessionStore newSessionStore() {
if (getConfigurationType().equalsIgnoreCase(DEVELOPMENT)) {
logger.warn("wicket development mode, using custom debug http session store");
/*
* The default second level cache session store does not play well
* with our custom reloading filter so we use a custom session store.
*/
return new DebugHttpSessionStore(this);
} else {
ISessionStore sessionStore = super.newSessionStore();
logger.info("wicket production mode, using: " + sessionStore);
return sessionStore;
}
}
/* (non-Javadoc)
* @see org.apache.wicket.Application#getHomePage()
*/
public Class<? extends Page> getHomePage() {
return DashboardPage.class;
}
/**
* The method handles the user authentication using the login name and
* password.
*
* @param loginName The login name of the user
* @param password The password of the user
* @return Returns the User object or null in case of an Exception.
*/
public User authenticate(String loginName, String password) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
loginName, password);
AuthenticationManager am = (AuthenticationManager) applicationContext.getBean("authenticationManager");
try {
Authentication authentication = am.authenticate(token);
return (User) authentication.getPrincipal();
} catch (AuthenticationException ae) {
logger.debug("acegi authentication failed: " + ae);
return null;
}
}
/**
* This method will try to identify the user by using cookies and tries to
* auto login the user.
*
* @return Returns <code>true</code> on successful identification otherwise <code>false</code>.
*/
private boolean attemptRememberMeAutoLogin() {
logger.debug("checking cookies for remember-me auto login");
Cookie[] cookies = ((WebRequest) RequestCycle.get().getRequest())
.getCookies();
if (cookies == null) {
logger.debug("no cookies found");
return false;
}
for (Cookie c : cookies) {
if (logger.isDebugEnabled()) {
logger.debug("examining cookie: " + WebUtils.getDebugStringForCookie(c));
}
if (!c.getName().equals("jtrac")) {
continue;
}
String value = c.getValue();
logger.debug("found jtrac cookie: " + value);
if (value == null) {
continue;
}
int index = value.indexOf(':');
if (index == -1) {
continue;
}
String loginName = value.substring(0, index);
String encodedPassword = value.substring(index + 1);
logger.debug("valid cookie, attempting authentication");
User user = (User) getJtrac().loadUserByUsername(loginName);
if (encodedPassword.equals(user.getPassword())) {
JtracSession.get().setUser(user);
logger.debug("remember me login success");
return true;
}
} // end for
// no valid cookies were found
return false;
}
}