This is an automated email from the ASF dual-hosted git repository.

alopresto pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/master by this push:
     new fe68d43  NIFI-6280 - Broke out the matching for /access/knox/** and 
/access/oidc/** to allow the Jetty security filters to be applied in the 
/access/oidc/logout and /access/knox/logout cases. NIFI-6280 - Updated 
terminology in JwtAuthenticationFilter to authentication instead of 
authorization. Added stricter token parsing using an explicit regex pattern. 
Added tests. NIFI-6280 - Updated terminology from Authorization to 
Authentication. NIFI-6280 - Updated the access logout method [...]
fe68d43 is described below

commit fe68d43e1dfd00807ccbc43fd43bff73d49de26c
Author: thenatog <[email protected]>
AuthorDate: Thu May 16 13:19:47 2019 -0400

    NIFI-6280 - Broke out the matching for /access/knox/** and /access/oidc/** 
to allow the Jetty security filters to be applied in the /access/oidc/logout 
and /access/knox/logout cases.
    NIFI-6280 - Updated terminology in JwtAuthenticationFilter to 
authentication instead of authorization. Added stricter token parsing using an 
explicit regex pattern. Added tests.
    NIFI-6280 - Updated terminology from Authorization to Authentication.
    NIFI-6280 - Updated the access logout method to use getNiFiUserIdentity(). 
Updated javascript logout method to handle errors.
    NIFI-6280 - Fixing checkstyle issues.
    NIFI-6280 - Added some javadoc comments and logging. Renamed some variables 
for clarity. Fixed handling of exception when JWT does not match expected 
format.
    NIFI-6280 - Cleaned up checkstyle, increased log severity level for logout 
action, and cleaned up Groovy syntax in test.
    
    This closes #3482.
    
    Signed-off-by: Andy LoPresto <[email protected]>
---
 .../apache/nifi/record/path/TestRecordPath.java    |  27 ++--
 .../src/main/resources/conf/logback.xml            |   3 +
 .../nifi/web/NiFiWebApiSecurityConfiguration.java  |   7 +-
 .../org/apache/nifi/web/api/AccessResource.java    |  69 ++++----
 .../accesscontrol/ITAccessTokenEndpoint.java       |   1 -
 .../web/security/NiFiAuthenticationFilter.java     |  43 +++--
 .../nifi/web/security/ProxiedEntitiesUtils.java    |  18 ++-
 .../web/security/jwt/JwtAuthenticationFilter.java  |  28 +++-
 .../apache/nifi/web/security/jwt/JwtService.java   |  13 +-
 .../jwt/JwtAuthenticationFilterTest.groovy         | 180 +++++++++++++++++++++
 .../nifi/web/security/jwt/JwtServiceTest.java      |   3 +-
 .../controllers/nf-ng-canvas-header-controller.js  |  10 +-
 12 files changed, 317 insertions(+), 85 deletions(-)

diff --git 
a/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java
 
b/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java
index 848886f..8714b33 100644
--- 
a/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java
+++ 
b/nifi-commons/nifi-record-path/src/test/java/org/apache/nifi/record/path/TestRecordPath.java
@@ -17,16 +17,9 @@
 
 package org.apache.nifi.record.path;
 
-import org.apache.nifi.record.path.exception.RecordPathException;
-import org.apache.nifi.serialization.SimpleRecordSchema;
-import org.apache.nifi.serialization.record.DataType;
-import org.apache.nifi.serialization.record.MapRecord;
-import org.apache.nifi.serialization.record.Record;
-import org.apache.nifi.serialization.record.RecordField;
-import org.apache.nifi.serialization.record.RecordFieldType;
-import org.apache.nifi.serialization.record.RecordSchema;
-import org.apache.nifi.serialization.record.util.DataTypeUtils;
-import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import java.nio.charset.IllegalCharsetNameException;
 import java.nio.charset.StandardCharsets;
@@ -42,10 +35,16 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import org.apache.nifi.record.path.exception.RecordPathException;
+import org.apache.nifi.serialization.SimpleRecordSchema;
+import org.apache.nifi.serialization.record.DataType;
+import org.apache.nifi.serialization.record.MapRecord;
+import org.apache.nifi.serialization.record.Record;
+import org.apache.nifi.serialization.record.RecordField;
+import org.apache.nifi.serialization.record.RecordFieldType;
+import org.apache.nifi.serialization.record.RecordSchema;
+import org.apache.nifi.serialization.record.util.DataTypeUtils;
+import org.junit.Test;
 
 public class TestRecordPath {
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/logback.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/logback.xml
index cf3af88..a16a49e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/logback.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/logback.xml
@@ -140,6 +140,9 @@
     <logger name="org.apache.nifi.web.filter.RequestLogger" level="INFO" 
additivity="false">
         <appender-ref ref="USER_FILE"/>
     </logger>
+    <logger name="org.apache.nifi.web.api.AccessResource" level="INFO" 
additivity="false">
+        <appender-ref ref="USER_FILE"/>
+    </logger>
 
 
     <!--
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
index d7fd89b..647e6c8 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
@@ -50,7 +50,8 @@ import 
org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import java.util.Arrays;
 
 /**
- * NiFi Web Api Spring security
+ * NiFi Web Api Spring security. Applies the various NiFiAuthenticationFilter 
servlet filters which will extract authentication
+ * credentials from API requests.
  */
 @Configuration
 @EnableWebSecurity
