This is an automated email from the ASF dual-hosted git repository. thenatog pushed a commit to branch NIFIREG-361 in repository https://gitbox.apache.org/repos/asf/nifi-registry.git
commit 32f9352465e877d71ad7f85b70f2304ba620e133 Author: Nathan Gough <[email protected]> AuthorDate: Thu Feb 13 17:24:47 2020 -0500 NIFIREG-361 - Improved handling of the /access/logout endpoint. --- .../nifi/registry/web/api/AccessResource.java | 38 ++++++++++++++ .../nifi/registry/web/api/ApplicationResource.java | 9 ++++ .../security/authentication/jwt/JwtService.java | 14 +++++ .../apache/nifi/registry/web/api/SecureLdapIT.java | 60 ++++++++++++++++++++++ .../src/main/webapp/nf-registry.js | 17 +++++- .../src/main/webapp/services/nf-registry.api.js | 31 +++++++++++ 6 files changed, 168 insertions(+), 1 deletion(-) diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java index 278f635..d8275cb 100644 --- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java @@ -48,7 +48,9 @@ import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -258,6 +260,42 @@ public class AccessResource extends ApplicationResource { return generateCreatedResponse(uri, token).build(); } + @DELETE + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.WILDCARD) + @Path("/logout") + @ApiOperation( + value = "Performs a logout for other providers that have been issued a JWT.", + 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 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(); + + if(userIdentity != null && !userIdentity.isEmpty()) { + try { + logger.info("Logging out user " + userIdentity); + jwtService.logOut(userIdentity); + return generateOkResponse().build(); + } catch (final JwtException e) { + logger.error("Logout of user " + userIdentity + " failed due to: " + e.getMessage()); + return Response.serverError().build(); + } + } else { + return Response.status(401, "Authentication token provided was empty or not in the correct JWT format.").build(); + } + } + @POST @Consumes(MediaType.WILDCARD) @Produces(MediaType.TEXT_PLAIN) diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java index d33fd8b..bce1e39 100644 --- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java @@ -171,6 +171,15 @@ public class ApplicationResource { } /** + * Generates an Ok response with no content. + * + * @return an Ok response with no content + */ + protected Response.ResponseBuilder generateOkResponse() { + return noCache(Response.ok()); + } + + /** * Generates a 201 Created response with the specified content. * * @param uri The URI diff --git a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java index d47b301..d24e665 100644 --- a/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java +++ b/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java @@ -170,6 +170,20 @@ public class JwtService { } + public void logOut(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."); + } + + try { + 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."); + throw e; + } + } + private static long validateTokenExpiration(long proposedTokenExpiration, String identity) { final long maxExpiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS); final long minExpiration = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES); diff --git a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java index 4147b3d..7490d3e 100644 --- a/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java +++ b/nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java @@ -91,6 +91,12 @@ public class SecureLdapIT extends IntegrationTestBase { private static final String tokenLoginPath = "access/token/login"; private static final String tokenIdentityProviderPath = "access/token/identity-provider"; + // A JWT signed by a key of 'secret' + private static final String SIGNED_BY_WRONG_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + + ".eyJzdWIiOiJuaWZpYWRtaW4iLCJpc3MiOiJMZGFwSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6IkxkYXB" + + "JZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibmlmaWFkbWluIiwia2lkIjoiNDd" + + "lMjA1NzctY2I3Yi00M2MzLWFhOGYtZjI0ZDcyODQ3MDEwIiwiaWF0IjoxNTgxNTI5NTA1LCJleHAiOjE" + + "1ODE1NzI3MDV9.vvMpwLJt1w_6Id_tlS1knxTkJ2gv7_j5ySG6PmNjF0s"; @TestConfiguration @Profile("ITSecureLdap") @@ -297,6 +303,60 @@ public class SecureLdapIT extends IntegrationTestBase { } @Test + public void testLogout() { + + // Given: the client is connected to an unsecured NiFi Registry + // and the /access endpoint is queried using a JWT for the nifiadmin LDAP user + final Response response = client + .target(createURL("/access")) + .request() + .header("Authorization", "Bearer " + adminAuthToken) + .get(Response.class); + + // and the server returns a 200 OK with the expected current user + assertEquals(200, response.getStatus()); + + // When: the /access/logout endpoint with the JWT for the nifiadmin logs out the user + final Response logout_response = client + .target(createURL("/access/logout")) + .request() + .header("Authorization", "Bearer " + adminAuthToken) + .delete(Response.class); + + assertEquals(200, logout_response.getStatus()); + + // Then: the /access endpoint is queried using the logged out JWT + final Response retryResponse = client + .target(createURL("/access")) + .request() + .header("Authorization", "Bearer " + adminAuthToken) + .get(Response.class); + + // and the server returns a 401 Unauthorized as the user is now logged out + assertEquals(401, retryResponse.getStatus()); + String retryJson = retryResponse.readEntity(String.class); + assertEquals("Unable to validate the access token. Contact the system administrator.\n", retryJson); + + // Reset: We successfully logged out our user. Run setup to fix up the user, so the @After code can run to re-establish authorizations. + setup(); + } + + @Test + public void testLogoutWithJWTSignedByWrongKey() throws Exception { + + // Given: use the /access/logout endpoint with the JWT for the nifiadmin LDAP user to log out + final Response logoutResponse = client + .target(createURL("/access")) + .request() + .header("Authorization", "Bearer " + SIGNED_BY_WRONG_KEY) + .delete(Response.class); + + assertEquals(401, logoutResponse.getStatus()); + String responseMessage = logoutResponse.readEntity(String.class); + assertEquals("Unable to validate the access token. Contact the system administrator.\n", responseMessage); + } + + @Test public void testUsers() throws Exception { // Given: the client and server have been configured correctly for LDAP authentication diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js index 0ad0434..1bd5e28 100644 --- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js +++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js @@ -71,9 +71,24 @@ NfRegistry.prototype = { * Invalidate old tokens and route to login page */ logout: function () { + /** + $.ajax({ + type: 'DELETE', + url: '../nifi-registry-api/access/logout', + }).done(function () { + delete this.nfRegistryService.currentUser.identity; + delete this.nfRegistryService.currentUser.anonymous; + this.nfStorage.removeItem('jwt'); + this.router.navigateByUrl('login'); + }).fail(nfErrorHandler.handleAjaxError); + **/ + + + this.nfRegistryApi.deleteToLogout().subscribe(function () { + + }); delete this.nfRegistryService.currentUser.identity; delete this.nfRegistryService.currentUser.anonymous; - this.nfStorage.removeItem('jwt'); this.router.navigateByUrl('login'); }, diff --git a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js index 676824e..29d9b54 100644 --- a/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js +++ b/nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js @@ -726,6 +726,37 @@ NfRegistryApi.prototype = { }, /** + * Logout a user. + * + * @returns {*} + */ + deleteToLogout: function () { + var self = this; + var options = { + headers: headers, + withCredentials: true, + responseType: 'text' + }; + + return this.http.delete('../nifi-registry-api/access/logout', options).pipe( + map(function (response) { + // remove the token from local storage + self.nfStorage.removeItem('jwt'); + return response; + }), + catchError(function (error) { + self.dialogService.openConfirm({ + title: 'Error', + message: 'Please contact your System Administrator.', + acceptButton: 'Ok', + acceptButtonColor: 'fds-warn' + }); + return of(''); + }) + ); + }, + + /** * Kerberos ticket exchange. * * @returns {*}
