You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2023/03/30 20:29:38 UTC

[nifi] branch support/nifi-1.x updated: NIFI-11365 Corrected OIDC Redirect URI resolution for reverse proxies (#7104)

This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch support/nifi-1.x
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/support/nifi-1.x by this push:
     new 006d1507d4 NIFI-11365 Corrected OIDC Redirect URI resolution for reverse proxies (#7104)
006d1507d4 is described below

commit 006d1507d45d8358a9bdda29f28b48c8fd0ad4a0
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Thu Mar 30 15:26:35 2023 -0500

    NIFI-11365 Corrected OIDC Redirect URI resolution for reverse proxies (#7104)
    
    - Added Authorization Request Resolver with support for building the base redirect URI using allowed proxy headers
    
    This closes #7104
    
    (cherry picked from commit 75eb449a312815b84545c1ee157144255b8fc97d)
---
 .../configuration/OidcSecurityConfiguration.java   |   4 +-
 ...StandardOAuth2AuthorizationRequestResolver.java | 101 ++++++++++++++++
 ...dardOAuth2AuthorizationRequestResolverTest.java | 133 +++++++++++++++++++++
 3 files changed, 237 insertions(+), 1 deletion(-)

diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
index ac37255590..2ced302598 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/OidcSecurityConfiguration.java
@@ -38,6 +38,7 @@ import org.apache.nifi.web.security.oidc.OidcConfigurationException;
 import org.apache.nifi.web.security.oidc.OidcUrlPath;
 import org.apache.nifi.web.security.oidc.client.web.AuthorizedClientExpirationCommand;
 import org.apache.nifi.web.security.oidc.client.web.OidcBearerTokenRefreshFilter;
+import org.apache.nifi.web.security.oidc.client.web.StandardOAuth2AuthorizationRequestResolver;
 import org.apache.nifi.web.security.oidc.client.web.converter.AuthenticationResultConverter;
 import org.apache.nifi.web.security.oidc.client.web.converter.AuthorizedClientConverter;
 import org.apache.nifi.web.security.oidc.client.web.StandardAuthorizationRequestRepository;
@@ -187,7 +188,8 @@ public class OidcSecurityConfiguration {
      */
     @Bean
     public OAuth2AuthorizationRequestRedirectFilter oAuth2AuthorizationRequestRedirectFilter() {
-        final OAuth2AuthorizationRequestRedirectFilter filter = new OAuth2AuthorizationRequestRedirectFilter(clientRegistrationRepository());
+        final StandardOAuth2AuthorizationRequestResolver authorizationRequestResolver = new StandardOAuth2AuthorizationRequestResolver(clientRegistrationRepository());
+        final OAuth2AuthorizationRequestRedirectFilter filter = new OAuth2AuthorizationRequestRedirectFilter(authorizationRequestResolver);
         filter.setAuthorizationRequestRepository(authorizationRequestRepository());
         filter.setRequestCache(nullRequestCache);
         return filter;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/StandardOAuth2AuthorizationRequestResolver.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/StandardOAuth2AuthorizationRequestResolver.java
new file mode 100644
index 0000000000..dd8ca86ee3
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/StandardOAuth2AuthorizationRequestResolver.java
@@ -0,0 +1,101 @@
+/*
+ * 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.nifi.web.security.oidc.client.web;
+
+import org.apache.nifi.web.util.RequestUriBuilder;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.URI;
+import java.util.Objects;
+
+/**
+ * Authorization Request Resolver supports handling of headers from reverse proxy servers
+ */
+public class StandardOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
+    private final OAuth2AuthorizationRequestResolver resolver;
+
+    /**
+     * Resolver constructor delegates to the Spring Security Default Resolver and uses the default Request Base URI
+     *
+     * @param clientRegistrationRepository Client Registration Repository
+     */
+    public StandardOAuth2AuthorizationRequestResolver(final ClientRegistrationRepository clientRegistrationRepository) {
+        Objects.requireNonNull(clientRegistrationRepository, "Repository required");
+        resolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
+    }
+
+    /**
+     * Resolve Authorization Request delegating to default resolver
+     *
+     * @param request HTTP Servlet Request
+     * @return OAuth2 Authorization Request or null when not resolved
+     */
+    @Override
+    public OAuth2AuthorizationRequest resolve(final HttpServletRequest request) {
+        final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(request);
+        return getResolvedAuthorizationRequest(authorizationRequest, request);
+    }
+
+    /**
+     * Resolve Authorization Request delegating to default resolver
+     *
+     * @param request HTTP Servlet Request
+     * @param clientRegistrationId Client Registration Identifier
+     * @return OAuth2 Authorization Request or null when not resolved
+     */
+    @Override
+    public OAuth2AuthorizationRequest resolve(final HttpServletRequest request, final String clientRegistrationId) {
+        final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(request, clientRegistrationId);
+        return getResolvedAuthorizationRequest(authorizationRequest, request);
+    }
+
+    private OAuth2AuthorizationRequest getResolvedAuthorizationRequest(final OAuth2AuthorizationRequest authorizationRequest, final HttpServletRequest request) {
+        final OAuth2AuthorizationRequest resolvedAuthorizationRequest;
+
+        if (authorizationRequest == null) {
+            resolvedAuthorizationRequest = null;
+        } else {
+            final String redirectUri = authorizationRequest.getRedirectUri();
+            if (redirectUri == null) {
+                resolvedAuthorizationRequest = authorizationRequest;
+            } else {
+                final String requestBasedRedirectUri = getRequestBasedRedirectUri(redirectUri, request);
+                resolvedAuthorizationRequest = OAuth2AuthorizationRequest.from(authorizationRequest).redirectUri(requestBasedRedirectUri).build();
+            }
+        }
+
+        return resolvedAuthorizationRequest;
+    }
+
+    private String getRequestBasedRedirectUri(final String redirectUri, final HttpServletRequest request) {
+        final String redirectUriPath = UriComponentsBuilder.fromUriString(redirectUri).build().getPath();
+        final URI baseUri =  RequestUriBuilder.fromHttpServletRequest(request).path(redirectUriPath).build();
+        return UriComponentsBuilder.fromUriString(redirectUri)
+                .scheme(baseUri.getScheme())
+                .host(baseUri.getHost())
+                .port(baseUri.getPort())
+                .replacePath(baseUri.getPath())
+                .build()
+                .toUriString();
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/StandardOAuth2AuthorizationRequestResolverTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/StandardOAuth2AuthorizationRequestResolverTest.java
new file mode 100644
index 0000000000..5644c68f66
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/StandardOAuth2AuthorizationRequestResolverTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.nifi.web.security.oidc.client.web;
+
+import org.apache.nifi.web.util.WebUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+
+import javax.servlet.ServletContext;
+import java.net.URI;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class StandardOAuth2AuthorizationRequestResolverTest {
+    private static final String REDIRECT_URI = "https://localhost:8443/nifi-api/callback";
+
+    private static final String FORWARDED_PATH = "/forwarded";
+
+    private static final String FORWARDED_REDIRECT_URI = String.format("https://localhost.localdomain%s/nifi-api/callback", FORWARDED_PATH);
+
+    private static final String ALLOWED_CONTEXT_PATHS_PARAMETER = "allowedContextPaths";
+
+    private static final String AUTHORIZATION_URI = "http://localhost/authorize";
+
+    private static final String TOKEN_URI = "http://localhost/token";
+
+    private static final String CLIENT_ID = "client-id";
+
+    private static final String REGISTRATION_ID = OidcRegistrationProperty.REGISTRATION_ID.getProperty();
+
+    MockHttpServletRequest httpServletRequest;
+
+    MockHttpServletResponse httpServletResponse;
+
+    @Mock
+    ClientRegistrationRepository clientRegistrationRepository;
+
+    StandardOAuth2AuthorizationRequestResolver resolver;
+
+    @BeforeEach
+    void setResolver() {
+        resolver = new StandardOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
+        httpServletRequest = new MockHttpServletRequest();
+        httpServletResponse = new MockHttpServletResponse();
+    }
+
+    @Test
+    void testResolveNotFound() {
+        final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(httpServletRequest);
+
+        assertNull(authorizationRequest);
+    }
+
+    @Test
+    void testResolveClientRegistrationIdNotFound() {
+        final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(httpServletRequest, null);
+
+        assertNull(authorizationRequest);
+    }
+
+    @Test
+    void testResolveFound() {
+        final URI redirectUri = URI.create(REDIRECT_URI);
+        httpServletRequest.setScheme(redirectUri.getScheme());
+        httpServletRequest.setServerPort(redirectUri.getPort());
+
+        final ClientRegistration clientRegistration = getClientRegistration();
+        when(clientRegistrationRepository.findByRegistrationId(eq(REGISTRATION_ID))).thenReturn(clientRegistration);
+
+        final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(httpServletRequest, REGISTRATION_ID);
+
+        assertNotNull(authorizationRequest);
+        assertEquals(REDIRECT_URI, authorizationRequest.getRedirectUri());
+    }
+
+    @Test
+    void testResolveFoundRedirectUriProxyHeaders() {
+        final ClientRegistration clientRegistration = getClientRegistration();
+        when(clientRegistrationRepository.findByRegistrationId(eq(REGISTRATION_ID))).thenReturn(clientRegistration);
+
+        final ServletContext servletContext = httpServletRequest.getServletContext();
+        servletContext.setInitParameter(ALLOWED_CONTEXT_PATHS_PARAMETER, FORWARDED_PATH);
+
+        final URI forwardedRedirectUri = URI.create(FORWARDED_REDIRECT_URI);
+        httpServletRequest.addHeader(WebUtils.PROXY_SCHEME_HTTP_HEADER, forwardedRedirectUri.getScheme());
+        httpServletRequest.addHeader(WebUtils.PROXY_HOST_HTTP_HEADER, forwardedRedirectUri.getHost());
+        httpServletRequest.addHeader(WebUtils.PROXY_PORT_HTTP_HEADER, forwardedRedirectUri.getPort());
+        httpServletRequest.addHeader(WebUtils.PROXY_CONTEXT_PATH_HTTP_HEADER, FORWARDED_PATH);
+
+        final OAuth2AuthorizationRequest authorizationRequest = resolver.resolve(httpServletRequest, REGISTRATION_ID);
+
+        assertNotNull(authorizationRequest);
+        assertEquals(FORWARDED_REDIRECT_URI, authorizationRequest.getRedirectUri());
+    }
+
+    ClientRegistration getClientRegistration() {
+        return ClientRegistration.withRegistrationId(OidcRegistrationProperty.REGISTRATION_ID.getProperty())
+                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+                .clientId(CLIENT_ID)
+                .redirectUri(REDIRECT_URI)
+                .authorizationUri(AUTHORIZATION_URI)
+                .tokenUri(TOKEN_URI)
+                .build();
+    }
+}