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

pvillard31 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new f2b6504ea3d NIFI-16301 Fixed deprecated Spring Security SAML 2 
Principal references (#11632)
f2b6504ea3d is described below

commit f2b6504ea3d38281b88db1018db15a230fabc98d
Author: David Handermann <[email protected]>
AuthorDate: Sat Sep 5 07:44:22 2026 -0500

    NIFI-16301 Fixed deprecated Spring Security SAML 2 Principal references 
(#11632)
---
 .../ResponseAuthenticationConverter.java           | 16 ++++--
 .../Saml2AuthenticationSuccessHandler.java         | 15 +++--
 .../identity/AttributeNameIdentityConverter.java   | 16 +++---
 .../Saml2AuthenticationSuccessHandlerTest.java     | 62 +++++++++++++++++++++
 .../AttributeNameIdentityConverterTest.java        | 65 ++++++++++++++++++++++
 5 files changed, 154 insertions(+), 20 deletions(-)

diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/service/authentication/ResponseAuthenticationConverter.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/service/authentication/ResponseAuthenticationConverter.java
index 60a204826d2..2c2b875bf95 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/service/authentication/ResponseAuthenticationConverter.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/service/authentication/ResponseAuthenticationConverter.java
@@ -26,7 +26,7 @@ import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import 
org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
 import 
org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider.ResponseToken;
-import 
org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
+import 
org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication;
 import 
org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
 
 import java.util.Collection;
@@ -53,7 +53,9 @@ public class ResponseAuthenticationConverter implements 
Converter<ResponseToken,
     }
 
     /**
-     * Convert SAML 2 Response Token using default Converter and process 
authorities based on Group Attribute Name
+     * Convert SAML 2 Response Token using default Converter and process 
authorities based on Group Attribute Name.
+     * The Response Assertion and Relying Party Registration from the default 
Converter are retained so that
+     * downstream handlers can read attributes from the Response Assertion.
      *
      * @param responseToken SAML 2 Response Token
      * @return SAML 2 Authentication
@@ -63,8 +65,14 @@ public class ResponseAuthenticationConverter implements 
Converter<ResponseToken,
         Objects.requireNonNull(responseToken, "Response Token required");
         final List<Assertion> assertions = 
responseToken.getResponse().getAssertions();
         final Saml2Authentication authentication = 
Objects.requireNonNull(defaultConverter.convert(responseToken), "Authentication 
required");
-        final Saml2AuthenticatedPrincipal principal = 
(Saml2AuthenticatedPrincipal) authentication.getPrincipal();
-        return new Saml2Authentication(principal, 
authentication.getSaml2Response(), getAuthorities(assertions));
+        final Collection<? extends GrantedAuthority> authorities = 
getAuthorities(assertions);
+
+        if (authentication instanceof final Saml2AssertionAuthentication 
assertionAuthentication) {
+            final String registrationId = 
assertionAuthentication.getRelyingPartyRegistrationId();
+            return new 
Saml2AssertionAuthentication(assertionAuthentication.getPrincipal(), 
assertionAuthentication.getCredentials(), authorities, registrationId);
+        }
+
+        return new Saml2Authentication(authentication.getPrincipal(), 
authentication.getSaml2Response(), authorities);
     }
 
     private Collection<? extends GrantedAuthority> getAuthorities(final 
List<Assertion> assertions) {
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java
index ba2b2c45469..90bd83816b7 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java
@@ -30,7 +30,8 @@ import org.springframework.core.convert.converter.Converter;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import 
org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
+import 
org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication;
+import 
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
 import 
org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
 
 import java.net.URI;
@@ -59,7 +60,7 @@ public class Saml2AuthenticationSuccessHandler extends 
SimpleUrlAuthenticationSu
 
     private final Duration expiration;
 
-    private Converter<Saml2AuthenticatedPrincipal, String> identityConverter = 
Saml2AuthenticatedPrincipal::getName;
+    private Converter<Saml2ResponseAssertionAccessor, String> 
identityConverter = Saml2ResponseAssertionAccessor::getNameId;
 
     /**
      * SAML 2 Authentication Success Handler requires Bearer Token Provider 
and expiration for generated tokens
@@ -82,11 +83,11 @@ public class Saml2AuthenticationSuccessHandler extends 
SimpleUrlAuthenticationSu
     }
 
     /**
-     * Set Identity Converter for customized mapping of SAML 2 Authenticated 
Principal to user identity
+     * Set Identity Converter for customized mapping of SAML 2 Response 
Assertion to user identity
      *
      * @param identityConverter Identity Converter required
      */
-    public void setIdentityConverter(final 
Converter<Saml2AuthenticatedPrincipal, String> identityConverter) {
+    public void setIdentityConverter(final 
Converter<Saml2ResponseAssertionAccessor, String> identityConverter) {
         this.identityConverter = Objects.requireNonNull(identityConverter, 
"Converter required");
     }
 
@@ -123,11 +124,9 @@ public class Saml2AuthenticationSuccessHandler extends 
SimpleUrlAuthenticationSu
     }
 
     private String getIdentity(final Authentication authentication) {
-        final Object principal = authentication.getPrincipal();
-
         final String identity;
-        if (principal instanceof final Saml2AuthenticatedPrincipal 
authenticatedPrincipal) {
-            identity = identityConverter.convert(authenticatedPrincipal);
+        if (authentication instanceof final Saml2AssertionAuthentication 
assertionAuthentication) {
+            identity = 
identityConverter.convert(assertionAuthentication.getCredentials());
         } else {
             identity = authentication.getName();
         }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverter.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverter.java
index 79cf485f13b..d067c62cc0a 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverter.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverter.java
@@ -17,14 +17,14 @@
 package org.apache.nifi.web.security.saml2.web.authentication.identity;
 
 import org.springframework.core.convert.converter.Converter;
-import 
org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
+import 
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
 
 import java.util.Objects;
 
 /**
  * Converter for customized User Identity using SAML Attribute Value
  */
-public class AttributeNameIdentityConverter implements 
Converter<Saml2AuthenticatedPrincipal, String> {
+public class AttributeNameIdentityConverter implements 
Converter<Saml2ResponseAssertionAccessor, String> {
     private final String attributeName;
 
     public AttributeNameIdentityConverter(final String attributeName) {
@@ -32,14 +32,14 @@ public class AttributeNameIdentityConverter implements 
Converter<Saml2Authentica
     }
 
     /**
-     * Convert Principal to identity using configured attribute name when found
+     * Convert Response Assertion to identity using configured attribute name 
when found
      *
-     * @param principal SAML 2 Authenticated Principal
-     * @return Attribute Value or Principal Name when attribute not found
+     * @param assertion SAML 2 Response Assertion
+     * @return Attribute Value or Name Identifier when attribute not found
      */
     @Override
-    public String convert(final Saml2AuthenticatedPrincipal principal) {
-        final Object attribute = principal.getFirstAttribute(attributeName);
-        return attribute == null ? principal.getName() : attribute.toString();
+    public String convert(final Saml2ResponseAssertionAccessor assertion) {
+        final Object attribute = assertion.getFirstAttribute(attributeName);
+        return attribute == null ? assertion.getNameId() : 
attribute.toString();
     }
 }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java
index 370fca56c22..97bfe125fc3 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java
@@ -21,10 +21,13 @@ import jakarta.servlet.http.Cookie;
 import org.apache.nifi.authorization.util.IdentityMapping;
 import org.apache.nifi.web.security.cookie.ApplicationCookieName;
 import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
+import 
org.apache.nifi.web.security.saml2.web.authentication.identity.AttributeNameIdentityConverter;
+import org.apache.nifi.web.security.token.LoginAuthenticationToken;
 import org.apache.nifi.web.servlet.shared.ProxyHeader;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.springframework.mock.web.MockHttpServletRequest;
@@ -32,13 +35,19 @@ import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
+import 
org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication;
+import 
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion;
+import 
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
 
 import java.time.Duration;
 import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.regex.Pattern;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.verify;
 
 @ExtendWith(MockitoExtension.class)
 class Saml2AuthenticationSuccessHandlerTest {
@@ -72,6 +81,20 @@ class Saml2AuthenticationSuccessHandlerTest {
 
     private static final String ALLOWED_CONTEXT_PATHS_PARAMETER = 
"allowedContextPaths";
 
+    private static final String IDENTITY_ATTRIBUTE_NAME = 
"urn:oid:0.9.2342.19200300.100.1.3";
+
+    private static final String IDENTITY_ATTRIBUTE_VALUE = 
"[email protected]";
+
+    private static final String MAPPED_IDENTITY_ATTRIBUTE_VALUE = 
"[email protected]";
+
+    private static final String NAME_IDENTIFIER = "name-identifier";
+
+    private static final String MAPPED_NAME_IDENTIFIER = "NAME-IDENTIFIER";
+
+    private static final String RESPONSE_VALUE = "<saml2p:Response/>";
+
+    private static final String REGISTRATION_ID = "consumer";
+
     private static final IdentityMapping UPPER_IDENTITY_MAPPING = new 
IdentityMapping(
             IdentityMapping.Transform.UPPER.toString(),
             MATCH_PATTERN,
@@ -128,6 +151,29 @@ class Saml2AuthenticationSuccessHandlerTest {
         assertBearerCookieAdded(FORWARDED_COOKIE_PATH);
     }
 
+    @Test
+    void testDetermineTargetUrlAssertionAuthenticationNameIdentifier() {
+        httpServletRequest.setRequestURI(REQUEST_URI);
+
+        final Authentication authentication = 
getAssertionAuthentication(Map.of());
+        final String targetUrl = 
handler.determineTargetUrl(httpServletRequest, httpServletResponse, 
authentication);
+
+        assertEquals(TARGET_URL, targetUrl);
+        assertBearerTokenIdentityEquals(MAPPED_NAME_IDENTIFIER);
+    }
+
+    @Test
+    void testDetermineTargetUrlAssertionAuthenticationIdentityConverter() {
+        handler.setIdentityConverter(new 
AttributeNameIdentityConverter(IDENTITY_ATTRIBUTE_NAME));
+        httpServletRequest.setRequestURI(REQUEST_URI);
+
+        final Authentication authentication = 
getAssertionAuthentication(Map.of(IDENTITY_ATTRIBUTE_NAME, 
List.of(IDENTITY_ATTRIBUTE_VALUE)));
+        final String targetUrl = 
handler.determineTargetUrl(httpServletRequest, httpServletResponse, 
authentication);
+
+        assertEquals(TARGET_URL, targetUrl);
+        assertBearerTokenIdentityEquals(MAPPED_IDENTITY_ATTRIBUTE_VALUE);
+    }
+
     void assertTargetUrlEquals(final String expectedTargetUrl) {
         final Authentication authentication = new 
TestingAuthenticationToken(IDENTITY, IDENTITY, AUTHORITY);
 
@@ -136,6 +182,22 @@ class Saml2AuthenticationSuccessHandlerTest {
         assertEquals(expectedTargetUrl, targetUrl);
     }
 
+    void assertBearerTokenIdentityEquals(final String expectedIdentity) {
+        final ArgumentCaptor<LoginAuthenticationToken> tokenCaptor = 
ArgumentCaptor.forClass(LoginAuthenticationToken.class);
+        verify(bearerTokenProvider).getBearerToken(tokenCaptor.capture());
+
+        assertEquals(expectedIdentity, tokenCaptor.getValue().getName());
+    }
+
+    private Authentication getAssertionAuthentication(final Map<String, 
List<Object>> attributes) {
+        final Saml2ResponseAssertionAccessor assertion = 
Saml2ResponseAssertion.withResponseValue(RESPONSE_VALUE)
+                .nameId(NAME_IDENTIFIER)
+                .attributes(attributes)
+                .build();
+
+        return new Saml2AssertionAuthentication(NAME_IDENTIFIER, assertion, 
Collections.emptyList(), REGISTRATION_ID);
+    }
+
     void assertBearerCookieAdded(final String expectedCookiePath) {
         final Cookie responseCookie = 
httpServletResponse.getCookie(ApplicationCookieName.AUTHORIZATION_BEARER.getCookieName());
 
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverterTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverterTest.java
new file mode 100644
index 00000000000..a1e017c3fa5
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/identity/AttributeNameIdentityConverterTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.saml2.web.authentication.identity;
+
+import org.junit.jupiter.api.Test;
+import 
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion;
+import 
org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class AttributeNameIdentityConverterTest {
+    private static final String ATTRIBUTE_NAME = 
"urn:oid:0.9.2342.19200300.100.1.3";
+
+    private static final String ATTRIBUTE_VALUE = "[email protected]";
+
+    private static final String OTHER_ATTRIBUTE_NAME = "urn:oid:2.5.4.3";
+
+    private static final String NAME_IDENTIFIER = "name-identifier";
+
+    private static final String RESPONSE_VALUE = "<saml2p:Response/>";
+
+    private final AttributeNameIdentityConverter converter = new 
AttributeNameIdentityConverter(ATTRIBUTE_NAME);
+
+    @Test
+    void testConvertConfiguredAttributeFound() {
+        final Saml2ResponseAssertionAccessor assertion = 
getAssertion(Map.of(ATTRIBUTE_NAME, List.of(ATTRIBUTE_VALUE)));
+
+        assertEquals(ATTRIBUTE_VALUE, converter.convert(assertion));
+    }
+
+    @Test
+    void testConvertConfiguredAttributeNotFoundReturnsNameIdentifier() {
+        final Saml2ResponseAssertionAccessor assertionWithoutAttributes = 
getAssertion(Map.of());
+
+        assertEquals(NAME_IDENTIFIER, 
converter.convert(assertionWithoutAttributes));
+
+        final Saml2ResponseAssertionAccessor assertionWithOtherAttribute = 
getAssertion(Map.of(OTHER_ATTRIBUTE_NAME, List.of(ATTRIBUTE_VALUE)));
+
+        assertEquals(NAME_IDENTIFIER, 
converter.convert(assertionWithOtherAttribute));
+    }
+
+    private Saml2ResponseAssertionAccessor getAssertion(final Map<String, 
List<Object>> attributes) {
+        return Saml2ResponseAssertion.withResponseValue(RESPONSE_VALUE)
+                .nameId(NAME_IDENTIFIER)
+                .attributes(attributes)
+                .build();
+    }
+}

Reply via email to