| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF 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 |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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.apache.knox.gateway.dispatch; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.http.Header; |
| import org.apache.http.client.methods.HttpUriRequest; |
| import org.apache.http.client.methods.RequestBuilder; |
| import org.apache.knox.gateway.security.SubjectUtils; |
| import org.apache.log4j.Logger; |
| |
| import javax.security.auth.Subject; |
| import javax.servlet.http.HttpServletRequest; |
| import java.io.IOException; |
| import java.util.Locale; |
| |
| class NiFiRequestUtil { |
| |
| static HttpUriRequest modifyOutboundRequest(HttpUriRequest outboundRequest, HttpServletRequest inboundRequest) throws IOException { |
| // preserve trailing slash from inbound request in the outbound request |
| if (inboundRequest.getPathInfo().endsWith("/")) { |
| String[] split = outboundRequest.getURI().toString().split("\\?"); |
| if (!split[0].endsWith("/")) { |
| outboundRequest = RequestBuilder.copy(outboundRequest).setUri(split[0] + "/" + (split.length == 2 ? "?" + split[1] : "")).build(); |
| } |
| } |
| // update the X-Forwarded-Context header to include the Knox-specific context path |
| final Header originalXForwardedContextHeader = outboundRequest.getFirstHeader(NiFiHeaders.X_FORWARDED_CONTEXT); |
| if (originalXForwardedContextHeader != null) { |
| String xForwardedContextHeaderValue = originalXForwardedContextHeader.getValue(); |
| if (xForwardedContextHeaderValue != null && !xForwardedContextHeaderValue.isEmpty()) { |
| // Inspect the inbound request and outbound request to determine the additional context path from the rewrite |
| // rules that needs to be added to the X-Forwarded-Context header to allow proper proxying to NiFi. |
| // |
| // NiFi does its own URL rewriting, and will not work with the context path provided by Knox |
| // (ie, "/gateway/sandbox"). |
| // |
| // For example, if Knox has a rewrite rule "*://*:*/**/nifi-app/{**}?{**}", "/nifi-app" needs to be added |
| // to the existing value of the X-Forwarded-Context header, which ends up being "/gateway/sandbox/nifi-app". |
| String inboundRequestPathInfo = inboundRequest.getPathInfo(); |
| String outboundRequestUriPath = outboundRequest.getURI().getPath(); |
| String outboundRequestUriPathNoTrailingSlash = StringUtils.removeEnd(outboundRequestUriPath, "/"); |
| String knoxRouteContext = null; |
| int index = inboundRequestPathInfo.lastIndexOf(outboundRequestUriPathNoTrailingSlash); |
| if (index >= 0) { |
| knoxRouteContext = inboundRequestPathInfo.substring(0, index); |
| } else { |
| Logger.getLogger(NiFiRequestUtil.class.getName()).error(String.format(Locale.ROOT, "Unable to find index of %s in %s", outboundRequestUriPathNoTrailingSlash, inboundRequestPathInfo)); |
| } |
| outboundRequest.setHeader(NiFiHeaders.X_FORWARDED_CONTEXT, xForwardedContextHeaderValue + knoxRouteContext); |
| } |
| } |
| |
| // NiFi requires the header "X-ProxiedEntitiesChain" to be set with the identity or identities of the authenticated requester. |
| // The effective principal (identity) in the requester subject must be added to "X-ProxiedEntitiesChain". |
| // If the request already has a populated "X-ProxiedEntitiesChain" header, the identities must be appended to it. |
| // If the user proxied through Knox is anonymous, the "Anonymous" identity needs to be represented in X-ProxiedEntitiesChain |
| // as empty angle brackets "<>". |
| final Subject subject = SubjectUtils.getCurrentSubject(); |
| String effectivePrincipalName = SubjectUtils.getEffectivePrincipalName(subject); |
| String proxiedEntitesChainHeader = inboundRequest.getHeader(NiFiHeaders.X_PROXIED_ENTITIES_CHAIN); |
| if(proxiedEntitesChainHeader == null) { |
| proxiedEntitesChainHeader = ""; |
| } |
| outboundRequest.setHeader(NiFiHeaders.X_PROXIED_ENTITIES_CHAIN, proxiedEntitesChainHeader + |
| String.format(Locale.ROOT, "<%s>", "anonymous".equalsIgnoreCase(effectivePrincipalName) ? "" : effectivePrincipalName)); |
| |
| // Make sure headers named "Cookie" are removed from the request to NiFi, since NiFi does not use cookies. |
| Header[] cookieHeaders = outboundRequest.getHeaders("Cookie"); |
| for (Header cookieHeader : cookieHeaders) { |
| outboundRequest.removeHeader(cookieHeader); |
| } |
| return outboundRequest; |
| } |
| } |