This is an automated email from the ASF dual-hosted git repository. bbende pushed a commit to branch support/nifi-1.x in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/support/nifi-1.x by this push: new db919bc494 NIFI-12418 Corrected Provider Groups Missing in Refreshed Tokens (#8126) db919bc494 is described below commit db919bc49401262edfad3c5beb00b169433954ce Author: David Handermann <exceptionfact...@apache.org> AuthorDate: Mon Dec 18 10:53:23 2023 -0600 NIFI-12418 Corrected Provider Groups Missing in Refreshed Tokens (#8126) - Updated OidcBearerTokenRefreshFilter to maintain current Identity Provider Groups when generating refreshed application Bearer Tokens - Refactored LoginAuthenticationToken to remove unnecessary optional constructors and use java.time.Instant for expiration - Added Issuer Provider with implementation for Bearer Token Issuer based on host and port properties --- .../org/apache/nifi/web/api/AccessResource.java | 18 +++-- .../JwtAuthenticationSecurityConfiguration.java | 9 ++- .../SamlAuthenticationSecurityConfiguration.java | 5 +- .../web/security/jwt/provider/IssuerProvider.java | 31 +++++++++ .../jwt/provider/StandardBearerTokenProvider.java | 26 +++----- .../jwt/provider/StandardIssuerProvider.java | 73 ++++++++++++++++++++ .../client/web/OidcBearerTokenRefreshFilter.java | 34 +++++++--- .../OidcAuthenticationSuccessHandler.java | 19 ++---- .../Saml2AuthenticationSuccessHandler.java | 11 ++- .../security/token/LoginAuthenticationToken.java | 71 +++----------------- .../provider/StandardBearerTokenProviderTest.java | 38 ++++++----- .../jwt/provider/StandardIssuerProviderTest.java | 78 ++++++++++++++++++++++ .../web/OidcBearerTokenRefreshFilterTest.java | 41 +++++++++++- .../OidcAuthenticationSuccessHandlerTest.java | 19 +----- .../Saml2AuthenticationSuccessHandlerTest.java | 5 +- 15 files changed, 321 insertions(+), 157 deletions(-) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java index 049e26eacc..16f2816905 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java @@ -34,17 +34,17 @@ import org.apache.nifi.authorization.user.NiFiUserDetails; import org.apache.nifi.authorization.user.NiFiUserUtils; import org.apache.nifi.authorization.util.IdentityMappingUtil; import org.apache.nifi.util.FormatUtils; -import org.apache.nifi.web.api.dto.AccessTokenExpirationDTO; -import org.apache.nifi.web.api.entity.AccessTokenExpirationEntity; -import org.apache.nifi.web.security.cookie.ApplicationCookieName; import org.apache.nifi.web.api.dto.AccessConfigurationDTO; import org.apache.nifi.web.api.dto.AccessStatusDTO; +import org.apache.nifi.web.api.dto.AccessTokenExpirationDTO; import org.apache.nifi.web.api.entity.AccessConfigurationEntity; import org.apache.nifi.web.api.entity.AccessStatusEntity; +import org.apache.nifi.web.api.entity.AccessTokenExpirationEntity; import org.apache.nifi.web.security.InvalidAuthenticationException; import org.apache.nifi.web.security.LogoutException; import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.apache.nifi.web.security.UntrustedProxyException; +import org.apache.nifi.web.security.cookie.ApplicationCookieName; import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider; import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener; import org.apache.nifi.web.security.kerberos.KerberosService; @@ -83,6 +83,7 @@ import javax.ws.rs.core.UriBuilder; import java.net.URI; import java.security.cert.X509Certificate; import java.time.Instant; +import java.util.Collections; import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -352,11 +353,13 @@ public class AccessResource extends ApplicationResource { } final String expirationFromProperties = properties.getKerberosAuthenticationExpiration(); - long expiration = Math.round(FormatUtils.getPreciseTimeDuration(expirationFromProperties, TimeUnit.MILLISECONDS)); + final long expirationDuration = Math.round(FormatUtils.getPreciseTimeDuration(expirationFromProperties, TimeUnit.MILLISECONDS)); + final Instant expiration = Instant.now().plusMillis(expirationDuration); + final String rawIdentity = authentication.getName(); final String mappedIdentity = IdentityMappingUtil.mapIdentity(rawIdentity, IdentityMappingUtil.getIdentityMappings(properties)); - final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(mappedIdentity, expiration, "KerberosService"); + final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(mappedIdentity, expiration, Collections.emptySet()); final String token = bearerTokenProvider.getBearerToken(loginAuthenticationToken); final URI uri = URI.create(generateResourceUri("access", "kerberos")); setBearerToken(httpServletResponse, token); @@ -422,10 +425,11 @@ public class AccessResource extends ApplicationResource { final AuthenticationResponse authenticationResponse = loginIdentityProvider.authenticate(new LoginCredentials(username, password)); final String rawIdentity = authenticationResponse.getIdentity(); final String mappedIdentity = IdentityMappingUtil.mapIdentity(rawIdentity, IdentityMappingUtil.getIdentityMappings(properties)); - final long expiration = authenticationResponse.getExpiration(); + final long expirationDuration = authenticationResponse.getExpiration(); + final Instant expiration = Instant.now().plusMillis(expirationDuration); // create the authentication token - loginAuthenticationToken = new LoginAuthenticationToken(mappedIdentity, expiration, authenticationResponse.getIssuer()); + loginAuthenticationToken = new LoginAuthenticationToken(mappedIdentity, expiration, Collections.emptySet()); } catch (final InvalidLoginCredentialsException ilce) { throw new IllegalArgumentException("The supplied username and password are not valid.", ilce); } catch (final IdentityAccessException iae) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java index 47a254a5bd..100e500cad 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/JwtAuthenticationSecurityConfiguration.java @@ -36,7 +36,9 @@ import org.apache.nifi.web.security.jwt.key.StandardVerificationKeySelector; import org.apache.nifi.web.security.jwt.key.service.StandardVerificationKeyService; import org.apache.nifi.web.security.jwt.key.service.VerificationKeyService; import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider; +import org.apache.nifi.web.security.jwt.provider.IssuerProvider; import org.apache.nifi.web.security.jwt.provider.StandardBearerTokenProvider; +import org.apache.nifi.web.security.jwt.provider.StandardIssuerProvider; import org.apache.nifi.web.security.jwt.provider.SupportedClaim; import org.apache.nifi.web.security.jwt.resolver.StandardBearerTokenResolver; import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener; @@ -181,7 +183,12 @@ public class JwtAuthenticationSecurityConfiguration { @Bean public BearerTokenProvider bearerTokenProvider() { - return new StandardBearerTokenProvider(jwsSignerProvider()); + return new StandardBearerTokenProvider(jwsSignerProvider(), issuerProvider()); + } + + @Bean + public IssuerProvider issuerProvider() { + return new StandardIssuerProvider(niFiProperties.getProperty(NiFiProperties.WEB_HTTPS_HOST), niFiProperties.getConfiguredHttpOrHttpsPort()); } @Bean diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/SamlAuthenticationSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/SamlAuthenticationSecurityConfiguration.java index 93070c8e58..2005dca4cb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/SamlAuthenticationSecurityConfiguration.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/configuration/SamlAuthenticationSecurityConfiguration.java @@ -368,14 +368,11 @@ public class SamlAuthenticationSecurityConfiguration { private Saml2AuthenticationSuccessHandler getAuthenticationSuccessHandler() { final long authenticationExpiration = (long) FormatUtils.getPreciseTimeDuration(properties.getSamlAuthenticationExpiration(), TimeUnit.MILLISECONDS); final Duration expiration = Duration.ofMillis(authenticationExpiration); - final String entityId = properties.getSamlServiceProviderEntityId(); - final String issuer = entityId == null ? Saml2RegistrationProperty.REGISTRATION_ID.getProperty() : entityId; final Saml2AuthenticationSuccessHandler handler = new Saml2AuthenticationSuccessHandler( bearerTokenProvider, IdentityMappingUtil.getIdentityMappings(properties), IdentityMappingUtil.getGroupMappings(properties), - expiration, - issuer + expiration ); final String identityAttributeName = properties.getSamlIdentityAttributeName(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/IssuerProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/IssuerProvider.java new file mode 100644 index 0000000000..83785de8f1 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/IssuerProvider.java @@ -0,0 +1,31 @@ +/* + * 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.jwt.provider; + +import java.net.URI; + +/** + * JSON Web Token Issuer Provider abstracts determination of local address for issuer claims + */ +public interface IssuerProvider { + /** + * Get Issuer URI for issuer claims + * + * @return Issuer URI + */ + URI getIssuer(); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java index 9ba3295faf..9e8d728067 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java @@ -30,9 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.GrantedAuthority; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; +import java.net.URI; import java.time.Duration; import java.time.Instant; import java.util.Collection; @@ -48,16 +46,17 @@ import java.util.stream.Collectors; public class StandardBearerTokenProvider implements BearerTokenProvider { private static final Logger LOGGER = LoggerFactory.getLogger(StandardBearerTokenProvider.class); - private static final String URL_ENCODED_CHARACTER_SET = StandardCharsets.UTF_8.name(); - private static final Duration MAXIMUM_EXPIRATION = Duration.ofHours(12); private static final Duration MINIMUM_EXPIRATION = Duration.ofMinutes(1); private final JwsSignerProvider jwsSignerProvider; - public StandardBearerTokenProvider(final JwsSignerProvider jwsSignerProvider) { + private final IssuerProvider issuerProvider; + + public StandardBearerTokenProvider(final JwsSignerProvider jwsSignerProvider, final IssuerProvider issuerProvider) { this.jwsSignerProvider = jwsSignerProvider; + this.issuerProvider = issuerProvider; } /** @@ -73,7 +72,10 @@ public class StandardBearerTokenProvider implements BearerTokenProvider { final String username = loginAuthenticationToken.getName(); final List<String> groups = getGroups(loginAuthenticationToken.getAuthorities()); - final String issuer = getUrlEncoded(loginAuthenticationToken.getIssuer()); + + final URI issuerUri = issuerProvider.getIssuer(); + final String issuer = issuerUri.toString(); + final Date now = new Date(); final Date expirationTime = getExpirationTime(loginAuthenticationToken); final JWTClaimsSet claims = new JWTClaimsSet.Builder() @@ -91,7 +93,7 @@ public class StandardBearerTokenProvider implements BearerTokenProvider { } private Date getExpirationTime(final LoginAuthenticationToken loginAuthenticationToken) { - Instant expiration = Instant.ofEpochMilli(loginAuthenticationToken.getExpiration()); + Instant expiration = loginAuthenticationToken.getExpiration(); final Instant maximumExpiration = Instant.now().plus(MAXIMUM_EXPIRATION); final Instant minimumExpiration = Instant.now().plus(MINIMUM_EXPIRATION); @@ -130,14 +132,6 @@ public class StandardBearerTokenProvider implements BearerTokenProvider { return jwsObject.serialize(); } - private String getUrlEncoded(final String string) { - try { - return URLEncoder.encode(string, URL_ENCODED_CHARACTER_SET); - } catch (final UnsupportedEncodingException e) { - throw new IllegalArgumentException(String.format("URL Encoding [%s] Failed", string), e); - } - } - private List<String> getGroups(final Collection<? extends GrantedAuthority> authorities) { return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardIssuerProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardIssuerProvider.java new file mode 100644 index 0000000000..f45bbffa37 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardIssuerProvider.java @@ -0,0 +1,73 @@ +/* + * 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.jwt.provider; + +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; + +/** + * Standard Issuer Provider with configurable host and port for HTTPS URI construction + */ +public class StandardIssuerProvider implements IssuerProvider { + private static final String URI_FORMAT = "https://%s:%d"; + + private final URI issuer; + + /** + * Standard Issuer Provider constructor with optional host and required port properties + * + * @param host HTTPS Host address can be null or empty to default to InetAddress.getLocalHost() resolution + * @param port HTTPS port number + */ + public StandardIssuerProvider(final String host, final int port) { + final String resolvedHost = getResolvedHost(host); + final String uri = String.format(URI_FORMAT, resolvedHost, port); + this.issuer = URI.create(uri); + } + + /** + * Get Issuer URI for issuer claims + * + * @return Issuer URI + */ + @Override + public URI getIssuer() { + return issuer; + } + + private String getResolvedHost(final String host) { + final String resolvedHost; + + if (host == null || host.isEmpty()) { + resolvedHost = getLocalHost(); + } else { + resolvedHost = host; + } + + return resolvedHost; + } + + private String getLocalHost() { + try { + final InetAddress localHostAddress = InetAddress.getLocalHost(); + return localHostAddress.getCanonicalHostName(); + } catch (final UnknownHostException e) { + throw new IllegalStateException("Failed to resolve local host address", e); + } + } +} 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/client/web/OidcBearerTokenRefreshFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/OidcBearerTokenRefreshFilter.java index e8f322d0d0..b9cb7c5d56 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/OidcBearerTokenRefreshFilter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/client/web/OidcBearerTokenRefreshFilter.java @@ -16,6 +16,7 @@ */ package org.apache.nifi.web.security.oidc.client.web; +import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserUtils; import org.apache.nifi.web.security.cookie.ApplicationCookieName; import org.apache.nifi.web.security.cookie.ApplicationCookieService; @@ -27,6 +28,8 @@ import org.apache.nifi.web.util.RequestUriBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; @@ -56,8 +59,10 @@ import java.time.Duration; import java.time.Instant; import java.util.Collections; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; /** * OpenID Connect Filter for evaluating the application Bearer Token and returning an updated Bearer Token after successful OAuth2 Refresh Token processing @@ -143,8 +148,7 @@ public class OidcBearerTokenRefreshFilter extends OncePerRequestFilter { logger.warn("Identity [{}] OpenID Connect Refresh Token not found", userIdentity); } else { final URI resourceUri = RequestUriBuilder.fromHttpServletRequest(request).path(ROOT_PATH).build(); - final String issuer = resourceUri.toString(); - final String bearerToken = getBearerToken(userIdentity, issuer, tokenResponse); + final String bearerToken = getBearerToken(userIdentity, tokenResponse); applicationCookieService.addSessionCookie(resourceUri, response, ApplicationCookieName.AUTHORIZATION_BEARER, bearerToken); } } @@ -174,20 +178,20 @@ public class OidcBearerTokenRefreshFilter extends OncePerRequestFilter { return authorizedClientRepository.loadAuthorizedClient(OidcRegistrationProperty.REGISTRATION_ID.getProperty(), principal, request); } - private String getBearerToken(final String userIdentity, final String issuer, final OAuth2AccessTokenResponse tokenResponse) { + private String getBearerToken(final String userIdentity, final OAuth2AccessTokenResponse tokenResponse) { final OAuth2AccessToken accessToken = tokenResponse.getAccessToken(); - final long sessionExpiration = getSessionExpiration(accessToken); - final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(userIdentity, userIdentity, sessionExpiration, issuer); + final Instant sessionExpiration = getSessionExpiration(accessToken); + final Set<? extends GrantedAuthority> providerAuthorities = getProviderAuthorities(); + final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(userIdentity, sessionExpiration, providerAuthorities); return bearerTokenProvider.getBearerToken(loginAuthenticationToken); } - private long getSessionExpiration(final OAuth2AccessToken accessToken) { + private Instant getSessionExpiration(final OAuth2AccessToken accessToken) { final Instant tokenExpiration = accessToken.getExpiresAt(); if (tokenExpiration == null) { throw new IllegalArgumentException("OpenID Connect Access Token expiration claim not found"); } - final Duration expiration = Duration.between(Instant.now(), tokenExpiration); - return expiration.toMillis(); + return tokenExpiration; } private OAuth2AccessTokenResponse getRefreshTokenResponse(final OidcAuthorizedClient authorizedClient, final HttpServletRequest request, final HttpServletResponse response) { @@ -229,4 +233,18 @@ public class OidcBearerTokenRefreshFilter extends OncePerRequestFilter { final OidcUser oidcUser = new DefaultOidcUser(Collections.emptyList(), idToken, SupportedClaim.SUBJECT.getClaim()); return new OAuth2AuthenticationToken(oidcUser, Collections.emptyList(), clientRegistration.getRegistrationId()); } + + private Set<? extends GrantedAuthority> getProviderAuthorities() { + final Set<? extends GrantedAuthority> authorities; + + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + final Set<String> providerGroups = user.getIdentityProviderGroups(); + if (providerGroups == null) { + authorities = Collections.emptySet(); + } else { + authorities = providerGroups.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()); + } + + return authorities; + } } 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/web/authentication/OidcAuthenticationSuccessHandler.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/web/authentication/OidcAuthenticationSuccessHandler.java index 8dbbc1c24b..27fcde484b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/web/authentication/OidcAuthenticationSuccessHandler.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/web/authentication/OidcAuthenticationSuccessHandler.java @@ -38,7 +38,6 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationSu import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.URI; -import java.time.Duration; import java.time.Instant; import java.util.Collections; import java.util.List; @@ -114,29 +113,23 @@ public class OidcAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuc final Set<String> groups = getGroups(oidcUser); final OAuth2AccessToken accessToken = getAccessToken(authenticationToken); - final String bearerToken = getBearerToken(identity, oidcUser, accessToken, groups); + final String bearerToken = getBearerToken(identity, accessToken, groups); applicationCookieService.addSessionCookie(resourceUri, response, ApplicationCookieName.AUTHORIZATION_BEARER, bearerToken); } - private String getBearerToken(final String identity, final OidcUser oidcUser, final OAuth2AccessToken accessToken, final Set<String> groups) { - final long sessionExpiration = getSessionExpiration(accessToken); - final String issuer = oidcUser.getIssuer().toString(); + private String getBearerToken(final String identity, final OAuth2AccessToken accessToken, final Set<String> groups) { + final Instant sessionExpiration = getSessionExpiration(accessToken); final Set<? extends GrantedAuthority> authorities = groups.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()); - final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(identity, identity, sessionExpiration, issuer, authorities); + final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(identity, sessionExpiration, authorities); return bearerTokenProvider.getBearerToken(loginAuthenticationToken); } - private long getSessionExpiration(final OAuth2Token token) { + private Instant getSessionExpiration(final OAuth2Token token) { final Instant tokenExpiration = token.getExpiresAt(); if (tokenExpiration == null) { throw new IllegalArgumentException("Token expiration claim not found"); } - final Instant tokenIssued = token.getIssuedAt(); - if (tokenIssued == null) { - throw new IllegalArgumentException("Token issued claim not found"); - } - final Duration expiration = Duration.between(tokenIssued, tokenExpiration); - return expiration.toMillis(); + return tokenExpiration; } private OAuth2AuthenticationToken getAuthenticationToken(final Authentication authentication) { diff --git a/nifi-nar-bundles/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-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java index 866d0928d4..b3376b3226 100644 --- a/nifi-nar-bundles/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-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandler.java @@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.URI; import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Objects; import java.util.Set; @@ -58,8 +59,6 @@ public class Saml2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSu private final Duration expiration; - private final String issuer; - private Converter<Saml2AuthenticatedPrincipal, String> identityConverter = Saml2AuthenticatedPrincipal::getName; /** @@ -69,20 +68,17 @@ public class Saml2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSu * @param userIdentityMappings User Identity Mappings * @param groupIdentityMappings Group Identity Mappings * @param expiration Expiration for generated tokens - * @param issuer Token Issuer */ public Saml2AuthenticationSuccessHandler( final BearerTokenProvider bearerTokenProvider, final List<IdentityMapping> userIdentityMappings, final List<IdentityMapping> groupIdentityMappings, - final Duration expiration, - final String issuer + final Duration expiration ) { this.bearerTokenProvider = Objects.requireNonNull(bearerTokenProvider, "Bearer Token Provider required"); this.userIdentityMappings = Objects.requireNonNull(userIdentityMappings, "User Identity Mappings required"); this.groupIdentityMappings = Objects.requireNonNull(groupIdentityMappings, "Group Identity Mappings required"); this.expiration = Objects.requireNonNull(expiration, "Expiration required"); - this.issuer = Objects.requireNonNull(issuer, "Issuer required"); } /** @@ -121,7 +117,8 @@ public class Saml2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSu private String getBearerToken(final String identity, final Set<String> groups) { final Set<? extends GrantedAuthority> authorities = groups.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()); - final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(identity, identity, expiration.toMillis(), issuer, authorities); + final Instant sessionExpiration = Instant.now().plus(expiration); + final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(identity, sessionExpiration, authorities); return bearerTokenProvider.getBearerToken(loginAuthenticationToken); } 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 ad345c8404..d0496fc18e 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 @@ -21,56 +21,29 @@ import org.springframework.security.core.GrantedAuthority; import java.time.Instant; import java.util.Collection; +import java.util.Objects; /** - * This is an Authentication Token for logging in. Once a user is authenticated, they can be issued an ID token. + * Login Authentication Token containing mapped Identity and Expiration Time to exchange for Application Bearer Token */ public class LoginAuthenticationToken extends AbstractAuthenticationToken { private final String identity; - private final String username; - private final long expiration; - private final String issuer; - /** - * Creates a representation of the authentication token for a user. - * - * @param identity The unique identifier 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 long expiration, final String 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 - */ - public LoginAuthenticationToken(final String identity, final String username, final long expiration, final String issuer) { - this(identity, username, expiration, issuer, null); - } + private final Instant expiration; /** * 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 expiration Instant at which the authenticated token is no longer valid * @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) { + public LoginAuthenticationToken(final String identity, final Instant expiration, Collection<? extends GrantedAuthority> authorities) { super(authorities); setAuthenticated(true); this.identity = identity; - this.username = username == null ? identity : username; - this.issuer = issuer; - this.expiration = Instant.now().plusMillis(expiration).toEpochMilli(); + this.expiration = Objects.requireNonNull(expiration); } @Override @@ -83,43 +56,17 @@ public class LoginAuthenticationToken extends AbstractAuthenticationToken { return identity; } - /** - * Returns the expiration instant in milliseconds. This value is an absolute point in time (i.e. Nov - * 16, 2015 11:30:00.000 GMT), not a relative time (i.e. 60 minutes). It is calculated by adding the - * relative expiration from the constructor to the timestamp at object creation. - * - * @return the expiration in millis - */ - public long getExpiration() { + public Instant getExpiration() { return expiration; } - public String getIssuer() { - return issuer; - } - @Override public String getName() { - return username; + return identity; } @Override public String toString() { - final Instant expirationTime = Instant.ofEpochMilli(expiration); - long remainingTime = expirationTime.toEpochMilli() - Instant.now().toEpochMilli(); - - return new StringBuilder("LoginAuthenticationToken for ") - .append(getName()) - .append(" issued by ") - .append(getIssuer()) - .append(" expiring at ") - .append(expirationTime) - .append(" [") - .append(getExpiration()) - .append(" ms, ") - .append(remainingTime) - .append(" ms remaining]") - .toString(); + return getName(); } - } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java index 2e105d3d34..1cce46dce4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java @@ -36,6 +36,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import java.net.URI; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; @@ -59,17 +60,15 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class StandardBearerTokenProviderTest { - private static final String USERNAME = "USERNAME"; - private static final String IDENTITY = "IDENTITY"; - private static final Duration EXPIRATION = Duration.ofHours(1); + private static final Instant EXPIRATION = Instant.now().plusSeconds(300); private static final Duration MAXIMUM_DURATION_EXCEEDED = Duration.parse("PT12H5M"); private static final Duration MINIMUM_DURATION_EXCEEDED = Duration.parse("PT30S"); - private static final String ISSUER = "ISSUER"; + private static final URI ISSUER = URI.create("https://localhost:8443"); private static final String KEY_ALGORITHM = "RSA"; @@ -84,6 +83,9 @@ public class StandardBearerTokenProviderTest { @Mock private JwsSignerProvider jwsSignerProvider; + @Mock + private IssuerProvider issuerProvider; + private StandardBearerTokenProvider provider; private JWSVerifier jwsVerifier; @@ -97,7 +99,7 @@ public class StandardBearerTokenProviderTest { @BeforeEach public void setProvider() { - provider = new StandardBearerTokenProvider(jwsSignerProvider); + provider = new StandardBearerTokenProvider(jwsSignerProvider, issuerProvider); jwsVerifier = new RSASSAVerifier((RSAPublicKey) keyPair.getPublic()); final JWSSigner jwsSigner = new RSASSASigner(keyPair.getPrivate()); @@ -105,11 +107,13 @@ public class StandardBearerTokenProviderTest { final String keyIdentifier = UUID.randomUUID().toString(); final JwsSignerContainer jwsSignerContainer = new JwsSignerContainer(keyIdentifier, JWS_ALGORITHM, jwsSigner); when(jwsSignerProvider.getJwsSignerContainer(isA(Instant.class))).thenReturn(jwsSignerContainer); + + when(issuerProvider.getIssuer()).thenReturn(ISSUER); } @Test public void testGetBearerToken() throws ParseException, JOSEException { - final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, USERNAME, EXPIRATION.toMillis(), ISSUER); + final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, EXPIRATION, Collections.emptySet()); final String bearerToken = provider.getBearerToken(loginAuthenticationToken); @@ -121,7 +125,7 @@ public class StandardBearerTokenProviderTest { final GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(GROUP); final Collection<GrantedAuthority> authorities = Collections.singletonList(grantedAuthority); - final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, USERNAME, EXPIRATION.toMillis(), ISSUER, authorities); + final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, EXPIRATION, authorities); final String bearerToken = provider.getBearerToken(loginAuthenticationToken); @@ -130,8 +134,8 @@ public class StandardBearerTokenProviderTest { @Test public void testGetBearerTokenExpirationMaximum() throws ParseException, JOSEException { - final long expiration = MAXIMUM_DURATION_EXCEEDED.toMillis(); - final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, USERNAME, expiration, ISSUER); + final Instant expiration = Instant.now().plus(MAXIMUM_DURATION_EXCEEDED); + final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, expiration, Collections.emptySet()); final String bearerToken = provider.getBearerToken(loginAuthenticationToken); @@ -140,7 +144,7 @@ public class StandardBearerTokenProviderTest { final Date claimExpirationTime = claims.getExpirationTime(); assertNotNull(claimExpirationTime, "Expiration Time not found"); - final Date loginExpirationTime = new Date(loginAuthenticationToken.getExpiration()); + final Date loginExpirationTime = Date.from(loginAuthenticationToken.getExpiration()); assertNotSame(loginExpirationTime.toString(), claimExpirationTime.toString(), "Expiration Time matched"); assertTrue(claimExpirationTime.toInstant().isBefore(loginExpirationTime.toInstant()), "Claim Expiration after Login Expiration"); @@ -148,8 +152,8 @@ public class StandardBearerTokenProviderTest { @Test public void testGetBearerTokenExpirationMinimum() throws ParseException, JOSEException { - final long expiration = MINIMUM_DURATION_EXCEEDED.toMillis(); - final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, USERNAME, expiration, ISSUER); + final Instant expiration = Instant.now().plus(MINIMUM_DURATION_EXCEEDED); + final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, expiration, Collections.emptySet()); final String bearerToken = provider.getBearerToken(loginAuthenticationToken); @@ -158,7 +162,7 @@ public class StandardBearerTokenProviderTest { final Date claimExpirationTime = claims.getExpirationTime(); assertNotNull(claimExpirationTime, "Expiration Time not found"); - final Date loginExpirationTime = new Date(loginAuthenticationToken.getExpiration()); + final Date loginExpirationTime = Date.from(loginAuthenticationToken.getExpiration()); assertNotSame(loginExpirationTime.toString(), claimExpirationTime.toString(), "Expiration Time matched"); assertTrue(claimExpirationTime.toInstant().isAfter(loginExpirationTime.toInstant()), "Claim Expiration before Login Expiration"); @@ -179,13 +183,13 @@ public class StandardBearerTokenProviderTest { final Date claimExpirationTime = claims.getExpirationTime(); assertNotNull(claimExpirationTime, "Expiration Time not found"); - final Date loginExpirationTime = new Date(loginAuthenticationToken.getExpiration()); + final Date loginExpirationTime = Date.from(loginAuthenticationToken.getExpiration()); assertEquals(loginExpirationTime.toString(), claimExpirationTime.toString(), "Expiration Time not matched"); - assertEquals(ISSUER, claims.getIssuer()); - assertEquals(Collections.singletonList(ISSUER), claims.getAudience()); + assertEquals(ISSUER.toString(), claims.getIssuer()); + assertEquals(Collections.singletonList(ISSUER.toString()), claims.getAudience()); assertEquals(IDENTITY, claims.getSubject()); - assertEquals(USERNAME, claims.getClaim(SupportedClaim.PREFERRED_USERNAME.getClaim())); + assertEquals(IDENTITY, claims.getClaim(SupportedClaim.PREFERRED_USERNAME.getClaim())); assertNotNull(claims.getJWTID(), "JSON Web Token Identifier not found"); final List<String> groups = claims.getStringListClaim(SupportedClaim.GROUPS.getClaim()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardIssuerProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardIssuerProviderTest.java new file mode 100644 index 0000000000..95438ffddf --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardIssuerProviderTest.java @@ -0,0 +1,78 @@ +/* + * 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.jwt.provider; + +import org.junit.jupiter.api.Test; + +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +class StandardIssuerProviderTest { + private static final String HTTPS_SCHEME = "https"; + + private static final String LOCALHOST = "localhost.localdomain"; + + private static final int PORT = 8443; + + private static final String EMPTY = ""; + + @Test + void testGetIssuer() { + final StandardIssuerProvider provider = new StandardIssuerProvider(LOCALHOST, PORT); + + final URI issuer = provider.getIssuer(); + + assertNotNull(issuer); + assertEquals(HTTPS_SCHEME, issuer.getScheme()); + assertEquals(LOCALHOST, issuer.getHost()); + assertEquals(PORT, issuer.getPort()); + assertEquals(EMPTY, issuer.getPath()); + assertNull(issuer.getQuery()); + } + + @Test + void testGetIssuerNullHostResolved() { + final String localHost = getLocalHost(); + assumeFalse(localHost == null); + + final StandardIssuerProvider provider = new StandardIssuerProvider(null, PORT); + + final URI issuer = provider.getIssuer(); + + assertNotNull(issuer); + assertEquals(HTTPS_SCHEME, issuer.getScheme()); + assertEquals(localHost, issuer.getHost()); + assertEquals(PORT, issuer.getPort()); + assertEquals(EMPTY, issuer.getPath()); + assertNull(issuer.getQuery()); + } + + private String getLocalHost() { + try { + final InetAddress localHostAddress = InetAddress.getLocalHost(); + return localHostAddress.getCanonicalHostName(); + } catch (final UnknownHostException e) { + return null; + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/OidcBearerTokenRefreshFilterTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/OidcBearerTokenRefreshFilterTest.java index a3d2465168..a01c7ca375 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/OidcBearerTokenRefreshFilterTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/client/web/OidcBearerTokenRefreshFilterTest.java @@ -16,16 +16,28 @@ */ package org.apache.nifi.web.security.oidc.client.web; +import org.apache.nifi.authorization.user.NiFiUser; +import org.apache.nifi.authorization.user.NiFiUserDetails; +import org.apache.nifi.authorization.user.StandardNiFiUser; import org.apache.nifi.web.security.cookie.ApplicationCookieName; import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider; +import org.apache.nifi.web.security.token.LoginAuthenticationToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationToken; +import org.junit.jupiter.api.AfterEach; 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.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; @@ -45,11 +57,14 @@ import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.Collections; +import java.util.Iterator; import java.util.Map; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -89,6 +104,10 @@ class OidcBearerTokenRefreshFilterTest { private static final int INSTANT_OFFSET = 1; + private static final String PROVIDER_GROUP = "Authorized"; + + private static final Set<String> PROVIDER_GROUPS = Collections.singleton(PROVIDER_GROUP); + @Mock BearerTokenProvider bearerTokenProvider; @@ -107,6 +126,9 @@ class OidcBearerTokenRefreshFilterTest { @Mock OidcAuthorizedClient authorizedClient; + @Captor + ArgumentCaptor<LoginAuthenticationToken> tokenArgumentCaptor; + MockHttpServletRequest request; MockHttpServletResponse response; @@ -130,6 +152,11 @@ class OidcBearerTokenRefreshFilterTest { filterChain = new MockFilterChain(); } + @AfterEach + void clearContext() { + SecurityContextHolder.clearContext(); + } + @Test void testDoFilterPathNotMatched() throws ServletException, IOException { filter.doFilter(request, response, filterChain); @@ -193,6 +220,12 @@ class OidcBearerTokenRefreshFilterTest { @Test void testDoFilterBearerTokenTokenRefreshed() throws ServletException, IOException { + final NiFiUser user = new StandardNiFiUser.Builder().identity(IDENTITY).identityProviderGroups(PROVIDER_GROUPS).build(); + final NiFiUserDetails userDetails = new NiFiUserDetails(user); + final NiFiAuthenticationToken authenticationToken = new NiFiAuthenticationToken(userDetails); + final SecurityContext securityContext = new SecurityContextImpl(authenticationToken); + SecurityContextHolder.setContext(securityContext); + request.setServletPath(CURRENT_USER_URI); when(bearerTokenResolver.resolve(eq(request))).thenReturn(BEARER_TOKEN); @@ -220,13 +253,19 @@ class OidcBearerTokenRefreshFilterTest { .build(); when(refreshTokenResponseClient.getTokenResponse(any())).thenReturn(tokenResponse); - when(bearerTokenProvider.getBearerToken(any())).thenReturn(BEARER_TOKEN_REFRESHED); + when(bearerTokenProvider.getBearerToken(tokenArgumentCaptor.capture())).thenReturn(BEARER_TOKEN_REFRESHED); filter.doFilter(request, response, filterChain); final Cookie cookie = response.getCookie(ApplicationCookieName.AUTHORIZATION_BEARER.getCookieName()); assertNotNull(cookie); assertEquals(BEARER_TOKEN_REFRESHED, cookie.getValue()); + + final LoginAuthenticationToken loginAuthenticationToken = tokenArgumentCaptor.getValue(); + final Iterator<GrantedAuthority> authorities = loginAuthenticationToken.getAuthorities().iterator(); + assertTrue(authorities.hasNext()); + final GrantedAuthority authority = authorities.next(); + assertEquals(PROVIDER_GROUP, authority.getAuthority()); } private Jwt getJwt(final Instant expiration) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/web/authentication/OidcAuthenticationSuccessHandlerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/web/authentication/OidcAuthenticationSuccessHandlerTest.java index 907c41c466..f0160b2b0b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/web/authentication/OidcAuthenticationSuccessHandlerTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/web/authentication/OidcAuthenticationSuccessHandlerTest.java @@ -40,9 +40,6 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser; import javax.servlet.ServletContext; import javax.servlet.http.Cookie; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -82,8 +79,6 @@ class OidcAuthenticationSuccessHandlerTest { private static final int SERVER_PORT = 8080; - private static final String ISSUER = "http://localhost/issuer"; - private static final String LOCALHOST_URL = "http://localhost:8080"; private static final String TARGET_URL = String.format("%s%s", LOCALHOST_URL, UI_PATH); @@ -183,18 +178,16 @@ class OidcAuthenticationSuccessHandlerTest { verify(bearerTokenProvider).getBearerToken(authenticationTokenCaptor.capture()); final LoginAuthenticationToken authenticationToken = authenticationTokenCaptor.getValue(); - final Instant expiration = Instant.ofEpochMilli(authenticationToken.getExpiration()); + final Instant expiration = authenticationToken.getExpiration(); final ChronoUnit truncation = ChronoUnit.MINUTES; final Instant expirationTruncated = expiration.truncatedTo(truncation); - final Instant expected = Instant.now().plus(TOKEN_EXPIRATION).truncatedTo(truncation); - assertEquals(expected, expirationTruncated); + assertEquals(ACCESS_TOKEN_EXPIRES, expirationTruncated); } void setOidcUser() { when(oidcUser.getClaimAsString(eq(USER_NAME_CLAIM))).thenReturn(IDENTITY); when(oidcUser.getClaimAsStringList(eq(GROUPS_CLAIM))).thenReturn(Collections.singletonList(AUTHORITY)); - when(oidcUser.getIssuer()).thenReturn(getIssuer()); } StandardOAuth2AuthenticationToken getAuthenticationToken() { @@ -202,12 +195,4 @@ class OidcAuthenticationSuccessHandlerTest { final Collection<? extends GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority(AUTHORITY)); return new StandardOAuth2AuthenticationToken(oidcUser, authorities, OidcRegistrationProperty.REGISTRATION_ID.getProperty(), accessToken); } - - URL getIssuer() { - try { - return URI.create(ISSUER).toURL(); - } catch (IllegalArgumentException | MalformedURLException e) { - throw new RuntimeException(e); - } - } } diff --git a/nifi-nar-bundles/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-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java index 17e7abb793..8bc197fd9f 100644 --- a/nifi-nar-bundles/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-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/saml2/web/authentication/Saml2AuthenticationSuccessHandlerTest.java @@ -42,8 +42,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; @ExtendWith(MockitoExtension.class) class Saml2AuthenticationSuccessHandlerTest { - private static final String ISSUER = Saml2AuthenticationSuccessHandlerTest.class.getSimpleName(); - private static final Duration EXPIRATION = Duration.ofMinutes(1); private static final String IDENTITY = Authentication.class.getSimpleName(); @@ -103,8 +101,7 @@ class Saml2AuthenticationSuccessHandlerTest { bearerTokenProvider, Collections.singletonList(UPPER_IDENTITY_MAPPING), Collections.singletonList(LOWER_IDENTITY_MAPPING), - EXPIRATION, - ISSUER + EXPIRATION ); httpServletRequest = new MockHttpServletRequest(); httpServletRequest.setServerPort(SERVER_PORT);