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