Repository: nifi Updated Branches: refs/heads/NIFI-655 93aa09dac -> b6d09b86b
NIFI-655: - Starting to implement the JWT service. - Parsing JWT on client side in order to render who the user currently is when logged in. Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/b6d09b86 Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/b6d09b86 Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/b6d09b86 Branch: refs/heads/NIFI-655 Commit: b6d09b86b6b0b6873dda192d28e704653ac9522d Parents: 93aa09d Author: Matt Gilman <matt.c.gil...@gmail.com> Authored: Thu Nov 5 18:26:00 2015 -0500 Committer: Matt Gilman <matt.c.gil...@gmail.com> Committed: Thu Nov 5 18:26:00 2015 -0500 ---------------------------------------------------------------------- LICENSE | 22 ++ nifi-assembly/LICENSE | 22 ++ .../nifi-web/nifi-web-security/pom.xml | 5 + .../web/security/RegistrationStatusFilter.java | 8 +- .../form/LoginAuthenticationFilter.java | 26 +- .../nifi/web/security/jwt/JwtService.java | 54 +++- .../token/LoginAuthenticationToken.java | 8 + .../resources/nifi-web-security-context.xml | 4 +- .../src/main/resources/META-INF/LICENSE | 22 ++ .../src/main/webapp/WEB-INF/pages/login.jsp | 1 + .../WEB-INF/partials/canvas/canvas-header.jsp | 4 +- .../partials/login/nifi-registration-form.jsp | 12 +- .../nifi-web-ui/src/main/webapp/css/header.css | 10 + .../nifi-web-ui/src/main/webapp/css/login.css | 16 +- .../src/main/webapp/js/jquery/jquery.base64.js | 123 +++++++++ .../src/main/webapp/js/nf/canvas/nf-canvas.js | 256 +++++++++---------- .../src/main/webapp/js/nf/login/nf-login.js | 132 +++++++--- .../src/main/webapp/js/nf/nf-common.js | 11 +- 18 files changed, 522 insertions(+), 214 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/LICENSE ---------------------------------------------------------------------- diff --git a/LICENSE b/LICENSE index 59741e6..f4be753 100644 --- a/LICENSE +++ b/LICENSE @@ -374,6 +374,28 @@ For details see http://jqueryui.com OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +This product bundles 'jquery.base64.js' which is available under an MIT style license. + + Copyright (c) 2013 Yannick Albert (http://yckart.com/) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + This product bundles 'SlickGrid v2.2' which is available under an MIT style license. Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-assembly/LICENSE ---------------------------------------------------------------------- diff --git a/nifi-assembly/LICENSE b/nifi-assembly/LICENSE index 5abc79a..a7a73b8 100644 --- a/nifi-assembly/LICENSE +++ b/nifi-assembly/LICENSE @@ -374,6 +374,28 @@ For details see http://jqueryui.com OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +This product bundles 'jquery.base64.js' which is available under an MIT style license. + + Copyright (c) 2013 Yannick Albert (http://yckart.com/) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + This product bundles 'SlickGrid v2.2' which is available under an MIT style license. Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml index c0db615..c954d0d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml @@ -78,6 +78,11 @@ <artifactId>nifi-framework-core</artifactId> </dependency> <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt</artifactId> + <version>0.6.0</version> + </dependency> + <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> </dependency> http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/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 index e914db5..606d2e3 100644 --- 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 @@ -98,7 +98,7 @@ public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFi return new RegistrationStatusAuthenticationToken(tokenCredentials); } else { // we have a certificate so let's consider a proxy chain - final String principal = extractPrincipal(certificate); + final String principal = principalExtractor.extractPrincipal(certificate).toString(); try { // validate the certificate @@ -144,12 +144,6 @@ public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFi 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()); - } - @Override protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authentication) throws IOException, ServletException { http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.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/form/LoginAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java index 388b81e..fb45363 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/form/LoginAuthenticationFilter.java @@ -44,7 +44,6 @@ 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.PreAuthenticatedCredentialsNotFoundException; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; /** @@ -88,16 +87,16 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF // if there is no certificate, look for an existing token if (certificate == null) { final String principal = jwtService.getAuthentication(request); - + if (principal == null) { throw new AuthenticationCredentialsNotFoundException("Unable to issue token as issue token as no credentials were found in the request."); } - + final LoginCredentials tokenCredentials = new LoginCredentials(principal, null); return new LoginAuthenticationToken(tokenCredentials); } else { // extract the principal - final String principal = extractPrincipal(certificate); + final String principal = principalExtractor.extractPrincipal(certificate).toString(); try { certificateValidator.validateClientCertificate(request, certificate); @@ -151,16 +150,14 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF } catch (final UsernameNotFoundException unfe) { // if a username not found exception was thrown, the proxies were authorized and now // we can issue a new ID token to the end user + } catch (final Exception e) { + // any other issue we're going to treat as an authentication exception which will return 401 + throw new AuthenticationException(e.getMessage(), e) { + }; } } } - 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"); @@ -178,20 +175,15 @@ public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingF // generate JWT for response jwtService.addToken(response, authentication); - - // mark as successful - response.setStatus(HttpServletResponse.SC_CREATED); - response.setContentType("text/plain"); - response.setContentLength(0); } @Override protected void unsuccessfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException failed) throws IOException, ServletException { response.setContentType("text/plain"); - + final PrintWriter out = response.getWriter(); out.println(failed.getMessage()); - + if (failed instanceof BadCredentialsException || failed instanceof AuthenticationCredentialsNotFoundException) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); } else { http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.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/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 2012d69..8afa15a 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 @@ -16,9 +16,22 @@ */ package org.apache.nifi.web.security.jwt; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.impl.TextCodec; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Calendar; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.util.NiFiProperties; import org.springframework.security.core.Authentication; /** @@ -28,6 +41,16 @@ public class JwtService { private final static String AUTHORIZATION = "Authorization"; + private final String key; + private final Integer expires; + + public JwtService(final NiFiProperties properties) { + // TODO - load key (and algo/provider?) and expiration from properties + + key = TextCodec.BASE64.encode("nififtw!"); + expires = 1; + } + /** * Gets the Authentication by extracting a JWT token from the specified request. * @@ -35,12 +58,16 @@ public class JwtService { * @return The user identifier from the token */ public String getAuthentication(final HttpServletRequest request) { - // TODO : actually extract/verify token - // extract/verify token from incoming request final String authorization = request.getHeader(AUTHORIZATION); - final String username = StringUtils.substringAfterLast(authorization, " "); - return username; + final String token = StringUtils.substringAfterLast(authorization, " "); + + try { + final Jws<Claims> jwt = Jwts.parser().setSigningKey(key).parseClaimsJws(token); + return jwt.getBody().getSubject(); + } catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException e) { + return null; + } } /** @@ -48,14 +75,25 @@ public class JwtService { * * @param response The response to add the token to * @param authentication The authentication to generate a token for + * @throws java.io.IOException if an io exception occurs */ - public void addToken(final HttpServletResponse response, final Authentication authentication) { - // TODO : actually create real token... in header or response body? + public void addToken(final HttpServletResponse response, final Authentication authentication) throws IOException { + // set expiration to one day from now + final Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, expires); // create a token the specified authentication - String token = authentication.getName(); + final String identity = authentication.getPrincipal().toString(); + final String username = authentication.getName(); + final String token = Jwts.builder().setSubject(identity).claim("preferred_username", username).setExpiration(calendar.getTime()).signWith(SignatureAlgorithm.HS512, key).compact(); // add the token as a response header - response.setHeader(AUTHORIZATION, "Bearer " + token); + final PrintWriter out = response.getWriter(); + out.print(token); + + // mark the response as successful + response.setStatus(HttpServletResponse.SC_CREATED); + response.setContentType("text/plain"); } + } http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.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/token/LoginAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java index 528b60b..f908d79 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java @@ -17,6 +17,7 @@ package org.apache.nifi.web.security.token; import org.apache.nifi.authentication.LoginCredentials; +import org.apache.nifi.security.util.CertificateUtils; import org.springframework.security.authentication.AbstractAuthenticationToken; /** @@ -45,4 +46,11 @@ public class LoginAuthenticationToken extends AbstractAuthenticationToken { public Object getPrincipal() { return credentials.getUsername(); } + + @Override + public String getName() { + // if the username is a DN this will extract the username or CN... if not will return what was passed + return CertificateUtils.extractUsername(credentials.getUsername()); + } + } http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml index 45d3ba3..fa0b5b8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml @@ -40,7 +40,9 @@ </bean> <!-- jwt service --> - <bean id="jwtService" class="org.apache.nifi.web.security.jwt.JwtService"></bean> + <bean id="jwtService" class="org.apache.nifi.web.security.jwt.JwtService"> + <constructor-arg ref="nifiProperties"/> + </bean> <!-- login identity provider --> <bean id="loginIdentityProvider" class="org.apache.nifi.web.security.spring.LoginIdentityProviderFactoryBean"> http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE index 40d0725..6769105 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE @@ -351,6 +351,28 @@ For details see http://jqueryui.com OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +This product bundles 'jquery.base64.js' which is available under an MIT style license. + + Copyright (c) 2013 Yannick Albert (http://yckart.com/) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + This product bundles 'SlickGrid v2.2' which is available under an MIT style license. Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/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 925f93c..e2b7b9b 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 @@ -27,6 +27,7 @@ <link rel="stylesheet" href="js/jquery/qtip2/jquery.qtip.min.css?" type="text/css" /> <link rel="stylesheet" href="js/jquery/ui-smoothness/jquery-ui-1.10.4.min.css" type="text/css" /> <script type="text/javascript" src="js/jquery/jquery-2.1.1.min.js"></script> + <script type="text/javascript" src="js/jquery/jquery.base64.js"></script> <script type="text/javascript" src="js/jquery/jquery.count.js"></script> <script type="text/javascript" src="js/jquery/jquery.center.js"></script> <script type="text/javascript" src="js/jquery/modal/jquery.modal.js?${project.version}"></script> http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/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 81296d2..204b1b3 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 @@ -45,7 +45,9 @@ <div id="header-links-container"> <ul> <li id="current-user-container"> - <span id="current-user"></span> + <div id="anonymous-user-alert"></div> + <div id="current-user"></div> + <div class="clear"></div> </li> <li id="login-link-container"> <span id="login-link" class="link">login</span> http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp index afa7687..59191ce 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp @@ -16,7 +16,17 @@ --%> <%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %> <div id="nifi-registration-container" class="hidden"> - <div id="nifi-registration-title" class="login-title">Submit Justification</div> + <div id="nifi-registration-title" class="login-title nifi-submit-justification">Submit Justification</div> + <div id="nifi-user-submit-justification-container" class="nifi-submit-justification"> + <div class="setting"> + <div class="setting-name">User</div> + <div class="setting-field"> + <div id="nifi-user-submit-justification"></div> + <span id="nifi-user-submit-justification-logout" class="link hidden">logout</span> + <div class="clear"></div> + </div> + </div> + </div> <div class="setting"> <div class="setting-name">Justification</div> <div class="setting-field"> http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/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 3f0b299..8f2450c 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,7 +506,17 @@ div.search-glass-pane { /* styles for the status link */ +#anonymous-user-alert { + float: left; + margin-top: -2px; + margin-right: 6px; + width: 18px; + height: 16px; + background-image: url(../images/iconAlert.png); +} + #current-user { + float: left; margin-right: 8px; font-weight: bold; } http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css index 2fbe107..38ce410 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css @@ -73,10 +73,24 @@ body.login-body input, body.login-body textarea { */ #nifi-registration-container { - margin-top: 10px; width: 412px; } +#nifi-user-submit-justification-container { + margin-bottom: 10px; +} + +#nifi-user-submit-justification { + float: left; + font-weight: bold; +} + +#nifi-user-submit-justification-logout { + margin-left: 10px; + float: left; + text-decoration: underline; +} + #nifi-registration-justification { height: 200px; } http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js new file mode 100644 index 0000000..32ceab0 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js @@ -0,0 +1,123 @@ +/*! + * jquery.base64.js 0.0.3 - https://github.com/yckart/jquery.base64.js + * Makes Base64 en & -decoding simpler as it is. + * + * Based upon: https://gist.github.com/Yaffle/1284012 + * + * Copyright (c) 2012 Yannick Albert (http://yckart.com) + * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php). + * 2013/02/10 + **/ +;(function($) { + + var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + a256 = '', + r64 = [256], + r256 = [256], + i = 0; + + var UTF8 = { + + /** + * Encode multi-byte Unicode string into utf-8 multiple single-byte characters + * (BMP / basic multilingual plane only) + * + * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars + * + * @param {String} strUni Unicode string to be encoded as UTF-8 + * @returns {String} encoded string + */ + encode: function(strUni) { + // use regular expressions & String.replace callback function for better efficiency + // than procedural approaches + var strUtf = strUni.replace(/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz + function(c) { + var cc = c.charCodeAt(0); + return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f); + }) + .replace(/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz + function(c) { + var cc = c.charCodeAt(0); + return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3F, 0x80 | cc & 0x3f); + }); + return strUtf; + }, + + /** + * Decode utf-8 encoded string back into multi-byte Unicode characters + * + * @param {String} strUtf UTF-8 string to be decoded back to Unicode + * @returns {String} decoded string + */ + decode: function(strUtf) { + // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char! + var strUni = strUtf.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars + function(c) { // (note parentheses for precence) + var cc = ((c.charCodeAt(0) & 0x0f) << 12) | ((c.charCodeAt(1) & 0x3f) << 6) | (c.charCodeAt(2) & 0x3f); + return String.fromCharCode(cc); + }) + .replace(/[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars + function(c) { // (note parentheses for precence) + var cc = (c.charCodeAt(0) & 0x1f) << 6 | c.charCodeAt(1) & 0x3f; + return String.fromCharCode(cc); + }); + return strUni; + } + }; + + while(i < 256) { + var c = String.fromCharCode(i); + a256 += c; + r256[i] = i; + r64[i] = b64.indexOf(c); + ++i; + } + + function code(s, discard, alpha, beta, w1, w2) { + s = String(s); + var buffer = 0, + i = 0, + length = s.length, + result = '', + bitsInBuffer = 0; + + while(i < length) { + var c = s.charCodeAt(i); + c = c < 256 ? alpha[c] : -1; + + buffer = (buffer << w1) + c; + bitsInBuffer += w1; + + while(bitsInBuffer >= w2) { + bitsInBuffer -= w2; + var tmp = buffer >> bitsInBuffer; + result += beta.charAt(tmp); + buffer ^= tmp << bitsInBuffer; + } + ++i; + } + if(!discard && bitsInBuffer > 0) result += beta.charAt(buffer << (w2 - bitsInBuffer)); + return result; + } + + var Plugin = $.base64 = function(dir, input, encode) { + return input ? Plugin[dir](input, encode) : dir ? null : this; + }; + + Plugin.btoa = Plugin.encode = function(plain, utf8encode) { + plain = Plugin.raw === false || Plugin.utf8encode || utf8encode ? UTF8.encode(plain) : plain; + plain = code(plain, false, r256, b64, 8, 6); + return plain + '===='.slice((plain.length % 4) || 4); + }; + + Plugin.atob = Plugin.decode = function(coded, utf8decode) { + coded = coded.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + coded = String(coded).split('='); + var i = coded.length; + do {--i; + coded[i] = code(coded[i], true, r64, a256, 6, 8); + } while (i > 0); + coded = coded.join(''); + return Plugin.raw === false || Plugin.utf8decode || utf8decode ? UTF8.decode(coded) : coded; + }; +}(jQuery)); http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/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 46578ab..c316ef2 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 @@ -64,7 +64,6 @@ nf.Canvas = (function () { bulletinBoard: '../nifi-api/controller/bulletin-board', banners: '../nifi-api/controller/banners', controller: '../nifi-api/controller', - token: '../nifi-api/token', controllerConfig: '../nifi-api/controller/config', loginConfig: '../nifi-api/controller/login/config', cluster: '../nifi-api/cluster', @@ -191,7 +190,7 @@ nf.Canvas = (function () { if (!refreshContainer.is(':visible')) { $('#stats-last-refreshed').addClass('alert'); var refreshMessage = "This flow has been modified by '" + revision.lastModifier + "'. Please refresh."; - + // update the tooltip var refreshRequiredIcon = $('#refresh-required-icon'); if (refreshRequiredIcon.data('qtip')) { @@ -201,10 +200,10 @@ nf.Canvas = (function () { content: refreshMessage }, nf.CanvasUtils.config.systemTooltipConfig)); } - + refreshContainer.show(); } - + // insert the refresh needed text in the settings - if necessary if (!settingsRefreshIcon.is(':visible')) { $('#settings-last-refreshed').addClass('alert'); @@ -336,7 +335,7 @@ nf.Canvas = (function () { 'offset': '100%', 'stop-color': '#ffffff' }); - + // define the gradient for the expiration icon var expirationBackground = defs.append('linearGradient') .attr({ @@ -346,7 +345,7 @@ nf.Canvas = (function () { 'x2': '0%', 'y2': '100%' }); - + expirationBackground.append('stop') .attr({ 'offset': '0%', @@ -400,105 +399,105 @@ nf.Canvas = (function () { // prevent further propagation (to parents and others handlers // on the same element to prevent zoom behavior) d3.event.stopImmediatePropagation(); - + // prevents the browser from changing to a text selection cursor d3.event.preventDefault(); } }) - .on('mousemove.selection', function () { - // update selection box if shift is held down - if (d3.event.shiftKey) { - // get the selection box - var selectionBox = d3.select('rect.selection'); - if (!selectionBox.empty()) { - // get the original position - var originalPosition = selectionBox.datum(); - var position = d3.mouse(canvas.node()); - - var d = {}; - if (originalPosition[0] < position[0]) { - d.x = originalPosition[0]; - d.width = position[0] - originalPosition[0]; - } else { - d.x = position[0]; - d.width = originalPosition[0] - position[0]; - } - - if (originalPosition[1] < position[1]) { - d.y = originalPosition[1]; - d.height = position[1] - originalPosition[1]; - } else { - d.y = position[1]; - d.height = originalPosition[1] - position[1]; - } + .on('mousemove.selection', function () { + // update selection box if shift is held down + if (d3.event.shiftKey) { + // get the selection box + var selectionBox = d3.select('rect.selection'); + if (!selectionBox.empty()) { + // get the original position + var originalPosition = selectionBox.datum(); + var position = d3.mouse(canvas.node()); + + var d = {}; + if (originalPosition[0] < position[0]) { + d.x = originalPosition[0]; + d.width = position[0] - originalPosition[0]; + } else { + d.x = position[0]; + d.width = originalPosition[0] - position[0]; + } - // update the selection box - selectionBox.attr(d); - - // prevent further propagation (to parents) - d3.event.stopPropagation(); - } - } - }) - .on('mouseup.selection', function () { - // ensure this originated from clicking the canvas, not a component. - // when clicking on a component, the event propagation is stopped so - // it never reaches the canvas. we cannot do this however on up events - // since the drag events break down - if (canvasClicked === false) { - return; - } + if (originalPosition[1] < position[1]) { + d.y = originalPosition[1]; + d.height = position[1] - originalPosition[1]; + } else { + d.y = position[1]; + d.height = originalPosition[1] - position[1]; + } - // reset the canvas click flag - canvasClicked = false; - - // get the selection box - var selectionBox = d3.select('rect.selection'); - if (!selectionBox.empty()) { - var selectionBoundingBox = { - x: parseInt(selectionBox.attr('x'), 10), - y: parseInt(selectionBox.attr('y'), 10), - width: parseInt(selectionBox.attr('width'), 10), - height: parseInt(selectionBox.attr('height'), 10) - }; + // update the selection box + selectionBox.attr(d); - // see if a component should be selected or not - d3.selectAll('g.component').classed('selected', function (d) { - // consider it selected if its already selected or enclosed in the bounding box - return d3.select(this).classed('selected') || - d.component.position.x >= selectionBoundingBox.x && (d.component.position.x + d.dimensions.width) <= (selectionBoundingBox.x + selectionBoundingBox.width) && - d.component.position.y >= selectionBoundingBox.y && (d.component.position.y + d.dimensions.height) <= (selectionBoundingBox.y + selectionBoundingBox.height); - }); + // prevent further propagation (to parents) + d3.event.stopPropagation(); + } + } + }) + .on('mouseup.selection', function () { + // ensure this originated from clicking the canvas, not a component. + // when clicking on a component, the event propagation is stopped so + // it never reaches the canvas. we cannot do this however on up events + // since the drag events break down + if (canvasClicked === false) { + return; + } - // see if a connection should be selected or not - d3.selectAll('g.connection').classed('selected', function (d) { - // consider all points - var points = [d.start].concat(d.bends, [d.end]); + // reset the canvas click flag + canvasClicked = false; - // determine the bounding box - var x = d3.extent(points, function (pt) { - return pt.x; - }); - var y = d3.extent(points, function (pt) { - return pt.y; - }); + // get the selection box + var selectionBox = d3.select('rect.selection'); + if (!selectionBox.empty()) { + var selectionBoundingBox = { + x: parseInt(selectionBox.attr('x'), 10), + y: parseInt(selectionBox.attr('y'), 10), + width: parseInt(selectionBox.attr('width'), 10), + height: parseInt(selectionBox.attr('height'), 10) + }; - // consider it selected if its already selected or enclosed in the bounding box - return d3.select(this).classed('selected') || - x[0] >= selectionBoundingBox.x && x[1] <= (selectionBoundingBox.x + selectionBoundingBox.width) && - y[0] >= selectionBoundingBox.y && y[1] <= (selectionBoundingBox.y + selectionBoundingBox.height); - }); + // see if a component should be selected or not + d3.selectAll('g.component').classed('selected', function (d) { + // consider it selected if its already selected or enclosed in the bounding box + return d3.select(this).classed('selected') || + d.component.position.x >= selectionBoundingBox.x && (d.component.position.x + d.dimensions.width) <= (selectionBoundingBox.x + selectionBoundingBox.width) && + d.component.position.y >= selectionBoundingBox.y && (d.component.position.y + d.dimensions.height) <= (selectionBoundingBox.y + selectionBoundingBox.height); + }); + + // see if a connection should be selected or not + d3.selectAll('g.connection').classed('selected', function (d) { + // consider all points + var points = [d.start].concat(d.bends, [d.end]); + + // determine the bounding box + var x = d3.extent(points, function (pt) { + return pt.x; + }); + var y = d3.extent(points, function (pt) { + return pt.y; + }); - // remove the selection box - selectionBox.remove(); - } else if (panning === false) { - // deselect as necessary if we are not panning - nf.CanvasUtils.getSelection().classed('selected', false); - } + // consider it selected if its already selected or enclosed in the bounding box + return d3.select(this).classed('selected') || + x[0] >= selectionBoundingBox.x && x[1] <= (selectionBoundingBox.x + selectionBoundingBox.width) && + y[0] >= selectionBoundingBox.y && y[1] <= (selectionBoundingBox.y + selectionBoundingBox.height); + }); + + // remove the selection box + selectionBox.remove(); + } else if (panning === false) { + // deselect as necessary if we are not panning + nf.CanvasUtils.getSelection().classed('selected', false); + } - // update the toolbar - nf.CanvasToolbar.refresh(); - }); + // update the toolbar + nf.CanvasToolbar.refresh(); + }); // define a function for update the graph dimensions var updateGraphSize = function () { @@ -513,7 +512,7 @@ nf.Canvas = (function () { var top = parseInt(canvasContainer.css('top'), 10); var windowHeight = $(window).height(); var canvasHeight = (windowHeight - (bottom + top)); - + // canvas/svg canvasContainer.css({ 'height': canvasHeight + 'px', @@ -539,7 +538,7 @@ nf.Canvas = (function () { } }).on('keydown', function (evt) { var isCtrl = evt.ctrlKey || evt.metaKey; - + // consider escape, before checking dialogs if (!isCtrl && evt.keyCode === 27) { // esc @@ -555,7 +554,7 @@ nf.Canvas = (function () { // first consider read only property detail dialog if ($('div.property-detail').is(':visible')) { nf.Common.removeAllPropertyDetailDialogs(); - + // prevent further bubbling as we're already handled it evt.stopPropagation(); evt.preventDefault(); @@ -573,7 +572,7 @@ nf.Canvas = (function () { var dialogMax = null; // identify the top most cancellable - $.each(cancellables, function(_, cancellable) { + $.each(cancellables, function (_, cancellable) { var dialog = $(cancellable); var zIndex = dialog.css('zIndex'); @@ -618,10 +617,10 @@ nf.Canvas = (function () { } } } - + return; } - + // if a dialog is open, disable canvas shortcuts if ($('.dialog').is(':visible')) { return; @@ -836,7 +835,7 @@ nf.Canvas = (function () { bulletinIcon.show(); } } - + // update controller service and reporting task bulletins nf.Settings.setBulletins(controllerStatus.controllerServiceBulletins, controllerStatus.reportingTaskBulletins); @@ -937,22 +936,18 @@ nf.Canvas = (function () { }; return { - ANONYMOUS_USER_TEXT: 'Anonymous user', CANVAS_OFFSET: 0, - /** * Determines if the current broswer supports SVG. */ SUPPORTS_SVG: !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect, - /** * Hides the splash that is displayed while the application is loading. */ hideSplash: function () { $('#splash').fadeOut(); }, - /** * Stop polling for revision. */ @@ -960,7 +955,6 @@ nf.Canvas = (function () { // set polling flag revisionPolling = false; }, - /** * Remove the status poller. */ @@ -968,7 +962,6 @@ nf.Canvas = (function () { // set polling flag statusPolling = false; }, - /** * Reloads the flow from the server based on the currently specified group id. * To load another group, update nf.Canvas.setGroupId and call nf.Canvas.reload. @@ -977,7 +970,7 @@ nf.Canvas = (function () { return $.Deferred(function (deferred) { // hide the context menu nf.ContextMenu.hide(); - + // get the process group to refresh everything var processGroupXhr = reloadProcessGroup(nf.Canvas.getGroupId()); var statusXhr = reloadFlowStatus(); @@ -1011,7 +1004,6 @@ nf.Canvas = (function () { }); }).promise(); }, - /** * Reloads the status. */ @@ -1025,7 +1017,6 @@ nf.Canvas = (function () { }); }).promise(); }, - /** * Initialize NiFi. */ @@ -1036,16 +1027,16 @@ nf.Canvas = (function () { url: config.urls.identity, dataType: 'json' }); - + // get the current user's authorities var authoritiesXhr = $.ajax({ type: 'GET', url: config.urls.authorities, dataType: 'json' }); - + // load the identity and authorities for the current user - var userXhr = $.Deferred(function(deferred) { + var userXhr = $.Deferred(function (deferred) { $.when(authoritiesXhr, identityXhr).done(function (authoritiesResult, identityResult) { var authoritiesResponse = authoritiesResult[0]; var identityResponse = identityResult[0]; @@ -1057,6 +1048,7 @@ nf.Canvas = (function () { // if the user is logged, we want to determine if they were logged in using a certificate if (identityResponse.identity !== 'anonymous') { + // rendner the users name $('#current-user').text(identityResponse.identity).show(); // render the logout button if there is a token locally @@ -1064,6 +1056,16 @@ nf.Canvas = (function () { $('#logout-link-container').show(); } } else { + // alert user's of anonymous access + $('#anonymous-user-alert').show().qtip($.extend({}, nf.Common.config.tooltipConfig, { + content: 'You are accessing with limited authority. Log in or request an account to access with additional authority granted to you by an administrator.', + position: { + my: 'top right', + at: 'bottom left' + } + })); + + // render the anonymous user text $('#current-user').text(nf.Canvas.ANONYMOUS_USER_TEXT).show(); } deferred.resolve(); @@ -1076,7 +1078,7 @@ nf.Canvas = (function () { } }); }).promise(); - + userXhr.done(function () { // get the controller config to register the status poller var configXhr = $.ajax({ @@ -1109,7 +1111,7 @@ nf.Canvas = (function () { } }); }).promise(); - + // ensure the config requests are loaded $.when(configXhr, loginXhr, userXhr).done(function (configResult, loginResult) { var configResponse = configResult[0]; @@ -1193,7 +1195,6 @@ nf.Canvas = (function () { }).fail(nf.Common.handleAjaxError); }).fail(nf.Common.handleAjaxError); }, - /** * Defines the gradient colors used to render processors. * @@ -1202,7 +1203,6 @@ nf.Canvas = (function () { defineProcessorColors: function (colors) { setColors(colors, 'processor'); }, - /** * Defines the gradient colors used to render label. * @@ -1211,7 +1211,6 @@ nf.Canvas = (function () { defineLabelColors: function (colors) { setColors(colors, 'label'); }, - /** * Return whether this instance of NiFi is clustered. * @@ -1220,14 +1219,12 @@ nf.Canvas = (function () { isClustered: function () { return clustered === true; }, - /** * Returns whether site to site communications is secure. */ isSecureSiteToSite: function () { return secureSiteToSite; }, - /** * Set the group id. * @@ -1236,14 +1233,12 @@ nf.Canvas = (function () { setGroupId: function (gi) { groupId = gi; }, - /** * Get the group id. */ getGroupId: function () { return groupId; }, - /** * Set the group name. * @@ -1252,14 +1247,12 @@ nf.Canvas = (function () { setGroupName: function (gn) { groupName = gn; }, - /** * Get the group name. */ getGroupName: function () { return groupName; }, - /** * Set the parent group id. * @@ -1268,16 +1261,14 @@ nf.Canvas = (function () { setParentGroupId: function (pgi) { parentGroupId = pgi; }, - /** * Get the parent group id. */ getParentGroupId: function () { return parentGroupId; }, - View: (function () { - + /** * Updates component visibility based on their proximity to the screen's viewport. */ @@ -1344,8 +1335,8 @@ nf.Canvas = (function () { .classed('entering', function () { return visible && !wasVisible; }).classed('leaving', function () { - return !visible && wasVisible; - }); + return !visible && wasVisible; + }); }; // get the all components @@ -1432,7 +1423,6 @@ nf.Canvas = (function () { // add the behavior to the canvas and disable dbl click zoom svg.call(behavior).on('dblclick.zoom', null); }, - /** * Whether or not a component should be rendered based solely on the current scale. * @@ -1441,7 +1431,6 @@ nf.Canvas = (function () { shouldRenderPerScale: function () { return nf.Canvas.View.scale() >= MIN_SCALE_TO_RENDER; }, - /** * Updates component visibility based on the current translation/scale. */ @@ -1449,7 +1438,6 @@ nf.Canvas = (function () { updateComponentVisibility(); nf.Graph.pan(); }, - /** * Sets/gets the current translation. * @@ -1462,7 +1450,6 @@ nf.Canvas = (function () { behavior.translate(translate); } }, - /** * Sets/gets the current scale. * @@ -1475,7 +1462,6 @@ nf.Canvas = (function () { behavior.scale(scale); } }, - /** * Zooms in a single zoom increment. */ @@ -1500,7 +1486,6 @@ nf.Canvas = (function () { height: 1 }); }, - /** * Zooms out a single zoom increment. */ @@ -1525,7 +1510,6 @@ nf.Canvas = (function () { height: 1 }); }, - /** * Zooms to fit the entire graph on the canvas. */ @@ -1572,7 +1556,6 @@ nf.Canvas = (function () { height: canvasHeight / newScale }); }, - /** * Zooms to the actual size (1 to 1). */ @@ -1621,7 +1604,6 @@ nf.Canvas = (function () { // center as appropriate nf.CanvasUtils.centerBoundingBox(box); }, - /** * Refreshes the view based on the configured translation and scale. * http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/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 4f4cb1d..6152867 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 @@ -75,7 +75,7 @@ nf.Login = (function () { var showUserRegistration = function () { showNiFiRegistration(); - $('#nifi-registration-title').hide(); + $('div.nifi-submit-justification').hide(); $('#user-registration-container').show(); $('#login-submission-button').text('Create'); }; @@ -109,41 +109,15 @@ nf.Login = (function () { 'username': $('#username').val(), 'password': $('#password').val() } - }).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]); + }).done(function (jwt) { + // store the jwt and reload the page + nf.Storage.setItem('jwt', jwt); - // reload as appropriate - if (top !== window) { - parent.window.location = '/nifi'; - } else { - window.location = '/nifi'; - } - return; - } else { - badToken = true; - } + // reload as appropriate + if (top !== window) { + parent.window.location = '/nifi'; } else { - badToken = true; - } - - if (badToken === true) { - $('#login-message-title').text('An unexpected error has occurred'); - $('#login-message').text('The user token could not be parsed.'); - - // update visibility - $('#login-container').hide(); - $('#login-submission-container').hide(); - $('#login-message-container').show(); + window.location = '/nifi'; } }).fail(function (xhr, status, error) { if (xhr.status === 400) { @@ -164,13 +138,25 @@ nf.Login = (function () { }; var createUserAccount = function () { + var password = $('#registration-password').val(); + var passwordConfirmation = $('#registration-password-confirmation').val(); + + // ensure the password matches + if (password !== passwordConfirmation) { + nf.Dialog.showOkDialog({ + dialogContent: 'The specified passwords do not match.', + overlayBackground: false + }); + return; + } + // attempt to create the user account registration $.ajax({ type: 'POST', url: config.urls.registration, data: { 'username': $('#registration-username').val(), - 'password': $('#registration-password').val(), + 'password': password, 'justification': $('#nifi-registration-justification').val() } }).done(function (response, status, xhr) { @@ -220,6 +206,33 @@ nf.Login = (function () { }); }; + /** + * Extracts the subject from the specified jwt. If the jwt is not as expected + * an empty string is returned. + * + * @param {string} jwt + * @returns {string} + */ + var getJwtSubject = function (jwt) { + if (nf.Common.isDefinedAndNotNull(jwt)) { + var segments = jwt.split(/\./); + if (segments.length !== 3) { + return ''; + } + + var rawPayload = $.base64.atob(segments[1]); + var payload = JSON.parse(rawPayload); + + if (nf.Common.isDefinedAndNotNull(payload['preferred_username'])) { + return payload['preferred_username']; + } else { + ''; + } + } + + return ''; + }; + return { /** * Initializes the login page. @@ -231,6 +244,16 @@ nf.Login = (function () { var needsLogin = false; var needsNiFiRegistration = false; + var logout = function () { + nf.Storage.removeItem('jwt'); + }; + + // handle logout + $('#nifi-user-submit-justification-logout').on('click', function () { + logout(); + window.location = '/nifi/login'; + }); + var token = $.ajax({ type: 'GET', url: config.urls.token @@ -250,7 +273,8 @@ nf.Login = (function () { isAnonymous = true; // request a token without including credentials, if successful then the user is using a certificate - token.done(function () { + token.done(function (jwt) { + // the user is using a certificate/token, see if their account is active/pending/revoked/etc $.ajax({ type: 'GET', @@ -263,6 +287,16 @@ nf.Login = (function () { $('#login-message').text('Your account is active and you are already logged in.'); }).fail(function (xhr, status, error) { if (xhr.status === 401) { + var user = getJwtSubject(jwt); + + // show the user + $('#nifi-user-submit-justification').text(user); + + // render the logout button if there is a token locally + if (nf.Storage.getItem('jwt') !== null) { + $('#nifi-user-submit-justification-logout').show(); + } + // anonymous user and 401 means they need nifi registration needsNiFiRegistration = true; } else { @@ -279,7 +313,12 @@ nf.Login = (function () { }).always(function () { deferred.resolve(); }); - }).fail(function () { + }).fail(function (tokenXhr) { + if (tokenXhr.status === 400) { + // no credentials supplied so 400 must be due to an invalid/expired token + logout(); + } + // no token granted, user has no certificate and needs to login with their credentials needsLogin = true; deferred.resolve(); @@ -296,10 +335,25 @@ nf.Login = (function () { // 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 () { + token.done(function (jwt) { + var user = getJwtSubject(jwt); + + // show the user + $('#nifi-user-submit-justification').text(user); + + // render the logout button if there is a token locally + if (nf.Storage.getItem('jwt') !== null) { + $('#nifi-user-submit-justification-logout').show(); + } + // 401 from identity request and 200 from token means they have a certificate/token but have not yet requested an account needsNiFiRegistration = true; - }).fail(function () { + }).fail(function (tokenXhr) { + if (tokenXhr.status === 400) { + // no credentials supplied so 400 must be due to an invalid/expired token + logout(); + } + // no token granted, user needs to login with their credentials needsLogin = true; }).always(function () { http://git-wip-us.apache.org/repos/asf/nifi/blob/b6d09b86/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 c798a61..8c023e7 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 @@ -206,7 +206,14 @@ nf.Common = { // if an error occurs while the splash screen is visible close the canvas show the error message if ($('#splash').is(':visible')) { - $('#message-title').text('An unexpected error has occurred'); + if (xhr.status === 401) { + $('#message-title').text('Unauthorized'); + } else if (xhr.status === 403) { + $('#message-title').text('Access Denied'); + } else { + $('#message-title').text('An unexpected error has occurred'); + } + if ($.trim(xhr.responseText) === '') { $('#message-content').text('Please check the logs.'); } else { @@ -249,7 +256,7 @@ nf.Common = { $('#message-content').text(xhr.responseText); } } else if (xhr.status === 403) { - $('#message-title').text('Forbidden'); + $('#message-title').text('Access Denied'); if ($.trim(xhr.responseText) === '') { $('#message-content').text('Unable to authorize you to use this NiFi.'); } else {