NIFI-7924 Add fallback claims for identifying user to OIDC provider

This closes #4630

Signed-off-by: Joey Frazee <jfrazee@apache.org>
This commit is contained in:
sjyang18 2020-10-13 02:49:26 +00:00 committed by Joey Frazee
parent 817f621d6f
commit f330078fff
6 changed files with 48 additions and 3 deletions

View File

@ -169,6 +169,7 @@ public abstract class NiFiProperties {
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 SECURITY_USER_OIDC_FALLBACK_CLAIMS_IDENTIFYING_USER = "nifi.security.user.oidc.fallback.claims.identifying.user";
// apache knox
public static final String SECURITY_USER_KNOX_URL = "nifi.security.user.knox.url";
@ -1011,6 +1012,21 @@ public abstract class NiFiProperties {
return getProperty(SECURITY_USER_OIDC_CLAIM_IDENTIFYING_USER, "email").trim();
}
/**
* Returns the list of fallback claims to be used to identify a user when the configured claim is empty for a user
*
* @return The list of fallback claims to be used to identify the user
*/
public List<String> getOidcFallbackClaimsIdentifyingUser() {
String rawProperty = getProperty(SECURITY_USER_OIDC_FALLBACK_CLAIMS_IDENTIFYING_USER, "").trim();
if (StringUtils.isBlank(rawProperty)) {
return Collections.emptyList();
} else {
List<String> fallbackClaims = Arrays.asList(rawProperty.split(","));
return fallbackClaims.stream().map(String::trim).filter(s->!s.isEmpty()).collect(Collectors.toList());
}
}
public boolean shouldSendServerVersion() {
return Boolean.parseBoolean(getProperty(WEB_SHOULD_SEND_SERVER_VERSION, DEFAULT_WEB_SHOULD_SEND_SERVER_VERSION));
}

View File

@ -373,6 +373,7 @@ If this value is `none`, NiFi will attempt to validate unsecured/plain tokens. O
JSON Web Key (JWK) provided through the jwks_uri in the metadata found at the discovery URL.
|`nifi.security.user.oidc.additional.scopes` | Comma separated scopes that are sent to OpenId Connect Provider in addition to `openid` and `email`.
|`nifi.security.user.oidc.claim.identifying.user` | Claim that identifies the user to be logged in; default is `email`. May need to be requested via the `nifi.security.user.oidc.additional.scopes` before usage.
|`nifi.security.user.oidc.fallback.claims.identifying.user` | Comma separated possible fallback claims used to identify the user in case `nifi.security.user.oidc.claim.identifying.user` claim is not present for the login user.
|==================================================================================================================================================
[[saml]]

View File

@ -164,6 +164,7 @@
<nifi.security.user.oidc.preferred.jwsalgorithm />
<nifi.security.user.oidc.additional.scopes />
<nifi.security.user.oidc.claim.identifying.user />
<nifi.security.user.oidc.fallback.claims.identifying.user />
<!-- nifi.properties: apache knox -->
<nifi.security.user.knox.url />

View File

@ -178,6 +178,7 @@ nifi.security.user.oidc.client.secret=${nifi.security.user.oidc.client.secret}
nifi.security.user.oidc.preferred.jwsalgorithm=${nifi.security.user.oidc.preferred.jwsalgorithm}
nifi.security.user.oidc.additional.scopes=${nifi.security.user.oidc.additional.scopes}
nifi.security.user.oidc.claim.identifying.user=${nifi.security.user.oidc.claim.identifying.user}
nifi.security.user.oidc.fallback.claims.identifying.user=${nifi.security.user.oidc.fallback.claims.identifying.user}
# Apache Knox SSO Properties #
nifi.security.user.knox.url=${nifi.security.user.knox.url}

View File

@ -439,8 +439,16 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider {
identity = claimsSet.getStringClaim(EMAIL_CLAIM);
logger.info("The 'email' claim was present. Using that claim to avoid extra remote call");
} else {
identity = retrieveIdentityFromUserInfoEndpoint(oidcTokens);
logger.info("Retrieved identity from UserInfo endpoint");
final List<String> fallbackClaims = properties.getOidcFallbackClaimsIdentifyingUser();
for (String fallbackClaim : fallbackClaims) {
if (availableClaims.contains(fallbackClaim)) {
identity = claimsSet.getStringClaim(fallbackClaim);
break;
}
}
if (StringUtils.isBlank(identity)) {
identity = retrieveIdentityFromUserInfoEndpoint(oidcTokens);
}
}
}

View File

@ -411,10 +411,28 @@ class StandardOidcIdentityProviderGroovyTest extends GroovyTestCase {
assert exp <= System.currentTimeMillis() + 10_000
}
@Test
void testConvertOIDCTokenToLoginAuthenticationTokenShouldHandleNoEmailClaimHasFallbackClaims() {
// Arrange
StandardOidcIdentityProvider soip = buildIdentityProviderWithMockTokenValidator(["getOidcClaimIdentifyingUser": "email", "getOidcFallbackClaimsIdentifyingUser": ["upn"] ])
String expectedUpn = "xxx@aaddomain";
OIDCTokenResponse mockResponse = mockOIDCTokenResponse(["email": null, "upn": expectedUpn])
logger.info("OIDC Token Response with no email and upn: ${mockResponse.dump()}")
String loginToken = soip.convertOIDCTokenToLoginAuthenticationToken(mockResponse)
logger.info("NiFi token create with upn: ${loginToken}")
// Assert
// Split JWT into components and decode Base64 to JSON
def (String contents, String expiration) = loginToken.tokenize("\\[\\]")
logger.info("Token contents: ${contents} | Expiration: ${expiration}")
assert contents =~ "LoginAuthenticationToken for ${expectedUpn} issued by https://accounts\\.issuer\\.com expiring at"
}
@Test
void testConvertOIDCTokenToLoginAuthNTokenShouldHandleBlankIdentityAndNoEmailClaim() {
// Arrange
StandardOidcIdentityProvider soip = buildIdentityProviderWithMockTokenValidator(["getOidcClaimIdentifyingUser": "non-existent-claim"])
StandardOidcIdentityProvider soip = buildIdentityProviderWithMockTokenValidator(["getOidcClaimIdentifyingUser": "non-existent-claim", "getOidcFallbackClaimsIdentifyingUser": [] ])
OIDCTokenResponse mockResponse = mockOIDCTokenResponse(["email": null])
logger.info("OIDC Token Response: ${mockResponse.dump()}")