Java tutorial
/* * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Common Development and * Distribution License (CDDL), Common Public License (CPL) the * licensors of this Program grant you additional permission to * convey the resulting work. */ package mitm.djigzo.web.services; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import mitm.application.djigzo.ws.EventLoggerWS; import mitm.common.event.EventLogger; import mitm.common.event.EventLoggerFactory; import mitm.common.security.crypto.RandomGenerator; import mitm.common.security.crypto.impl.RandomGeneratorImpl; import mitm.djigzo.web.common.RedirectRequestExceptionHandler; import mitm.djigzo.web.common.event.RemoteEventLoggerImpl; import mitm.djigzo.web.services.security.AuthenticationEventListener; import mitm.djigzo.web.services.security.CSRFFilter; import mitm.djigzo.web.services.security.CSRFFilterImpl; import mitm.djigzo.web.services.security.HMACFilter; import mitm.djigzo.web.services.security.HMACFilterImpl; import mitm.djigzo.web.services.security.LogoutServiceImpl; import mitm.djigzo.web.services.security.RoleFilter; import mitm.djigzo.web.services.security.RoleFilterImpl; import mitm.djigzo.web.services.security.SecurityChecker; import mitm.djigzo.web.services.security.SpringSecurityWorker; import mitm.djigzo.web.services.security.StaticSecurityChecker; import mitm.djigzo.web.utils.PasswordCodec; import mitm.djigzo.web.utils.PasswordCodecImpl; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.apache.tapestry5.corelib.components.Form; import org.apache.tapestry5.internal.services.LinkFactory; import org.apache.tapestry5.internal.services.RequestPageCache; import org.apache.tapestry5.ioc.MappedConfiguration; import org.apache.tapestry5.ioc.OrderedConfiguration; import org.apache.tapestry5.ioc.ServiceBinder; import org.apache.tapestry5.ioc.annotations.EagerLoad; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.annotations.Match; import org.apache.tapestry5.ioc.annotations.Value; import org.apache.tapestry5.services.ApplicationStateManager; import org.apache.tapestry5.services.ComponentClassTransformWorker; import org.apache.tapestry5.services.ComponentEventRequestFilter; import org.apache.tapestry5.services.HttpServletRequestFilter; import org.apache.tapestry5.services.HttpServletRequestHandler; import org.apache.tapestry5.services.MarkupRendererFilter; import org.apache.tapestry5.services.PartialMarkupRendererFilter; import org.apache.tapestry5.services.Request; import org.apache.tapestry5.services.RequestExceptionHandler; import org.apache.tapestry5.services.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.AccessDecisionManager; import org.springframework.security.AccessDeniedException; import org.springframework.security.AuthenticationManager; import org.springframework.security.providers.encoding.PasswordEncoder; import org.springframework.security.ui.logout.LogoutHandler; import org.springframework.security.ui.logout.SecurityContextLogoutHandler; /** * Tapestry module for most security related services * * @author Martijn Brinkers * */ public class SecurityModule { private final static Logger logger = LoggerFactory.getLogger(SecurityModule.class); public static void bind(final ServiceBinder binder) { binder.bind(LogoutService.class, LogoutServiceImpl.class); binder.bind(RandomGenerator.class, RandomGeneratorImpl.class); binder.bind(RoleFilter.class, RoleFilterImpl.class); } public static void contributeLogoutService(final OrderedConfiguration<LogoutHandler> cfg) { cfg.add("securityContextLogoutHandler", new SecurityContextLogoutHandler()); } public static void contributeComponentClassTransformWorker( OrderedConfiguration<ComponentClassTransformWorker> configuration, SecurityChecker securityChecker) { /* * Make the SpringSecurityWorker the last one so the security check is done first. * * We need to add it just before UnclaimedField otherwise well get a warning like: * * "WARN Unable to add 'UnclaimedField' as a dependency of 'SpringSecurityWorker', * as that forms a dependency cycle ('SpringSecurityWorker' depends on itself via * 'UnclaimedField'). The dependency has been ignored." * * It seems that UnclaimedField should always be the last one */ configuration.add("SpringSecurityWorker", new SpringSecurityWorker(securityChecker), "before:UnclaimedField"); } public static SecurityChecker buildSecurityChecker(final AccessDecisionManager accessDecisionManager, final AuthenticationManager authenticationManager) throws Exception { StaticSecurityChecker checker = new StaticSecurityChecker(); checker.setAccessDecisionManager(accessDecisionManager); checker.setAuthenticationManager(authenticationManager); checker.afterPropertiesSet(); return checker; } public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration) { configuration.add("login-processing-url", "/check"); } /* * RequestExceptionHandler that intercepts AccessDeniedException and redirects * to the provided page. */ private static class AccessDeniedRedirectRequestExceptionHandler extends RedirectRequestExceptionHandler<AccessDeniedException> { public AccessDeniedRedirectRequestExceptionHandler(RequestExceptionHandler delegate, Response response, LinkFactory linkFactory, String redirectToPage, Object... context) { super(delegate, response, linkFactory, redirectToPage, context); } } /* * RequestExceptionHandler that intercepts and access denied related exceptions */ @Match(value = { "RequestExceptionHandler" }) public static RequestExceptionHandler decorateAccessDeniedRequestExceptionHandler(final Object delegate, final Response response, final LinkFactory linkFactory, @Inject @Value("${access-denied-page}") final String redirectToPage) { return new AccessDeniedRedirectRequestExceptionHandler((RequestExceptionHandler) delegate, response, linkFactory, redirectToPage); } /* * Builds the 'Cross Site Request Forgeries' (CSRF) filter. * * Note: must be EagerLoad'ed because we want the LinkFactory listener to be active from the * start. */ @EagerLoad public static CSRFFilter buildCSRFLinkFactoryListener(ApplicationStateManager asm, Request request, Response response, LinkFactory linkFactory, RequestPageCache requestPageCache, @Inject @Value("${csrf.redirectTo}") String redirectTo) { CSRFFilterImpl filter = new CSRFFilterImpl(asm, request, response, linkFactory, requestPageCache, redirectTo); linkFactory.addListener(filter); return filter; } /* * This factory is used to inject a Tapestry bean into a spring bean. That's why it has to be * eager loaded. */ @EagerLoad public EventLoggerFactory buildEventLoggerFactory(AuthenticationEventListener authenticationEventListener, final EventLoggerWS eventLoggerWS) { EventLoggerFactory factory = new EventLoggerFactory() { @Override public EventLogger create() { return new RemoteEventLoggerImpl(eventLoggerWS); } }; authenticationEventListener.setEventLoggerFactory(factory); return factory; } /* * Builds the PasswordCodec service */ public PasswordCodec buildPasswordCodec(PasswordEncoder passwordEncoder) { return new PasswordCodecImpl(passwordEncoder); } public void contributeComponentEventRequestHandler( OrderedConfiguration<ComponentEventRequestFilter> configuration, CSRFFilter csrfFilter, HMACFilter hMACFilter, RoleFilter roleFilter) { /* * Make sure the CSRF filter is injected before anything else */ configuration.add("CSRFFilter", csrfFilter, "after:UploadException", "before:Ajax"); configuration.add("HMACFilter", hMACFilter, "after:CSRFFilter", "before:Ajax"); configuration.add("RoleFilter", roleFilter, "after:HMACFilter", "before:Ajax"); } public static HMACFilter buildHMACFilter(ApplicationStateManager asm, Request request, Response response, LinkFactory linkFactory, RequestPageCache requestPageCache, @Inject @Value("${hmac.redirectTo}") String redirectTo) { String[] protectedElements = { Form.FORM_DATA, "t:state:client" }; HMACFilter filter = new HMACFilterImpl(asm, request, response, linkFactory, requestPageCache, redirectTo, protectedElements); return filter; } public void contributeMarkupRenderer(OrderedConfiguration<MarkupRendererFilter> configuration, HMACFilter hMACFilter) { configuration.add("HMACFilter", hMACFilter, "after:*"); } public void contributePartialMarkupRenderer(OrderedConfiguration<PartialMarkupRendererFilter> configuration, HMACFilter hMACFilter) { configuration.add("HMACFilter", hMACFilter, "after:*"); } private static final String[] ASSET_WHITE_LIST = { "jpg", "jpeg", "png", "gif", "js", "css", "ico" }; /* * All the assets that are allowed to be downloaded using the assets service (including files without extension and dirs) */ private static final Set<String> assetsWhitelist = Collections .synchronizedSet(new HashSet<String>(Arrays.asList(ASSET_WHITE_LIST))); public void contributeHttpServletRequestHandler(OrderedConfiguration<HttpServletRequestFilter> configuration, @Inject @Value("${access-denied-page}") final String accessDeniedPage) { /* * Create a filter that will block access to some assets. The asset service allows access to some assets we do * not want to expose. The asset service will show all files in /assets/ directory and allows you (by default) * to download some files which you do not want to expose. */ HttpServletRequestFilter filter = new HttpServletRequestFilter() { @Override public boolean service(HttpServletRequest request, HttpServletResponse response, HttpServletRequestHandler handler) throws IOException { String path = request.getServletPath(); if (path.startsWith("/assets") && (!assetsWhitelist.contains(StringUtils.lowerCase(FilenameUtils.getExtension(path))))) { logger.warn("access to asset " + path + " denied"); response.sendRedirect(request.getContextPath() + "/" + accessDeniedPage); return true; } return handler.service(request, response); } }; configuration.add("AssetProtectionFilter", filter, "before:*"); } }