/**
* 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.jasig.portlet.emailpreview.dao.exchange;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.http.*;
import org.apache.http.client.methods.*;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.protocol.HttpContext;
import org.jasig.portlet.emailpreview.EmailPreviewException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* Redirect strategy for http client that allows following redirects (HTTP 301, 302, 307) for POST
* messages after validating the redirect location is "safe".
*
* <p>This class does not limit redirect hops; DefaultHttpClient limits redirect hops to 100 (see
* ClientPNames.MAX_REDIRECTS ='http.protocol.max-redirects' in
* http://hc.apache.org/httpcomponents-client-ga/tutorial/pdf/httpclient-tutorial.pdf.
*
* @author James Wennmacher, jwennmacher@unicon.net
*/
@Component
public class AutodiscoverRedirectStrategy extends LaxRedirectStrategy {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private List<String> unsafeUriExclusionPatterns;
private List<Pattern> unsafeUriPatterns = new ArrayList<Pattern>();
private List<String> requiredUriPatterns;
private List<Pattern> uriRequirementPatterns = new ArrayList<Pattern>();
public AutodiscoverRedirectStrategy() {
setRequiredUriPatterns(Arrays.asList(new String[] {"^https:.*"}));
}
public void setUnsafeUriExclusionPatterns(List<String> unsafeUriExclusionPatterns) {
this.unsafeUriExclusionPatterns = unsafeUriExclusionPatterns;
unsafeUriPatterns = new ArrayList<Pattern>();
for (String pattern : unsafeUriExclusionPatterns) {
unsafeUriPatterns.add(Pattern.compile(pattern));
}
}
public void setRequiredUriPatterns(List<String> requiredUriPatterns) {
this.requiredUriPatterns = requiredUriPatterns;
uriRequirementPatterns = new ArrayList<Pattern>();
for (String pattern : requiredUriPatterns) {
uriRequirementPatterns.add(Pattern.compile(pattern));
}
}
private boolean matchesPatternSet(URI uri, List<Pattern> patterns) {
for (Pattern pattern : patterns) {
Matcher matcher = pattern.matcher(uri.toString());
if (matcher.matches()) {
return true;
}
}
return false;
}
/**
* Overrides behavior to follow redirects for POST messages, AND to have the redirect be a POST.
* Behavior of <code>DefaultRedirectStrategy</code> is to use a GET for the redirect (though
* against spec this is the de-facto standard, see
* http://www.mail-archive.com/httpclient-users@hc.apache.org/msg06327.html and
* http://www.alanflavell.org.uk/www/post-redirect.html).
*
* <p>For our application, we want to follow the redirect for a 302 as long as it is to a safe
* location and have the redirect be a POST.
*
* <p>This code is modified from http-components' http-client 4.2.5. Since we only use POST the
* code for the other HTTP methods has been removed to simplify this method.
*
* @param request Http request
* @param response Http response
* @param context Http context
* @return Request to issue to the redirected location
* @throws ProtocolException protocol exception
*/
@Override
public HttpUriRequest getRedirect(
final HttpRequest request, final HttpResponse response, final HttpContext context)
throws ProtocolException {
URI uri = getLocationURI(request, response, context);
log.info("Following redirect to {}", uri.toString());
String method = request.getRequestLine().getMethod();
int status = response.getStatusLine().getStatusCode();
// Insure location is safe
if (matchesPatternSet(uri, unsafeUriPatterns)) {
log.warn("Not following to URI {} - matches a configured unsafe URI pattern", uri.toString());
throw new EmailPreviewException("Autodiscover redirected to unsafe URI " + uri.toString());
}
if (!matchesPatternSet(uri, uriRequirementPatterns) && uriRequirementPatterns.size() > 0) {
log.warn(
"Not following to URI {} - URI does not match a required URI pattern", uri.toString());
throw new EmailPreviewException(
"Autodiscover redirected to URI not matching required pattern. URI=" + uri.toString());
}
// Follow forwards for 301 and 302 in addition to 307, to validate the redirect location,
// and to use a POST method.
if (status == HttpStatus.SC_TEMPORARY_REDIRECT
|| status == HttpStatus.SC_MOVED_PERMANENTLY
|| status == HttpStatus.SC_MOVED_TEMPORARILY) {
if (method.equalsIgnoreCase(HttpPost.METHOD_NAME)) {
return copyEntity(new HttpPost(uri), request);
}
}
// Should not get here, but return sensible value just in case. A GET will likely fail.
return new HttpGet(uri);
}
private HttpUriRequest copyEntity(
final HttpEntityEnclosingRequestBase redirect, final HttpRequest original) {
if (original instanceof HttpEntityEnclosingRequest) {
redirect.setEntity(((HttpEntityEnclosingRequest) original).getEntity());
}
return redirect;
}
}