001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2012-2015 ForgeRock AS. 015 */ 016package org.forgerock.openig.filter; 017 018import static java.lang.String.format; 019import static org.forgerock.openig.el.Bindings.bindings; 020import static org.forgerock.openig.util.JsonValues.asExpression; 021 022import java.net.URI; 023import java.net.URISyntaxException; 024 025import org.forgerock.http.Filter; 026import org.forgerock.http.Handler; 027import org.forgerock.http.header.LocationHeader; 028import org.forgerock.http.protocol.Request; 029import org.forgerock.http.protocol.Response; 030import org.forgerock.http.protocol.ResponseException; 031import org.forgerock.http.routing.UriRouterContext; 032import org.forgerock.http.util.Uris; 033import org.forgerock.openig.el.Bindings; 034import org.forgerock.openig.el.Expression; 035import org.forgerock.openig.heap.GenericHeapObject; 036import org.forgerock.openig.heap.GenericHeaplet; 037import org.forgerock.openig.heap.HeapException; 038import org.forgerock.openig.http.Responses; 039import org.forgerock.services.context.Context; 040import org.forgerock.util.Function; 041import org.forgerock.util.promise.NeverThrowsException; 042import org.forgerock.util.promise.Promise; 043 044/** 045 * Rewrites Location headers on responses that generate a redirect that would 046 * take the user directly to the application being proxied rather than taking 047 * the user through OpenIG. Options: 048 * 049 * <pre> 050 * {@code 051 * { 052 * "baseURI" : expression [OPTIONAL - default to the original URI 053 * of the request ] 054 * } 055 * } 056 * </pre> 057 * 058 * Example: 059 * 060 * <pre> 061 * {@code 062 * { 063 * "name": "LocationRewriter", 064 * "type": "LocationHeaderFilter", 065 * "config": { 066 * "baseURI": "https://proxy.example.com:443/" 067 * } 068 * } 069 * } 070 * </pre> 071 */ 072public class LocationHeaderFilter extends GenericHeapObject implements Filter { 073 074 /** The base URI of the OpenIG instance, used to rewrite Location headers. */ 075 private Expression<String> baseURI; 076 077 /** 078 * Sets the base URI used to rewrite Location headers. 079 * @param baseURI expression that, when evaluated, will represents the base URI of this OpenIG instance 080 */ 081 public void setBaseURI(final Expression<String> baseURI) { 082 this.baseURI = baseURI; 083 } 084 085 /** 086 * Rewrite Location header if it would have the user go directly to the application. 087 * 088 * @param response http message to be updated 089 * @param bindings bindings to use when evaluating the new {@literal Location} value 090 * @param originalUri request's original Uri 091 */ 092 private Response processResponse(Response response, Bindings bindings, final URI originalUri) { 093 LocationHeader header = LocationHeader.valueOf(response); 094 if (header.getLocationUri() != null) { 095 try { 096 URI currentURI = new URI(header.getLocationUri()); 097 URI rebasedURI = Uris.rebase(currentURI, evaluateBaseUri(bindings, originalUri)); 098 // Only rewrite header if it has changed 099 if (!currentURI.equals(rebasedURI)) { 100 response.getHeaders().put(LocationHeader.NAME, rebasedURI.toString()); 101 } 102 } catch (URISyntaxException | ResponseException ex) { 103 logger.debug(ex); 104 return Responses.newInternalServerError(ex); 105 } 106 } 107 108 return response; 109 } 110 111 private URI evaluateBaseUri(final Bindings bindings, final URI originalUri) 112 throws URISyntaxException, ResponseException { 113 if (baseURI != null) { 114 String uri = baseURI.eval(bindings); 115 116 if (uri == null) { 117 throw logger.debug(new ResponseException(format("The baseURI expression '%s' could not be resolved", 118 baseURI.toString()))); 119 } 120 return new URI(uri); 121 } else { 122 return originalUri; 123 } 124 } 125 126 @Override 127 public Promise<Response, NeverThrowsException> filter(final Context context, 128 final Request request, 129 final Handler next) { 130 // We only care about responses so just call the next handler in the chain. 131 return next.handle(context, request) 132 .then(new Function<Response, Response, NeverThrowsException>() { 133 @Override 134 public Response apply(final Response value) { 135 UriRouterContext routerContext = context.asContext(UriRouterContext.class); 136 return processResponse(value, bindings(context, request, value), 137 routerContext.getOriginalUri()); 138 } 139 }); 140 } 141 142 /** Creates and initializes a LocationHeaderFilter in a heap environment. */ 143 public static class Heaplet extends GenericHeaplet { 144 @Override 145 public Object create() throws HeapException { 146 147 LocationHeaderFilter filter = new LocationHeaderFilter(); 148 filter.baseURI = asExpression(config.get("baseURI"), String.class); 149 150 return filter; 151 } 152 } 153}