mirror of https://github.com/apache/nifi.git
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.
This commit is contained in:
parent
93aa09dace
commit
b6d09b86b6
22
LICENSE
22
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -77,6 +77,11 @@
|
|||
<groupId>org.apache.nifi</groupId>
|
||||
<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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
|
@ -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,106 +399,106 @@ 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];
|
||||
.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];
|
||||
}
|
||||
|
||||
// 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)
|
||||
};
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
// 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 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;
|
||||
}
|
||||
|
||||
// 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)
|
||||
};
|
||||
|
||||
// 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);
|
||||
// update the toolbar
|
||||
nf.CanvasToolbar.refresh();
|
||||
});
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
// define a function for update the graph dimensions
|
||||
var updateGraphSize = function () {
|
||||
// get the location of the bottom of the graph
|
||||
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}).done(function (jwt) {
|
||||
// store the jwt and reload the page
|
||||
nf.Storage.setItem('jwt', jwt);
|
||||
|
||||
// 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]);
|
||||
|
||||
// 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 () {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue