Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -1011,6 +1012,21 @@ public String getOidcClaimIdentifyingUser() {
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));
}
Expand Down
1 change: 1 addition & 0 deletions nifi-docs/src/main/asciidoc/administration-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,16 @@ private LoginAuthenticationToken convertOIDCTokenToLoginAuthenticationToken(OIDC
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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()}")
Expand Down