/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.url; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.math.NumberUtils; import org.apereo.portal.security.IPerson; import org.apereo.portal.security.IPersonManager; import org.apereo.portal.user.IUserInstanceManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.filter.OncePerRequestFilter; /** * Sets request info headers and forces canonical portal URLs * */ public class UrlCanonicalizingFilter extends OncePerRequestFilter { private static final String COOKIE_NAME = "UrlCanonicalizingFilter.REDIRECT_COUNT"; protected final Logger logger = LoggerFactory.getLogger(getClass()); private IUrlSyntaxProvider urlSyntaxProvider; private IPersonManager personManager; private IUserInstanceManager userInstanceManager; private int maximumRedirects = 5; private LoginRefUrlEncoder loginRefUrlEncoder; @Autowired public void setUrlSyntaxProvider(IUrlSyntaxProvider urlSyntaxProvider) { this.urlSyntaxProvider = urlSyntaxProvider; } @Autowired public void setPersonManager(IPersonManager personManager) { this.personManager = personManager; } @Autowired public void setUserInstanceManager(IUserInstanceManager userInstanceManager) { this.userInstanceManager = userInstanceManager; } @Autowired(required = false) public void setLoginRefUrlEncoder(LoginRefUrlEncoder loginRefUrlEncoder) { this.loginRefUrlEncoder = loginRefUrlEncoder; } /** Maximum number of consecutive redirects that are allowed. Defaults to 5. */ public void setMaximumRedirects(int maximumRedirects) { this.maximumRedirects = maximumRedirects; } @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if ("GET".equals(request.getMethod())) { final String canonicalUrl = this.urlSyntaxProvider.getCanonicalUrl(request); final String canonicalUri; final int queryStringIndex = canonicalUrl.indexOf("?"); if (queryStringIndex < 0) { canonicalUri = canonicalUrl; } else { canonicalUri = canonicalUrl.substring(0, queryStringIndex); } String requestURI = request.getRequestURI(); // If cookies are disabled and Tomcat has appended the sessionId to the URL, remove the // jsessionid for the purposes of comparing the request URI to the canonical URI. This // allows a search indexing engine such as Googlebot to access the guest view of a uportal // page which typically renders OK (not guaranteed depending upon content). See UP-4414. if (requestURI.contains(";jsessionid")) { requestURI = requestURI.substring(0, requestURI.indexOf(";")); } final int redirectCount = this.getRedirectCount(request); if (!canonicalUri.equals(requestURI)) { if (redirectCount < this.maximumRedirects) { this.setRedirectCount(request, response, redirectCount + 1); /* * This is the place where we should decide if... * - (1) the user is a guest * - (2) the canonicalUrl is not the requested content * - (3) there is a strategy for external login * * If all of these are true, we should attempt to send the * user to external login with a properly-encoded deep-linking * service URL attached. */ String encodedTargetUrl = null; IPerson person = personManager.getPerson(request); if ( /* #1 */ person.isGuest() && /* #2 */ urlSyntaxProvider .doesRequestPathReferToSpecificAndDifferentContentVsCanonicalPath( requestURI, canonicalUri) && /* #3 */ loginRefUrlEncoder != null) { encodedTargetUrl = loginRefUrlEncoder.encodeLoginAndRefUrl(request); } if (encodedTargetUrl == null) { // For whatever reason, we haven't chosen to send the // user through external login, so we use the canonicalUrl encodedTargetUrl = response.encodeRedirectURL(canonicalUrl); } response.sendRedirect(encodedTargetUrl); logger.debug( "Redirecting from {} to canonicalized URL {}, redirect {}", requestURI, canonicalUri, redirectCount); return; } this.clearRedirectCount(request, response); logger.debug( "Not redirecting from {} to canonicalized URL {} due to limit of {} redirects", requestURI, canonicalUri, redirectCount); } else { logger.trace( "Requested URI {} is the canonical URL {}, " + "so no (further?) redirect is necessary (after {} redirects).", requestURI, canonicalUri, redirectCount); if (redirectCount > 0) { this.clearRedirectCount(request, response); } } } final IPortalRequestInfo portalRequestInfo = this.urlSyntaxProvider.getPortalRequestInfo(request); final UrlType urlType = portalRequestInfo.getUrlType(); final UrlState urlState = portalRequestInfo.getUrlState(); final PortalHttpServletResponseWrapper httpServletResponseWrapper = new PortalHttpServletResponseWrapper(response); final PortalHttpServletRequestWrapper httpServletRequestWrapper = new PortalHttpServletRequestWrapper( request, httpServletResponseWrapper, this.userInstanceManager); httpServletRequestWrapper.setHeader(IPortalRequestInfo.URL_TYPE_HEADER, urlType.toString()); httpServletRequestWrapper.setHeader( IPortalRequestInfo.URL_STATE_HEADER, urlState.toString()); //Hack to make PortalController work in light of https://jira.springsource.org/secure/attachment/18283/SPR7346.patch httpServletRequestWrapper.setHeader( IPortalRequestInfo.URL_TYPE_HEADER + "." + urlType, Boolean.TRUE.toString()); httpServletRequestWrapper.setHeader( IPortalRequestInfo.URL_STATE_HEADER + "." + urlState, Boolean.TRUE.toString()); filterChain.doFilter(httpServletRequestWrapper, httpServletResponseWrapper); } protected void clearRedirectCount(HttpServletRequest request, HttpServletResponse response) { final Cookie cookie = new Cookie(COOKIE_NAME, ""); cookie.setPath(request.getContextPath()); cookie.setMaxAge(0); response.addCookie(cookie); } protected void setRedirectCount( HttpServletRequest request, HttpServletResponse response, int count) { final Cookie cookie = new Cookie(COOKIE_NAME, Integer.toString(count)); cookie.setPath(request.getContextPath()); cookie.setMaxAge(30); response.addCookie(cookie); } protected int getRedirectCount(HttpServletRequest request) { final Cookie[] cookies = request.getCookies(); if (cookies == null) { return 0; } for (final Cookie cookie : cookies) { if (COOKIE_NAME.equals(cookie.getName())) { final String value = cookie.getValue(); return NumberUtils.toInt(value, 0); } } return 0; } }