001    /* Copyright 2006-2009 the original author or authors.
002     *
003     * Licensed under the Apache License, Version 2.0 (the "License");
004     * you may not use this file except in compliance with the License.
005     * You may obtain a copy of the License at
006     *
007     *      http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software
010     * distributed under the License is distributed on an "AS IS" BASIS,
011     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012     * See the License for the specific language governing permissions and
013     * limitations under the License.
014     */
015    package org.codehaus.groovy.grails.plugins.springsecurity;
016    
017    import java.io.IOException;
018    
019    import javax.servlet.ServletRequest;
020    import javax.servlet.ServletResponse;
021    import javax.servlet.http.HttpServletRequest;
022    import javax.servlet.http.HttpServletResponse;
023    
024    import org.springframework.beans.factory.InitializingBean;
025    import org.springframework.security.AccessDeniedException;
026    import org.springframework.security.Authentication;
027    import org.springframework.security.AuthenticationTrustResolver;
028    import org.springframework.security.AuthenticationTrustResolverImpl;
029    import org.springframework.security.context.SecurityContextHolder;
030    import org.springframework.security.ui.AbstractProcessingFilter;
031    import org.springframework.security.ui.AccessDeniedHandler;
032    import org.springframework.security.ui.savedrequest.SavedRequest;
033    import org.springframework.security.userdetails.UserDetails;
034    import org.springframework.security.util.PortResolver;
035    import org.springframework.util.Assert;
036    
037    /**
038     * {@link AccessDeniedHandler} for redirect to errorPage (not RequestDispatcher#forward).
039     *
040     * @author T.Yamamoto
041     * @author <a href='mailto:beckwithb@studentsonly.com'>Burt Beckwith</a>
042     */
043    public class GrailsAccessDeniedHandlerImpl implements AccessDeniedHandler, InitializingBean {
044    
045            private String errorPage;
046            private String ajaxErrorPage;
047            private String ajaxHeader = WithAjaxAuthenticationProcessingFilterEntryPoint.AJAX_HEADER;
048            private PortResolver portResolver;
049            private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
050    
051            /**
052             * {@inheritDoc}
053             * @see org.springframework.security.ui.AccessDeniedHandler#handle(
054             *      javax.servlet.ServletRequest, javax.servlet.ServletResponse,
055             *      org.springframework.security.AccessDeniedException)
056             */
057            public void handle(final ServletRequest req, final ServletResponse res, final AccessDeniedException e)
058                            throws IOException {
059    
060                    HttpServletRequest request = (HttpServletRequest)req;
061                    HttpServletResponse response = (HttpServletResponse)res;
062    
063                    if (e != null && isLoggedIn() && authenticationTrustResolver.isRememberMe(getAuthentication())) {
064                            // user has a cookie but is getting bounced because of IS_AUTHENTICATED_FULLY,
065                            // so Acegi won't save the original request
066                            request.getSession().setAttribute(
067                                            AbstractProcessingFilter.SPRING_SECURITY_SAVED_REQUEST_KEY,
068                                            new SavedRequest(request, portResolver));
069                    }
070    
071                    if (errorPage != null || (ajaxErrorPage != null && request.getHeader(ajaxHeader) != null)) {
072                            boolean includePort = true;
073                            String scheme = request.getScheme();
074                            String serverName = request.getServerName();
075                            int serverPort = portResolver.getServerPort(request);
076                            String contextPath = request.getContextPath();
077                            boolean inHttp = "http".equals(scheme.toLowerCase());
078                            boolean inHttps = "https".equals(scheme.toLowerCase());
079    
080                            if (inHttp && (serverPort == 80)) {
081                                    includePort = false;
082                            }
083                            else if (inHttps && (serverPort == 443)) {
084                                    includePort = false;
085                            }
086    
087                            String commonRedirectUrl = scheme + "://" + serverName + ((includePort) ? (":" + serverPort) : "")
088                                            + contextPath;
089                            String redirectUrl = commonRedirectUrl;
090                            if (ajaxErrorPage != null && request.getHeader(ajaxHeader) != null) {
091                                    redirectUrl += ajaxErrorPage;
092                            }
093                            else if (errorPage != null) {
094                                    redirectUrl += errorPage;
095                            }
096                            else {
097                                    response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
098                            }
099    
100                            response.sendRedirect(response.encodeRedirectURL(redirectUrl));
101                    }
102    
103                    if (!response.isCommitted()) {
104                            // Send 403 (we do this after response has been written)
105                            response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
106                    }
107            }
108    
109            private boolean isLoggedIn() {
110                    if (getAuthentication() == null) {
111                            return false;
112                    }
113                    return getAuthentication().getPrincipal() instanceof UserDetails;
114            }
115    
116            private Authentication getAuthentication() {
117                    return SecurityContextHolder.getContext() == null ? null
118                                    : SecurityContextHolder.getContext().getAuthentication();
119            }
120    
121            /**
122             * Dependency injection for the error page, e.g. '/login/denied'.
123             * @param page  the page
124             */
125            public void setErrorPage(final String page) {
126                    if (page != null && !page.startsWith("/")) {
127                            throw new IllegalArgumentException("ErrorPage must begin with '/'");
128                    }
129                    errorPage = page;
130            }
131    
132            /**
133             * Dependency injection for the Ajax error page, e.g. '/login/deniedAjax'.
134             * @param page  the page
135             */
136            public void setAjaxErrorPage(final String page) {
137                    if (page != null && !page.startsWith("/")) {
138                            throw new IllegalArgumentException("ErrorPage must begin with '/'");
139                    }
140                    ajaxErrorPage = page;
141            }
142    
143            /**
144             * Dependency injection for the Ajax header name; defaults to 'X-Requested-With'.
145             * @param header  the header name
146             */
147            public void setAjaxHeader(final String header) {
148                    ajaxHeader = header;
149            }
150    
151            /**
152             * Dependency injection for the port resolver.
153             * @param resolver  the resolver
154             */
155            public void setPortResolver(final PortResolver resolver) {
156                    portResolver = resolver;
157            }
158    
159            /**
160             * {@inheritDoc}
161             * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
162             */
163            public void afterPropertiesSet() {
164                    Assert.notNull(ajaxHeader, "ajaxHeader is required");
165                    Assert.notNull(portResolver, "portResolver is required");
166            }
167    }