You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2015/11/03 17:10:56 UTC

nifi git commit: NIFI-655: - Adding a new endpoint to obtain the status of a user registration. - Updated the login page loading to ensure all possible states work.

Repository: nifi
Updated Branches:
  refs/heads/NIFI-655 7f9807f46 -> 71d84117e


NIFI-655:
- Adding a new endpoint to obtain the status of a user registration.
- Updated the login page loading to ensure all possible states work.

Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/71d84117
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/71d84117
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/71d84117

Branch: refs/heads/NIFI-655
Commit: 71d84117e4a3b67a90d9c83f2ff3f27bcdea6b95
Parents: 7f9807f
Author: Matt Gilman <ma...@gmail.com>
Authored: Tue Nov 3 11:10:32 2015 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Tue Nov 3 11:10:32 2015 -0500

----------------------------------------------------------------------
 .../web/NiFiWebApiSecurityConfiguration.java    |  15 +-
 .../apache/nifi/web/api/ControllerResource.java |   9 +-
 .../web/security/RegistrationStatusFilter.java  | 217 +++++++++++++++++++
 .../src/main/webapp/WEB-INF/pages/login.jsp     |   1 +
 .../WEB-INF/partials/canvas/canvas-header.jsp   |   3 +
 .../WEB-INF/partials/login/login-message.jsp    |  20 ++
 .../WEB-INF/partials/login/login-submission.jsp |   2 +-
 .../nifi-web-ui/src/main/webapp/css/header.css  |   4 +
 .../webapp/js/nf/canvas/nf-canvas-header.js     |   5 +
 .../src/main/webapp/js/nf/canvas/nf-canvas.js   |   3 +-
 .../src/main/webapp/js/nf/login/nf-login.js     | 128 ++++++++---
 .../src/main/webapp/js/nf/nf-common.js          |  10 +
 12 files changed, 382 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
----------------------------------------------------------------------
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 e30f812..d6feef0 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
@@ -23,6 +23,7 @@ import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.NiFiAuthenticationProvider;
 import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter;
 import org.apache.nifi.web.security.NiFiAuthenticationEntryPoint;
+import org.apache.nifi.web.security.RegistrationStatusFilter;
 import org.apache.nifi.web.security.form.LoginAuthenticationFilter;
 import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
 import org.apache.nifi.web.security.jwt.JwtService;
@@ -90,11 +91,14 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
             // login authentication for /token - exchanges for JWT for subsequent API usage
             http.addFilterBefore(buildLoginFilter("/token"), UsernamePasswordAuthenticationFilter.class);
 
-            // verify the configured login authenticator supports registration
+            // verify the configured login authenticator supports user login registration
             if (loginIdentityProvider.supportsRegistration()) {
                 http.addFilterBefore(buildRegistrationFilter("/registration"), UsernamePasswordAuthenticationFilter.class);
             }
         }
+        
+        // registration status - will check the status of a user's account registration (regardless if its based on login or not)
+        http.addFilterBefore(buildRegistrationStatusFilter("/registration/status"), UsernamePasswordAuthenticationFilter.class);
 
         // cluster authorized user
         http.addFilterBefore(buildNodeAuthorizedUserFilter(), AnonymousAuthenticationFilter.class);
@@ -134,6 +138,15 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
     private Filter buildRegistrationFilter(final String url) {
         return null;
     }
