Skip to content
This repository was archived by the owner on Jul 22, 2021. It is now read-only.

Commit 32f9352

Browse files
committed
NIFIREG-361 - Improved handling of the /access/logout endpoint.
1 parent c30245f commit 32f9352

6 files changed

Lines changed: 168 additions & 1 deletion

File tree

nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/AccessResource.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@
4848
import org.springframework.stereotype.Component;
4949

5050
import javax.servlet.http.HttpServletRequest;
51+
import javax.servlet.http.HttpServletResponse;
5152
import javax.ws.rs.Consumes;
53+
import javax.ws.rs.DELETE;
5254
import javax.ws.rs.GET;
5355
import javax.ws.rs.POST;
5456
import javax.ws.rs.Path;
@@ -258,6 +260,42 @@ public Response createAccessTokenUsingBasicAuthCredentials(@Context HttpServletR
258260
return generateCreatedResponse(uri, token).build();
259261
}
260262

263+
@DELETE
264+
@Consumes(MediaType.WILDCARD)
265+
@Produces(MediaType.WILDCARD)
266+
@Path("/logout")
267+
@ApiOperation(
268+
value = "Performs a logout for other providers that have been issued a JWT.",
269+
notes = NON_GUARANTEED_ENDPOINT
270+
)
271+
@ApiResponses(
272+
value = {
273+
@ApiResponse(code = 200, message = "User was logged out successfully."),
274+
@ApiResponse(code = 401, message = "Authentication token provided was empty or not in the correct JWT format."),
275+
@ApiResponse(code = 500, message = "Client failed to log out."),
276+
}
277+
)
278+
public Response logOut(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) {
279+
if (!httpServletRequest.isSecure()) {
280+
throw new IllegalStateException("User authentication/authorization is only supported when running over HTTPS.");
281+
}
282+
283+
String userIdentity = NiFiUserUtils.getNiFiUserIdentity();
284+
285+
if(userIdentity != null && !userIdentity.isEmpty()) {
286+
try {
287+
logger.info("Logging out user " + userIdentity);
288+
jwtService.logOut(userIdentity);
289+
return generateOkResponse().build();
290+
} catch (final JwtException e) {
291+
logger.error("Logout of user " + userIdentity + " failed due to: " + e.getMessage());
292+
return Response.serverError().build();
293+
}
294+
} else {
295+
return Response.status(401, "Authentication token provided was empty or not in the correct JWT format.").build();
296+
}
297+
}
298+
261299
@POST
262300
@Consumes(MediaType.WILDCARD)
263301
@Produces(MediaType.TEXT_PLAIN)

nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ApplicationResource.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,15 @@ protected Response.ResponseBuilder generateOkResponse(final Object entity) {
170170
return noCache(response);
171171
}
172172

