From 844751cec0cbef47f60f861df38819522ccf9286 Mon Sep 17 00:00:00 2001 From: Emilio Setiadarma Date: Thu, 3 Nov 2022 19:51:08 -0700 Subject: [PATCH] NIFI-10177: Implemented ID token logout and revoke access token logout for NiFi Registry when using OIDC/OAuth 2.0 providers NIFI-10177: Addressed latest PR reviews. Reworded comments in the logout endpoint, use nifi registry properties to configure HTTP client timeouts for OIDC logout request, used NiFiUserUtils.getNiFiUserIdentity to retrieve identity used to delete the key Signed-off-by: Nathan Gough This closes #6637. --- nifi-registry/nifi-registry-assembly/NOTICE | 7 + .../nifi-registry-web-api/pom.xml | 4 + .../nifi/registry/web/api/AccessResource.java | 379 ++++++++++++++---- .../registry/web/api/ApplicationResource.java | 3 + .../jwt/JwtIdentityProvider.java | 2 +- .../authentication/jwt/JwtService.java | 22 +- .../oidc/OidcIdentityProvider.java | 27 +- .../authentication/oidc/OidcService.java | 45 ++- .../oidc/StandardOidcIdentityProvider.java | 131 +++++- .../authentication/oidc/OidcServiceTest.java | 14 +- .../registry/web/filter/LogoutFilter.java | 3 +- .../src/main/webapp/nf-registry.js | 4 +- .../main/webapp/services/nf-registry.api.js | 4 +- 13 files changed, 536 insertions(+), 109 deletions(-) diff --git a/nifi-registry/nifi-registry-assembly/NOTICE b/nifi-registry/nifi-registry-assembly/NOTICE index 07548043b5..9a3925e883 100644 --- a/nifi-registry/nifi-registry-assembly/NOTICE +++ b/nifi-registry/nifi-registry-assembly/NOTICE @@ -260,6 +260,13 @@ The following binary components are provided under the Apache Software License v Guava Copyright 2015 The Guava Authors + (ASLv2) Apache HttpComponents Client + The following NOTICE information applies: + Copyright 1999-2022 The Apache Software Foundation + + This product includes software developed at + The Apache Software Foundation (https://www.apache.org/). + ************************ Common Development and Distribution License 1.1 ************************ diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/pom.xml b/nifi-registry/nifi-registry-core/nifi-registry-web-api/pom.xml index bee75d862f..ae71667c81 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/pom.xml +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/pom.xml @@ -480,5 +480,9 @@ 2.5.18 test + + org.springframework.security + spring-security-oauth2-resource-server + diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java index 3c5db2670a..162cb10243 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java @@ -20,6 +20,7 @@ import com.nimbusds.oauth2.sdk.AuthorizationCode; import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant; import com.nimbusds.oauth2.sdk.AuthorizationGrant; import com.nimbusds.oauth2.sdk.ParseException; +import com.nimbusds.oauth2.sdk.http.HTTPResponse; import com.nimbusds.oauth2.sdk.id.State; import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse; import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser; @@ -31,6 +32,14 @@ import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import io.swagger.annotations.Authorization; import org.apache.commons.lang3.StringUtils; +import org.apache.http.NameValuePair; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +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.registry.authorization.CurrentUser; import org.apache.nifi.registry.event.EventService; import org.apache.nifi.registry.exception.AdministrationException; @@ -44,6 +53,7 @@ import org.apache.nifi.registry.security.authentication.exception.IdentityAccess import org.apache.nifi.registry.security.authentication.exception.InvalidCredentialsException; import org.apache.nifi.registry.security.authorization.user.NiFiUser; import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils; +import org.apache.nifi.registry.util.FormatUtils; import org.apache.nifi.registry.web.exception.UnauthorizedException; import org.apache.nifi.registry.web.security.authentication.jwt.JwtService; import org.apache.nifi.registry.web.security.authentication.kerberos.KerberosSpnegoIdentityProvider; @@ -56,6 +66,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; +import javax.net.ssl.SSLContext; import javax.servlet.ServletContext; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -72,11 +83,14 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; +import java.io.IOException; import java.net.URI; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Component @@ -91,6 +105,9 @@ public class AccessResource extends ApplicationResource { private static final String OIDC_REQUEST_IDENTIFIER = "oidc-request-identifier"; private static final String OIDC_ERROR_TITLE = "Unable to continue login sequence"; + private static final String REVOKE_ACCESS_TOKEN_LOGOUT = "oidc_access_token_logout"; + private static final String ID_TOKEN_LOGOUT = "oidc_id_token_logout"; + private static final String STANDARD_LOGOUT = "oidc_standard_logout"; private NiFiRegistryProperties properties; private JwtService jwtService; @@ -298,17 +315,17 @@ public class AccessResource extends ApplicationResource { @ApiResponse(code = 500, message = "Client failed to log out."), } ) - public Response logOut(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) { + public Response logout(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) { if (!httpServletRequest.isSecure()) { throw new IllegalStateException("User authentication/authorization is only supported when running over HTTPS."); } - String userIdentity = NiFiUserUtils.getNiFiUserIdentity(); + final String userIdentity = NiFiUserUtils.getNiFiUserIdentity(); if(userIdentity != null && !userIdentity.isEmpty()) { try { logger.info("Logging out user " + userIdentity); - jwtService.logOut(userIdentity); + jwtService.deleteKey(userIdentity); return generateOkResponse().build(); } catch (final JwtException e) { logger.error("Logout of user " + userIdentity + " failed due to: " + e.getMessage()); @@ -319,6 +336,30 @@ public class AccessResource extends ApplicationResource { } } + @GET + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.WILDCARD) + @Path("/logout/complete") + @ApiOperation( + value = "Completes the logout sequence.", + notes = NON_GUARANTEED_ENDPOINT + ) + @ApiResponses( + value = { + @ApiResponse(code = 200, message = "User was logged out successfully."), + @ApiResponse(code = 401, message = "Authentication token provided was empty or not in the correct JWT format."), + @ApiResponse(code = 500, message = "Client failed to log out."), + } + ) + public void logoutComplete(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) throws IOException { + if (!httpServletRequest.isSecure()) { + throw new IllegalStateException("User logout is only supported when running over HTTPS."); + } + + // redirect to NiFi Registry page after logout completes + httpServletResponse.sendRedirect(getNiFiRegistryUri()); + } + @POST @Consumes(MediaType.WILDCARD) @Produces(MediaType.TEXT_PLAIN) @@ -542,30 +583,10 @@ public class AccessResource extends ApplicationResource { throw new IllegalStateException("OpenId Connect is not configured."); } - final String oidcRequestIdentifier = UUID.randomUUID().toString(); - - // generate a cookie to associate this login sequence - final Cookie cookie = new Cookie(OIDC_REQUEST_IDENTIFIER, oidcRequestIdentifier); - cookie.setPath("/"); - cookie.setHttpOnly(true); - cookie.setMaxAge(60); - cookie.setSecure(true); - httpServletResponse.addCookie(cookie); - - // get the state for this request - final State state = oidcService.createState(oidcRequestIdentifier); - - // build the authorization uri - final URI authorizationUri = UriBuilder.fromUri(oidcService.getAuthorizationEndpoint()) - .queryParam("client_id", oidcService.getClientId()) - .queryParam("response_type", "code") - .queryParam("scope", oidcService.getScope().toString()) - .queryParam("state", state.getValue()) - .queryParam("redirect_uri", getOidcCallback()) - .build(); + final URI authorizationURI = oidcRequestAuthorizationCode(httpServletResponse, getOidcCallback()); // generate the response - httpServletResponse.sendRedirect(authorizationUri.toString()); + httpServletResponse.sendRedirect(authorizationURI.toString()); } @GET @@ -589,44 +610,25 @@ public class AccessResource extends ApplicationResource { throw new IllegalStateException("OpenId Connect is not configured."); } - final String oidcRequestIdentifier = getCookieValue(httpServletRequest.getCookies(), OIDC_REQUEST_IDENTIFIER); + final String oidcRequestIdentifier = getOidcRequestIdentifier(httpServletRequest); if (oidcRequestIdentifier == null) { throw new IllegalStateException("The login request identifier was not found in the request. Unable to continue."); } - final com.nimbusds.openid.connect.sdk.AuthenticationResponse oidcResponse; - try { - oidcResponse = AuthenticationResponseParser.parse(getRequestUri()); - } catch (final ParseException e) { - logger.error("Unable to parse the redirect URI from the OpenId Connect Provider. Unable to continue login process."); - // remove the oidc request cookie - removeOidcRequestCookie(httpServletResponse); - - // forward to the error page - throw new IllegalStateException("Unable to parse the redirect URI from the OpenId Connect Provider. Unable to continue login process."); - } + final com.nimbusds.openid.connect.sdk.AuthenticationResponse oidcResponse = + parseAuthenticationResponse(getRequestUri(), httpServletResponse, true); if (oidcResponse.indicatesSuccess()) { final AuthenticationSuccessResponse successfulOidcResponse = (AuthenticationSuccessResponse) oidcResponse; - // confirm state - final State state = successfulOidcResponse.getState(); - if (state == null || !oidcService.isStateValid(oidcRequestIdentifier, state)) { - logger.error("The state value returned by the OpenId Connect Provider does not match the stored state. Unable to continue login process."); - - // remove the oidc request cookie - removeOidcRequestCookie(httpServletResponse); - - // forward to the error page - throw new IllegalStateException("Purposed state does not match the stored state. Unable to continue login process."); - } + validateOIDCState(oidcRequestIdentifier, successfulOidcResponse, httpServletResponse, true); try { // exchange authorization code for id token final AuthorizationCode authorizationCode = successfulOidcResponse.getAuthorizationCode(); final AuthorizationGrant authorizationGrant = new AuthorizationCodeGrant(authorizationCode, URI.create(getOidcCallback())); - oidcService.exchangeAuthorizationCode(oidcRequestIdentifier, authorizationGrant); + oidcService.exchangeAuthorizationCodeForLoginAuthenticationToken(oidcRequestIdentifier, authorizationGrant); } catch (final Exception e) { logger.error("Unable to exchange authorization for ID token: " + e.getMessage(), e); @@ -669,7 +671,7 @@ public class AccessResource extends ApplicationResource { throw new IllegalStateException("OpenId Connect is not configured."); } - final String oidcRequestIdentifier = getCookieValue(httpServletRequest.getCookies(), OIDC_REQUEST_IDENTIFIER); + final String oidcRequestIdentifier = getOidcRequestIdentifier(httpServletRequest); if (oidcRequestIdentifier == null) { throw new IllegalArgumentException("The login request identifier was not found in the request. Unable to continue."); } @@ -687,7 +689,7 @@ public class AccessResource extends ApplicationResource { return generateOkResponse(jwt).build(); } - @DELETE + @GET @Consumes(MediaType.WILDCARD) @Produces(MediaType.WILDCARD) @Path("/oidc/logout") @@ -704,20 +706,130 @@ public class AccessResource extends ApplicationResource { throw new IllegalStateException("OpenId Connect is not configured."); } - final String tokenHeader = httpServletRequest.getHeader(JwtService.AUTHORIZATION); - jwtService.logOutUsingAuthHeader(tokenHeader); + // Checks if OIDC service supports logout using either by invoking the revocation endpoint (for OAuth 2.0 providers) + // or the end session endpoint (for OIDC providers). If either is supported, send a request to get an authorization + // code that can be eventually exchanged for a token that is required as a parameter for the logout request. + final String logoutMethod = determineLogoutMethod(); + switch (logoutMethod) { + case REVOKE_ACCESS_TOKEN_LOGOUT: + case ID_TOKEN_LOGOUT: + final URI authorizationURI = oidcRequestAuthorizationCode(httpServletResponse, getOidcLogoutCallback()); + httpServletResponse.sendRedirect(authorizationURI.toString()); + break; + default: + // If the above logout methods are not supported, last ditch effort to logout by providing the client_id, + // to the end session endpoint if it exists. This is a way to logout defined in the OIDC specs, but the + // id_token_hint logout method is recommended. This option is not available when using the POST request + // to the revocation endpoint (OAuth 2.0 providers). + final URI endSessionEndpoint = oidcService.getEndSessionEndpoint(); + if (endSessionEndpoint != null) { + final String postLogoutRedirectUri = getNiFiRegistryUri(); + final URI logoutUri = UriBuilder.fromUri(endSessionEndpoint) + .queryParam("post_logout_redirect_uri", postLogoutRedirectUri) + .queryParam("client_id", oidcService.getClientId()) + .build(); + httpServletResponse.sendRedirect(logoutUri.toString()); + } else { + throw new IllegalStateException("Unable to initiate logout: Logout method unrecognized"); + } + break; + } + } - URI endSessionEndpoint = oidcService.getEndSessionEndpoint(); - String postLogoutRedirectUri = generateResourceUri("..", "nifi-registry"); + @GET + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.WILDCARD) + @Path("/oidc/logout/callback") + @ApiOperation( + value = "Redirect/callback URI for processing the result of the OpenId Connect logout sequence.", + notes = NON_GUARANTEED_ENDPOINT + ) + public void oidcLogoutCallback(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) throws Exception { + if (!httpServletRequest.isSecure()) { + throw new IllegalStateException("User logout is only supported when running over HTTPS."); + } - if (endSessionEndpoint == null) { - // handle the case, where the OpenID Provider does not have an end session endpoint - //httpServletResponse.sendRedirect(postLogoutRedirectUri); + if (!oidcService.isOidcEnabled()) { + throw new IllegalStateException("OpenId Connect is not configured."); + } + + final String oidcRequestIdentifier = getOidcRequestIdentifier(httpServletRequest); + if (oidcRequestIdentifier == null) { + throw new IllegalStateException("The OIDC request identifier was not found in the request. Unable to continue."); + } + + final com.nimbusds.openid.connect.sdk.AuthenticationResponse oidcResponse = + parseAuthenticationResponse(getRequestUri(), httpServletResponse, false); + + if (oidcResponse.indicatesSuccess()) { + final AuthenticationSuccessResponse successfulOidcResponse = (AuthenticationSuccessResponse) oidcResponse; + + validateOIDCState(oidcRequestIdentifier, successfulOidcResponse, httpServletResponse, false); + + try { + // exchange authorization code for id token + final AuthorizationCode authorizationCode = successfulOidcResponse.getAuthorizationCode(); + final AuthorizationGrant authorizationGrant = new AuthorizationCodeGrant(authorizationCode, URI.create(getOidcLogoutCallback())); + + final String logoutMethod = determineLogoutMethod(); + switch(logoutMethod) { + case REVOKE_ACCESS_TOKEN_LOGOUT: + final String accessToken; + try { + accessToken = oidcService.exchangeAuthorizationCodeForAccessToken(authorizationGrant); + } catch (final Exception e) { + final String errorMsg = "Unable to exchange authorization for the access token: " + e.getMessage(); + logger.error(errorMsg, e); + + throw new IllegalStateException(errorMsg); + } + + final URI revokeEndpoint = oidcService.getRevocationEndpoint(); + try { + revokeEndpointRequest(httpServletResponse, accessToken, revokeEndpoint); + } catch (final IOException e) { + final String errorMsg = "There was an error logging out of the OpenID Connect Provider: " + e.getMessage(); + logger.error(errorMsg, e); + + throw new IllegalStateException(errorMsg); + } + break; + case ID_TOKEN_LOGOUT: + final String idToken; + try { + idToken = oidcService.exchangeAuthorizationCodeForIdToken(authorizationGrant); + } catch (final Exception e) { + final String errorMsg = "Unable to exchange authorization for the ID token: " + e.getMessage(); + logger.error(errorMsg, e); + + throw new IllegalStateException(errorMsg); + } + + final URI endSessionEndpoint = oidcService.getEndSessionEndpoint(); + final String postLogoutRedirectUri = getNiFiRegistryUri(); + final URI logoutUri = UriBuilder.fromUri(endSessionEndpoint) + .queryParam("id_token_hint", idToken) + .queryParam("post_logout_redirect_uri", postLogoutRedirectUri) + .build(); + httpServletResponse.sendRedirect(logoutUri.toString()); + break; + default: + // there should be no other way to logout at this point, return error + throw new IllegalStateException("Unable to complete logout: Logout method unrecognized"); + } + } catch (final Exception e) { + logger.error(e.getMessage(), e); + + removeOidcRequestCookie(httpServletResponse); + + throw e; + } } else { - URI logoutUri = UriBuilder.fromUri(endSessionEndpoint) - .queryParam("post_logout_redirect_uri", postLogoutRedirectUri) - .build(); - httpServletResponse.sendRedirect(logoutUri.toString()); + removeOidcRequestCookie(httpServletResponse); + + // report the unsuccessful logout + final AuthenticationErrorResponse errorOidcResponse = (AuthenticationErrorResponse) oidcResponse; + throw new IllegalStateException("Unsuccessful logout attempt: " + errorOidcResponse.getErrorObject().getDescription()); } } @@ -748,6 +860,10 @@ public class AccessResource extends ApplicationResource { return generateResourceUri("access", "oidc", "callback"); } + private String getOidcLogoutCallback() { + return generateResourceUri("access", "oidc", "logout", "callback"); + } + private void removeOidcRequestCookie(final HttpServletResponse httpServletResponse) { final Cookie cookie = new Cookie(OIDC_REQUEST_IDENTIFIER, null); cookie.setPath("/"); @@ -761,12 +877,6 @@ public class AccessResource extends ApplicationResource { return uriInfo.getRequestUri(); } - private String getNiFiRegistryUri() { - final String nifiRegistryApiUrl = generateResourceUri(); - final String baseUrl = StringUtils.substringBeforeLast(nifiRegistryApiUrl, "/nifi-registry-api"); - return baseUrl + "/nifi-registry"; - } - private void forwardToMessagePage(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final String message) throws Exception { httpServletRequest.setAttribute("title", OIDC_ERROR_TITLE); httpServletRequest.setAttribute("messages", message); @@ -832,4 +942,137 @@ public class AccessResource extends ApplicationResource { private boolean isOIDCLoginSupported(HttpServletRequest request) { return request.isSecure() && oidcService != null && oidcService.isOidcEnabled(); } + + private String determineLogoutMethod() { + if (oidcService.getEndSessionEndpoint() != null) { + return ID_TOKEN_LOGOUT; + } else if (oidcService.getRevocationEndpoint() != null) { + return REVOKE_ACCESS_TOKEN_LOGOUT; + } else { + return STANDARD_LOGOUT; + } + } + + /** + * Generates the request Authorization URI for the OpenID Connect Provider. Returns an authorization + * URI using the provided callback URI. + * + * @param httpServletResponse the servlet response + * @param callback the OIDC callback URI + * @return the authorization URI + */ + private URI oidcRequestAuthorizationCode(@Context final HttpServletResponse httpServletResponse, final String callback) { + final String oidcRequestIdentifier = UUID.randomUUID().toString(); + // generate a cookie to associate this login sequence + final Cookie cookie = new Cookie(OIDC_REQUEST_IDENTIFIER, oidcRequestIdentifier); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setMaxAge(60); + cookie.setSecure(true); + httpServletResponse.addCookie(cookie); + + // get the state for this request + final State state = oidcService.createState(oidcRequestIdentifier); + + // build the authorization uri + final URI authorizationUri = UriBuilder.fromUri(oidcService.getAuthorizationEndpoint()) + .queryParam("client_id", oidcService.getClientId()) + .queryParam("response_type", "code") + .queryParam("scope", oidcService.getScope().toString()) + .queryParam("state", state.getValue()) + .queryParam("redirect_uri", callback) + .build(); + return authorizationUri; + } + + private String getOidcRequestIdentifier(final HttpServletRequest httpServletRequest) { + return getCookieValue(httpServletRequest.getCookies(), OIDC_REQUEST_IDENTIFIER); + } + + private com.nimbusds.openid.connect.sdk.AuthenticationResponse parseAuthenticationResponse(final URI requestUri, + final HttpServletResponse httpServletResponse, + final boolean isLogin) { + final com.nimbusds.openid.connect.sdk.AuthenticationResponse oidcResponse; + try { + oidcResponse = AuthenticationResponseParser.parse(requestUri); + } catch (final ParseException e) { + final String loginOrLogoutString = isLogin ? "login" : "logout"; + logger.error(String.format("Unable to parse the redirect URI from the OpenId Connect Provider. Unable to continue %s process.", loginOrLogoutString)); + + // remove the oidc request cookie + removeOidcRequestCookie(httpServletResponse); + + throw new IllegalStateException(String.format("Unable to parse the redirect URI from the OpenId Connect Provider. Unable to continue %s process.", loginOrLogoutString)); + } + return oidcResponse; + } + + private void validateOIDCState(final String oidcRequestIdentifier, + final AuthenticationSuccessResponse successfulOidcResponse, + final HttpServletResponse httpServletResponse, + final boolean isLogin) { + // confirm state + final State state = successfulOidcResponse.getState(); + if (state == null || !oidcService.isStateValid(oidcRequestIdentifier, state)) { + final String loginOrLogoutMessage = isLogin ? "login" : "logout"; + logger.error(String.format("The state value returned by the OpenId Connect Provider does not match the stored state. Unable to continue %s process.", loginOrLogoutMessage)); + + // remove the oidc request cookie + removeOidcRequestCookie(httpServletResponse); + + throw new IllegalStateException(String.format("Proposed state does not match the stored state. Unable to continue %s process.", loginOrLogoutMessage)); + } + } + + /** + * Sends a POST request to the revoke endpoint to log out of the ID Provider. + * + * @param httpServletResponse the servlet response + * @param accessToken the OpenID Connect Provider access token + * @param revokeEndpoint the name of the cookie + * @throws IOException exceptional case for communication error with the OpenId Connect Provider + */ + private void revokeEndpointRequest(@Context HttpServletResponse httpServletResponse, String accessToken, URI revokeEndpoint) throws IOException, NoSuchAlgorithmException { + final CloseableHttpClient httpClient = getHttpClient(); + HttpPost httpPost = new HttpPost(revokeEndpoint); + + List params = new ArrayList<>(); + // Append a query param with the access token + params.add(new BasicNameValuePair("token", accessToken)); + httpPost.setEntity(new UrlEncodedFormEntity(params)); + + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + if (response.getStatusLine().getStatusCode() == HTTPResponse.SC_OK) { + // redirect to NiFi Registry page after logout completes + logger.debug("You are logged out of the OpenId Connect Provider."); + final String postLogoutRedirectUri = getNiFiRegistryUri(); + httpServletResponse.sendRedirect(postLogoutRedirectUri); + } else { + logger.error("There was an error logging out of the OpenId Connect Provider. " + + "Response status: " + response.getStatusLine().getStatusCode()); + } + } finally { + httpClient.close(); + } + } + + private CloseableHttpClient getHttpClient() throws NoSuchAlgorithmException { + final String rawConnectTimeout = properties.getOidcConnectTimeout(); + final String rawReadTimeout = properties.getOidcReadTimeout(); + final int oidcConnectTimeout = (int) FormatUtils.getPreciseTimeDuration(rawConnectTimeout, TimeUnit.MILLISECONDS); + final int oidcReadTimeout = (int) FormatUtils.getPreciseTimeDuration(rawReadTimeout, TimeUnit.MILLISECONDS); + + final RequestConfig config = RequestConfig.custom() + .setConnectTimeout(oidcConnectTimeout) + .setConnectionRequestTimeout(oidcReadTimeout) + .setSocketTimeout(oidcReadTimeout) + .build(); + + final HttpClientBuilder builder = HttpClientBuilder + .create() + .setDefaultRequestConfig(config) + .setSSLContext(SSLContext.getDefault()); + + return builder.build(); + } } diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java index bce1e3911c..b16bae5905 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java @@ -230,4 +230,7 @@ public class ApplicationResource { return revisionInfo; } + protected String getNiFiRegistryUri() { + return generateResourceUri("..", "nifi-registry"); + } } diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtIdentityProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtIdentityProvider.java index d3f12c9114..b318e3a0cb 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtIdentityProvider.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtIdentityProvider.java @@ -69,7 +69,7 @@ public class JwtIdentityProvider extends BearerAuthIdentityProvider implements I } try { - final String jwtPrincipal = jwtService.getAuthenticationFromToken(jwtAuthToken); + final String jwtPrincipal = jwtService.getUserIdentityFromToken(jwtAuthToken); return new AuthenticationResponse(jwtPrincipal, jwtPrincipal, expiration, issuer); } catch (JwtException e) { throw new InvalidAuthenticationException(e.getMessage(), e); diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java index a0e2d252c7..95e54e8325 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java @@ -31,7 +31,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.nifi.registry.security.authentication.AuthenticationResponse; import org.apache.nifi.registry.security.key.Key; import org.apache.nifi.registry.security.key.KeyService; -import org.apache.nifi.registry.web.security.authentication.exception.InvalidAuthenticationException; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -40,7 +39,6 @@ import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; import java.util.regex.Pattern; // TODO, look into replacing this JwtService service with Apache Licensed JJWT library @@ -62,7 +60,7 @@ public class JwtService { this.keyService = keyService; } - public String getAuthenticationFromToken(final String base64EncodedToken) throws JwtException { + public String getUserIdentityFromToken(final String base64EncodedToken) throws JwtException { // The library representations of the JWT should be kept internal to this service. try { final Jws jws = parseTokenFromBase64EncodedString(base64EncodedToken); @@ -175,7 +173,7 @@ public class JwtService { } - public void logOut(String userIdentity) { + public void deleteKey(final String userIdentity) { if (userIdentity == null || userIdentity.isEmpty()) { throw new JwtException("Log out failed: The user identity was not present in the request token to log out user."); } @@ -184,7 +182,7 @@ public class JwtService { keyService.deleteKey(userIdentity); logger.info("Deleted token from database."); } catch (Exception e) { - logger.error("Unable to log out user: " + userIdentity + ". Failed to remove their token from database."); + logger.error("Unable to delete token for user: [" + userIdentity + "]."); throw e; } } @@ -228,18 +226,4 @@ public class JwtService { .append(" ms remaining]") .toString(); } - - public void logOutUsingAuthHeader(String authorizationHeader) { - String base64EncodedToken = getTokenFromHeader(authorizationHeader); - logOut(getAuthenticationFromToken(base64EncodedToken)); - } - - public static String getTokenFromHeader(String authenticationHeader) { - Matcher matcher = tokenPattern.matcher(authenticationHeader); - if(matcher.matches()) { - return matcher.group(1); - } else { - throw new InvalidAuthenticationException("JWT did not match expected pattern."); - } - } } diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcIdentityProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcIdentityProvider.java index 53e3fe22a0..008f3ac69a 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcIdentityProvider.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcIdentityProvider.java @@ -60,6 +60,13 @@ public interface OidcIdentityProvider { */ URI getEndSessionEndpoint(); + /** + * Returns the URI for the revocation endpoint. + * + * @return uri for the revocation endpoint + */ + URI getRevocationEndpoint(); + /** * Returns the scopes supported by the OIDC provider. * @@ -75,5 +82,23 @@ public interface OidcIdentityProvider { * @return a NiFi JWT * @throws IOException if there was an exceptional error while communicating with the OIDC provider */ - String exchangeAuthorizationCode(AuthorizationGrant authorizationGrant) throws IOException; + String exchangeAuthorizationCodeForLoginAuthenticationToken(AuthorizationGrant authorizationGrant) throws IOException; + + /** + * Exchanges the supplied authorization grant for an Access Token. + * + * @param authorizationGrant authorization grant for invoking the Token Endpoint + * @return an Access Token String + * @throws Exception if there was an exceptional error while communicating with the OIDC provider + */ + String exchangeAuthorizationCodeForAccessToken(AuthorizationGrant authorizationGrant) throws Exception; + + /** + * Exchanges the supplied authorization grant for an ID Token. + * + * @param authorizationGrant authorization grant for invoking the Token Endpoint + * @return an ID Token String + * @throws IOException if there was an exceptional error while communicating with the OIDC provider + */ + String exchangeAuthorizationCodeForIdToken(final AuthorizationGrant authorizationGrant) throws IOException; } diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcService.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcService.java index 1dd3cfbf0b..0ea870ce84 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcService.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcService.java @@ -101,6 +101,15 @@ public class OidcService { return identityProvider.getEndSessionEndpoint(); } + /** + * Returns the URI for the revocation endpoint. + * + * @return uri for the revocation endpoint + */ + public URI getRevocationEndpoint() { + return identityProvider.getRevocationEndpoint(); + } + /** * Returns the OpenId Connect scope. * @@ -195,13 +204,13 @@ public class OidcService { * @param authorizationGrant authorization grant * @throws IOException exceptional case for communication error with the OpenId Connect provider */ - public void exchangeAuthorizationCode(final String oidcRequestIdentifier, final AuthorizationGrant authorizationGrant) throws IOException { + public void exchangeAuthorizationCodeForLoginAuthenticationToken(final String oidcRequestIdentifier, final AuthorizationGrant authorizationGrant) throws IOException { if (!isOidcEnabled()) { throw new IllegalStateException(OidcIdentityProvider.OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED); } final CacheKey oidcRequestIdentifierKey = new CacheKey(oidcRequestIdentifier); - final String nifiJwt = identityProvider.exchangeAuthorizationCode(authorizationGrant); + final String nifiJwt = identityProvider.exchangeAuthorizationCodeForLoginAuthenticationToken(authorizationGrant); try { // cache the jwt for later retrieval @@ -216,6 +225,38 @@ public class OidcService { } } + /** + * Exchanges the specified authorization grant for an access token. + * + * @param authorizationGrant authorization grant + * @return an Access Token string + * @throws IOException exceptional case for communication error with the OpenId Connect provider + */ + public String exchangeAuthorizationCodeForAccessToken(final AuthorizationGrant authorizationGrant) throws Exception { + if (!isOidcEnabled()) { + throw new IllegalStateException(OidcIdentityProvider.OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED); + } + + // Retrieve access token + return identityProvider.exchangeAuthorizationCodeForAccessToken(authorizationGrant); + } + + /** + * Exchanges the specified authorization grant for an ID Token. + * + * @param authorizationGrant authorization grant + * @return an ID Token string + * @throws IOException exceptional case for communication error with the OpenId Connect provider + */ + public String exchangeAuthorizationCodeForIdToken(final AuthorizationGrant authorizationGrant) throws IOException { + if (!isOidcEnabled()) { + throw new IllegalStateException(OidcIdentityProvider.OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED); + } + + // Retrieve ID token + return identityProvider.exchangeAuthorizationCodeForIdToken(authorizationGrant); + } + /** * Returns the resulting JWT for the given request identifier. Will return null if the request * identifier is not associated with a JWT or if the login sequence was not completed before diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/StandardOidcIdentityProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/StandardOidcIdentityProvider.java index f43bef08fc..77b1b3c075 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/StandardOidcIdentityProvider.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/oidc/StandardOidcIdentityProvider.java @@ -26,6 +26,11 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; + +import com.nimbusds.oauth2.sdk.token.AccessToken; +import com.nimbusds.openid.connect.sdk.claims.AccessTokenHash; +import com.nimbusds.openid.connect.sdk.validators.AccessTokenValidator; +import com.nimbusds.openid.connect.sdk.validators.InvalidHashException; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.registry.properties.NiFiRegistryProperties; import org.apache.nifi.registry.security.authentication.exception.IdentityAccessException; @@ -276,6 +281,14 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider { return oidcProviderMetadata.getEndSessionEndpointURI(); } + @Override + public URI getRevocationEndpoint() { + if (!isOidcEnabled()) { + throw new IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED); + } + return oidcProviderMetadata.getRevocationEndpointURI(); + } + @Override public Scope getScope() { if (!isOidcEnabled()) { @@ -302,7 +315,7 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider { } @Override - public String exchangeAuthorizationCode(final AuthorizationGrant authorizationGrant) throws IOException { + public String exchangeAuthorizationCodeForLoginAuthenticationToken(final AuthorizationGrant authorizationGrant) throws IOException { // Check if OIDC is enabled if (!isOidcEnabled()) { throw new IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED); @@ -314,20 +327,60 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider { try { // Build the token request final HTTPRequest tokenHttpRequest = createTokenHTTPRequest(authorizationGrant, clientAuthentication); - return authorizeClient(tokenHttpRequest); - + final TokenResponse response = authorizeClient(tokenHttpRequest); + return convertOIDCTokenToNiFiToken((OIDCTokenResponse) response); } catch (final ParseException | JOSEException | BadJOSEException | java.text.ParseException e) { throw new RuntimeException("Unable to parse the response from the Token request: " + e.getMessage()); } } - private String authorizeClient(HTTPRequest tokenHttpRequest) throws ParseException, IOException, BadJOSEException, JOSEException, java.text.ParseException { + @Override + public String exchangeAuthorizationCodeForAccessToken(final AuthorizationGrant authorizationGrant) throws Exception { + // Check if OIDC is enabled + if (!isOidcEnabled()) { + throw new IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED); + } + + // Build ClientAuthentication + final ClientAuthentication clientAuthentication = createClientAuthentication(); + + try { + // Build the token request + final HTTPRequest tokenHttpRequest = createTokenHTTPRequest(authorizationGrant, clientAuthentication); + final TokenResponse response = authorizeClient(tokenHttpRequest); + return getAccessTokenString((OIDCTokenResponse) response); + } catch (final ParseException | JOSEException | BadJOSEException | java.text.ParseException e) { + throw new RuntimeException("Unable to parse the response from the Token request: " + e.getMessage()); + } + } + + @Override + public String exchangeAuthorizationCodeForIdToken(final AuthorizationGrant authorizationGrant) { + // Check if OIDC is enabled + if (!isOidcEnabled()) { + throw new IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED); + } + + // Build ClientAuthentication + final ClientAuthentication clientAuthentication = createClientAuthentication(); + + try { + // Build the token request + final HTTPRequest tokenHttpRequest = createTokenHTTPRequest(authorizationGrant, clientAuthentication); + final TokenResponse response = authorizeClient(tokenHttpRequest); + return getIdTokenString((OIDCTokenResponse) response); + } catch (final RuntimeException | JOSEException | BadJOSEException | ParseException | IOException | java.text.ParseException e) { + throw new RuntimeException("Unable to parse the response from the Token request: " + e.getMessage(), e); + } + } + + private TokenResponse authorizeClient(HTTPRequest tokenHttpRequest) throws ParseException, IOException, BadJOSEException, JOSEException, java.text.ParseException { // Get the token response final TokenResponse response = OIDCTokenResponseParser.parse(tokenHttpRequest.send()); // Handle success if (response.indicatesSuccess()) { - return convertOIDCTokenToNiFiToken((OIDCTokenResponse) response); + return response; } else { // If the response was not successful final TokenErrorResponse errorResponse = (TokenErrorResponse) response; @@ -463,4 +516,72 @@ public class StandardOidcIdentityProvider implements OidcIdentityProvider { } } + private String getAccessTokenString(final OIDCTokenResponse response) throws Exception { + final OIDCTokens oidcTokens = response.getOIDCTokens(); + + // Validate the Access Token + validateAccessToken(oidcTokens); + + // Return the Access Token String + return oidcTokens.getAccessToken().getValue(); + } + + private String getIdTokenString(OIDCTokenResponse response) throws BadJOSEException, JOSEException { + final OIDCTokens oidcTokens = response.getOIDCTokens(); + + // Validate the Token - no nonce required for authorization code flow + validateIdToken(oidcTokens.getIDToken()); + + // Return the ID Token string + return oidcTokens.getIDTokenString(); + } + + private void validateAccessToken(OIDCTokens oidcTokens) throws Exception { + // Get the Access Token to validate + final AccessToken accessToken = oidcTokens.getAccessToken(); + + // Get the preferredJwsAlgorithm for validation + final JWSAlgorithm jwsAlgorithm = extractJwsAlgorithm(); + + // Get the accessTokenHash for validation + final String atHashString = oidcTokens + .getIDToken() + .getJWTClaimsSet() + .getStringClaim("at_hash"); + + // Compute the Access Token hash + final AccessTokenHash atHash = new AccessTokenHash(atHashString); + + try { + // Validate the Token + AccessTokenValidator.validate(accessToken, jwsAlgorithm, atHash); + } catch (InvalidHashException e) { + throw new Exception("Unable to validate the Access Token: " + e.getMessage()); + } + } + + private IDTokenClaimsSet validateIdToken(JWT oidcJwt) throws BadJOSEException, JOSEException { + try { + return tokenValidator.validate(oidcJwt, null); + } catch (BadJOSEException e) { + throw new BadJOSEException("Unable to validate the ID Token: " + e.getMessage()); + } + } + + private JWSAlgorithm extractJwsAlgorithm() { + + final String rawPreferredJwsAlgorithm = properties.getOidcPreferredJwsAlgorithm(); + + final JWSAlgorithm preferredJwsAlgorithm; + if (StringUtils.isBlank(rawPreferredJwsAlgorithm)) { + preferredJwsAlgorithm = JWSAlgorithm.RS256; + } else { + if ("none".equalsIgnoreCase(rawPreferredJwsAlgorithm)) { + preferredJwsAlgorithm = null; + } else { + preferredJwsAlgorithm = JWSAlgorithm.parse(rawPreferredJwsAlgorithm); + } + } + return preferredJwsAlgorithm; + } } diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcServiceTest.java b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcServiceTest.java index c3e5701d7c..2b3b2d08ff 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcServiceTest.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/security/authentication/oidc/OidcServiceTest.java @@ -83,14 +83,14 @@ public class OidcServiceTest { @Test(expected = IllegalStateException.class) public void testOidcNotEnabledExchangeCode() throws Exception { final OidcService service = getServiceWithNoOidcSupport(); - service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); + service.exchangeAuthorizationCodeForLoginAuthenticationToken(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); } @Test(expected = IllegalStateException.class) public void testExchangeCodeMultipleInvocation() throws Exception { final OidcService service = getServiceWithOidcSupport(); - service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); - service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); + service.exchangeAuthorizationCodeForLoginAuthenticationToken(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); + service.exchangeAuthorizationCodeForLoginAuthenticationToken(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); } @Test(expected = IllegalStateException.class) @@ -102,14 +102,14 @@ public class OidcServiceTest { @Test public void testGetJwt() throws Exception { final OidcService service = getServiceWithOidcSupport(); - service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); + service.exchangeAuthorizationCodeForLoginAuthenticationToken(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); assertNotNull(service.getJwt(TEST_REQUEST_IDENTIFIER)); } @Test public void testGetJwtExpiration() throws Exception { final OidcService service = getServiceWithOidcSupportAndCustomExpiration(1, TimeUnit.SECONDS); - service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); + service.exchangeAuthorizationCodeForLoginAuthenticationToken(TEST_REQUEST_IDENTIFIER, getAuthorizationCodeGrant()); Thread.sleep(3 * 1000); @@ -129,7 +129,7 @@ public class OidcServiceTest { private OidcService getServiceWithOidcSupport() throws Exception { final OidcIdentityProvider provider = mock(OidcIdentityProvider.class); when(provider.isOidcEnabled()).thenReturn(true); - when(provider.exchangeAuthorizationCode(any())).then(invocation -> UUID.randomUUID().toString()); + when(provider.exchangeAuthorizationCodeForLoginAuthenticationToken(any())).then(invocation -> UUID.randomUUID().toString()); final OidcService service = new OidcService(provider); assertTrue(service.isOidcEnabled()); @@ -140,7 +140,7 @@ public class OidcServiceTest { private OidcService getServiceWithOidcSupportAndCustomExpiration(final int duration, final TimeUnit units) throws Exception { final OidcIdentityProvider provider = mock(OidcIdentityProvider.class); when(provider.isOidcEnabled()).thenReturn(true); - when(provider.exchangeAuthorizationCode(any())).then(invocation -> UUID.randomUUID().toString()); + when(provider.exchangeAuthorizationCodeForLoginAuthenticationToken(any())).then(invocation -> UUID.randomUUID().toString()); final OidcService service = new OidcService(provider, duration, units); assertTrue(service.isOidcEnabled()); diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/java/org/apache/nifi/registry/web/filter/LogoutFilter.java b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/java/org/apache/nifi/registry/web/filter/LogoutFilter.java index a005d5dfdc..627fb89c08 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/java/org/apache/nifi/registry/web/filter/LogoutFilter.java +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/java/org/apache/nifi/registry/web/filter/LogoutFilter.java @@ -40,13 +40,12 @@ public class LogoutFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { final boolean supportsOidc = Boolean.parseBoolean(servletContext.getInitParameter("oidc-supported")); - if (supportsOidc) { final ServletContext apiContext = servletContext.getContext("/nifi-registry-api"); apiContext.getRequestDispatcher("/access/oidc/logout").forward(request, response); } else { final ServletContext apiContext = servletContext.getContext("/nifi-registry-api"); - apiContext.getRequestDispatcher("/access/logout").forward(request, response); + apiContext.getRequestDispatcher("/access/logout/complete").forward(request, response); } } diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js index 2d7130871e..827ca389f4 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js @@ -72,7 +72,7 @@ NfRegistry.prototype = { */ logout: function () { var self = this; - self.nfRegistryApi.deleteToLogout('../nifi-registry/logout').subscribe( + self.nfRegistryApi.deleteToLogout().subscribe( function () { // next call }, @@ -84,7 +84,7 @@ NfRegistry.prototype = { self.nfStorage.removeItem('jwt'); delete self.nfRegistryService.currentUser.identity; delete self.nfRegistryService.currentUser.anonymous; - self.router.navigateByUrl('login'); + window.location.href = location.origin + '/nifi-registry/logout'; } ); }, diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js index bb04339b58..fbc6740afe 100644 --- a/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js +++ b/nifi-registry/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js @@ -866,7 +866,7 @@ NfRegistryApi.prototype = { * * @returns {*} */ - deleteToLogout: function (url) { + deleteToLogout: function () { var self = this; var options = { headers: headers, @@ -874,7 +874,7 @@ NfRegistryApi.prototype = { responseType: 'text' }; - return this.http.delete(url, options).pipe( + return this.http.delete('../nifi-registry-api/access/logout', options).pipe( map(function (response) { return response; }),