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}