NIFIREG-361 - Improved handling of the /access/logout endpoint.
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.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 @@
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 @@
}
/**
+ * 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 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 @@
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 @@
}
@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 @@
* 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 @@
},
/**
+ * 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 {*}