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;