+    
+    private Filter buildRegistrationStatusFilter(final String url) {
+        final RegistrationStatusFilter registrationFilter = new RegistrationStatusFilter(url);
+        registrationFilter.setCertificateExtractor(certificateExtractor);
+        registrationFilter.setPrincipalExtractor(principalExtractor);
+        registrationFilter.setProperties(properties);
+        registrationFilter.setUserDetailsService(userDetailsService);
+        return registrationFilter;
+    }
 
     private NodeAuthorizedUserFilter buildNodeAuthorizedUserFilter() {
         return new NodeAuthorizedUserFilter(properties);

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
index 1a23626..78e7d94 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
@@ -929,7 +929,7 @@ public class ControllerResource extends ApplicationResource {
         IdentityEntity entity = new IdentityEntity();
         entity.setRevision(revision);
         entity.setUserId(user.getId());
-        entity.setIdentity(user.getDn());
+        entity.setIdentity(user.getUserName());
 
         // generate the response
         return clusterContext(generateOkResponse(entity)).build();
@@ -945,14 +945,17 @@ public class ControllerResource extends ApplicationResource {
     @Consumes(MediaType.WILDCARD)
     @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
     @Path("/authorities")
-    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN', 'ROLE_PROXY', 'ROLE_NIFI', 'ROLE_PROVENANCE')")
     @ApiOperation(
             value = "Retrieves the user details, including the authorities, about the user making the request",
             response = AuthorityEntity.class,
             authorizations = {
                 @Authorization(value = "Read Only", type = "ROLE_MONITOR"),
                 @Authorization(value = "Data Flow Manager", type = "ROLE_DFM"),
-                @Authorization(value = "Administrator", type = "ROLE_ADMIN")
+                @Authorization(value = "Administrator", type = "ROLE_ADMIN"),
+                @Authorization(value = "Proxy", type = "ROLE_PROXY"),
+                @Authorization(value = "NiFi", type = "ROLE_NIFI"),
+                @Authorization(value = "Provenance", type = "ROLE_PROVENANCE")
             }
     )
     @ApiResponses(

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/RegistrationStatusFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/RegistrationStatusFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/RegistrationStatusFilter.java
new file mode 100644
index 0000000..073de67
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/RegistrationStatusFilter.java
@@ -0,0 +1,217 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.nifi.authentication.LoginCredentials;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.StringUtils;
+import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
+import org.apache.nifi.web.security.x509.X509CertificateExtractor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AccountStatusException;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
+
+/**
+ * Exchanges a successful login with the configured provider for a ID token for accessing the API.
+ */
+public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFilter {
+
+    private static final Logger logger = LoggerFactory.getLogger(RegistrationStatusFilter.class);
+
+    private NiFiProperties properties;
+    private AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService;
+    private X509CertificateExtractor certificateExtractor;
+    private X509PrincipalExtractor principalExtractor;
+
+    public RegistrationStatusFilter(final String defaultFilterProcessesUrl) {
+        super(defaultFilterProcessesUrl);
+
+        // do not continue filter chain... simply exchaning authentication for token
+        setContinueChainBeforeSuccessfulAuthentication(false);
+    }
+
+    @Override
+    public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
+        // only suppport login when running securely
+        if (!request.isSecure()) {
+            return null;
+        }
+
+        // look for a certificate
+        final X509Certificate certificate = certificateExtractor.extractClientCertificate(request);
+        
+        // if no certificate, just check the credentials
+        if (certificate == null) {
+            final LoginCredentials credentials = getLoginCredentials(request);
+            
+            if (credentials == null) {
+                throw new BadCredentialsException("Unable to check registration status as no credentials were included with the request.");
+            }
+            
+            checkAuthorization(ProxiedEntitiesUtils.buildProxyChain(request, credentials.getUsername()));
+            return new RegistrationStatusAuthenticationToken(credentials);
+        } else {
+            // we have a certificate so let's use that
+            final String principal = extractPrincipal(certificate);
+            checkAuthorization(ProxiedEntitiesUtils.buildProxyChain(request, principal));
+
+            final LoginCredentials preAuthenticatedCredentials = new LoginCredentials(principal, null);
+            return new RegistrationStatusAuthenticationToken(preAuthenticatedCredentials);
+        }
+    }
+
+    /**
+     * Checks the status of the proxy.
+     *
+     * @param proxyChain the proxy chain
+     * @throws AuthenticationException if the proxy chain is not authorized
+     */
+    private void checkAuthorization(final List<String> proxyChain) throws AuthenticationException {
+        userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain));
+    }
+
+    private String extractPrincipal(final X509Certificate certificate) {
+        // extract the principal
+        final Object certificatePrincipal = principalExtractor.extractPrincipal(certificate);
+        return ProxiedEntitiesUtils.formatProxyDn(certificatePrincipal.toString());
+    }
+
+    private LoginCredentials getLoginCredentials(HttpServletRequest request) {
+        final String username = request.getParameter("username");
+        final String password = request.getParameter("password");
+
+        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
+            return null;
+        } else {
+            return new LoginCredentials(username, password);
+        }
+    }
+
+    @Override
+    protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authentication)
+            throws IOException, ServletException {
+
+        // mark as successful
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setContentType("text/plain");
+        response.setContentLength(0);
+    }
+
+    @Override
+    protected void unsuccessfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException ae) throws IOException, ServletException {
+        // set the response status
+        response.setContentType("text/plain");
+
+        // write the response message
+        PrintWriter out = response.getWriter();
+
+        // use the type of authentication exception to determine the response code
+        if (ae instanceof UsernameNotFoundException) {
+            if (properties.getSupportNewAccountRequests()) {
+                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+                out.println("Not authorized.");
+            } else {
+                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+                out.println("Access is denied.");
+            }
+        } else if (ae instanceof AccountStatusException) {
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            out.println(ae.getMessage());
+        } else if (ae instanceof UntrustedProxyException) {
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            out.println(ae.getMessage());
+        } else if (ae instanceof AuthenticationServiceException) {
+            logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae);
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            out.println(String.format("Unable to authorize: %s", ae.getMessage()));
+        } else {
+            logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae);
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            out.println("Access is denied.");
+        }
+
+        // log the failure
+        logger.info(String.format("Rejecting access to web api: %s", ae.getMessage()));
+
+        // optionally log the stack trace
+        if (logger.isDebugEnabled()) {
+            logger.debug(StringUtils.EMPTY, ae);
+        }
+    }
+
+    /**
+     * This is an Authentication Token for logging in. Once a user is authenticated, they can be issues an ID token.
+     */
+    public static class RegistrationStatusAuthenticationToken extends AbstractAuthenticationToken {
+
+        final LoginCredentials credentials;
+
+        public RegistrationStatusAuthenticationToken(final LoginCredentials credentials) {
+            super(null);
+            setAuthenticated(true);
+            this.credentials = credentials;
+        }
+
+        public LoginCredentials getLoginCredentials() {
+            return credentials;
+        }
+
+        @Override
+        public Object getCredentials() {
+            return credentials.getPassword();
+        }
+
+        @Override
+        public Object getPrincipal() {
+            return credentials.getUsername();
+        }
+    }
+
+    public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
+        this.certificateExtractor = certificateExtractor;
+    }
+
+    public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
+        this.principalExtractor = principalExtractor;
+    }
+
+    public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+    public void setProperties(NiFiProperties properties) {
+        this.properties = properties;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp
index 5cdce5b..58bdaad 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp
@@ -35,6 +35,7 @@
         ${nf.login.script.tags}
     </head>
     <body>
+        <jsp:include page="/WEB-INF/partials/login/login-message.jsp"/>
         <jsp:include page="/WEB-INF/partials/login/login-form.jsp"/>
         <jsp:include page="/WEB-INF/partials/login/user-registration-form.jsp"/>
         <jsp:include page="/WEB-INF/partials/login/nifi-registration-form.jsp"/>

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
index 54f6811..43e306e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
@@ -48,6 +48,9 @@
                 <span id="current-user" class="hidden"></span>
                 <span id="login-link" class="link">login</span>
             </li>
+            <li id="logout-link-container" style="display: none;">
+                <span id="logout-link" class="link">logout</span>
+            </li>
             <li>
                 <span id="help-link" class="link">help</span>
             </li>

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-message.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-message.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-message.jsp
new file mode 100644
index 0000000..5284c23
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-message.jsp
@@ -0,0 +1,20 @@
+<%--
+ 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.
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="login-message-container" class="hidden">
+    <div id="login-message"></div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-submission.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-submission.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-submission.jsp
index 787bb56..5749ab9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-submission.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-submission.jsp
@@ -15,6 +15,6 @@
   limitations under the License.
 --%>
 <%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
-<div id="login-submission-container">
+<div id="login-submission-container" class="hidden">
     <button id="login-submission-button" type="submit" class="btn">Log in</button>
 </div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css
index 0f08b47..7161a8c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css
@@ -506,6 +506,10 @@ div.search-glass-pane {
 
 /* styles for the status link */
 
+#current-user {
+    margin-right: 8px;
+}
+
 #utilities-container {
     float: right;
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
index 653d895..551f33e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
@@ -147,6 +147,11 @@ nf.CanvasHeader = (function () {
             } else {
                 $('#login-link-container').css('display', 'none');
             }
+            
+            // logout link
+            $('#logout-link').click(function () {
+                nf.Storage.removeItem("jwt");
+            });
 
             // initialize the new template dialog
             $('#new-template-dialog').modal({

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
index d2d9e82..28359d7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
@@ -1054,6 +1054,7 @@ nf.Canvas = (function () {
                     nf.Common.setAuthorities(authoritiesResponse.authorities);
 
                     // at this point the user may be themselves or anonymous
+                    $('#current-user').text(identityResponse.identity).show();
 
                     // if the user is logged, we want to determine if they were logged in using a certificate
                     if (identityResponse.identity !== 'anonymous') {
@@ -1064,7 +1065,7 @@ nf.Canvas = (function () {
                         }).fail(function () {
                             // if this request succeeds, it means the user is logged in using their certificate.
                             // if this request fails, it means the user is logged in with login credentials so we want to render a logout button.
-                            // TODO - render logout button
+                            $('#logout-link-container').show();
                         }).always(function () {
                             deferred.resolve();
                         });

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
index 695a11c..45ef691 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
@@ -25,6 +25,8 @@ nf.Login = (function () {
     
     var config = {
         urls: {
+            registrationStatus: '../nifi-api/registration/status',
+            registration: '../nifi-api/registration',
             identity: '../nifi-api/controller/identity',
             users: '../nifi-api/controller/users',
             token: '../nifi-api/token',
@@ -32,6 +34,10 @@ nf.Login = (function () {
         }
     };
 
+    var initializeMessage = function () {
+        $('#login-message-container').show();
+    };
+
     var initializeLogin = function () {
         return $.ajax({
             type: 'GET',
@@ -45,8 +51,29 @@ nf.Login = (function () {
                 
                 // handle login click
                 $('#login-button').on('click', function () {
-                    login().done(function (response) {
+                    login().done(function (response, status, xhr) {
+                        var authorization = xhr.getResponseHeader('Authorization');
+                        var badToken = false;
+                        
+                        // ensure there was a token in the response
+                        if (authorization) {
+                            var tokens = authorization.split(/ /);
+                            
+                            // ensure the token is the appropriate length
+                            if (tokens.length === 2) {
+                                // store the jwt and reload the page
+                                nf.Storage.setItem('jwt', tokens[1]);
+                                window.location = '/nifi';
+                            } else {
+                                badToken = true;
+                            }
+                        } else {
+                            badToken = true;
+                        }
                         
+                        if (badToken === true) {
+                            // TODO - show unable to parse response token
+                        }
                     });
                 });
                 
@@ -112,17 +139,19 @@ nf.Login = (function () {
                         'justification': justification
                     }
                 }).done(function (response) {
-                    // TODO
-    //                // hide the registration pane
-    //                $('#registration-pane').hide();
-    //
-    //                // show the message pane
-    //                $('#message-pane').show();
-    //                $('#message-title').text('Thanks');
-    //                $('#message-content').text('Your request will be processed shortly.');
-                }).fail(nf.Common.handleAjaxError);
+                    $('#login-message').text('Thanks! Your request will be processed shortly.');
+                }).fail(function (xhr, status, error) {
+                    $('#login-message').text(xhr.responseText);
+                }).always(function () {
+                    // update form visibility
+                    $('#nifi-registration-container').hide();
+                    $('#login-submission-container').hide();
+                    $('#login-message-container').show();
+                });
             }
         });
+        
+        $('#login-submission-container').show();
     };
 
     return {
@@ -132,6 +161,7 @@ nf.Login = (function () {
         init: function () {
             nf.Storage.init();
             
+            var showMessage = false;
             var needsLogin = false;
             var needsNiFiRegistration = false;
             
@@ -140,47 +170,87 @@ nf.Login = (function () {
                 url: config.urls.token
             });
             
+            var identity = $.ajax({
+                type: 'GET',
+                url: config.urls.identity,
+                dataType: 'json'
+            });
+            
             var pageStateInit = $.Deferred(function(deferred) {
                 // get the current user's identity
-                $.ajax({
-                    type: 'GET',
-                    url: config.urls.identity,
-                    dataType: 'json'
-                }).done(function (response) {
-                    var identity = response.identity;
-
-                    // if the user is anonymous they need to login
-                    if (identity === 'anonymous') {
+                identity.done(function (response) {
+                    // if the user is anonymous see if they need to login or if they are working with a certificate
+                    if (response.identity === 'anonymous') {
+                        // request a token without including credentials, if successful then the user is using a certificate
                         token.done(function () {
-                            // anonymous user and 200 from token means they have a certificate but have not yet requested an account
-                            needsNiFiRegistration = true;
-                        }).fail(function (xhr, status, error) {
-                            // no token granted, user needs to login with their credentials
+                            // the user is using a certificate, see if their account is active/pending/revoked/etc
+                            $.ajax({
+                                type: 'GET',
+                                url: config.urls.registrationStatus
+                            }).done(function () {
+                                showMessage = true;
+                                
+                                // account is active and good
+                                $('#login-message').text('Your account is active and you are already logged in.');
+                                deferred.resolve();
+                            }).fail(function (xhr, status, error) {
+                                if (xhr.status === 401) {
+                                    // anonymous user and 401 means they need nifi registration
+                                    needsNiFiRegistration = true;
+                                } else {
+                                    showMessage = true;
+                                    
+                                    // anonymous user and non-401 means they already have an account and it's pending/revoked 
+                                    if ($.trim(xhr.responseText) === '') {
+                                        $('#login-message').text('Unable to check registration status.');
+                                    } else {
+                                        $('#login-message').text(xhr.responseText);
+                                    }
+                                }
+                                deferred.resolve();
+                            });
+                        }).fail(function () {
+                            // no token granted, user has no certificate and needs to login with their credentials
                             needsLogin = true;
+                            deferred.resolve();
                         });
+                    } else {
+                        showMessage = true;
+                        
+                        // the user is not anonymous and has an active account (though maybe role-less)
+                        $('#login-message').text('Your account is active and you are already logged in.');
+                        deferred.resolve();
                     }
                 }).fail(function (xhr, status, error) {
+                    // unable to get identity (and no anonymous user) see if we can offer login
                     if (xhr.status === 401) {
                         // attempt to get a token for the current user without passing login credentials
                         token.done(function () {
                             // 401 from identity request and 200 from token means they have a certificate but have not yet requested an account 
                             needsNiFiRegistration = true;
-                        }).fail(function (xhr, status, error) {
+                        }).fail(function () {
                             // no token granted, user needs to login with their credentials
                             needsLogin = true;
                         });
-                    } else if (xhr.status === 403) {
-                        // the user is logged in with certificate or credentials but their account is still pending. error message should indicate
-                        // TODO - show error
+                    } else {
+                        showMessage = true;
+                        
+                        // the user is logged in with certificate or credentials but their account is pending/revoked. error message should indicate
+                        if ($.trim(xhr.responseText) === '') {
+                            $('#login-message').text('Unable to authorize you to use this NiFi and anonymous access is disabled.');
+                        } else {
+                            $('#login-message').text(xhr.responseText);
+                        }
                     }
-                }).always(function () {
                     deferred.resolve();
                 });
             }).promise();
             
             // render the page accordingly
             $.when(pageStateInit).done(function () {
-                if (needsLogin === true) {
+                if (showMessage === true) {
+                    initializeMessage();
+                } else if (needsLogin === true) {
                     initializeLogin();
                 } else if (needsNiFiRegistration === true) {
                     initializeNiFiRegistration();

http://git-wip-us.apache.org/repos/asf/nifi/blob/71d84117/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index 642bc31..c798a61 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -50,6 +50,16 @@ $(document).ready(function () {
         // hide the loading indicator 
         $('div.loading-container').removeClass('ajax-loading');
     });
+    
+    // include jwt when possible
+    $.ajaxSetup({
+        'beforeSend': function(xhr) {
+            var token = nf.Storage.getItem('jwt');
+            if (token) {
+                xhr.setRequestHeader('Authorization', 'Bearer ' + token);
+            }
+        }
+    });
 
     // initialize the tooltips
     $('img.setting-icon').qtip(nf.Common.config.tooltipConfig);