mirror of https://github.com/apache/nifi.git
NIFI-655:
- Added an endpoint for access details including configuration, creating tokens, and checking status. - Updated DTOs and client side to utilize new endpoints.
This commit is contained in:
parent
9ccf61aff1
commit
7529694f23
|
@ -20,12 +20,13 @@ import com.wordnik.swagger.annotations.ApiModelProperty;
|
||||||
import javax.xml.bind.annotation.XmlType;
|
import javax.xml.bind.annotation.XmlType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Details for the login configuration.
|
* Details for the access configuration.
|
||||||
*/
|
*/
|
||||||
@XmlType(name = "loginConfig")
|
@XmlType(name = "accessConfig")
|
||||||
public class LoginConfigurationDTO {
|
public class AccessConfigurationDTO {
|
||||||
|
|
||||||
private Boolean supportsLogin;
|
private Boolean supportsLogin;
|
||||||
|
private Boolean supportsAnonymous;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Indicates whether or not this NiFi supports user login.
|
* @return Indicates whether or not this NiFi supports user login.
|
||||||
|
@ -41,4 +42,20 @@ public class LoginConfigurationDTO {
|
||||||
public void setSupportsLogin(Boolean supportsLogin) {
|
public void setSupportsLogin(Boolean supportsLogin) {
|
||||||
this.supportsLogin = supportsLogin;
|
this.supportsLogin = supportsLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Indicates whether or not this NiFi supports anonymous access.
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(
|
||||||
|
value = "Indicates whether or not this NiFi supports anonymous.",
|
||||||
|
readOnly = true
|
||||||
|
)
|
||||||
|
public Boolean getSupportsAnonymous() {
|
||||||
|
return supportsAnonymous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSupportsAnonymous(Boolean supportsAnonymous) {
|
||||||
|
this.supportsAnonymous = supportsAnonymous;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.nifi.web.api.dto;
|
||||||
|
|
||||||
|
import com.wordnik.swagger.annotations.ApiModelProperty;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds the users access status.
|
||||||
|
*/
|
||||||
|
@XmlRootElement(name = "accessStatus")
|
||||||
|
public class AccessStatusDTO {
|
||||||
|
|
||||||
|
public static enum Status {
|
||||||
|
|
||||||
|
UNKNOWN,
|
||||||
|
UNREGISTERED,
|
||||||
|
NOT_ACTIVE,
|
||||||
|
ACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
private String identity;
|
||||||
|
private String username;
|
||||||
|
private String status;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the user identity
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(
|
||||||
|
value = "The user identity.",
|
||||||
|
readOnly = true
|
||||||
|
)
|
||||||
|
public String getIdentity() {
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdentity(String identity) {
|
||||||
|
this.identity = identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the username
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(
|
||||||
|
value = "The username.",
|
||||||
|
readOnly = true
|
||||||
|
)
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the user access status
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(
|
||||||
|
value = "The user access status.",
|
||||||
|
readOnly = true
|
||||||
|
)
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return additional details about the user access status
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(
|
||||||
|
value = "Additional details about the user access status.",
|
||||||
|
readOnly = true
|
||||||
|
)
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,26 +17,26 @@
|
||||||
package org.apache.nifi.web.api.entity;
|
package org.apache.nifi.web.api.entity;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import org.apache.nifi.web.api.dto.LoginConfigurationDTO;
|
import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a LoginConfigurationDTO.
|
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a AccessConfigurationDTO.
|
||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "loginConfigurationEntity")
|
@XmlRootElement(name = "accessConfigurationEntity")
|
||||||
public class LoginConfigurationEntity extends Entity {
|
public class AccessConfigurationEntity extends Entity {
|
||||||
|
|
||||||
private LoginConfigurationDTO config;
|
private AccessConfigurationDTO config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The LoginConfigurationDTO that is being serialized.
|
* The AccessConfigurationDTO that is being serialized.
|
||||||
*
|
*
|
||||||
* @return The LoginConfigurationDTO object
|
* @return The AccessConfigurationDTO object
|
||||||
*/
|
*/
|
||||||
public LoginConfigurationDTO getConfig() {
|
public AccessConfigurationDTO getConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConfig(LoginConfigurationDTO config) {
|
public void setConfig(AccessConfigurationDTO config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.nifi.web.api.entity;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import org.apache.nifi.web.api.dto.AccessStatusDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a AccessStatusDTO.
|
||||||
|
*/
|
||||||
|
@XmlRootElement(name = "accessStatusEntity")
|
||||||
|
public class AccessStatusEntity extends Entity {
|
||||||
|
|
||||||
|
private AccessStatusDTO accessStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AccessStatusDTO that is being serialized.
|
||||||
|
*
|
||||||
|
* @return The AccessStatusDTO object
|
||||||
|
*/
|
||||||
|
public AccessStatusDTO getAccessStatus() {
|
||||||
|
return accessStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessStatus(AccessStatusDTO accessStatus) {
|
||||||
|
this.accessStatus = accessStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -43,7 +43,6 @@ import org.apache.nifi.web.api.dto.ProcessGroupDTO;
|
||||||
import org.apache.nifi.web.api.dto.ProcessorDTO;
|
import org.apache.nifi.web.api.dto.ProcessorDTO;
|
||||||
import org.apache.nifi.web.api.dto.ComponentHistoryDTO;
|
import org.apache.nifi.web.api.dto.ComponentHistoryDTO;
|
||||||
import org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO;
|
import org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO;
|
||||||
import org.apache.nifi.web.api.dto.LoginConfigurationDTO;
|
|
||||||
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
|
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
|
||||||
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
|
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
|
||||||
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
|
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
|
||||||
|
@ -178,13 +177,6 @@ public interface NiFiServiceFacade {
|
||||||
*/
|
*/
|
||||||
ControllerConfigurationDTO getControllerConfiguration();
|
ControllerConfigurationDTO getControllerConfiguration();
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the login configuration for this controller.
|
|
||||||
*
|
|
||||||
* @return The login configuration
|
|
||||||
*/
|
|
||||||
LoginConfigurationDTO getLoginConfiguration();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the configuration for this controller.
|
* Updates the configuration for this controller.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,15 +16,12 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.web;
|
package org.apache.nifi.web;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import org.apache.nifi.admin.service.UserService;
|
import org.apache.nifi.admin.service.UserService;
|
||||||
import org.apache.nifi.authentication.LoginIdentityProvider;
|
import org.apache.nifi.authentication.LoginIdentityProvider;
|
||||||
import org.apache.nifi.util.NiFiProperties;
|
import org.apache.nifi.util.NiFiProperties;
|
||||||
import org.apache.nifi.web.security.NiFiAuthenticationProvider;
|
import org.apache.nifi.web.security.NiFiAuthenticationProvider;
|
||||||
import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter;
|
import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter;
|
||||||
import org.apache.nifi.web.security.NiFiAuthenticationEntryPoint;
|
import org.apache.nifi.web.security.NiFiAuthenticationEntryPoint;
|
||||||
import org.apache.nifi.web.security.RegistrationStatusFilter;
|
|
||||||
import org.apache.nifi.web.security.login.LoginAuthenticationFilter;
|
|
||||||
import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
|
import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
|
||||||
import org.apache.nifi.web.security.jwt.JwtService;
|
import org.apache.nifi.web.security.jwt.JwtService;
|
||||||
import org.apache.nifi.web.security.node.NodeAuthorizedUserFilter;
|
import org.apache.nifi.web.security.node.NodeAuthorizedUserFilter;
|
||||||
|
@ -35,7 +32,6 @@ import org.apache.nifi.web.security.x509.X509CertificateValidator;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
@ -46,7 +42,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
||||||
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +67,9 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(WebSecurity webSecurity) throws Exception {
|
public void configure(WebSecurity webSecurity) throws Exception {
|
||||||
webSecurity.ignoring().antMatchers(HttpMethod.GET, "/controller/login/config");
|
webSecurity
|
||||||
|
.ignoring()
|
||||||
|
.antMatchers("/access/**");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -80,19 +77,13 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||||
http
|
http
|
||||||
.rememberMe().disable()
|
.rememberMe().disable()
|
||||||
.exceptionHandling()
|
.exceptionHandling()
|
||||||
.authenticationEntryPoint(new NiFiAuthenticationEntryPoint(properties))
|
.authenticationEntryPoint(new NiFiAuthenticationEntryPoint(properties))
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.anyRequest().fullyAuthenticated()
|
.anyRequest().fullyAuthenticated()
|
||||||
.and()
|
.and()
|
||||||
.sessionManagement()
|
.sessionManagement()
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||||
|
|
||||||
// login authentication for /token - exchanges for JWT for subsequent API usage
|
|
||||||
http.addFilterBefore(buildLoginFilter("/token"), UsernamePasswordAuthenticationFilter.class);
|
|
||||||
|
|
||||||
// registration status - will check the status of a user's account registration (regardless if its based on login or not)
|
|
||||||
http.addFilterBefore(buildRegistrationStatusFilter("/registration/status"), UsernamePasswordAuthenticationFilter.class);
|
|
||||||
|
|
||||||
// cluster authorized user
|
// cluster authorized user
|
||||||
http.addFilterBefore(buildNodeAuthorizedUserFilter(), AnonymousAuthenticationFilter.class);
|
http.addFilterBefore(buildNodeAuthorizedUserFilter(), AnonymousAuthenticationFilter.class);
|
||||||
|
@ -121,28 +112,6 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||||
auth.authenticationProvider(new NiFiAuthenticationProvider(userDetailsService));
|
auth.authenticationProvider(new NiFiAuthenticationProvider(userDetailsService));
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoginAuthenticationFilter buildLoginFilter(final String url) {
|
|
||||||
final LoginAuthenticationFilter loginFilter = new LoginAuthenticationFilter(url);
|
|
||||||
loginFilter.setJwtService(jwtService);
|
|
||||||
loginFilter.setLoginIdentityProvider(loginIdentityProvider);
|
|
||||||
loginFilter.setUserDetailsService(userDetailsService);
|
|
||||||
loginFilter.setCertificateExtractor(certificateExtractor);
|
|
||||||
loginFilter.setPrincipalExtractor(principalExtractor);
|
|
||||||
loginFilter.setCertificateValidator(certificateValidator);
|
|
||||||
return loginFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Filter buildRegistrationStatusFilter(final String url) {
|
|
||||||
final RegistrationStatusFilter registrationStatusFilter = new RegistrationStatusFilter(url);
|
|
||||||
registrationStatusFilter.setCertificateExtractor(certificateExtractor);
|
|
||||||
registrationStatusFilter.setPrincipalExtractor(principalExtractor);
|
|
||||||
registrationStatusFilter.setCertificateValidator(certificateValidator);
|
|
||||||
registrationStatusFilter.setProperties(properties);
|
|
||||||
registrationStatusFilter.setJwtService(jwtService);
|
|
||||||
registrationStatusFilter.setUserDetailsService(userDetailsService);
|
|
||||||
return registrationStatusFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NodeAuthorizedUserFilter buildNodeAuthorizedUserFilter() {
|
private NodeAuthorizedUserFilter buildNodeAuthorizedUserFilter() {
|
||||||
return new NodeAuthorizedUserFilter(properties);
|
return new NodeAuthorizedUserFilter(properties);
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,7 +153,6 @@ import org.apache.nifi.web.util.SnippetUtils;
|
||||||
|
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.nifi.authentication.LoginIdentityProvider;
|
|
||||||
import org.apache.nifi.components.PropertyDescriptor;
|
import org.apache.nifi.components.PropertyDescriptor;
|
||||||
import org.apache.nifi.components.Validator;
|
import org.apache.nifi.components.Validator;
|
||||||
import org.apache.nifi.controller.ReportingTaskNode;
|
import org.apache.nifi.controller.ReportingTaskNode;
|
||||||
|
@ -164,7 +163,6 @@ import org.apache.nifi.controller.service.ControllerServiceState;
|
||||||
import org.apache.nifi.reporting.ComponentType;
|
import org.apache.nifi.reporting.ComponentType;
|
||||||
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
|
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
|
||||||
import org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO;
|
import org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO;
|
||||||
import org.apache.nifi.web.api.dto.LoginConfigurationDTO;
|
|
||||||
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
|
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
|
||||||
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
|
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
|
||||||
import org.apache.nifi.web.api.dto.status.ClusterProcessGroupStatusDTO;
|
import org.apache.nifi.web.api.dto.status.ClusterProcessGroupStatusDTO;
|
||||||
|
@ -207,7 +205,6 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
|
||||||
// administrative services
|
// administrative services
|
||||||
private AuditService auditService;
|
private AuditService auditService;
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
private LoginIdentityProvider loginIdentityProvider;
|
|
||||||
|
|
||||||
// cluster manager
|
// cluster manager
|
||||||
private WebClusterManager clusterManager;
|
private WebClusterManager clusterManager;
|
||||||
|
@ -2350,16 +2347,6 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
|
||||||
return controllerFacade.getInstanceId();
|
return controllerFacade.getInstanceId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoginConfigurationDTO getLoginConfiguration() {
|
|
||||||
final LoginConfigurationDTO loginConfiguration = new LoginConfigurationDTO();
|
|
||||||
|
|
||||||
// specify whether login should be supported
|
|
||||||
loginConfiguration.setSupportsLogin(loginIdentityProvider != null);
|
|
||||||
|
|
||||||
return loginConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ControllerConfigurationDTO getControllerConfiguration() {
|
public ControllerConfigurationDTO getControllerConfiguration() {
|
||||||
ControllerConfigurationDTO controllerConfig = new ControllerConfigurationDTO();
|
ControllerConfigurationDTO controllerConfig = new ControllerConfigurationDTO();
|
||||||
|
@ -3420,10 +3407,6 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
|
||||||
this.snippetUtils = snippetUtils;
|
this.snippetUtils = snippetUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) {
|
|
||||||
this.loginIdentityProvider = loginIdentityProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPrimaryNode(String nodeId) {
|
private boolean isPrimaryNode(String nodeId) {
|
||||||
final Node primaryNode = clusterManager.getPrimaryNode();
|
final Node primaryNode = clusterManager.getPrimaryNode();
|
||||||
return (primaryNode != null && primaryNode.getNodeId().getId().equals(nodeId));
|
return (primaryNode != null && primaryNode.getNodeId().getId().equals(nodeId));
|
||||||
|
|
|
@ -0,0 +1,412 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.nifi.web.api;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import org.apache.nifi.util.NiFiProperties;
|
||||||
|
import com.wordnik.swagger.annotations.Api;
|
||||||
|
import com.wordnik.swagger.annotations.ApiOperation;
|
||||||
|
import com.wordnik.swagger.annotations.ApiParam;
|
||||||
|
import com.wordnik.swagger.annotations.ApiResponse;
|
||||||
|
import com.wordnik.swagger.annotations.ApiResponses;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.cert.CertificateExpiredException;
|
||||||
|
import java.security.cert.CertificateNotYetValidException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.ws.rs.DefaultValue;
|
||||||
|
import javax.ws.rs.FormParam;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import org.apache.nifi.admin.service.AdministrationException;
|
||||||
|
import org.apache.nifi.authentication.AuthenticationResponse;
|
||||||
|
import org.apache.nifi.authentication.LoginCredentials;
|
||||||
|
import org.apache.nifi.authentication.LoginIdentityProvider;
|
||||||
|
import org.apache.nifi.authentication.exception.IdentityAccessException;
|
||||||
|
import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
|
||||||
|
import org.apache.nifi.security.util.CertificateUtils;
|
||||||
|
import org.apache.nifi.util.StringUtils;
|
||||||
|
import static org.apache.nifi.web.api.ApplicationResource.CLIENT_ID;
|
||||||
|
import org.apache.nifi.web.api.dto.AccessStatusDTO;
|
||||||
|
import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
|
||||||
|
import org.apache.nifi.web.api.dto.RevisionDTO;
|
||||||
|
import org.apache.nifi.web.api.entity.AccessStatusEntity;
|
||||||
|
import org.apache.nifi.web.api.entity.AccessConfigurationEntity;
|
||||||
|
import org.apache.nifi.web.api.request.ClientIdParameter;
|
||||||
|
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
|
||||||
|
import org.apache.nifi.web.security.UntrustedProxyException;
|
||||||
|
import org.apache.nifi.web.security.jwt.JwtService;
|
||||||
|
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
|
||||||
|
import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
|
||||||
|
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
|
||||||
|
import org.apache.nifi.web.security.x509.X509CertificateValidator;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.authentication.AccountStatusException;
|
||||||
|
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||||
|
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||||
|
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.preauth.x509.X509PrincipalExtractor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RESTful endpoint for managing a cluster.
|
||||||
|
*/
|
||||||
|
@Path("/access")
|
||||||
|
@Api(
|
||||||
|
value = "/access",
|
||||||
|
description = "Endpoints for obtaining an access token or checking access status"
|
||||||
|
)
|
||||||
|
public class AccessResource extends ApplicationResource {
|
||||||
|
|
||||||
|
private NiFiProperties properties;
|
||||||
|
|
||||||
|
private X509CertificateValidator certificateValidator;
|
||||||
|
private X509CertificateExtractor certificateExtractor;
|
||||||
|
private X509PrincipalExtractor principalExtractor;
|
||||||
|
|
||||||
|
private LoginIdentityProvider loginIdentityProvider;
|
||||||
|
private JwtService jwtService;
|
||||||
|
|
||||||
|
private AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the access configuration for this NiFi.
|
||||||
|
*
|
||||||
|
* @param httpServletRequest the servlet request
|
||||||
|
* @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response.
|
||||||
|
* @return A accessConfigurationEntity
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Consumes(MediaType.WILDCARD)
|
||||||
|
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
|
||||||
|
@Path("/config")
|
||||||
|
@ApiOperation(
|
||||||
|
value = "Retrieves the access configuration for this NiFi",
|
||||||
|
response = AccessConfigurationEntity.class
|
||||||
|
)
|
||||||
|
public Response getLoginConfig(
|
||||||
|
@Context HttpServletRequest httpServletRequest,
|
||||||
|
@ApiParam(
|
||||||
|
value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.",
|
||||||
|
required = false
|
||||||
|
)
|
||||||
|
@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId) {
|
||||||
|
|
||||||
|
final AccessConfigurationDTO accessConfiguration = new AccessConfigurationDTO();
|
||||||
|
|
||||||
|
// specify whether login should be supported and only support for secure requests
|
||||||
|
accessConfiguration.setSupportsLogin(loginIdentityProvider != null && httpServletRequest.isSecure());
|
||||||
|
accessConfiguration.setSupportsAnonymous(!properties.getAnonymousAuthorities().isEmpty() || !httpServletRequest.isSecure());
|
||||||
|
|
||||||
|
// create the revision
|
||||||
|
final RevisionDTO revision = new RevisionDTO();
|
||||||
|
revision.setClientId(clientId.getClientId());
|
||||||
|
|
||||||
|
// create the response entity
|
||||||
|
final AccessConfigurationEntity entity = new AccessConfigurationEntity();
|
||||||
|
entity.setRevision(revision);
|
||||||
|
entity.setConfig(accessConfiguration);
|
||||||
|
|
||||||
|
// generate the response
|
||||||
|
return clusterContext(generateOkResponse(entity)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the status the client's access.
|
||||||
|
*
|
||||||
|
* @param httpServletRequest the servlet request
|
||||||
|
* @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response.
|
||||||
|
* @return A accessStatusEntity
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Consumes(MediaType.WILDCARD)
|
||||||
|
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
|
||||||
|
@Path("")
|
||||||
|
@ApiOperation(
|
||||||
|
value = "Gets the status the client's access",
|
||||||
|
response = AccessStatusEntity.class
|
||||||
|
)
|
||||||
|
@ApiResponses(
|
||||||
|
value = {
|
||||||
|
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
|
||||||
|
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public Response getAccessStatus(
|
||||||
|
@Context HttpServletRequest httpServletRequest,
|
||||||
|
@ApiParam(
|
||||||
|
value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.",
|
||||||
|
required = false
|
||||||
|
)
|
||||||
|
@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId) {
|
||||||
|
|
||||||
|
// only consider user specific access over https
|
||||||
|
if (!httpServletRequest.isSecure()) {
|
||||||
|
throw new IllegalStateException("User authentication/authorization is only supported when running over HTTPS.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final AccessStatusDTO accessStatus = new AccessStatusDTO();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// look for a certificate
|
||||||
|
final X509Certificate certificate = certificateExtractor.extractClientCertificate(httpServletRequest);
|
||||||
|
|
||||||
|
// if no certificate, just check the credentials
|
||||||
|
if (certificate == null) {
|
||||||
|
final String principal = jwtService.getAuthentication(httpServletRequest);
|
||||||
|
|
||||||
|
// ensure we have something we can work with (certificate or crendentials)
|
||||||
|
if (principal == null) {
|
||||||
|
accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name());
|
||||||
|
accessStatus.setMessage("No credentials supplied, unknown user.");
|
||||||
|
} else {
|
||||||
|
// set the user identity
|
||||||
|
accessStatus.setIdentity(principal);
|
||||||
|
accessStatus.setUsername(CertificateUtils.extractUsername(principal));
|
||||||
|
|
||||||
|
// without a certificate, this is not a proxied request
|
||||||
|
final List<String> chain = Arrays.asList(principal);
|
||||||
|
|
||||||
|
// check authorization for this user
|
||||||
|
checkAuthorization(chain);
|
||||||
|
|
||||||
|
// no issues with authorization
|
||||||
|
accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
|
||||||
|
accessStatus.setStatus("Account is active and authorized");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we have a certificate so let's consider a proxy chain
|
||||||
|
final String principal = principalExtractor.extractPrincipal(certificate).toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// validate the certificate
|
||||||
|
certificateValidator.validateClientCertificate(httpServletRequest, certificate);
|
||||||
|
} catch (CertificateExpiredException cee) {
|
||||||
|
throw new IllegalArgumentException(String.format("Client certificate for (%s) is expired.", principal), cee);
|
||||||
|
} catch (CertificateNotYetValidException cnyve) {
|
||||||
|
throw new IllegalArgumentException(String.format("Client certificate for (%s) is not yet valid.", principal), cnyve);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new IllegalArgumentException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the user identity
|
||||||
|
accessStatus.setIdentity(principal);
|
||||||
|
accessStatus.setUsername(CertificateUtils.extractUsername(principal));
|
||||||
|
|
||||||
|
// ensure the proxy chain is authorized
|
||||||
|
checkAuthorization(ProxiedEntitiesUtils.buildProxyChain(httpServletRequest, principal));
|
||||||
|
|
||||||
|
// no issues with authorization
|
||||||
|
accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
|
||||||
|
accessStatus.setStatus("Account is active and authorized");
|
||||||
|
}
|
||||||
|
} catch (final UsernameNotFoundException unfe) {
|
||||||
|
accessStatus.setStatus(AccessStatusDTO.Status.UNREGISTERED.name());
|
||||||
|
accessStatus.setMessage(String.format("Unregistered user %s", accessStatus.getIdentity()));
|
||||||
|
} catch (final AccountStatusException ase) {
|
||||||
|
accessStatus.setStatus(AccessStatusDTO.Status.NOT_ACTIVE.name());
|
||||||
|
accessStatus.setMessage(ase.getMessage());
|
||||||
|
} catch (final UntrustedProxyException upe) {
|
||||||
|
throw new AccessDeniedException(upe.getMessage(), upe);
|
||||||
|
} catch (final AuthenticationServiceException ase) {
|
||||||
|
throw new AdministrationException(ase.getMessage(), ase);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the revision
|
||||||
|
final RevisionDTO revision = new RevisionDTO();
|
||||||
|
revision.setClientId(clientId.getClientId());
|
||||||
|
|
||||||
|
// create the entity
|
||||||
|
final AccessStatusEntity entity = new AccessStatusEntity();
|
||||||
|
entity.setRevision(revision);
|
||||||
|
entity.setAccessStatus(accessStatus);
|
||||||
|
|
||||||
|
return generateOkResponse(entity).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the status of the proxy.
|
||||||
|
*
|
||||||
|
* @param proxyChain the proxy chain
|
||||||
|
* @throws AuthenticationException if the proxy chain is not authorized
|
||||||
|
*/
|
||||||
|
private void checkAuthorization(final List<String> proxyChain) throws AuthenticationException {
|
||||||
|
userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a token for accessing the REST API via username/password.
|
||||||
|
*
|
||||||
|
* @param httpServletRequest the servlet request
|
||||||
|
* @param username the username
|
||||||
|
* @param password the password
|
||||||
|
* @return A JWT (string)
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
|
@Path("/token")
|
||||||
|
@ApiOperation(
|
||||||
|
value = "Creates a token for accessing the REST API via username/password",
|
||||||
|
response = String.class
|
||||||
|
)
|
||||||
|
@ApiResponses(
|
||||||
|
value = {
|
||||||
|
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
|
||||||
|
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public Response createAccessToken(
|
||||||
|
@Context HttpServletRequest httpServletRequest,
|
||||||
|
@FormParam("username") String username,
|
||||||
|
@FormParam("password") String password) {
|
||||||
|
|
||||||
|
// only support access tokens when communicating over HTTPS
|
||||||
|
if (!httpServletRequest.isSecure()) {
|
||||||
|
throw new IllegalStateException("Access tokens are only issued over HTTPS.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not configuration for login, don't consider credentials
|
||||||
|
if (loginIdentityProvider == null) {
|
||||||
|
throw new IllegalStateException("Username/Password login not supported by this NiFi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final LoginAuthenticationToken loginAuthenticationToken;
|
||||||
|
|
||||||
|
// if we don't have username/password, consider JWT or x509
|
||||||
|
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
|
||||||
|
// look for a certificate
|
||||||
|
final X509Certificate certificate = certificateExtractor.extractClientCertificate(httpServletRequest);
|
||||||
|
|
||||||
|
// if there is no certificate, look for an existing token
|
||||||
|
if (certificate == null) {
|
||||||
|
// if not configured for login, don't consider existing tokens
|
||||||
|
if (loginIdentityProvider == null) {
|
||||||
|
throw new IllegalStateException("Login not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// look for the principal
|
||||||
|
final String principal = jwtService.getAuthentication(httpServletRequest);
|
||||||
|
if (principal == null) {
|
||||||
|
throw new AuthenticationCredentialsNotFoundException("Unable to issue token as issue token as no credentials were found in the request.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the authentication token
|
||||||
|
loginAuthenticationToken = new LoginAuthenticationToken(principal, loginIdentityProvider.getExpiration());
|
||||||
|
} else {
|
||||||
|
// extract the principal
|
||||||
|
final String principal = principalExtractor.extractPrincipal(certificate).toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
certificateValidator.validateClientCertificate(httpServletRequest, certificate);
|
||||||
|
} catch (CertificateExpiredException cee) {
|
||||||
|
throw new IllegalArgumentException(String.format("Client certificate for (%s) is expired.", principal), cee);
|
||||||
|
} catch (CertificateNotYetValidException cnyve) {
|
||||||
|
throw new IllegalArgumentException(String.format("Client certificate for (%s) is not yet valid.", principal), cnyve);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new IllegalArgumentException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// authorize the proxy if necessary
|
||||||
|
authorizeProxyIfNecessary(ProxiedEntitiesUtils.buildProxyChain(httpServletRequest, principal));
|
||||||
|
|
||||||
|
// create the authentication token
|
||||||
|
loginAuthenticationToken = new LoginAuthenticationToken(principal, loginIdentityProvider.getExpiration());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// attempt to authenticate
|
||||||
|
final AuthenticationResponse authenticationResponse = loginIdentityProvider.authenticate(new LoginCredentials(username, password));
|
||||||
|
|
||||||
|
// create the authentication token
|
||||||
|
loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getUsername(), loginIdentityProvider.getExpiration());
|
||||||
|
} catch (final InvalidLoginCredentialsException ilce) {
|
||||||
|
throw new IllegalArgumentException("The supplied username and password are not valid.", ilce);
|
||||||
|
} catch (final IdentityAccessException iae) {
|
||||||
|
throw new AdministrationException(iae.getMessage(), iae);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate JWT for response
|
||||||
|
final String token = jwtService.generateSignedToken(loginAuthenticationToken);
|
||||||
|
|
||||||
|
// build the response
|
||||||
|
final URI uri = URI.create(generateResourceUri("access", "token"));
|
||||||
|
return generateCreatedResponse(uri, token).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the proxyChain is authorized before allowing the user to be authenticated.
|
||||||
|
*
|
||||||
|
* @param proxyChain the proxy chain
|
||||||
|
* @throws AuthenticationException if the proxy chain is not authorized
|
||||||
|
*/
|
||||||
|
private void authorizeProxyIfNecessary(final List<String> proxyChain) throws AuthenticationException {
|
||||||
|
if (proxyChain.size() > 1) {
|
||||||
|
try {
|
||||||
|
userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain));
|
||||||
|
} 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 AdministrationException(e.getMessage(), e) {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setters
|
||||||
|
public void setProperties(NiFiProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) {
|
||||||
|
this.loginIdentityProvider = loginIdentityProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJwtService(JwtService jwtService) {
|
||||||
|
this.jwtService = jwtService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCertificateValidator(X509CertificateValidator certificateValidator) {
|
||||||
|
this.certificateValidator = certificateValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
|
||||||
|
this.certificateExtractor = certificateExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
|
||||||
|
this.principalExtractor = principalExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) {
|
||||||
|
this.userDetailsService = userDetailsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -78,10 +78,8 @@ import org.apache.nifi.web.api.request.ClientIdParameter;
|
||||||
import org.apache.nifi.web.api.request.IntegerParameter;
|
import org.apache.nifi.web.api.request.IntegerParameter;
|
||||||
import org.apache.nifi.web.api.request.LongParameter;
|
import org.apache.nifi.web.api.request.LongParameter;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.nifi.web.api.dto.LoginConfigurationDTO;
|
|
||||||
import org.apache.nifi.web.api.entity.ControllerServiceTypesEntity;
|
import org.apache.nifi.web.api.entity.ControllerServiceTypesEntity;
|
||||||
import org.apache.nifi.web.api.entity.IdentityEntity;
|
import org.apache.nifi.web.api.entity.IdentityEntity;
|
||||||
import org.apache.nifi.web.api.entity.LoginConfigurationEntity;
|
|
||||||
import org.apache.nifi.web.api.entity.ReportingTaskTypesEntity;
|
import org.apache.nifi.web.api.entity.ReportingTaskTypesEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
|
||||||
|
@ -656,52 +654,6 @@ public class ControllerResource extends ApplicationResource {
|
||||||
return clusterContext(generateOkResponse(entity)).build();
|
return clusterContext(generateOkResponse(entity)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the login configuration for this NiFi.
|
|
||||||
*
|
|
||||||
* @param httpServletRequest the servlet request
|
|
||||||
* @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response.
|
|
||||||
* @return A loginConfigurationEntity
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Consumes(MediaType.WILDCARD)
|
|
||||||
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
|
|
||||||
@Path("/login/config")
|
|
||||||
@ApiOperation(
|
|
||||||
value = "Retrieves the login configuration for this NiFi",
|
|
||||||
response = LoginConfigurationEntity.class
|
|
||||||
)
|
|
||||||
public Response getLoginConfig(
|
|
||||||
@Context HttpServletRequest httpServletRequest,
|
|
||||||
@ApiParam(
|
|
||||||
value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.",
|
|
||||||
required = false
|
|
||||||
)
|
|
||||||
@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId) {
|
|
||||||
|
|
||||||
// replicate if cluster manager
|
|
||||||
if (properties.isClusterManager()) {
|
|
||||||
return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders()).getResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
final LoginConfigurationDTO loginConfig = serviceFacade.getLoginConfiguration();
|
|
||||||
|
|
||||||
// only support login/registration when running securely
|
|
||||||
loginConfig.setSupportsLogin(loginConfig.getSupportsLogin() && httpServletRequest.isSecure());
|
|
||||||
|
|
||||||
// create the revision
|
|
||||||
final RevisionDTO revision = new RevisionDTO();
|
|
||||||
revision.setClientId(clientId.getClientId());
|
|
||||||
|
|
||||||
// create the response entity
|
|
||||||
final LoginConfigurationEntity entity = new LoginConfigurationEntity();
|
|
||||||
entity.setRevision(revision);
|
|
||||||
entity.setConfig(loginConfig);
|
|
||||||
|
|
||||||
// generate the response
|
|
||||||
return clusterContext(generateOkResponse(entity)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the configuration for this NiFi.
|
* Retrieves the configuration for this NiFi.
|
||||||
*
|
*
|
||||||
|
|
|
@ -122,7 +122,6 @@
|
||||||
<property name="optimisticLockingManager" ref="webOptimisticLockingManager"/>
|
<property name="optimisticLockingManager" ref="webOptimisticLockingManager"/>
|
||||||
<property name="dtoFactory" ref="dtoFactory"/>
|
<property name="dtoFactory" ref="dtoFactory"/>
|
||||||
<property name="clusterManager" ref="clusterManager"/>
|
<property name="clusterManager" ref="clusterManager"/>
|
||||||
<property name="loginIdentityProvider" ref="loginIdentityProvider"/>
|
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- depecrated -->
|
<!-- depecrated -->
|
||||||
|
@ -242,6 +241,15 @@
|
||||||
<bean id="systemDiagnosticsResource" class="org.apache.nifi.web.api.SystemDiagnosticsResource" scope="singleton">
|
<bean id="systemDiagnosticsResource" class="org.apache.nifi.web.api.SystemDiagnosticsResource" scope="singleton">
|
||||||
<property name="serviceFacade" ref="serviceFacade"/>
|
<property name="serviceFacade" ref="serviceFacade"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
<bean id="accessResource" class="org.apache.nifi.web.api.AccessResource" scope="singleton">
|
||||||
|
<property name="properties" ref="nifiProperties"/>
|
||||||
|
<property name="certificateValidator" ref="certificateValidator"/>
|
||||||
|
<property name="certificateExtractor" ref="certificateExtractor"/>
|
||||||
|
<property name="principalExtractor" ref="principalExtractor"/>
|
||||||
|
<property name="loginIdentityProvider" ref="loginIdentityProvider"/>
|
||||||
|
<property name="jwtService" ref="jwtService"/>
|
||||||
|
<property name="userDetailsService" ref="userDetailsService"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<!-- configuration for jaxb serialization -->
|
<!-- configuration for jaxb serialization -->
|
||||||
<bean class="org.apache.nifi.web.util.ObjectMapperResolver" scope="singleton"/>
|
<bean class="org.apache.nifi.web.util.ObjectMapperResolver" scope="singleton"/>
|
||||||
|
|
|
@ -1,251 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.nifi.web.security;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.security.cert.CertificateExpiredException;
|
|
||||||
import java.security.cert.CertificateNotYetValidException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.apache.nifi.authentication.LoginCredentials;
|
|
||||||
import org.apache.nifi.util.NiFiProperties;
|
|
||||||
import org.apache.nifi.util.StringUtils;
|
|
||||||
import org.apache.nifi.web.security.jwt.JwtService;
|
|
||||||
import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
|
|
||||||
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
|
|
||||||
import org.apache.nifi.web.security.x509.X509CertificateValidator;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
|
||||||
import org.springframework.security.authentication.AccountStatusException;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
|
||||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exchanges a successful login with the configured provider for a ID token for accessing the API.
|
|
||||||
*/
|
|
||||||
public class RegistrationStatusFilter extends AbstractAuthenticationProcessingFilter {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RegistrationStatusFilter.class);
|
|
||||||
|
|
||||||
private NiFiProperties properties;
|
|
||||||
private JwtService jwtService;
|
|
||||||
private AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService;
|
|
||||||
private X509CertificateValidator certificateValidator;
|
|
||||||
private X509CertificateExtractor certificateExtractor;
|
|
||||||
private X509PrincipalExtractor principalExtractor;
|
|
||||||
|
|
||||||
public RegistrationStatusFilter(final String defaultFilterProcessesUrl) {
|
|
||||||
super(defaultFilterProcessesUrl);
|
|
||||||
|
|
||||||
// do not continue filter chain... simply exchaning authentication for token
|
|
||||||
setContinueChainBeforeSuccessfulAuthentication(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
|
|
||||||
// only suppport login when running securely
|
|
||||||
if (!request.isSecure()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// look for a certificate
|
|
||||||
final X509Certificate certificate = certificateExtractor.extractClientCertificate(request);
|
|
||||||
|
|
||||||
// if no certificate, just check the credentials
|
|
||||||
if (certificate == null) {
|
|
||||||
final String principal = jwtService.getAuthentication(request);
|
|
||||||
|
|
||||||
// ensure we have something we can work with (certificate or crendentials)
|
|
||||||
if (principal == null) {
|
|
||||||
throw new BadCredentialsException("Unable to check registration status as no credentials were included with the request.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// without a certificate, this is not a proxied request
|
|
||||||
final List<String> chain = Arrays.asList(principal);
|
|
||||||
|
|
||||||
// check authorization for this user
|
|
||||||
checkAuthorization(chain);
|
|
||||||
|
|
||||||
// no issues with authorization
|
|
||||||
final LoginCredentials tokenCredentials = new LoginCredentials(principal, null);
|
|
||||||
return new RegistrationStatusAuthenticationToken(tokenCredentials);
|
|
||||||
} else {
|
|
||||||
// we have a certificate so let's consider a proxy chain
|
|
||||||
final String principal = principalExtractor.extractPrincipal(certificate).toString();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// validate the certificate
|
|
||||||
certificateValidator.validateClientCertificate(request, certificate);
|
|
||||||
} catch (CertificateExpiredException cee) {
|
|
||||||
final String message = String.format("Client certificate for (%s) is expired.", principal);
|
|
||||||
logger.info(message, cee);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("", cee);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (CertificateNotYetValidException cnyve) {
|
|
||||||
final String message = String.format("Client certificate for (%s) is not yet valid.", principal);
|
|
||||||
logger.info(message, cnyve);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("", cnyve);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
logger.info(e.getMessage());
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure the proxy chain is authorized
|
|
||||||
checkAuthorization(ProxiedEntitiesUtils.buildProxyChain(request, principal));
|
|
||||||
|
|
||||||
// no issues with authorization
|
|
||||||
final LoginCredentials preAuthenticatedCredentials = new LoginCredentials(principal, null);
|
|
||||||
return new RegistrationStatusAuthenticationToken(preAuthenticatedCredentials);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the status of the proxy.
|
|
||||||
*
|
|
||||||
* @param proxyChain the proxy chain
|
|
||||||
* @throws AuthenticationException if the proxy chain is not authorized
|
|
||||||
*/
|
|
||||||
private void checkAuthorization(final List<String> proxyChain) throws AuthenticationException {
|
|
||||||
userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authentication)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
|
|
||||||
// mark as successful
|
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
|
||||||
response.setContentType("text/plain");
|
|
||||||
response.setContentLength(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void unsuccessfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException ae) throws IOException, ServletException {
|
|
||||||
// set the response status
|
|
||||||
response.setContentType("text/plain");
|
|
||||||
|
|
||||||
// write the response message
|
|
||||||
PrintWriter out = response.getWriter();
|
|
||||||
|
|
||||||
// use the type of authentication exception to determine the response code
|
|
||||||
if (ae instanceof UsernameNotFoundException) {
|
|
||||||
if (properties.getSupportNewAccountRequests()) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
out.println("Not authorized.");
|
|
||||||
} else {
|
|
||||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
out.println("Access is denied.");
|
|
||||||
}
|
|
||||||
} else if (ae instanceof AccountStatusException) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
out.println(ae.getMessage());
|
|
||||||
} else if (ae instanceof UntrustedProxyException) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
out.println(ae.getMessage());
|
|
||||||
} else if (ae instanceof AuthenticationServiceException) {
|
|
||||||
logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae);
|
|
||||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
||||||
out.println(String.format("Unable to authorize: %s", ae.getMessage()));
|
|
||||||
} else {
|
|
||||||
logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae);
|
|
||||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
out.println("Access is denied.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// log the failure
|
|
||||||
logger.info(String.format("Rejecting access to web api: %s", ae.getMessage()));
|
|
||||||
|
|
||||||
// optionally log the stack trace
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(StringUtils.EMPTY, ae);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an Authentication Token for logging in. Once a user is authenticated, they can be issues an ID token.
|
|
||||||
*/
|
|
||||||
public static class RegistrationStatusAuthenticationToken extends AbstractAuthenticationToken {
|
|
||||||
|
|
||||||
final LoginCredentials credentials;
|
|
||||||
|
|
||||||
public RegistrationStatusAuthenticationToken(final LoginCredentials credentials) {
|
|
||||||
super(null);
|
|
||||||
setAuthenticated(true);
|
|
||||||
this.credentials = credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoginCredentials getLoginCredentials() {
|
|
||||||
return credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getCredentials() {
|
|
||||||
return credentials.getPassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getPrincipal() {
|
|
||||||
return credentials.getUsername();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJwtService(JwtService jwtService) {
|
|
||||||
this.jwtService = jwtService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCertificateValidator(X509CertificateValidator certificateValidator) {
|
|
||||||
this.certificateValidator = certificateValidator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
|
|
||||||
this.certificateExtractor = certificateExtractor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
|
|
||||||
this.principalExtractor = principalExtractor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) {
|
|
||||||
this.userDetailsService = userDetailsService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProperties(NiFiProperties properties) {
|
|
||||||
this.properties = properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -26,12 +26,9 @@ import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.SignatureException;
|
import io.jsonwebtoken.SignatureException;
|
||||||
import io.jsonwebtoken.SigningKeyResolverAdapter;
|
import io.jsonwebtoken.SigningKeyResolverAdapter;
|
||||||
import io.jsonwebtoken.UnsupportedJwtException;
|
import io.jsonwebtoken.UnsupportedJwtException;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.nifi.admin.service.AdministrationException;
|
import org.apache.nifi.admin.service.AdministrationException;
|
||||||
import org.apache.nifi.admin.service.KeyService;
|
import org.apache.nifi.admin.service.KeyService;
|
||||||
|
@ -83,35 +80,25 @@ public class JwtService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a token for the specified authentication in the specified response.
|
* Generates a signed JWT token from the provided (Spring Security) login authentication token.
|
||||||
*
|
*
|
||||||
* @param response The response to add the token to
|
* @param authenticationToken
|
||||||
* @param authentication The authentication to generate a token for
|
* @return a signed JWT containing the user identity and the identity provider, Base64-encoded
|
||||||
* @throws java.io.IOException if an io exception occurs
|
|
||||||
*/
|
*/
|
||||||
public void addToken(final HttpServletResponse response, final LoginAuthenticationToken authentication) throws IOException {
|
public String generateSignedToken(final LoginAuthenticationToken authenticationToken) {
|
||||||
// set expiration to one day from now
|
// set expiration to one day from now
|
||||||
final Calendar calendar = Calendar.getInstance();
|
final Calendar calendar = Calendar.getInstance();
|
||||||
calendar.setTimeInMillis(calendar.getTimeInMillis() + authentication.getExpiration());
|
calendar.setTimeInMillis(calendar.getTimeInMillis() + authenticationToken.getExpiration());
|
||||||
|
|
||||||
// create a token the specified authentication
|
// create a token the specified authentication
|
||||||
final String identity = authentication.getPrincipal().toString();
|
final String identity = authenticationToken.getPrincipal().toString();
|
||||||
final String username = authentication.getName();
|
final String username = authenticationToken.getName();
|
||||||
|
|
||||||
// get/create the key for this user
|
// get/create the key for this user
|
||||||
final String key = keyService.getOrCreateKey(identity);
|
final String key = keyService.getOrCreateKey(identity);
|
||||||
final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
|
final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
// build the token
|
// build the token
|
||||||
final String token = Jwts.builder().setSubject(identity).claim("preferred_username", username).setExpiration(calendar.getTime()).signWith(SignatureAlgorithm.HS512, keyBytes).compact();
|
return Jwts.builder().setSubject(identity).claim("preferred_username", username).setExpiration(calendar.getTime()).signWith(SignatureAlgorithm.HS512, keyBytes).compact();
|
||||||
|
|
||||||
// add the token as a response header
|
|
||||||
final PrintWriter out = response.getWriter();
|
|
||||||
out.print(token);
|
|
||||||
|
|
||||||
// mark the response as successful
|
|
||||||
response.setStatus(HttpServletResponse.SC_CREATED);
|
|
||||||
response.setContentType("text/plain");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,243 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.nifi.web.security.login;
|
|
||||||
|
|
||||||
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.security.cert.CertificateExpiredException;
|
|
||||||
import java.security.cert.CertificateNotYetValidException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.apache.nifi.admin.service.AdministrationException;
|
|
||||||
import org.apache.nifi.authentication.AuthenticationResponse;
|
|
||||||
import org.apache.nifi.authentication.LoginCredentials;
|
|
||||||
import org.apache.nifi.authentication.LoginIdentityProvider;
|
|
||||||
import org.apache.nifi.authentication.exception.IdentityAccessException;
|
|
||||||
import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
|
|
||||||
import org.apache.nifi.util.StringUtils;
|
|
||||||
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
|
|
||||||
import org.apache.nifi.web.security.jwt.JwtService;
|
|
||||||
import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
|
|
||||||
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
|
|
||||||
import org.apache.nifi.web.security.x509.X509CertificateValidator;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
|
||||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exchanges a successful login with the configured provider for a ID token for accessing the API.
|
|
||||||
*/
|
|
||||||
public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(LoginAuthenticationFilter.class);
|
|
||||||
|
|
||||||
private AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService;
|
|
||||||
|
|
||||||
private X509CertificateValidator certificateValidator;
|
|
||||||
private X509CertificateExtractor certificateExtractor;
|
|
||||||
private X509PrincipalExtractor principalExtractor;
|
|
||||||
|
|
||||||
private LoginIdentityProvider loginIdentityProvider;
|
|
||||||
private JwtService jwtService;
|
|
||||||
|
|
||||||
public LoginAuthenticationFilter(final String defaultFilterProcessesUrl) {
|
|
||||||
super(defaultFilterProcessesUrl);
|
|
||||||
|
|
||||||
// do not continue filter chain... simply exchanging authentication for token
|
|
||||||
setContinueChainBeforeSuccessfulAuthentication(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
|
|
||||||
// only suppport login when running securely
|
|
||||||
if (!request.isSecure()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// look for the credentials in the request
|
|
||||||
final LoginCredentials credentials = getLoginCredentials(request);
|
|
||||||
|
|
||||||
// if the credentials were not part of the request, attempt to log in with the certificate in the request
|
|
||||||
if (credentials == null) {
|
|
||||||
// look for a certificate
|
|
||||||
final X509Certificate certificate = certificateExtractor.extractClientCertificate(request);
|
|
||||||
|
|
||||||
// if there is no certificate, look for an existing token
|
|
||||||
if (certificate == null) {
|
|
||||||
// if not configured for login, don't consider existing tokens
|
|
||||||
if (loginIdentityProvider == null) {
|
|
||||||
throw new BadCredentialsException("Login not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LoginAuthenticationToken(principal, loginIdentityProvider.getExpiration());
|
|
||||||
} else {
|
|
||||||
// extract the principal
|
|
||||||
final String principal = principalExtractor.extractPrincipal(certificate).toString();
|
|
||||||
|
|
||||||
try {
|
|
||||||
certificateValidator.validateClientCertificate(request, certificate);
|
|
||||||
} catch (CertificateExpiredException cee) {
|
|
||||||
final String message = String.format("Client certificate for (%s) is expired.", principal);
|
|
||||||
logger.info(message, cee);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("", cee);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (CertificateNotYetValidException cnyve) {
|
|
||||||
final String message = String.format("Client certificate for (%s) is not yet valid.", principal);
|
|
||||||
logger.info(message, cnyve);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("", cnyve);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
logger.info(e.getMessage());
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// authorize the proxy if necessary
|
|
||||||
authorizeProxyIfNecessary(ProxiedEntitiesUtils.buildProxyChain(request, principal));
|
|
||||||
|
|
||||||
return new LoginAuthenticationToken(principal, loginIdentityProvider.getExpiration());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if not configuration for login, don't consider credentials
|
|
||||||
if (loginIdentityProvider == null) {
|
|
||||||
throw new BadCredentialsException("Login not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// attempt to authenticate
|
|
||||||
final AuthenticationResponse authenticationResponse = loginIdentityProvider.authenticate(credentials);
|
|
||||||
|
|
||||||
// create the authentication token
|
|
||||||
return new LoginAuthenticationToken(authenticationResponse.getUsername(), loginIdentityProvider.getExpiration());
|
|
||||||
} catch (final InvalidLoginCredentialsException ilce) {
|
|
||||||
throw new BadCredentialsException("The supplied username and password are not valid.", ilce);
|
|
||||||
} catch (final IdentityAccessException iae) {
|
|
||||||
throw new AuthenticationServiceException(iae.getMessage(), iae);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures the proxyChain is authorized before allowing the user to be authenticated.
|
|
||||||
*
|
|
||||||
* @param proxyChain the proxy chain
|
|
||||||
* @throws AuthenticationException if the proxy chain is not authorized
|
|
||||||
*/
|
|
||||||
private void authorizeProxyIfNecessary(final List<String> proxyChain) throws AuthenticationException {
|
|
||||||
if (proxyChain.size() > 1) {
|
|
||||||
try {
|
|
||||||
userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain));
|
|
||||||
} 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 LoginCredentials getLoginCredentials(HttpServletRequest request) {
|
|
||||||
final String username = request.getParameter("username");
|
|
||||||
final String password = request.getParameter("password");
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return new LoginCredentials(username, password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authentication)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
|
|
||||||
try {
|
|
||||||
// generate JWT for response
|
|
||||||
jwtService.addToken(response, (LoginAuthenticationToken) authentication);
|
|
||||||
} catch (final AdministrationException ae) {
|
|
||||||
unsuccessfulAuthentication(request, response, new AuthenticationServiceException(ae.getMessage(), ae));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void unsuccessfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException failed) throws IOException, ServletException {
|
|
||||||
if (failed instanceof BadCredentialsException || failed instanceof AuthenticationCredentialsNotFoundException) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
|
||||||
} else if (failed instanceof AuthenticationServiceException) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
||||||
} else {
|
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.setContentType("text/plain");
|
|
||||||
|
|
||||||
final PrintWriter out = response.getWriter();
|
|
||||||
out.println(failed.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJwtService(JwtService jwtService) {
|
|
||||||
this.jwtService = jwtService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) {
|
|
||||||
this.loginIdentityProvider = loginIdentityProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCertificateValidator(X509CertificateValidator certificateValidator) {
|
|
||||||
this.certificateValidator = certificateValidator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
|
|
||||||
this.certificateExtractor = certificateExtractor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
|
|
||||||
this.principalExtractor = principalExtractor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) {
|
|
||||||
this.userDetailsService = userDetailsService;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -29,7 +29,7 @@
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- x509 validator -->
|
<!-- x509 validator -->
|
||||||
<bean id="x509Validator" class="org.apache.nifi.web.security.x509.X509CertificateValidator">
|
<bean id="certificateValidator" class="org.apache.nifi.web.security.x509.X509CertificateValidator">
|
||||||
<property name="ocspValidator" ref="ocspValidator"/>
|
<property name="ocspValidator" ref="ocspValidator"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ nf.Canvas = (function () {
|
||||||
banners: '../nifi-api/controller/banners',
|
banners: '../nifi-api/controller/banners',
|
||||||
controller: '../nifi-api/controller',
|
controller: '../nifi-api/controller',
|
||||||
controllerConfig: '../nifi-api/controller/config',
|
controllerConfig: '../nifi-api/controller/config',
|
||||||
loginConfig: '../nifi-api/controller/login/config',
|
accessConfig: '../nifi-api/access/config',
|
||||||
cluster: '../nifi-api/cluster',
|
cluster: '../nifi-api/cluster',
|
||||||
d3Script: 'js/d3/d3.min.js'
|
d3Script: 'js/d3/d3.min.js'
|
||||||
}
|
}
|
||||||
|
@ -1090,7 +1090,7 @@ nf.Canvas = (function () {
|
||||||
// get the login config
|
// get the login config
|
||||||
var loginXhr = $.ajax({
|
var loginXhr = $.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: config.urls.loginConfig,
|
url: config.urls.accessConfig,
|
||||||
dataType: 'json'
|
dataType: 'json'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,9 @@ nf.Login = (function () {
|
||||||
registration: '../nifi-api/registration',
|
registration: '../nifi-api/registration',
|
||||||
identity: '../nifi-api/controller/identity',
|
identity: '../nifi-api/controller/identity',
|
||||||
users: '../nifi-api/controller/users',
|
users: '../nifi-api/controller/users',
|
||||||
token: '../nifi-api/token',
|
token: '../nifi-api/access/token',
|
||||||
loginConfig: '../nifi-api/controller/login/config'
|
accessStatus: '../nifi-api/access',
|
||||||
|
accessConfig: '../nifi-api/access/config'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -210,167 +211,71 @@ nf.Login = (function () {
|
||||||
init: function () {
|
init: function () {
|
||||||
nf.Storage.init();
|
nf.Storage.init();
|
||||||
|
|
||||||
var showMessage = false;
|
|
||||||
var needsLogin = false;
|
|
||||||
var needsNiFiRegistration = false;
|
|
||||||
|
|
||||||
if (nf.Storage.getItem('jwt') !== null) {
|
if (nf.Storage.getItem('jwt') !== null) {
|
||||||
showLogoutLink();
|
showLogoutLink();
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = $.ajax({
|
// access status
|
||||||
|
var accessStatus = $.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: config.urls.token
|
url: config.urls.accessStatus,
|
||||||
|
dataType: 'json'
|
||||||
|
}).fail(function (xhr, status, error) {
|
||||||
|
$('#login-message-title').text('Unable to check Access Status');
|
||||||
|
$('#login-message').text(xhr.responseText);
|
||||||
|
initializeMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
var identity = $.ajax({
|
// access config
|
||||||
|
var accessConfigXhr = $.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: config.urls.identity,
|
url: config.urls.accessConfig,
|
||||||
dataType: 'json'
|
dataType: 'json'
|
||||||
});
|
});
|
||||||
|
|
||||||
var pageStateInit = $.Deferred(function (deferred) {
|
$.when(accessStatus, accessConfigXhr).done(function (accessStatusResult, accessConfigResult) {
|
||||||
// get the current user's identity
|
var accessStatusResponse = accessStatusResult[0];
|
||||||
identity.done(function (response) {
|
var accessStatus = accessStatusResponse.accessStatus;
|
||||||
// if the user is anonymous see if they need to login or if they are working with a certificate
|
|
||||||
if (response.identity === 'anonymous') {
|
var accessConfigResponse = accessConfigResult[0];
|
||||||
supportsAnonymous = true;
|
var accessConfig = accessConfigResponse.config;
|
||||||
|
|
||||||
// request a token without including credentials, if successful then the user is using a certificate
|
// record whether this NiFi supports anonymous access
|
||||||
token.done(function (jwt) {
|
supportsAnonymous = accessConfig.supportsAnonymous;
|
||||||
|
|
||||||
// the user is using a certificate/token, see if their account is active/pending/revoked/etc
|
// possible login states
|
||||||
$.ajax({
|
var needsLogin = false;
|
||||||
type: 'GET',
|
var needsNiFiRegistration = false;
|
||||||
url: config.urls.registrationStatus
|
var showMessage = false;
|
||||||
}).done(function () {
|
|
||||||
showMessage = true;
|
// handle the status appropriately
|
||||||
|
if (accessStatus.status === 'UNKNOWN') {
|
||||||
// account is active and good
|
needsLogin = true;
|
||||||
$('#login-message-title').text('Success');
|
} else if (accessStatus.status === 'UNREGISTERED') {
|
||||||
$('#login-message').text('Your account is active and you are already logged in.');
|
needsNiFiRegistration = true;
|
||||||
}).fail(function (xhr, status, error) {
|
|
||||||
if (xhr.status === 401) {
|
$('#nifi-user-submit-justification').text(accessStatus.username);
|
||||||
var user = nf.Common.getJwtSubject(jwt);
|
} else if (accessStatus.status === 'NOT_ACTIVE') {
|
||||||
|
showMessage = true;
|
||||||
// show the user
|
|
||||||
$('#nifi-user-submit-justification').text(user);
|
$('#login-message-title').text('Access Denied');
|
||||||
|
$('#login-message').text(accessStatus.message);
|
||||||
// anonymous user and 401 means they need nifi registration
|
} else if (accessStatus.status === 'ACTIVE') {
|
||||||
needsNiFiRegistration = true;
|
showMessage = true;
|
||||||
} else {
|
|
||||||
showMessage = true;
|
$('#login-message-title').text('Success');
|
||||||
|
$('#login-message').text('Your account is active and you are already logged in.');
|
||||||
// anonymous user and non-401 means they already have an account and it's pending/revoked
|
}
|
||||||
$('#login-message-title').text('Access Denied');
|
|
||||||
if ($.trim(xhr.responseText) === '') {
|
|
||||||
$('#login-message').text('Unable to check registration status.');
|
|
||||||
} else {
|
|
||||||
$('#login-message').text(xhr.responseText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).always(function () {
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
}).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();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showMessage = true;
|
|
||||||
|
|
||||||
// the user is not anonymous and has an active account (though maybe role-less)
|
|
||||||
$('#login-message-title').text('Success');
|
|
||||||
$('#login-message').text('Your account is active and you are already logged in.');
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
}).fail(function (xhr, status, error) {
|
|
||||||
// unable to get identity (and no anonymous user) see if we can offer login
|
|
||||||
if (xhr.status === 401) {
|
|
||||||
// attempt to get a token for the current user without passing login credentials
|
|
||||||
token.done(function (jwt) {
|
|
||||||
var user = nf.Common.getJwtSubject(jwt);
|
|
||||||
|
|
||||||
// show the user
|
|
||||||
$('#nifi-user-submit-justification').text(user);
|
|
||||||
|
|
||||||
// 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 (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 () {
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
} else if (xhr.status === 403) {
|
|
||||||
// attempt to get a token for the current user without passing login credentials
|
|
||||||
token.done(function () {
|
|
||||||
showMessage = true;
|
|
||||||
|
|
||||||
// the user is logged in with certificate or credentials but their account is pending/revoked. error message should indicate
|
|
||||||
$('#login-message-title').text('Access Denied');
|
|
||||||
if ($.trim(xhr.responseText) === '') {
|
|
||||||
$('#login-message').text('Unable to authorize you to use this NiFi and anonymous access is disabled.');
|
|
||||||
} else {
|
|
||||||
$('#login-message').text(xhr.responseText);
|
|
||||||
}
|
|
||||||
}).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 () {
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showMessage = true;
|
|
||||||
|
|
||||||
// the user is logged in with certificate or credentials but their account is pending/revoked. error message should indicate
|
|
||||||
$('#login-message-title').text('Access Denied');
|
|
||||||
if ($.trim(xhr.responseText) === '') {
|
|
||||||
$('#login-message').text('Unable to authorize you to use this NiFi and anonymous access is disabled.');
|
|
||||||
} else {
|
|
||||||
$('#login-message').text(xhr.responseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).promise();
|
|
||||||
|
|
||||||
var loginConfigXhr = $.ajax({
|
|
||||||
type: 'GET',
|
|
||||||
url: config.urls.loginConfig,
|
|
||||||
dataType: 'json'
|
|
||||||
});
|
|
||||||
|
|
||||||
// render the page accordingly
|
|
||||||
$.when(loginConfigXhr, pageStateInit).done(function (loginResult) {
|
|
||||||
var loginResponse = loginResult[0];
|
|
||||||
var loginConfig = loginResponse.config;
|
|
||||||
|
|
||||||
// if login is required, verify its supported
|
// if login is required, verify its supported
|
||||||
if (loginConfig.supportsLogin === false && needsLogin === true) {
|
if (accessConfig.supportsLogin === false && needsLogin === true) {
|
||||||
$('#login-message-title').text('Access Denied');
|
$('#login-message-title').text('Access Denied');
|
||||||
$('#login-message').text('This NiFi is not configured to support login.');
|
$('#login-message').text('This NiFi is not configured to support login.');
|
||||||
showMessage = true;
|
showMessage = true;
|
||||||
needsLogin = false;
|
needsLogin = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize the page as appropriate
|
||||||
if (showMessage === true) {
|
if (showMessage === true) {
|
||||||
initializeMessage();
|
initializeMessage();
|
||||||
} else if (needsLogin === true) {
|
} else if (needsLogin === true) {
|
||||||
|
|
|
@ -102,7 +102,7 @@ nf.Common = (function () {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
urls: {
|
urls: {
|
||||||
token: '../nifi-api/token'
|
token: '../nifi-api/access/token'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ nf.Common = (function () {
|
||||||
if (timeRemaining < interval) {
|
if (timeRemaining < interval) {
|
||||||
// if the token will expire before the next interval minus some bonus time, refresh now
|
// if the token will expire before the next interval minus some bonus time, refresh now
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'POST',
|
||||||
url: nf.Common.config.urls.token
|
url: nf.Common.config.urls.token
|
||||||
}).done(function (jwt) {
|
}).done(function (jwt) {
|
||||||
nf.Storage.setItem('jwt', jwt, nf.Common.getJwtExpiration(jwt));
|
nf.Storage.setItem('jwt', jwt, nf.Common.getJwtExpiration(jwt));
|
||||||
|
|
Loading…
Reference in New Issue