@@ -88,7 +89,9 @@ public class NiFiWebApiSecurityConfiguration extends 
WebSecurityConfigurerAdapte
         // the /access/download-token and /access/ui-extension-token endpoints
         webSecurity
                 .ignoring()
-                    .antMatchers("/access", "/access/config", "/access/token", 
"/access/kerberos", "/access/oidc/**", "/access/knox/**");
+                    .antMatchers("/access", "/access/config", "/access/token", 
"/access/kerberos",
+                            "/access/oidc/exchange", "/access/oidc/callback", 
"/access/oidc/request",
+                            "/access/knox/callback", "/access/knox/request");
     }
 
     @Override
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 8796dce..8b632f8 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
@@ -29,6 +29,25 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
+import java.net.URI;
+import java.security.cert.X509Certificate;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletContext;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.admin.service.AdministrationException;
 import org.apache.nifi.authentication.AuthenticationResponse;
@@ -69,25 +88,6 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import 
org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
 
-import javax.servlet.ServletContext;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.FormParam;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
-import java.net.URI;
-import java.security.cert.X509Certificate;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-
 /**
  * RESTful endpoint for managing access.
  */
@@ -103,6 +103,7 @@ 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 X509CertificateExtractor certificateExtractor;
     private X509AuthenticationProvider x509AuthenticationProvider;
     private X509PrincipalExtractor principalExtractor;
@@ -344,9 +345,6 @@ public class AccessResource extends ApplicationResource {
                     .build();
             httpServletResponse.sendRedirect(logoutUri.toString());
         }
-
-        String authorizationHeader = 
httpServletRequest.getHeader(JwtAuthenticationFilter.AUTHORIZATION);
-        jwtService.logOut(authorizationHeader);
     }
 
     @GET