173+
/**
174+
* Generates an Ok response with no content.
175+
*
176+
* @return an Ok response with no content
177+
*/
178+
protected Response.ResponseBuilder generateOkResponse() {
179+
return noCache(Response.ok());
180+
}
181+
173182
/**
174183
* Generates a 201 Created response with the specified content.
175184
*

nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ public String generateSignedToken(String identity, String preferredUsername, Str
170170

171171
}
172172

173+
public void logOut(String userIdentity) {
174+
if (userIdentity == null || userIdentity.isEmpty()) {
175+
throw new JwtException("Log out failed: The user identity was not present in the request token to log out user.");
176+
}
177+
178+
try {
179+
keyService.deleteKey(userIdentity);
180+
logger.info("Deleted token from database.");
181+
} catch (Exception e) {
182+
logger.error("Unable to log out user: " + userIdentity + ". Failed to remove their token from database.");
183+
throw e;
184+
}
185+
}
186+
173187
private static long validateTokenExpiration(long proposedTokenExpiration, String identity) {
174188
final long maxExpiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS);
175189
final long minExpiration = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);

nifi-registry-core/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ public class SecureLdapIT extends IntegrationTestBase {
9191

9292
private static final String tokenLoginPath = "access/token/login";
9393
private static final String tokenIdentityProviderPath = "access/token/identity-provider";
94+
// A JWT signed by a key of 'secret'
95+
private static final String SIGNED_BY_WRONG_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
96+
".eyJzdWIiOiJuaWZpYWRtaW4iLCJpc3MiOiJMZGFwSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6IkxkYXB" +
97+
"JZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibmlmaWFkbWluIiwia2lkIjoiNDd" +
98+
"lMjA1NzctY2I3Yi00M2MzLWFhOGYtZjI0ZDcyODQ3MDEwIiwiaWF0IjoxNTgxNTI5NTA1LCJleHAiOjE" +
99+
"1ODE1NzI3MDV9.vvMpwLJt1w_6Id_tlS1knxTkJ2gv7_j5ySG6PmNjF0s";
94100

95101
@TestConfiguration
96102
@Profile("ITSecureLdap")
@@ -296,6 +302,60 @@ public void testGetCurrentUser() throws Exception {
296302

297303
}
298304

305+
@Test
306+
public void testLogout() {
307+
308+
// Given: the client is connected to an unsecured NiFi Registry
309+
// and the /access endpoint is queried using a JWT for the nifiadmin LDAP user
310+
final Response response = client
311+
.target(createURL("/access"))
312+
.request()
313+
.header("Authorization", "Bearer " + adminAuthToken)
314+
.get(Response.class);
315+
316+
// and the server returns a 200 OK with the expected current user
317+
assertEquals(200, response.getStatus());
318+
319+
// When: the /access/logout endpoint with the JWT for the nifiadmin logs out the user
320+
final Response logout_response = client
321+
.target(createURL("/access/logout"))
322+
.request()
323+
.header("Authorization", "Bearer " + adminAuthToken)
324+
.delete(Response.class);
325+
326+
assertEquals(200, logout_response.getStatus());
327+
328+
// Then: the /access endpoint is queried using the logged out JWT
329+
final Response retryResponse = client
330+
.target(createURL("/access"))
331+
.request()
332+
.header("Authorization", "Bearer " + adminAuthToken)
333+
.get(Response.class);
334+
335+
// and the server returns a 401 Unauthorized as the user is now logged out
336+
assertEquals(401, retryResponse.getStatus());
337+
String retryJson = retryResponse.readEntity(String.class);
338+
assertEquals("Unable to validate the access token. Contact the system administrator.\n", retryJson);
339+
340+
// Reset: We successfully logged out our user. Run setup to fix up the user, so the @After code can run to re-establish authorizations.
341+
setup();
342+
}
343+
344+
@Test
345+
public void testLogoutWithJWTSignedByWrongKey() throws Exception {
346+
347+
// Given: use the /access/logout endpoint with the JWT for the nifiadmin LDAP user to log out
348+
final Response logoutResponse = client
349+
.target(createURL("/access"))
350+
.request()
351+
.header("Authorization", "Bearer " + SIGNED_BY_WRONG_KEY)
352+
.delete(Response.class);
353+
354+
assertEquals(401, logoutResponse.getStatus());
355+
String responseMessage = logoutResponse.readEntity(String.class);
356+
assertEquals("Unable to validate the access token. Contact the system administrator.\n", responseMessage);
357+
}
358+
299359
@Test
300360
public void testUsers() throws Exception {
301361

nifi-registry-core/nifi-registry-web-ui/src/main/webapp/nf-registry.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,24 @@ NfRegistry.prototype = {
7171
* Invalidate old tokens and route to login page
7272
*/
7373
logout: function () {
74+
/**
75+
$.ajax({
76+
type: 'DELETE',
77+
url: '../nifi-registry-api/access/logout',
78+
}).done(function () {
79+
delete this.nfRegistryService.currentUser.identity;
80+
delete this.nfRegistryService.currentUser.anonymous;
81+
this.nfStorage.removeItem('jwt');
82+
this.router.navigateByUrl('login');
83+
}).fail(nfErrorHandler.handleAjaxError);
84+
**/
85+
86+
87+
this.nfRegistryApi.deleteToLogout().subscribe(function () {
88+
89+
});
7490
delete this.nfRegistryService.currentUser.identity;
7591
delete this.nfRegistryService.currentUser.anonymous;
76-
this.nfStorage.removeItem('jwt');
7792
this.router.navigateByUrl('login');
7893
},
7994

nifi-registry-core/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,37 @@ NfRegistryApi.prototype = {
725725
);
726726
},
727727

728+
/**
729+
* Logout a user.
730+
*
731+
* @returns {*}
732+
*/
733+
deleteToLogout: function () {
734+
var self = this;
735+
var options = {
736+
headers: headers,
737+
withCredentials: true,
738+
responseType: 'text'
739+
};
740+
741+
return this.http.delete('../nifi-registry-api/access/logout', options).pipe(
742+
map(function (response) {
743+
// remove the token from local storage
744+
self.nfStorage.removeItem('jwt');
745+
return response;
746+
}),
747+
catchError(function (error) {
748+
self.dialogService.openConfirm({
749+
title: 'Error',
750+
message: 'Please contact your System Administrator.',
751+
acceptButton: 'Ok',
752+
acceptButtonColor: 'fds-warn'
753+
});
754+
return of('');
755+
})
756+
);
757+
},
758+
728759
/**
729760
* Kerberos ticket exchange.
730761
*

0 commit comments

Comments
 (0)