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

exceptionfactory 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 831a11d0b5 NIFI-7823 Added groups mapping from OIDC token claim
831a11d0b5 is described below

commit 831a11d0b5b118002e9969778d3b164401761a7d
Author: Malthe Borch <[email protected]>
AuthorDate: Wed Sep 28 11:16:50 2022 +0200

    NIFI-7823 Added groups mapping from OIDC token claim
    
    This closes #6454
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../java/org/apache/nifi/util/NiFiProperties.java  | 12 ++++++++++
 .../src/main/java/org/apache/nifi/idp/IdpType.java |  1 +
 .../apache/nifi/web/api/OIDCAccessResource.java    | 19 +++++++++++++++
 .../src/main/resources/nifi-web-api-context.xml    |  1 +
 .../nifi/web/api/OIDCAccessResourceTest.java       | 13 +++++++++++
 .../oidc/StandardOidcIdentityProvider.java         | 17 +++++++++++++-
 .../security/token/LoginAuthenticationToken.java   | 27 +++++++++++++++++-----
 7 files changed, 83 insertions(+), 7 deletions(-)

diff --git 
a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
 
b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index ec6385fe6c..73c3ae20c0 100644
--- 
a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ 
b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -193,6 +193,7 @@ public class NiFiProperties extends ApplicationProperties {
     public static final String SECURITY_USER_OIDC_PREFERRED_JWSALGORITHM = 
"nifi.security.user.oidc.preferred.jwsalgorithm";
     public static final String SECURITY_USER_OIDC_ADDITIONAL_SCOPES = 
"nifi.security.user.oidc.additional.scopes";
     public static final String SECURITY_USER_OIDC_CLAIM_IDENTIFYING_USER = 
"nifi.security.user.oidc.claim.identifying.user";
+    public static final String NIFI_SECURITY_USER_OIDC_CLAIM_GROUPS = 
"nifi.security.user.oidc.claim.groups";
     public static final String 
SECURITY_USER_OIDC_FALLBACK_CLAIMS_IDENTIFYING_USER = 
"nifi.security.user.oidc.fallback.claims.identifying.user";
 
     // apache knox
@@ -1145,6 +1146,17 @@ public class NiFiProperties extends 
ApplicationProperties {
         return getProperty(SECURITY_USER_OIDC_CLAIM_IDENTIFYING_USER, 
"email").trim();
     }
 
+    /**
+     * Returns the claim to be used to extract user groups from the OIDC 
payload.
+     * Claim must be requested by adding the scope for it.
+     * Default is 'groups'.
+     *
+     * @return The claim to be used to extract user groups.
+     */
+    public String getOidcClaimGroups() {
+        return getProperty(NIFI_SECURITY_USER_OIDC_CLAIM_GROUPS, 
"groups").trim();
+    }
+
     /**
      * Returns the list of fallback claims to be used to identify a user when 
the configured claim is empty for a user
      *
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/idp/IdpType.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/idp/IdpType.java
index 2ce9b34c66..cc3c5ff0fa 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/idp/IdpType.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/idp/IdpType.java
@@ -21,6 +21,7 @@ package org.apache.nifi.idp;
  */
 public enum IdpType {
 
+    OIDC,
     SAML;
 
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
index d13a3eebb0..ac5f4227fb 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
@@ -35,8 +35,10 @@ import org.apache.http.client.methods.HttpPost;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.message.BasicNameValuePair;
+import org.apache.nifi.admin.service.IdpUserGroupService;
 import 
org.apache.nifi.authentication.exception.AuthenticationNotSupportedException;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.idp.IdpType;
 import org.apache.nifi.security.util.SslContextFactory;
 import org.apache.nifi.security.util.StandardTlsConfiguration;
 import org.apache.nifi.security.util.TlsConfiguration;
@@ -66,11 +68,14 @@ import javax.ws.rs.core.UriBuilder;
 import java.io.IOException;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.UUID;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 
 @Path(OIDCEndpoints.OIDC_ACCESS_ROOT)
@@ -94,6 +99,7 @@ public class OIDCAccessResource extends ApplicationResource {
     private static final boolean LOGGING_IN = true;
 
     private OidcService oidcService;
+    private IdpUserGroupService idpUserGroupService;
     private BearerTokenProvider bearerTokenProvider;
 
     public OIDCAccessResource() {
@@ -156,6 +162,12 @@ public class OIDCAccessResource extends 
ApplicationResource {
 
                 // store the NiFi token
                 oidcService.storeJwt(oidcRequestIdentifier, bearerToken);
+
+                Set<String> groups = 
oidcToken.getAuthorities().stream().map(authority -> 
authority.getAuthority()).collect(Collectors.collectingAndThen(
+                        Collectors.toSet(),
+                        Collections::unmodifiableSet
+                ));
+                idpUserGroupService.replaceUserGroups(oidcToken.getName(), 
IdpType.OIDC, groups);
             } catch (final Exception e) {
                 logger.error(OIDC_ID_TOKEN_AUTHN_ERROR + e.getMessage(), e);
 
@@ -236,6 +248,9 @@ public class OIDCAccessResource extends ApplicationResource 
{
         applicationCookieService.removeCookie(getCookieResourceUri(), 
httpServletResponse, ApplicationCookieName.AUTHORIZATION_BEARER);
         logger.debug("Invalidated JWT for user [{}]", mappedUserIdentity);
 
+        idpUserGroupService.deleteUserGroups(mappedUserIdentity);
+        logger.debug("Deleted user groups for user [{}]", mappedUserIdentity);
+
         // Get the oidc discovery url
         String oidcDiscoveryUrl = properties.getOidcDiscoveryUrl();
 
@@ -558,6 +573,10 @@ public class OIDCAccessResource extends 
ApplicationResource {
         this.oidcService = oidcService;
     }
 
+    public void setIdpUserGroupService(IdpUserGroupService 
idpUserGroupService) {
+        this.idpUserGroupService = idpUserGroupService;
+    }
+
     public void setBearerTokenProvider(final BearerTokenProvider 
bearerTokenProvider) {
         this.bearerTokenProvider = bearerTokenProvider;
     }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
index a59ad30d63..6c38f0c029 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
@@ -629,6 +629,7 @@
     </bean>
     <bean id="oidcResource" class="org.apache.nifi.web.api.OIDCAccessResource" 
scope="singleton">
         <property name="oidcService" ref="oidcService"/>
+        <property name="idpUserGroupService" ref="idpUserGroupService" />
         <property name="properties" ref="nifiProperties"/>
         <property name="bearerTokenProvider" ref="bearerTokenProvider"/>
     </bean>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/OIDCAccessResourceTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/OIDCAccessResourceTest.java
index 710def6c2e..8bd9cabce3 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/OIDCAccessResourceTest.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/OIDCAccessResourceTest.java
@@ -22,6 +22,7 @@ import com.nimbusds.oauth2.sdk.ErrorObject;
 import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import org.apache.nifi.admin.service.IdpUserGroupService;
 import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
 import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider;
 import org.apache.nifi.web.security.oidc.OidcService;
@@ -29,13 +30,17 @@ import 
org.apache.nifi.web.security.token.LoginAuthenticationToken;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.net.URI;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
+import static org.apache.nifi.idp.IdpType.OIDC;
 import static 
org.apache.nifi.web.security.cookie.ApplicationCookieName.OIDC_REQUEST_IDENTIFIER;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
@@ -43,6 +48,7 @@ import static org.mockito.ArgumentMatchers.any;
 public class OIDCAccessResourceTest {
 
     final static String REQUEST_IDENTIFIER = "an-identifier";
+
     final static String OIDC_LOGIN_FAILURE_MESSAGE = "Unsuccessful login 
attempt.";
 
     @Test
@@ -53,8 +59,11 @@ public class OIDCAccessResourceTest {
         Mockito.when(mockRequest.getCookies()).thenReturn(cookies);
         OidcService oidcService = Mockito.mock(OidcService.class);
         MockOIDCAccessResource accessResource = new 
MockOIDCAccessResource(oidcService, true);
+        IdpUserGroupService idpUserGroupService = 
Mockito.mock(IdpUserGroupService.class);
+        accessResource.setIdpUserGroupService(idpUserGroupService);
         accessResource.oidcCallback(mockRequest, mockResponse);
         Mockito.verify(oidcService).storeJwt(any(String.class), 
any(String.class));
+        
Mockito.verify(idpUserGroupService).replaceUserGroups(MockOIDCAccessResource.IDENTITY,
 OIDC, Stream.of(MockOIDCAccessResource.ROLE).collect(Collectors.toSet()));
     }
 
     @Test
@@ -83,6 +92,8 @@ public class OIDCAccessResourceTest {
     public class MockOIDCAccessResource extends OIDCAccessResource {
 
         final static String BEARER_TOKEN = "bearer_token";
+        final static String IDENTITY = "identity";
+        final static String ROLE = "role";
         final static String AUTHORIZATION_CODE = "authorization_code";
         final static String CALLBACK_URL = 
"https://nifi.apache.org/nifi-api/access/oidc/callback";;
         final static String RESOURCE_URI = "resource_uri";
@@ -95,6 +106,8 @@ public class OIDCAccessResourceTest {
             setOidcService(oidcService);
             setBearerTokenProvider(bearerTokenProvider);
             final LoginAuthenticationToken token = 
Mockito.mock(LoginAuthenticationToken.class);
+            Mockito.when(token.getName()).thenReturn(IDENTITY);
+            Mockito.when(token.getAuthorities()).thenReturn(Stream.of(new 
SimpleGrantedAuthority(ROLE)).collect(Collectors.toSet()));
             
Mockito.when(oidcService.exchangeAuthorizationCodeForLoginAuthenticationToken(any(AuthorizationGrant.class))).thenReturn(token);
         }
 
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/StandardOidcIdentityProvider.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
index a2b4ba2be5..82837ba75e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
@@ -66,6 +66,7 @@ import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.token.LoginAuthenticationToken;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 
 import javax.net.ssl.SSLContext;
 import java.io.IOException;
@@ -73,9 +74,11 @@ import java.net.URI;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -89,6 +92,7 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
     private final String EMAIL_CLAIM = "email";
 
     private final NiFiProperties properties;
+
     private OIDCProviderMetadata oidcProviderMetadata;
     private int oidcConnectTimeout;
     private int oidcReadTimeout;
@@ -443,6 +447,10 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
         String identityClaim = properties.getOidcClaimIdentifyingUser();
         String identity = claimsSet.getStringClaim(identityClaim);
 
+        // Attempt to extract groups from the configured claim; default is 
'groups'
+        String groupsClaim = properties.getOidcClaimGroups();
+        List<String> groups = claimsSet.getStringListClaim(groupsClaim);
+
         // If default identity not available, attempt secondary identity 
extraction
         if (StringUtils.isBlank(identity)) {
             // Provide clear message to admin that desired claim is missing 
and present available claims
@@ -474,8 +482,15 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
         final Date expiration = claimsSet.getExpirationTime();
         final long expiresIn = expiration.getTime() - now.getTimeInMillis();
 
+        Set<SimpleGrantedAuthority> authorities = groups != null ? 
groups.stream().map(
+                group -> new SimpleGrantedAuthority(group)).collect(
+                Collectors.collectingAndThen(
+                        Collectors.toSet(),
+                        Collections::unmodifiableSet
+                )) : null;
+
         return new LoginAuthenticationToken(
-                identity, identity, expiresIn, 
claimsSet.getIssuer().getValue());
+                identity, identity, expiresIn, 
claimsSet.getIssuer().getValue(), authorities);
     }
 
     private OIDCTokens getOidcTokens(OIDCTokenResponse response) {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
index c0e895c58e..33f4c3bd6d 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
@@ -18,8 +18,10 @@ package org.apache.nifi.web.security.token;
 
 import org.apache.nifi.security.util.CertificateUtils;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
 
 import java.time.Instant;
+import java.util.Collection;
 
 /**
  * This is an Authentication Token for logging in. Once a user is 
authenticated, they can be issued an ID token.
@@ -39,19 +41,32 @@ public class LoginAuthenticationToken extends 
AbstractAuthenticationToken {
      * @param issuer     The IdentityProvider implementation that generated 
this token
      */
     public LoginAuthenticationToken(final String identity, final long 
expiration, final String issuer) {
-        this(identity, null, expiration, issuer);
+        this(identity, null, expiration, issuer, null);
     }
 
     /**
      * Creates a representation of the authentication token for a user.
      *
-     * @param identity   The unique identifier for this user (cannot be null 
or empty)
-     * @param username   The preferred username for this user
-     * @param expiration The relative time to expiration in milliseconds
-     * @param issuer     The IdentityProvider implementation that generated 
this token
+     * @param identity    The unique identifier for this user (cannot be null 
or empty)
+     * @param username    The preferred username for this user
+     * @param expiration  The relative time to expiration in milliseconds
+     * @param issuer      The IdentityProvider implementation that generated 
this token
      */
     public LoginAuthenticationToken(final String identity, final String 
username, final long expiration, final String issuer) {
-        super(null);
+        this(identity, username, expiration, issuer, null);
+    }
+
+    /**
+     * Creates a representation of the authentication token for a user.
+     *
+     * @param identity    The unique identifier for this user (cannot be null 
or empty)
+     * @param username    The preferred username for this user
+     * @param expiration  The relative time to expiration in milliseconds
+     * @param issuer      The IdentityProvider implementation that generated 
this token
+     * @param authorities The authorities that have been granted this token.
+     */
+    public LoginAuthenticationToken(final String identity, final String 
username, final long expiration, final String issuer, Collection<? extends 
GrantedAuthority> authorities) {
+        super(authorities);
         setAuthenticated(true);
         this.identity = identity;
         this.username = username;

Reply via email to