@@ -375,7 +373,7 @@ public class AccessResource extends ApplicationResource {
 
         // build the authorization uri
         final URI authorizationUri = 
UriBuilder.fromUri(knoxService.getKnoxUrl())
-                .queryParam("originalUrl", originalUri.toString())
+                .queryParam("originalUrl", originalUri)
                 .build();
 
         // generate the response
@@ -416,7 +414,7 @@ public class AccessResource extends ApplicationResource {
     )
     public void knoxLogout(@Context HttpServletRequest httpServletRequest, 
@Context HttpServletResponse httpServletResponse) throws Exception {
         String redirectPath = generateResourceUri("..", "nifi", "login");
-        httpServletResponse.sendRedirect(redirectPath.toString());
+        httpServletResponse.sendRedirect(redirectPath);
     }
 
     /**
@@ -747,7 +745,7 @@ public class AccessResource extends ApplicationResource {
         return generateCreatedResponse(uri, token).build();
     }
 
-    @GET
+    @DELETE
     @Consumes(MediaType.WILDCARD)
     @Produces(MediaType.WILDCARD)
     @Path("/logout")
@@ -758,6 +756,7 @@ public class AccessResource extends ApplicationResource {
     @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."),
             }
     )
@@ -766,13 +765,19 @@ public class AccessResource extends ApplicationResource {
             throw new IllegalStateException("User authentication/authorization 
is only supported when running over HTTPS.");
         }
 
-        String authorizationHeader = 
httpServletRequest.getHeader(JwtAuthenticationFilter.AUTHORIZATION);
-        final String token = 
StringUtils.substringAfterLast(authorizationHeader, " ");
-        try {
-            jwtService.logOut(token);
-            return generateOkResponse().build();
-        } catch (final JwtException e) {
-            return Response.serverError().build();
+        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();
         }
     }
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
index 406619f..0dc359f 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java
@@ -410,7 +410,6 @@ public class ITAccessTokenEndpoint {
         String logoutUrl = BASE_URL + "/access/logout";
 
         Response response = TOKEN_USER.testCreateToken(accessTokenUrl, user, 
password);
-        Response responseA = TOKEN_USER.testCreateToken(accessTokenUrl, 
"jack", password);
 
         // ensure the request is successful
         Assert.assertEquals(201, response.getStatus());
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
index 030b19e..f07e43b 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
@@ -38,7 +38,8 @@ import java.io.IOException;
 import java.io.PrintWriter;
 
 /**
- *
+ * The NiFiAuthenticationFilter abstract class defines the base methods for 
NiFi's various existing and future
+ * authentication mechanisms. The subclassed filters are applied in 
NiFiWebApiSecurityConfiguration.
  */
 public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
 
@@ -74,16 +75,16 @@ public abstract class NiFiAuthenticationFilter extends 
GenericFilterBean {
                 log.info(String.format("Attempting request for (%s) %s %s 
(source ip: %s)", authenticationRequest.toString(), request.getMethod(),
                         request.getRequestURL().toString(), 
request.getRemoteAddr()));
 
-                // attempt to authorize the user
+                // attempt to authenticate the user
                 final Authentication authenticated = 
authenticationManager.authenticate(authenticationRequest);
-                successfulAuthorization(request, response, authenticated);
+                successfulAuthentication(request, response, authenticated);
             }
         } catch (final AuthenticationException ae) {
             // invalid authentication - always error out
-            unsuccessfulAuthorization(request, response, ae);
+            unsuccessfulAuthentication(request, response, ae);
             return;
         } catch (final Exception e) {
-            log.error(String.format("Unable to authorize: %s", 
e.getMessage()), e);
+            log.error(String.format("Unable to authenticate: %s", 
e.getMessage()), e);
 
             // set the response status
             response.setContentType("text/plain");
@@ -91,7 +92,7 @@ public abstract class NiFiAuthenticationFilter extends 
GenericFilterBean {
 
             // other exception - always error out
             PrintWriter out = response.getWriter();
-            out.println(String.format("Failed to authorize request. Please 
contact the system administrator."));
+            out.println(String.format("Failed to authenticate request. Please 
contact the system administrator."));
             return;
         }
 
@@ -107,16 +108,32 @@ public abstract class NiFiAuthenticationFilter extends 
GenericFilterBean {
      */
     public abstract Authentication attemptAuthentication(HttpServletRequest 
request);
 
-    protected void successfulAuthorization(HttpServletRequest request, 
HttpServletResponse response, Authentication authResult) {
+    /**
+     * If authentication was successful, apply the successful authentication 
result to the security context and add
+     * proxy headers to the response if the request was made via a proxy.
+     *
+     * @param request The original client request that was successfully 
authenticated.
+     * @param response Servlet response to the client containing the 
successful authentication details.
+     * @param authResult The Authentication 'token'/object created by one of 
the various NiFiAuthenticationFilter subclasses.
+     */
+    protected void successfulAuthentication(HttpServletRequest request, 
HttpServletResponse response, Authentication authResult) {
         log.info("Authentication success for " + authResult);
 
         SecurityContextHolder.getContext().setAuthentication(authResult);
-        ProxiedEntitiesUtils.successfulAuthorization(request, response, 
authResult);
+        ProxiedEntitiesUtils.successfulAuthentication(request, response);
     }
 
-    protected void unsuccessfulAuthorization(HttpServletRequest request, 
HttpServletResponse response, AuthenticationException ae) throws IOException {
+    /**
+     * If authentication was unsuccessful, update the response with the 
appropriate status and give the reason for why
+     * the user was not able to be authenticated. Update the response with 
proxy headers if the request was made via a proxy.
+     *
+     * @param request The original client request that failed to be 
authenticated.
+     * @param response Servlet response to the client containing the 
unsuccessful authentication attempt details.
+     * @param ae The related exception thrown and explanation for the 
unsuccessful authentication attempt.
+     */
+    protected void unsuccessfulAuthentication(HttpServletRequest request, 
HttpServletResponse response, AuthenticationException ae) throws IOException {
         // populate the response
-        ProxiedEntitiesUtils.unsuccessfulAuthorization(request, response, ae);
+        ProxiedEntitiesUtils.unsuccessfulAuthentication(request, response, ae);
 
         // set the response status
         response.setContentType("text/plain");
@@ -132,11 +149,11 @@ public abstract class NiFiAuthenticationFilter extends 
GenericFilterBean {
             response.setStatus(HttpServletResponse.SC_FORBIDDEN);
             out.println(ae.getMessage());
         } else if (ae instanceof AuthenticationServiceException) {
-            log.error(String.format("Unable to authorize: %s", 
ae.getMessage()), ae);
+            log.error(String.format("Unable to authenticate: %s", 
ae.getMessage()), ae);
             response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-            out.println(String.format("Unable to authorize: %s", 
ae.getMessage()));
+            out.println(String.format("Unable to authenticate: %s", 
ae.getMessage()));
         } else {
-            log.error(String.format("Unable to authorize: %s", 
ae.getMessage()), ae);
+            log.error(String.format("Unable to authenticate: %s", 
ae.getMessage()), ae);
             response.setStatus(HttpServletResponse.SC_FORBIDDEN);
             out.println("Access is denied.");
         }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java
index 09f45bf..2c3c0ea 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java
@@ -27,7 +27,6 @@ import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 
 /**
@@ -148,13 +147,26 @@ public class ProxiedEntitiesUtils {
         return StringUtils.join(proxyChain, "");
     }
 
-    public static void successfulAuthorization(HttpServletRequest request, 
HttpServletResponse response, Authentication authResult) {
+    /**
+     * If a successfully authenticated request was made via a proxy, relevant 
proxy headers will be added to the response.
+     *
+     * @param request The proxied client request that was successfully 
authenticated.
+     * @param response A servlet response to the client containing the 
successful authentication details.
+     */
+    public static void successfulAuthentication(HttpServletRequest request, 
HttpServletResponse response) {
         if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) {
             response.setHeader(PROXY_ENTITIES_ACCEPTED, 
Boolean.TRUE.toString());
         }
     }
 
-    public static void unsuccessfulAuthorization(HttpServletRequest request, 
HttpServletResponse response, AuthenticationException failed) {
+    /**
+     * If an unauthenticated request was made via a proxy, add proxy headers 
to explain why authentication failed.
+     *
+     * @param request The original client request that failed to be 
authenticated.
+     * @param response Servlet response to the client containing the 
unsuccessful authentication attempt details.
+     * @param failed The related exception thrown and explanation for the 
unsuccessful authentication attempt.
+     */
+    public static void unsuccessfulAuthentication(HttpServletRequest request, 
HttpServletResponse response, AuthenticationException failed) {
         if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) {
             response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage());
         }
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/JwtAuthenticationFilter.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
index b237041..0d21a8a 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java
@@ -16,13 +16,15 @@
  */
 package org.apache.nifi.web.security.jwt;
 
-import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
 import org.apache.nifi.web.security.NiFiAuthenticationFilter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.core.Authentication;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  */
@@ -30,8 +32,9 @@ public class JwtAuthenticationFilter extends 
NiFiAuthenticationFilter {
 
     private static final Logger logger = 
LoggerFactory.getLogger(JwtAuthenticationFilter.class);
 
+    // The Authorization header contains authentication credentials
     public static final String AUTHORIZATION = "Authorization";
-    public static final String BEARER = "Bearer ";
+    private static final Pattern tokenPattern = Pattern.compile("^Bearer 
(\\S*\\.\\S*\\.\\S*)$");
 
     @Override
     public Authentication attemptAuthentication(final HttpServletRequest 
request) {
@@ -43,15 +46,30 @@ public class JwtAuthenticationFilter extends 
NiFiAuthenticationFilter {
         // TODO: Refactor request header extraction logic to shared utility as 
it is duplicated in AccessResource
 
         // get the principal out of the user token
-        final String authorization = request.getHeader(AUTHORIZATION);
+        final String authorizationHeader = request.getHeader(AUTHORIZATION);
 
         // if there is no authorization header, we don't know the user
-        if (authorization == null || !StringUtils.startsWith(authorization, 
BEARER)) {
+        if (authorizationHeader == null || 
!validJwtFormat(authorizationHeader)) {
             return null;
         } else {
             // Extract the Base64 encoded token from the Authorization header
-            final String token = StringUtils.substringAfterLast(authorization, 
" ");
+            final String token = getTokenFromHeader(authorizationHeader);
             return new JwtAuthenticationRequestToken(token, 
request.getRemoteAddr());
         }
     }
+
+    private boolean validJwtFormat(String authenticationHeader) {
+        Matcher matcher = tokenPattern.matcher(authenticationHeader);
+        return matcher.matches();
+    }
+
+    private 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-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
index 63392a8..ecbfc67 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java
@@ -32,7 +32,6 @@ import java.util.Calendar;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.admin.service.AdministrationException;
 import org.apache.nifi.admin.service.KeyService;
-import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.key.Key;
 import org.apache.nifi.web.security.token.LoginAuthenticationToken;
 import org.slf4j.LoggerFactory;
@@ -170,17 +169,15 @@ public class JwtService {
         }
     }
 
-    public void logOut(String authorizationHeader) {
-        if (authorizationHeader == null || authorizationHeader.isEmpty()) {
-            throw new JwtException("Log out failed: The required Authorization 
header was not present in the request to log out user.");
+    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.");
         }
 
-        String identity = NiFiUserUtils.getNiFiUserIdentity();
-
         try {
-            keyService.deleteKey(identity);
+            keyService.deleteKey(userIdentity);
         } catch (Exception e) {
-            logger.error("Unable to log out user: " + identity + ". Failed to 
remove their token from database.");
+            logger.error("Unable to log out user: " + userIdentity + ". Failed 
to remove their token from database.");
             throw e;
         }
     }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/jwt/JwtAuthenticationFilterTest.groovy
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/jwt/JwtAuthenticationFilterTest.groovy
new file mode 100644
index 0000000..dcabb0f
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/jwt/JwtAuthenticationFilterTest.groovy
@@ -0,0 +1,180 @@
+/*
+ * 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
+
+import groovy.json.JsonOutput
+import org.apache.nifi.web.security.InvalidAuthenticationException
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ExpectedException
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4.class)
+class JwtAuthenticationFilterTest extends GroovyTestCase {
+
+    public static String jwtString
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none()
+
+    @BeforeClass
+    static void setUpOnce() throws Exception {
+        final String ALG_HEADER = "{\"alg\":\"HS256\"}"
+        final int EXPIRATION_SECONDS = 500
+        Calendar now = Calendar.getInstance()
+        final long currentTime = (long) (now.getTimeInMillis() / 1000.0)
+        final long TOKEN_ISSUED_AT = currentTime
+        final long TOKEN_EXPIRATION_SECONDS = currentTime + EXPIRATION_SECONDS
+
+        // Generate a token that we will add a valid signature from a 
different token
+        // Always use LinkedHashMap to enforce order of the keys because the 
signature depends on order
+        final String EXPECTED_PAYLOAD =
+                JsonOutput.toJson(
+                    sub:'unknownuser',
+                    iss:'MockIdentityProvider',
+                    aud:'MockIdentityProvider',
+                    preferred_username:'unknownuser',
+                    kid:1,
+                    exp:TOKEN_EXPIRATION_SECONDS,
+                    iat:TOKEN_ISSUED_AT)
+
+        // Set up our JWT string with a test token
+        jwtString = JwtServiceTest.generateHS256Token(ALG_HEADER, 
EXPECTED_PAYLOAD, true, true)
+
+    }
+
+    @AfterClass
+    static void tearDownOnce() throws Exception {
+
+    }
+
+    @Before
+    void setUp() throws Exception {
+
+    }
+
+    @After
+    void tearDown() throws Exception {
+
+    }
+
+    @Test
+    void testValidAuthenticationHeaderString() {
+        // Arrange
+        String authenticationHeader = "Bearer " + jwtString
+
+        // Act
+        boolean isValidHeader = new 
JwtAuthenticationFilter().validJwtFormat(authenticationHeader)
+
+        // Assert
+        assertTrue(isValidHeader)
+    }
+
+    @Test
+    void testMissingBearer() {
+        // Arrange
+
+        // Act
+        boolean isValidHeader = new 
JwtAuthenticationFilter().validJwtFormat(jwtString)
+
+        // Assert
+        assertFalse(isValidHeader)
+    }
+
+    @Test
+    void testExtraCharactersAtBeginningOfToken() {
+        // Arrange
+        String authenticationHeader = "xBearer " + jwtString
+
+        // Act
+        boolean isValidToken = new 
JwtAuthenticationFilter().validJwtFormat(authenticationHeader)
+
+        // Assert
+        assertFalse(isValidToken)
+    }
+
+    @Test
+    void testBadTokenFormat() {
+        // Arrange
+        String[] tokenStrings = jwtString.split("\\.")
+        String badToken = "Bearer " + tokenStrings[1] + tokenStrings[2]
+
+        // Act
+        boolean isValidToken = new 
JwtAuthenticationFilter().validJwtFormat(badToken)
+
+        // Assert
+        assertFalse(isValidToken)
+    }
+
+    @Test
+    void testMultipleTokenInvalid() {
+        // Arrange
+        String authenticationHeader = "Bearer " + jwtString
+        authenticationHeader = authenticationHeader + " " + 
authenticationHeader
+
+        // Act
+        boolean isValidToken = new 
JwtAuthenticationFilter().validJwtFormat(authenticationHeader)
+
+        // Assert
+        assertFalse(isValidToken)
+    }
+
+    @Test
+    void testExtractToken() {
+        // Arrange
+        String authenticationHeader = "Bearer " + jwtString
+
+        // Act
+        String extractedToken = new 
JwtAuthenticationFilter().getTokenFromHeader(authenticationHeader)
+
+        // Assert
+        assertEquals(jwtString, extractedToken)
+    }
+
+    @Test
+    void testMultipleTokenDottedInvalid() {
+        // Arrange
+        String authenticationHeader = "Bearer " + jwtString
+        authenticationHeader = authenticationHeader + "." + 
authenticationHeader
+
+        // Act
+        boolean isValidToken = new 
JwtAuthenticationFilter().validJwtFormat(authenticationHeader)
+
+        // Assert
+        assertFalse(isValidToken)
+    }
+
+    @Test
+    void testMultipleTokenNotExtracted() {
+        // Arrange
+        expectedException.expect(InvalidAuthenticationException.class)
+        expectedException.expectMessage("JWT did not match expected pattern.")
+        String authenticationHeader = "Bearer " + jwtString
+        authenticationHeader = authenticationHeader + " " + 
authenticationHeader
+
+        // Act
+        String token = new 
JwtAuthenticationFilter().getTokenFromHeader(authenticationHeader)
+
+        // Assert
+        // Expect InvalidAuthenticationException
+    }
+}
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/JwtServiceTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
index 368851e..1a1f95d 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java
@@ -511,7 +511,7 @@ public class JwtServiceTest {
     public void testLogoutWhenAuthTokenIsEmptyShouldThrowError() throws 
Exception {
         // Arrange
         expectedException.expect(JwtException.class);
-        expectedException.expectMessage("Log out failed: The required 
Authorization header was not present in the request to log out user.");
+        expectedException.expectMessage("Log out failed: The user identity was 
not present in the request token to log out user.");
 
         // Act
         jwtService.logOut(null);
@@ -520,5 +520,4 @@ public class JwtServiceTest {
         // Should throw exception when authorization header is null
     }
 
-
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-header-controller.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-header-controller.js
index 2f2cea7..c271348 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-header-controller.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-header-controller.js
@@ -118,12 +118,12 @@
             this.logoutCtrl = {
                 logout: function () {
                     $.ajax({
-                        type: 'GET',
+                        type: 'DELETE',
                         url: '../nifi-api/access/logout',
-                        dataType: 'json'
-                    })
-                    nfStorage.removeItem("jwt");
-                    window.location = '../nifi/logout';
+                    }).done(function () {
+                        nfStorage.removeItem("jwt");
+                        window.location = '../nifi/logout';
+                    }).fail(nfErrorHandler.handleAjaxError);
                 }
             };
         }

Reply via email to