001package org.tynamo.security.internal.services.impl;
002
003import java.io.IOException;
004
005import javax.servlet.http.Cookie;
006import javax.servlet.http.HttpServletRequest;
007import javax.servlet.http.HttpServletResponse;
008
009import org.apache.shiro.web.util.WebUtils;
010import org.apache.tapestry5.EventContext;
011import org.apache.tapestry5.Link;
012import org.apache.tapestry5.SymbolConstants;
013import org.apache.tapestry5.internal.services.LinkSource;
014import org.apache.tapestry5.internal.services.RequestImpl;
015import org.apache.tapestry5.internal.services.ResponseImpl;
016import org.apache.tapestry5.internal.services.TapestrySessionFactory;
017import org.apache.tapestry5.ioc.annotations.Inject;
018import org.apache.tapestry5.ioc.annotations.Symbol;
019import org.apache.tapestry5.services.ComponentEventLinkEncoder;
020import org.apache.tapestry5.services.ComponentEventRequestParameters;
021import org.apache.tapestry5.services.LocalizationSetter;
022import org.apache.tapestry5.services.Request;
023import org.apache.tapestry5.services.RequestGlobals;
024import org.tynamo.security.SecuritySymbols;
025import org.tynamo.security.internal.services.LoginContextService;
026
027public class LoginContextServiceImpl implements LoginContextService {
028
029        protected final String loginPage;
030        protected final String defaultSuccessPage;
031        protected final String unauthorizedPage;
032        protected final HttpServletRequest servletRequest;
033        protected final HttpServletResponse servletResponse;
034        protected final ComponentEventLinkEncoder linkEncoder;
035        protected final TapestrySessionFactory sessionFactory;
036        protected final RequestGlobals requestGlobals;
037        protected final String requestEncoding;
038        private final LinkSource linkSource;
039        private final LocalizationSetter localizationSetter;
040
041        public LoginContextServiceImpl(@Inject @Symbol(SecuritySymbols.SUCCESS_URL) String successUrl,
042                @Inject @Symbol(SecuritySymbols.LOGIN_URL) String loginUrl,
043                @Inject @Symbol(SecuritySymbols.UNAUTHORIZED_URL) String unauthorizedUrl,
044                @Inject @Symbol(SymbolConstants.CHARSET) String requestEncoding, HttpServletRequest serlvetRequest,
045                HttpServletResponse servletResponse, LocalizationSetter localizationSetter, LinkSource linkSource,
046                ComponentEventLinkEncoder linkEncoder, TapestrySessionFactory sessionFactory, RequestGlobals requestGlobals) {
047                this.servletRequest = serlvetRequest;
048                this.servletResponse = servletResponse;
049                this.linkSource = linkSource;
050                this.linkEncoder = linkEncoder;
051                this.sessionFactory = sessionFactory;
052                this.requestGlobals = requestGlobals;
053                this.localizationSetter = localizationSetter;
054                this.requestEncoding = requestEncoding;
055                this.loginPage = urlToPage(loginUrl);
056                this.defaultSuccessPage = urlToPage(successUrl);
057                this.unauthorizedPage = urlToPage(unauthorizedUrl);
058        }
059
060        @Override
061        @Deprecated
062        public String getLoginPage() {
063                return loginPage;
064        }
065
066        @Override
067        @Deprecated
068        public String getSuccessPage() {
069                return defaultSuccessPage;
070        }
071
072        @Override
073        @Deprecated
074        public String getUnauthorizedPage() {
075                return unauthorizedPage;
076        }
077
078        @Override
079        public String getLoginURL() {
080                return getLoginPage();
081        }
082
083        @Override
084        public String getSuccessURL() {
085                return getSuccessPage();
086        }
087
088        @Override
089        public String getUnauthorizedURL() {
090                return getUnauthorizedURL();
091        }
092
093        private static String urlToPage(String url) {
094                if (url.charAt(0) == '/') {
095                        url = url.substring(1);
096                }
097                return url;
098        }
099
100        @Override
101        public String getLocalelessPathWithinApplication() {
102                String path = WebUtils.getPathWithinApplication(servletRequest);
103                String locale = getLocaleFromPath(path);
104                return locale == null ? path : path.substring(locale.length() + 1);
105        }
106
107        @Override
108        public String getLocaleFromPath(String path) {
109                // we have to get the possibly encoded locale from the request, but we are not yet in the Tapestry request processing pipeline.
110                // the following was copied and modified from AppPageRenderLinkTransformer.decodePageRenderRequest(...)
111                String[] split = path.substring(1).split("/");
112                if (split.length > 1 && !"".equals(split[0])) {
113                        String possibleLocaleName = split[0];
114                        // Might be just the page activation context, or it might be locale then page
115                        // activation context
116                        return localizationSetter.isSupportedLocaleName(possibleLocaleName) ? possibleLocaleName : null;
117                }
118                return null;
119        }
120
121        public void removeSavedRequest() {
122                Cookie cookie = new Cookie(WebUtils.SAVED_REQUEST_KEY, null);
123                cookie.setPath(getContextPath());
124                cookie.setMaxAge(0);
125                servletResponse.addCookie(cookie);
126        }
127
128        // In 0.7 I plan to remove the contextPath param and make this operation protected for easy overriding
129        private Cookie createSavedRequestCookie(String contextPath) {
130                String requestUri;
131
132                // create a T5 request wrapper so we can take advantage of T5'S link decoding services.
133                // The security filter intentionally runs as part of the HttpServletRequest chain, i.e. before T5 request and response wrappers
134                // we also need to create the response and store them to requestGlobals because LinkSource uses the request object
135                final Request request = new RequestImpl(servletRequest, requestEncoding, sessionFactory);
136                requestGlobals.storeRequestResponse(request, new ResponseImpl(servletRequest, servletResponse));
137                Cookie cookie = new Cookie(WebUtils.SAVED_REQUEST_KEY, "");
138                cookie.setPath(contextPath);
139
140                if (!"GET".equalsIgnoreCase(servletRequest.getMethod())) {
141                        // POST request? => Redirect to target page via HTTP GET?
142                        ComponentEventRequestParameters eventParameters = linkEncoder.decodeComponentEventRequest(request);
143                        // Event URL => Redirect to target page via HTTP GET
144                        if (eventParameters != null) requestUri = createPageRenderLink(eventParameters);
145                        else {
146                                // REST API call? => Don't do redirects but set a delete cookie
147                                cookie.setMaxAge(0);
148                                return cookie;
149                        }
150                } else {
151                        ComponentEventRequestParameters eventParameters = linkEncoder.decodeComponentEventRequest(request);
152
153                        // Event URL => Redirect to target page via HTTP GET
154                        if (eventParameters != null) requestUri = createPageRenderLink(eventParameters);
155                        else {
156                                // Page render request? => Keep the same URL
157                                requestUri = WebUtils.getRequestUri(servletRequest);
158                                if (servletRequest.getQueryString() != null) requestUri += "?" + servletRequest.getQueryString();
159                        }
160                }
161
162                cookie.setValue(requestUri);
163                return cookie;
164        }
165
166        private String createPageRenderLink(ComponentEventRequestParameters eventParameters) {
167                EventContext eventContext = eventParameters.getPageActivationContext();
168                Link link = linkSource.createPageRenderLink(eventParameters.getActivePageName(), true,
169                        (Object[]) eventContext.toStrings());
170                return link.toRedirectURI();
171        }
172
173        private String getContextPath() {
174                String contextPath = servletRequest.getContextPath();
175                if ("".equals(contextPath)) contextPath = "/";
176                return contextPath;
177        }
178
179        @Override
180        public void saveRequest() {
181                servletResponse.addCookie(createSavedRequestCookie(getContextPath()));
182        }
183
184        @Override
185        @Deprecated
186        public void saveRequest(String contextPath) {
187                servletResponse.addCookie(createSavedRequestCookie(contextPath));
188        }
189
190        @Override
191        public void redirectToSavedRequest(String fallbackUrl) throws IOException {
192                Cookie[] cookies = servletRequest.getCookies();
193
194                String requestUri = null;
195                if (cookies != null) for (Cookie cookie : cookies)
196                        if (WebUtils.SAVED_REQUEST_KEY.equals(cookie.getName())) {
197                                requestUri = cookie.getValue();
198                                // delete cookie
199                                cookie.setMaxAge(0);
200                                servletResponse.addCookie(cookie);
201                        break;
202                }
203                if (requestUri == null)
204                // FIXME in 0.7.0, as part of issue #16, we should only prepend contextPath if fallbackUrl doesn't start with a leading slash
205                        requestUri = fallbackUrl.startsWith(getContextPath()) ? fallbackUrl : getContextPath() + fallbackUrl;
206
207                // don't use response.sendRedirect() as that sends SC_FOUND (i.e. 302) and this redirect is typically invoked
208                // as a response to a successful (POST) login request
209                servletResponse.setStatus(303);
210                servletResponse.setHeader("Location", servletResponse.encodeRedirectURL(requestUri));
211                // if you don't flush the buffer, filters can and will change the headers afterwards
212                servletResponse.flushBuffer();
213                // servletResponse.sendRedirect(servletResponse.encodeRedirectURL(requestUri));
214        }
215}