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 fbbb8046df NIFI-11370 Corrected JWK Set retrieval for NIFI Trust 
Strategy (#7108)
fbbb8046df is described below

commit fbbb8046df3793eb21789a68f302637a64fde488
Author: exceptionfactory <[email protected]>
AuthorDate: Fri Mar 31 18:26:02 2023 -0500

    NIFI-11370 Corrected JWK Set retrieval for NIFI Trust Strategy (#7108)
    
    - Added StandardOidcIdTokenDecoderFactory based on Spring Security 
OidcIdTokenDecoderFactory with custom REST Operations
    
    Merged #7108 into main.
    
    (cherry picked from commit e4f0508c90f7f354b4210dd80f3dbed5b3254bcd)
---
 .../configuration/OidcSecurityConfiguration.java   |  32 +---
 .../StandardOidcIdTokenDecoderFactory.java         | 184 +++++++++++++++++++++
 .../StandardOidcIdTokenDecoderFactoryTest.java     | 113 +++++++++++++
 3 files changed, 299 insertions(+), 30 deletions(-)

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 2ced302598..14e99d50bf 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
@@ -30,12 +30,12 @@ import org.apache.nifi.security.util.TlsConfiguration;
 import org.apache.nifi.security.util.TlsException;
 import org.apache.nifi.util.FormatUtils;
 import org.apache.nifi.util.NiFiProperties;
-import org.apache.nifi.util.StringUtils;
 import org.apache.nifi.web.security.StandardAuthenticationEntryPoint;
 import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
 import org.apache.nifi.web.security.logout.LogoutRequestManager;
 import org.apache.nifi.web.security.oidc.OidcConfigurationException;
 import org.apache.nifi.web.security.oidc.OidcUrlPath;
+import 
org.apache.nifi.web.security.oidc.authentication.StandardOidcIdTokenDecoderFactory;
 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;
@@ -69,7 +69,6 @@ import 
org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResp
 import 
org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
 import 
org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
 import 
org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider;
-import 
org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
 import 
org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
 import 
org.springframework.security.oauth2.client.registration.ClientRegistration;
 import 
org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@@ -81,9 +80,6 @@ import 
org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequest
 import 
org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
 import 
org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import 
org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
-import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
-import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
-import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
 import 
org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
@@ -375,14 +371,8 @@ public class OidcSecurityConfiguration {
      */
     @Bean
     public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
-        OidcIdTokenDecoderFactory idTokenDecoderFactory = new 
OidcIdTokenDecoderFactory();
-
         final String preferredJwdAlgorithm = 
properties.getOidcPreferredJwsAlgorithm();
-        if (StringUtils.isNotEmpty(preferredJwdAlgorithm)) {
-            idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration 
-> getJwsAlgorithm(preferredJwdAlgorithm));
-        }
-
-        return idTokenDecoderFactory;
+        return new StandardOidcIdTokenDecoderFactory(preferredJwdAlgorithm, 
oidcRestOperations());
     }
 
     /**
@@ -497,22 +487,4 @@ public class OidcSecurityConfiguration {
                 properties.getOidcClaimGroups()
         );
     }
-
-    private JwsAlgorithm getJwsAlgorithm(final String preferredJwsAlgorithm) {
-        final JwsAlgorithm jwsAlgorithm;
-
-        final MacAlgorithm macAlgorithm = 
MacAlgorithm.from(preferredJwsAlgorithm);
-        if (macAlgorithm == null) {
-            final SignatureAlgorithm signatureAlgorithm = 
SignatureAlgorithm.from(preferredJwsAlgorithm);
-            if (signatureAlgorithm == null) {
-                final String message = String.format("Preferred JWS Algorithm 
[%s] not supported", preferredJwsAlgorithm);
-                throw new OidcConfigurationException(message);
-            }
-            jwsAlgorithm = signatureAlgorithm;
-        } else {
-            jwsAlgorithm = macAlgorithm;
-        }
-
-        return jwsAlgorithm;
-    }
 }
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/authentication/StandardOidcIdTokenDecoderFactory.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/authentication/StandardOidcIdTokenDecoderFactory.java
new file mode 100644
index 0000000000..ed8ff9ee70
--- /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/authentication/StandardOidcIdTokenDecoderFactory.java
@@ -0,0 +1,184 @@
+/*
+ * 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.authentication;
+
+import org.apache.nifi.web.security.oidc.OidcConfigurationException;
+import 
org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
+import 
org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenValidator;
+import 
org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
+import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
+import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
+import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.web.client.RestOperations;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * OpenID Connect ID Token Decoder Factory with configurable REST Operations 
for retrieval of JSON Web Keys
+ */
+public class StandardOidcIdTokenDecoderFactory implements 
JwtDecoderFactory<ClientRegistration> {
+    private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = 
"missing_signature_verifier";
+
+    private static final String UNSPECIFIED_ERROR_URI = null;
+
+    private static final JwsAlgorithm DEFAULT_JWS_ALGORITHM = 
SignatureAlgorithm.RS256;
+
+    private static final Map<JwsAlgorithm, String> SECRET_KEY_ALGORITHMS;
+
+    static {
+        final Map<JwsAlgorithm, String> mappings = new HashMap<>();
+        mappings.put(MacAlgorithm.HS256, "HmacSHA256");
+        mappings.put(MacAlgorithm.HS384, "HmacSHA384");
+        mappings.put(MacAlgorithm.HS512, "HmacSHA512");
+        SECRET_KEY_ALGORITHMS = Collections.unmodifiableMap(mappings);
+    }
+
+    private static final ClaimTypeConverter DEFAULT_CLAIM_TYPE_CONVERTER = new 
ClaimTypeConverter(
+            OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters()
+    );
+
+    private final Map<String, JwtDecoder> jwtDecoders = new 
ConcurrentHashMap<>();
+
+    private final JwsAlgorithm configuredJwsAlgorithm;
+
+    private final RestOperations restOperations;
+
+    /**
+     * Standard constructor with optional JWS Algorithm and required REST 
Operations for retrieving JSON Web Keys
+     *
+     * @param preferredJwsAlgorithm Preferred JSON Web Signature Algorithm 
default to RS256 when not provided
+     * @param restOperations REST Operations required for retrieving JSON Web 
Key Set with Signature Algorithms
+     */
+    public StandardOidcIdTokenDecoderFactory(final String 
preferredJwsAlgorithm, final RestOperations restOperations) {
+        this.configuredJwsAlgorithm = getJwsAlgorithm(preferredJwsAlgorithm);
+        this.restOperations = Objects.requireNonNull(restOperations, "REST 
Operations required");
+    }
+
+    /**
+     * Create JSON Web Token Decoder based on Client Registration
+     *
+     * @param clientRegistration Client Registration required
+     * @return JSON Web Token Decoder for OpenID Connect ID Tokens
+     */
+    @Override
+    public JwtDecoder createDecoder(final ClientRegistration 
clientRegistration) {
+        Objects.requireNonNull(clientRegistration, "Client Registration 
required");
+        final String registrationId = clientRegistration.getRegistrationId();
+        return jwtDecoders.computeIfAbsent(registrationId, (key) -> {
+            final NimbusJwtDecoder decoder = buildDecoder(clientRegistration);
+            decoder.setClaimSetConverter(DEFAULT_CLAIM_TYPE_CONVERTER);
+            final OAuth2TokenValidator<Jwt> tokenValidator = 
getTokenValidator(clientRegistration);
+            decoder.setJwtValidator(tokenValidator);
+            return decoder;
+        });
+    }
+
+    private NimbusJwtDecoder buildDecoder(final ClientRegistration 
clientRegistration) {
+        final NimbusJwtDecoder decoder;
+
+        final Class<? extends JwsAlgorithm> jwsAlgorithmClass = 
configuredJwsAlgorithm.getClass();
+        if (SignatureAlgorithm.class.isAssignableFrom(jwsAlgorithmClass)) {
+            final String jwkSetUri = 
clientRegistration.getProviderDetails().getJwkSetUri();
+            if (jwkSetUri == null || jwkSetUri.isEmpty()) {
+                final String message = String.format("JSON Web Key Set URI 
required for Signature Verifier JWS Algorithm [%s]", configuredJwsAlgorithm);
+                final OAuth2Error error = getVerifierError(message);
+                throw new OAuth2AuthenticationException(error, message);
+            }
+
+            final SignatureAlgorithm signatureAlgorithm = (SignatureAlgorithm) 
configuredJwsAlgorithm;
+            decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
+                    .jwsAlgorithm(signatureAlgorithm)
+                    .restOperations(restOperations)
+                    .build();
+        } else if (MacAlgorithm.class.isAssignableFrom(jwsAlgorithmClass)) {
+            final String clientSecret = clientRegistration.getClientSecret();
+            if (clientSecret == null || clientSecret.isEmpty()) {
+                final String message = String.format("Client Secret required 
for MAC Verifier JWS Algorithm [%s]", configuredJwsAlgorithm);
+                final OAuth2Error error = getVerifierError(message);
+                throw new OAuth2AuthenticationException(error, message);
+            }
+
+            final byte[] clientSecretKey = 
clientSecret.getBytes(StandardCharsets.UTF_8);
+            final String secretKeyAlgorithm = 
SECRET_KEY_ALGORITHMS.get(configuredJwsAlgorithm);
+            final SecretKey secretKey = new SecretKeySpec(clientSecretKey, 
secretKeyAlgorithm);
+            final MacAlgorithm macAlgorithm = (MacAlgorithm) 
configuredJwsAlgorithm;
+            decoder = 
NimbusJwtDecoder.withSecretKey(secretKey).macAlgorithm(macAlgorithm).build();
+        } else {
+            final String message = String.format("Signature Verifier JWS 
Algorithm [%s] not supported", configuredJwsAlgorithm);
+            final OAuth2Error error = getVerifierError(message);
+            throw new OAuth2AuthenticationException(error, message);
+        }
+
+        return decoder;
+    }
+
+    /**
+     * Get OAuth2 Token Validator based on Spring Security 
DefaultOidcIdTokenValidatorFactory
+     *
+     * @param clientRegistration Client Registration
+     * @return OAuth2 Token Validator with Timestamp and OpenID Connect ID 
Token Validators
+     */
+    private OAuth2TokenValidator<Jwt> getTokenValidator(final 
ClientRegistration clientRegistration) {
+        return new DelegatingOAuth2TokenValidator<>(
+                new JwtTimestampValidator(),
+                new OidcIdTokenValidator(clientRegistration)
+        );
+    }
+
+    private JwsAlgorithm getJwsAlgorithm(final String preferredJwsAlgorithm) {
+        final JwsAlgorithm jwsAlgorithm;
+
+        if (preferredJwsAlgorithm == null || preferredJwsAlgorithm.isEmpty()) {
+            jwsAlgorithm = DEFAULT_JWS_ALGORITHM;
+        } else {
+            final MacAlgorithm macAlgorithm = 
MacAlgorithm.from(preferredJwsAlgorithm);
+            if (macAlgorithm == null) {
+                final SignatureAlgorithm signatureAlgorithm = 
SignatureAlgorithm.from(preferredJwsAlgorithm);
+                if (signatureAlgorithm == null) {
+                    final String message = String.format("Preferred JWS 
Algorithm [%s] not supported", preferredJwsAlgorithm);
+                    throw new OidcConfigurationException(message);
+                }
+                jwsAlgorithm = signatureAlgorithm;
+            } else {
+                jwsAlgorithm = macAlgorithm;
+            }
+        }
+
+        return jwsAlgorithm;
+    }
+
+    private OAuth2Error getVerifierError(final String message) {
+        return new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE, message, 
UNSPECIFIED_ERROR_URI);
+    }
+}
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/authentication/StandardOidcIdTokenDecoderFactoryTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/authentication/StandardOidcIdTokenDecoderFactoryTest.java
new file mode 100644
index 0000000000..4bc1412f8e
--- /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/authentication/StandardOidcIdTokenDecoderFactoryTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.authentication;
+
+import org.apache.nifi.web.security.oidc.client.web.OidcRegistrationProperty;
+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.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.web.client.RestOperations;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class StandardOidcIdTokenDecoderFactoryTest {
+    private static final String REDIRECT_URI = 
"https://localhost:8443/nifi-api/callback";;
+
+    private static final String AUTHORIZATION_URI = 
"http://localhost/authorize";;
+
+    private static final String TOKEN_URI = "http://localhost/token";;
+
+    private static final String JWK_SET_URI = "http://localhost/token";;
+
+    private static final String CLIENT_ID = "client-id";
+
+    private static final String CLIENT_SECRET = "client-secret";
+
+    private static final String NULL_JWS_ALGORITHM = null;
+
+    private static final SignatureAlgorithm DEFAULT_JWS_ALGORITHM = 
SignatureAlgorithm.RS256;
+
+    @Mock
+    RestOperations restOperations;
+
+    StandardOidcIdTokenDecoderFactory factory;
+
+    @Test
+    void testNullJwsAlgorithmJwkSetUriRequired() {
+        factory = new StandardOidcIdTokenDecoderFactory(NULL_JWS_ALGORITHM, 
restOperations);
+
+        final ClientRegistration clientRegistration = 
getClientRegistrationBuilder().jwkSetUri(JWK_SET_URI).build();
+        final JwtDecoder decoder = factory.createDecoder(clientRegistration);
+
+        assertNotNull(decoder);
+    }
+
+    @Test
+    void testNullJwsAlgorithmJwkSetUriNotFound() {
+        factory = new StandardOidcIdTokenDecoderFactory(NULL_JWS_ALGORITHM, 
restOperations);
+
+        final ClientRegistration clientRegistration = 
getClientRegistrationBuilder().build();
+        final OAuth2AuthenticationException exception = 
assertThrows(OAuth2AuthenticationException.class, () -> 
factory.createDecoder(clientRegistration));
+
+        final OAuth2Error error = exception.getError();
+        final String description = error.getDescription();
+        assertTrue(description.contains(DEFAULT_JWS_ALGORITHM.name()));
+    }
+
+    @Test
+    void testMacJwsAlgorithmClientSecretRequired() {
+        factory = new 
StandardOidcIdTokenDecoderFactory(MacAlgorithm.HS256.getName(), restOperations);
+
+        final ClientRegistration clientRegistration = 
getClientRegistrationBuilder().clientSecret(CLIENT_SECRET).build();
+        final JwtDecoder decoder = factory.createDecoder(clientRegistration);
+
+        assertNotNull(decoder);
+    }
+
+    @Test
+    void testMacJwsAlgorithmClientSecretNotFound() {
+        final MacAlgorithm macAlgorithm = MacAlgorithm.HS256;
+        factory = new 
StandardOidcIdTokenDecoderFactory(macAlgorithm.getName(), restOperations);
+
+        final ClientRegistration clientRegistration = 
getClientRegistrationBuilder().build();
+        final OAuth2AuthenticationException exception = 
assertThrows(OAuth2AuthenticationException.class, () -> 
factory.createDecoder(clientRegistration));
+
+        final OAuth2Error error = exception.getError();
+        final String description = error.getDescription();
+        assertTrue(description.contains(macAlgorithm.name()));
+    }
+
+    ClientRegistration.Builder getClientRegistrationBuilder() {
+        return 
ClientRegistration.withRegistrationId(OidcRegistrationProperty.REGISTRATION_ID.getProperty())
+                
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+                .clientId(CLIENT_ID)
+                .redirectUri(REDIRECT_URI)
+                .authorizationUri(AUTHORIZATION_URI)
+                .tokenUri(TOKEN_URI);
+    }
+}

Reply via email to