NIFI-655:

- Refactoring web security to use Spring Security Java Configuration.
- Introducing security in Web UI in order to get JWT.

NIFI-655:
- Setting up the resources (js/css) for the login page.

NIFI-655:
- Adding support for configuring anonymous roles.
- Addressing checkstyle violations.

NIFI-655:
- Moving to token api to web-api.
- Creating an LoginProvider API for user/pass based authentication.
- Creating a module for funneling access to the authorized useres.

NIFI-655:
- Moving away from usage of DN to identity throughout the application (from the user db to the authorization provider).
- Updating the authorized users schema to support login users.
- Creating an extension point for authentication of users based on username/password.

NIFI-655:
- Creating an endpoint for returning the identity of the current user.
- Updating the LoginAuthenticationFilter.

NIFI-655:
- Moving NiFi registration to the login page.
- Running the authentication filters in a different order to ensure we can disambiguate each case.
- Starting to layout each case... Forbidden, Login, Create User, Create NiFi Account.

NIFI-655:
- Addressing checkstyle issues.

NIFI-655:
- Making nf-storage available in the login page.
- Requiring use of local storage.
- Ignoring security for GET requests when obtaining the login configuration.

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

NIFI-655:
- Ensuring we know the necessary state before we attempt to render the login page.
- Building the proxy chain in the JWT authentication filter.
- Only rendering the login when appropriate.

NIFI-655:
- Starting to style the login page.
- Added simple 'login' support by identifying username/password. Issuing JWT token coming...
- Added logout support
- Rendering the username when appropriate.

NIFI-655:
- Extracting certificate validation into a utility class.
- Fixing checkstyle issues.
- Cleaning up the web security context.
- Removing proxy chain checking where possible.

NIFI-655:
- Starting to add support for registration.
- Creating registration form.

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.

NIFI-655:
- Allowing the user to link back to the log in page from the new account page.
- Renaming DN to identity where possible.

NIFI-655:
- Fixing checkstyle issues.

NIFI-655:
- Adding more/better support for logging out.

NIFI-655:
- Fixing checkstyle issues.

NIFI-655:
- Adding a few new exceptions for the login identity provider.

NIFI-655:
- Disabling log in by default initially.
- Restoring authorization service unit test.

NIFI-655:
- Fixing checkstyle issues.

NIFI-655:
- Updating packages for log in filters.
- Handling new registration exceptions.
- Code clean up.

NIFI-655:
- Removing registration support.
- Removing file based implementation.

NIFI-655:
- Removing file based implementation.

NIFI-655:
- Removing unused spring configuration files.

NIFI-655:
- Making the auto wiring more explicit.

NIFI-655:
- Removing unused dependencies.

NIFI-655:
- Removing unused filter.

NIFI-655:
- Updating the login API authenticate method to use a richer set of exceptions.
- UI code clean.

NIFI-655:
- Ensuring the login identity provider is able to switch context classloaders via the standard NAR mechanisms.

NIFI-655:
- Initial commit of the LDAP based identity providers.
- Fixed issue when attempting to log into a NiFi that does not support new account requests.

NIFI-655:
- Allowing the ldap provider to specify if client authentication is required/desired.

NIFI-655:
- Persisting keys to sign user tokens.
- Allowing the identity provider to specify the token expiration.
- Code clean up.

NIFI-655:
- Ensuring identities are unique in the key table.

NIFI-655:
- Adding support for specifying the user search base and user search filter in the active directory provider.

NIFI-655:
- Fixing checkstyle issues.

NIFI-655:
- Adding automatic client side token renewal.

NIFI-655:
- Ensuring the logout link is rendered when appropriate.

NIFI-655:
- Adding configuration options for referrals and connect/read timeouts

NIFI-655:
- Added an endpoint for access details including configuration, creating tokens, and checking status.
- Updated DTOs and client side to utilize new endpoints.

NIFI-655:
- Refactoring certificate extraction and validation.
- Refactoring how expiration is specified in the login identity providers.
- Adding unit tests for the access endpoints.
- Code clean up.

NIFI-655:
- Keeping token expiration between 1 minute and 12 hours.

NIFI-655:
- Using the user identity provided by the login identity provider.

NIFI-655: - Fixed typo in error message for unrecognized authentication strategy.

Signed-off-by: Matt Gilman <matt.c.gilman@gmail.com>

NIFI-655. - Added logback-test.xml configuration resource for nifi-web-security.

Signed-off-by: Matt Gilman <matt.c.gilman@gmail.com>

NIFI-655. - Added issuer field to LoginAuthenticationToken. - Updated AccessResource to pass identity provider class name when creating LoginAuthenticationTokens. - Began refactoring JWT logic from request parsing logic in JwtService. - Added unit tests for JWT logic.

Signed-off-by: Matt Gilman <matt.c.gilman@gmail.com>

NIFI-655. - Changed issuer field to use FQ class name because some classes return an empty string for getSimpleName(). - Finished refactoring JWT logic from request parsing logic in JwtService. - Updated AccessResource and JwtAuthenticationFilter to call new JwtService methods decoupled from request header parsing. - Added extensive unit tests for JWT logic.

Signed-off-by: Matt Gilman <matt.c.gilman@gmail.com>

NIFI-655:
- Refactoring key service to expose the key id.
- Handling client side expiration better.
- Removing specialized active directory provider and abstract ldap provider.

NIFI-655. - Updated JwtService and JwtServiceTest to use Key POJO instead of raw String key from KeyService.

Signed-off-by: Matt Gilman <matt.c.gilman@gmail.com>

NIFI-655:
- Fixing typo when loading the ldap connect timeout.
- Providing a better experience for session expiration.
- Using ellipsis for lengthly user name.
- Adding an issuer to the authentication response so the LIP can specify the appropriate value.

NIFI-655:
- Showing a logging in notification during the log in process.

NIFI-655:
- Removing unnecessary class.

NIFI-655:
- Fixing checkstyle issues.
- Showing the progress spinner while submitting account justification.

NIFI-655:
- Removing deprecated authentication strategy.
- Renaming TLS to START_TLS.
- Allowing the protocol to be configured.

NIFI-655:
- Fixing issue detecting the presence of DN column

NIFI-655:
- Pre-populating the login-identity-providers.xml file with necessary properties and documentation.
- Renaming the Authentication Duration property name.

NIFI-655:
- Updating documentation for the failure response codes.

NIFI-655:
- Ensuring the user identity is not too long.

NIFI-655:
- Updating default authentication expiration to 12 hours.

NIFI-655:
- Remaining on the login form when there is any unsuccessful login attempt.
- Fixing checkstyle issues.
This commit is contained in:
Matt Gilman 2015-10-07 13:33:34 -04:00
parent 4e2c94d659
commit aaf14c45c9
192 changed files with 8701 additions and 2434 deletions

22
LICENSE
View File

@ -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

View File

@ -0,0 +1,65 @@
/*
* 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.authentication;
/**
* Authentication response for a user login attempt.
*/
public class AuthenticationResponse {
private final String identity;
private final String username;
private final long expiration;
private final String issuer;
/**
* Creates an authentication response. The username and how long the authentication is valid in milliseconds
*
* @param identity The user identity
* @param username The username
* @param expiration The expiration in milliseconds
* @param issuer The issuer of the token
*/
public AuthenticationResponse(final String identity, final String username, final long expiration, final String issuer) {
this.identity = identity;
this.username = username;
this.expiration = expiration;
this.issuer = issuer;
}
public String getIdentity() {
return identity;
}
public String getUsername() {
return username;
}
public String getIssuer() {
return issuer;
}
/**
* Returns the expiration of a given authentication in milliseconds.
*
* @return The expiration in milliseconds
*/
public long getExpiration() {
return expiration;
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.authentication;
/**
* Login credentials for a user.
*/
public class LoginCredentials {
private final String username;
private final String password;
public LoginCredentials(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.authentication;
import org.apache.nifi.authentication.exception.IdentityAccessException;
import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
import org.apache.nifi.authorization.exception.ProviderCreationException;
import org.apache.nifi.authorization.exception.ProviderDestructionException;
/**
* Identity provider that is able to authentication a user with username/password credentials.
*/
public interface LoginIdentityProvider {
/**
* Authenticates the specified login credentials.
*
* @param credentials the credentials
* @return The authentication response
* @throws InvalidLoginCredentialsException The login credentials were invalid
* @throws IdentityAccessException Unable to register the user due to an issue accessing the underlying storage
*/
AuthenticationResponse authenticate(LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException;
/**
* Called immediately after instance creation for implementers to perform additional setup
*
* @param initializationContext in which to initialize
* @throws ProviderCreationException Unable to initialize
*/
void initialize(LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException;
/**
* Called to configure the AuthorityProvider.
*
* @param configurationContext at the time of configuration
* @throws ProviderCreationException for any issues configuring the provider
*/
void onConfigured(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException;
/**
* Called immediately before instance destruction for implementers to release resources.
*
* @throws ProviderDestructionException If pre-destruction fails.
*/
void preDestruction() throws ProviderDestructionException;
}

View File

@ -0,0 +1,48 @@
/*
* 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.authentication;
import java.util.Map;
/**
*
*/
public interface LoginIdentityProviderConfigurationContext {
/**
* @return identifier for the authority provider
*/
String getIdentifier();
/**
* Retrieves all properties the component currently understands regardless
* of whether a value has been set for them or not. If no value is present
* then its value is null and thus any registered default for the property
* descriptor applies.
*
* @return Map of all properties
*/
Map<String, String> getProperties();
/**
* @param property to lookup the descriptor and value of
* @return the value the component currently understands for the given
* PropertyDescriptor. This method does not substitute default
* PropertyDescriptor values, so the value returned will be null if not set
*/
String getProperty(String property);
}

View File

@ -0,0 +1,27 @@
/*
* 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.authentication;
/**
*
*/
public interface LoginIdentityProviderInitializationContext {
public String getIdentifier();
public LoginIdentityProviderLookup getAuthorityProviderLookup();
}

View File

@ -0,0 +1,25 @@
/*
* 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.authentication;
/**
*
*/
public interface LoginIdentityProviderLookup {
LoginIdentityProvider getLoginIdentityProvider(String identifier);
}

View File

@ -0,0 +1,35 @@
/*
* 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.authentication.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*
*/
@Documented
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LoginIdentityProviderContext {
}

View File

@ -0,0 +1,33 @@
/*
* 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.authentication.exception;
/**
* Represents the case when the identity could not be confirmed because it was unable
* to access the backing store.
*/
public class IdentityAccessException extends RuntimeException {
public IdentityAccessException(String message, Throwable cause) {
super(message, cause);
}
public IdentityAccessException(String message) {
super(message);
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.authentication.exception;
/**
* Represents the case when the identity could not be confirmed because the
* login credentials were invalid.
*/
public class InvalidLoginCredentialsException extends RuntimeException {
public InvalidLoginCredentialsException(String message, Throwable cause) {
super(message, cause);
}
public InvalidLoginCredentialsException(String message) {
super(message);
}
}

View File

@ -31,33 +31,33 @@ import org.apache.nifi.authorization.exception.UnknownIdentityException;
public interface AuthorityProvider {
/**
* @param dn of the user
* @return whether the user with the specified DN is known to this authority
* @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user.
* @return whether the user with the specified identity is known to this authority
* provider. It is not necessary for the user to have any authorities
*/
boolean doesDnExist(String dn) throws AuthorityAccessException;
boolean doesDnExist(String identity) throws AuthorityAccessException;
/**
* Get the authorities for the specified user. If the specified user exists
* but does not have any authorities, an empty set should be returned.
*
* @param dn of the user to lookup
* @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user.
* @return the authorities for the specified user. If the specified user
* exists but does not have any authorities, an empty set should be returned
* @throws UnknownIdentityException if identity is not known
* @throws AuthorityAccessException if unable to access authorities
*/
Set<Authority> getAuthorities(String dn) throws UnknownIdentityException, AuthorityAccessException;
Set<Authority> getAuthorities(String identity) throws UnknownIdentityException, AuthorityAccessException;
/**
* Sets the specified authorities for the specified user.
*
* @param dn the specified user
* @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user.
* @param authorities the new authorities for the user
* @throws UnknownIdentityException if identity is not known
* @throws AuthorityAccessException if unable to access authorities
*/
void setAuthorities(String dn, Set<Authority> authorities) throws UnknownIdentityException, AuthorityAccessException;
void setAuthorities(String identity, Set<Authority> authorities) throws UnknownIdentityException, AuthorityAccessException;
/**
* Gets the users for the specified authority.
@ -72,32 +72,32 @@ public interface AuthorityProvider {
* Revokes the specified user. Its up to the implementor to determine the
* semantics of revocation.
*
* @param dn the dn of the user
* @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user.
* @throws UnknownIdentityException if the user is not known
* @throws AuthorityAccessException if unable to access the authorities
*/
void revokeUser(String dn) throws UnknownIdentityException, AuthorityAccessException;
void revokeUser(String identity) throws UnknownIdentityException, AuthorityAccessException;
/**
* Add the specified user.
*
* @param dn of the user
* @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user.
* @param group Optional
* @throws UnknownIdentityException if the user is not known
* @throws AuthorityAccessException if unable to access the authorities
*/
void addUser(String dn, String group) throws IdentityAlreadyExistsException, AuthorityAccessException;
void addUser(String identity, String group) throws IdentityAlreadyExistsException, AuthorityAccessException;
/**
* Gets the group for the specified user. Return null if the user does not
* belong to a group.
*
* @param dn the user
* @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user.
* @return the group of the given user
* @throws UnknownIdentityException if the user is not known
* @throws AuthorityAccessException if unable to access the authorities
*/
String getGroupForUser(String dn) throws UnknownIdentityException, AuthorityAccessException;
String getGroupForUser(String identity) throws UnknownIdentityException, AuthorityAccessException;
/**
* Revokes all users for a specified group. Its up to the implementor to
@ -112,21 +112,21 @@ public interface AuthorityProvider {
/**
* Adds the specified users to the specified group.
*
* @param dn the set of users to add to the group
* @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user.
* @param group to add users to
* @throws UnknownIdentityException if the user is not known
* @throws AuthorityAccessException if unable to access the authorities
*/
void setUsersGroup(Set<String> dn, String group) throws UnknownIdentityException, AuthorityAccessException;
void setUsersGroup(Set<String> identity, String group) throws UnknownIdentityException, AuthorityAccessException;
/**
* Ungroups the specified user.
*
* @param dn of the user
* @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user.
* @throws UnknownIdentityException if the user is not known
* @throws AuthorityAccessException if unable to access the authorities
*/
void ungroupUser(String dn) throws UnknownIdentityException, AuthorityAccessException;
void ungroupUser(String identity) throws UnknownIdentityException, AuthorityAccessException;
/**
* Ungroups the specified group. Since the semantics of revocation is up to
@ -143,18 +143,18 @@ public interface AuthorityProvider {
* Determines whether the user in the specified dnChain should be able to
* download the content for the flowfile with the specified attributes.
*
* The first dn in the chain is the end user that the request was issued on
* behalf of. The subsequent dn's in the chain represent entities proxying
* The first identity in the chain is the end user that the request was issued on
* behalf of. The subsequent identities in the chain represent entities proxying
* the user's request with the last being the proxy that sent the current
* request.
*
* @param dnChain of the user
* @param proxyChain proxy chain of user identities that for the download request
* @param attributes of the flowfile being requested
* @return the authorization result
* @throws UnknownIdentityException if the user is not known
* @throws AuthorityAccessException if unable to access the authorities
*/
DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes) throws UnknownIdentityException, AuthorityAccessException;
DownloadAuthorization authorizeDownload(List<String> proxyChain, Map<String, String> attributes) throws UnknownIdentityException, AuthorityAccessException;
/**
* Called immediately after instance creation for implementers to perform

View File

@ -54,7 +54,7 @@ public interface NiFiWebConfigurationContext {
void saveActions(NiFiWebRequestContext requestContext, Collection<ConfigurationAction> actions);
/**
* @return the current user dn. Returns null if no user is found
* @return the current user identity. The value may be a DN, an email, a username, or any string that identities the user. Returns null if no user is found
*/
String getCurrentUserDn();

View File

@ -51,7 +51,7 @@ public interface NiFiWebContext {
void saveActions(Collection<ProcessorConfigurationAction> actions);
/**
* @return the current user dn. Returns null if no user is found
* @return the current user identity. It may be a dn, an email, a username, or any string that identities the user. Returns null if no user is found
*/
String getCurrentUserDn();

View File

@ -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

View File

@ -172,6 +172,11 @@ language governing permissions and limitations under the License. -->
<artifactId>nifi-flume-nar</artifactId>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-ldap-iaa-providers-nar</artifactId>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-dbcp-service-nar</artifactId>
@ -268,6 +273,7 @@ language governing permissions and limitations under the License. -->
<nifi.flow.configuration.file>./conf/flow.xml.gz</nifi.flow.configuration.file>
<nifi.flow.configuration.archive.dir>./conf/archive/</nifi.flow.configuration.archive.dir>
<nifi.login.identity.provider.configuration.file>./conf/login-identity-providers.xml</nifi.login.identity.provider.configuration.file>
<nifi.authority.provider.configuration.file>./conf/authority-providers.xml</nifi.authority.provider.configuration.file>
<nifi.templates.directory>./conf/templates</nifi.templates.directory>
<nifi.database.directory>./database_repository</nifi.database.directory>
@ -353,8 +359,10 @@ language governing permissions and limitations under the License. -->
<nifi.security.authorizedUsers.file>./conf/authorized-users.xml</nifi.security.authorizedUsers.file>
<nifi.security.user.credential.cache.duration>24 hours</nifi.security.user.credential.cache.duration>
<nifi.security.user.authority.provider>file-provider</nifi.security.user.authority.provider>
<nifi.security.user.login.identity.provider></nifi.security.user.login.identity.provider>
<nifi.security.x509.principal.extractor />
<nifi.security.support.new.account.requests />
<nifi.security.anonymous.authorities>ROLE_MONITOR,ROLE_DFM,ROLE_ADMIN,ROLE_PROVENANCE,ROLE_NIFI</nifi.security.anonymous.authorities>
<nifi.security.ocsp.responder.url />
<nifi.security.ocsp.responder.certificate />

View File

@ -25,10 +25,14 @@ import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,6 +49,7 @@ public class NiFiProperties extends Properties {
public static final String FLOW_CONFIGURATION_FILE = "nifi.flow.configuration.file";
public static final String FLOW_CONFIGURATION_ARCHIVE_FILE = "nifi.flow.configuration.archive.file";
public static final String AUTHORITY_PROVIDER_CONFIGURATION_FILE = "nifi.authority.provider.configuration.file";
public static final String LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE = "nifi.login.identity.provider.configuration.file";
public static final String REPOSITORY_DATABASE_DIRECTORY = "nifi.database.directory";
public static final String RESTORE_DIRECTORY = "nifi.restore.directory";
public static final String VERSION = "nifi.version";
@ -126,11 +131,12 @@ public class NiFiProperties extends Properties {
public static final String SECURITY_TRUSTSTORE_PASSWD = "nifi.security.truststorePasswd";
public static final String SECURITY_NEED_CLIENT_AUTH = "nifi.security.needClientAuth";
public static final String SECURITY_USER_AUTHORITY_PROVIDER = "nifi.security.user.authority.provider";
public static final String SECURITY_USER_LOGIN_IDENTITY_PROVIDER = "nifi.security.user.login.identity.provider";
public static final String SECURITY_CLUSTER_AUTHORITY_PROVIDER_PORT = "nifi.security.cluster.authority.provider.port";
public static final String SECURITY_CLUSTER_AUTHORITY_PROVIDER_THREADS = "nifi.security.cluster.authority.provider.threads";
public static final String SECURITY_USER_CREDENTIAL_CACHE_DURATION = "nifi.security.user.credential.cache.duration";
public static final String SECURITY_SUPPORT_NEW_ACCOUNT_REQUESTS = "nifi.security.support.new.account.requests";
public static final String SECURITY_DEFAULT_USER_ROLES = "nifi.security.default.user.roles";
public static final String SECURITY_ANONYMOUS_AUTHORITIES = "nifi.security.anonymous.authorities";
public static final String SECURITY_OCSP_RESPONDER_URL = "nifi.security.ocsp.responder.url";
public static final String SECURITY_OCSP_RESPONDER_CERTIFICATE = "nifi.security.ocsp.responder.certificate";
@ -187,6 +193,7 @@ public class NiFiProperties extends Properties {
public static final String DEFAULT_TITLE = "NiFi";
public static final Boolean DEFAULT_AUTO_RESUME_STATE = true;
public static final String DEFAULT_AUTHORITY_PROVIDER_CONFIGURATION_FILE = "conf/authority-providers.xml";
public static final String DEFAULT_LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE = "conf/login-identity-providers.xml";
public static final String DEFAULT_USER_CREDENTIAL_CACHE_DURATION = "24 hours";
public static final Integer DEFAULT_REMOTE_INPUT_PORT = null;
public static final Path DEFAULT_TEMPLATE_DIRECTORY = Paths.get("conf", "templates");
@ -234,8 +241,7 @@ public class NiFiProperties extends Properties {
}
/**
* This is the method through which the NiFiProperties object should be
* obtained.
* This is the method through which the NiFiProperties object should be obtained.
*
* @return the NiFiProperties object to use
* @throws RuntimeException if unable to load properties file
@ -424,8 +430,7 @@ public class NiFiProperties extends Properties {
}
/**
* Returns whether the processors should be started automatically when the
* application loads.
* Returns whether the processors should be started automatically when the application loads.
*
* @return Whether to auto start the processors or not
*/
@ -436,8 +441,7 @@ public class NiFiProperties extends Properties {
}
/**
* Returns the number of partitions that should be used for the FlowFile
* Repository
* Returns the number of partitions that should be used for the FlowFile Repository
*
* @return the number of partitions
*/
@ -448,8 +452,7 @@ public class NiFiProperties extends Properties {
}
/**
* Returns the number of milliseconds between FlowFileRepository
* checkpointing
* Returns the number of milliseconds between FlowFileRepository checkpointing
*
* @return the number of milliseconds between checkpoint events
*/
@ -482,6 +485,18 @@ public class NiFiProperties extends Properties {
}
}
/**
* @return the user authorities file
*/
public File getLoginIdentityProviderConfiguraitonFile() {
final String value = getProperty(LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE);
if (StringUtils.isBlank(value)) {
return new File(DEFAULT_LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE);
} else {
return new File(value);
}
}
/**
* Will default to true unless the value is explicitly set to false.
*
@ -510,6 +525,19 @@ public class NiFiProperties extends Properties {
return shouldSupport;
}
public Set<String> getAnonymousAuthorities() {
final Set<String> authorities;
final String rawAnonymousAuthorities = getProperty(SECURITY_ANONYMOUS_AUTHORITIES);
if (!StringUtils.isEmpty(rawAnonymousAuthorities)) {
authorities = new HashSet<>(Arrays.asList(rawAnonymousAuthorities.split(",")));
} else {
authorities = Collections.EMPTY_SET;
}
return authorities;
}
// getters for web properties //
public Integer getPort() {
Integer port = null;
@ -851,8 +879,7 @@ public class NiFiProperties extends Properties {
}
/**
* Returns the database repository path. It simply returns the value
* configured. No directories will be created as a result of this operation.
* Returns the database repository path. It simply returns the value configured. No directories will be created as a result of this operation.
*
* @return database repository path
* @throws InvalidPathException If the configured path is invalid
@ -862,8 +889,7 @@ public class NiFiProperties extends Properties {
}
/**
* Returns the flow file repository path. It simply returns the value
* configured. No directories will be created as a result of this operation.
* Returns the flow file repository path. It simply returns the value configured. No directories will be created as a result of this operation.
*
* @return database repository path
* @throws InvalidPathException If the configured path is invalid
@ -873,10 +899,8 @@ public class NiFiProperties extends Properties {
}
/**
* Returns the content repository paths. This method returns a mapping of
* file repository name to file repository paths. It simply returns the
* values configured. No directories will be created as a result of this
* operation.
* Returns the content repository paths. This method returns a mapping of file repository name to file repository paths. It simply returns the values configured. No directories will be created as
* a result of this operation.
*
* @return file repositories paths
* @throws InvalidPathException If any of the configured paths are invalid
@ -900,10 +924,8 @@ public class NiFiProperties extends Properties {
}
/**
* Returns the provenance repository paths. This method returns a mapping of
* file repository name to file repository paths. It simply returns the
* values configured. No directories will be created as a result of this
* operation.
* Returns the provenance repository paths. This method returns a mapping of file repository name to file repository paths. It simply returns the values configured. No directories will be created
* as a result of this operation.
*
* @return the name and paths of all provenance repository locations
*/

View File

@ -34,8 +34,7 @@ public final class CertificateUtils {
private static final Logger logger = LoggerFactory.getLogger(CertificateUtils.class);
/**
* Returns true if the given keystore can be loaded using the given keystore
* type and password. Returns false otherwise.
* Returns true if the given keystore can be loaded using the given keystore type and password. Returns false otherwise.
*
* @param keystore the keystore to validate
* @param keystoreType the type of the keystore
@ -77,40 +76,29 @@ public final class CertificateUtils {
}
/**
* Extracts the username from the specified DN. If the username cannot be
* extracted because the CN is in an unrecognized format, the entire CN is
* returned. If the CN cannot be extracted because the DN is in an
* unrecognized format, the entire DN is returned.
* Extracts the username from the specified DN. If the username cannot be extracted because the CN is in an unrecognized format, the entire CN is returned. If the CN cannot be extracted because
* the DN is in an unrecognized format, the entire DN is returned.
*
* @param dn the dn to extract the username from
* @return the exatracted username
*/
public static String extractUsername(String dn) {
String username = dn;
String cn = "";
// ensure the dn is specified
if (StringUtils.isNotBlank(dn)) {
// determine the separate
final String separator = StringUtils.indexOfIgnoreCase(dn, "/cn=") > 0 ? "/" : ",";
// attempt to locate the cn
if (dn.startsWith("CN=")) {
cn = StringUtils.substringBetween(dn, "CN=", ",");
} else if (dn.startsWith("/CN=")) {
cn = StringUtils.substringBetween(dn, "CN=", "/");
} else if (dn.startsWith("C=") || dn.startsWith("/C=")) {
cn = StringUtils.substringAfter(dn, "CN=");
} else if (dn.startsWith("/") && StringUtils.contains(dn, "CN=")) {
cn = StringUtils.substringAfter(dn, "CN=");
}
// attempt to get the username from the cn
if (StringUtils.isNotBlank(cn)) {
if (cn.endsWith(")")) {
username = StringUtils.substringBetween(cn, "(", ")");
} else if (cn.contains(" ")) {
username = StringUtils.substringAfterLast(cn, " ");
// attempt to locate the cd
final String cnPattern = "cn=";
final int cnIndex = StringUtils.indexOfIgnoreCase(dn, cnPattern);
if (cnIndex >= 0) {
int separatorIndex = StringUtils.indexOf(dn, separator, cnIndex);
if (separatorIndex > 0) {
username = StringUtils.substring(dn, cnIndex + cnPattern.length(), separatorIndex);
} else {
username = cn;
username = StringUtils.substring(dn, cnIndex + cnPattern.length());
}
}
}
@ -119,9 +107,7 @@ public final class CertificateUtils {
}
/**
* Returns a list of subject alternative names. Any name that is represented
* as a String by X509Certificate.getSubjectAlternativeNames() is converted
* to lowercase and returned.
* Returns a list of subject alternative names. Any name that is represented as a String by X509Certificate.getSubjectAlternativeNames() is converted to lowercase and returned.
*
* @param certificate a certificate
* @return a list of subject alternative names; list is never null
@ -137,12 +123,9 @@ public final class CertificateUtils {
final List<String> result = new ArrayList<>();
for (final List<?> generalName : altNames) {
/**
* generalName has the name type as the first element a String or
* byte array for the second element. We return any general names
* that are String types.
* generalName has the name type as the first element a String or byte array for the second element. We return any general names that are String types.
*
* We don't inspect the numeric name type because some certificates
* incorrectly put IPs and DNS names under the wrong name types.
* We don't inspect the numeric name type because some certificates incorrectly put IPs and DNS names under the wrong name types.
*/
final Object value = generalName.get(1);
if (value instanceof String) {

View File

@ -53,7 +53,6 @@
<excludes>**/authorization/generated/*.java,</excludes>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>

View File

@ -45,8 +45,8 @@ public class AuditDataSourceFactoryBean implements FactoryBean {
// ------------
private static final String CREATE_ACTION_TABLE = "CREATE TABLE ACTION ("
+ "ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
+ "USER_DN VARCHAR2(255) NOT NULL, "
+ "USER_NAME VARCHAR2(100) NOT NULL, "
+ "IDENTITY VARCHAR2(4096) NOT NULL, "
+ "USER_NAME VARCHAR2(4096) NOT NULL, "
+ "SOURCE_ID VARCHAR2(100) NOT NULL, "
+ "SOURCE_NAME VARCHAR2(1000) NOT NULL, "
+ "SOURCE_TYPE VARCHAR2(1000) NOT NULL, "
@ -107,6 +107,10 @@ public class AuditDataSourceFactoryBean implements FactoryBean {
+ "FOREIGN KEY (ACTION_ID) REFERENCES ACTION(ID)"
+ ")";
private static final String RENAME_DN_COLUMN = "ALTER TABLE ACTION ALTER COLUMN USER_DN RENAME TO IDENTITY";
private static final String RESIZE_IDENTITY_COLUMN = "ALTER TABLE ACTION MODIFY IDENTITY VARCHAR(4096)";
private static final String RESIZE_USER_NAME_COLUMN = "ALTER TABLE ACTION MODIFY USER_NAME VARCHAR(4096)";
private JdbcConnectionPool connectionPool;
private NiFiProperties properties;
@ -148,15 +152,15 @@ public class AuditDataSourceFactoryBean implements FactoryBean {
connection = connectionPool.getConnection();
connection.setAutoCommit(false);
// create a statement for initializing the database
statement = connection.createStatement();
// determine if the tables need to be created
rs = connection.getMetaData().getTables(null, null, "ACTION", null);
if (!rs.next()) {
logger.info("Database not built for repository: " + databaseUrl + ". Building now...");
RepositoryUtils.closeQuietly(rs);
// create a statement for initializing the database
statement = connection.createStatement();
// action table
statement.execute(CREATE_ACTION_TABLE);
@ -171,6 +175,15 @@ public class AuditDataSourceFactoryBean implements FactoryBean {
statement.execute(CREATE_PURGE_DETAILS_TABLE);
} else {
logger.info("Existing database found and connected to at: " + databaseUrl);
RepositoryUtils.closeQuietly(rs);
// check if the DN column exists to see if we need to transform the table
rs = connection.getMetaData().getColumns(null, null, "ACTION", "USER_DN");
if (rs.next()) {
statement.execute(RENAME_DN_COLUMN);
statement.execute(RESIZE_IDENTITY_COLUMN);
statement.execute(RESIZE_USER_NAME_COLUMN);
}
}
// commit any changes

View File

@ -0,0 +1,154 @@
/*
* 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.admin;
import java.io.File;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.commons.lang3.StringUtils;
import org.h2.jdbcx.JdbcConnectionPool;
import org.apache.nifi.util.NiFiProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
/**
*
*/
public class KeyDataSourceFactoryBean implements FactoryBean {
private static final Logger logger = LoggerFactory.getLogger(KeyDataSourceFactoryBean.class);
private static final String NF_USERNAME_PASSWORD = "nf";
private static final int MAX_CONNECTIONS = 5;
// database file name
private static final String KEY_DATABASE_FILE_NAME = "nifi-key";
// ----------
// keys table
// ----------
private static final String CREATE_KEY_TABLE = "CREATE TABLE KEY ("
+ "ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
+ "IDENTITY VARCHAR2(4096) NOT NULL UNIQUE, "
+ "KEY VARCHAR2(100) NOT NULL"
+ ")";
private JdbcConnectionPool connectionPool;
private NiFiProperties properties;
@Override
public Object getObject() throws Exception {
if (connectionPool == null) {
// locate the repository directory
String repositoryDirectoryPath = properties.getProperty(NiFiProperties.REPOSITORY_DATABASE_DIRECTORY);
// ensure the repository directory is specified
if (repositoryDirectoryPath == null) {
throw new NullPointerException("Database directory must be specified.");
}
// create a handle to the repository directory
File repositoryDirectory = new File(repositoryDirectoryPath);
// get a handle to the database file
File databaseFile = new File(repositoryDirectory, KEY_DATABASE_FILE_NAME);
// format the database url
String databaseUrl = "jdbc:h2:" + databaseFile + ";AUTOCOMMIT=OFF;DB_CLOSE_ON_EXIT=FALSE;LOCK_MODE=3";
String databaseUrlAppend = properties.getProperty(NiFiProperties.H2_URL_APPEND);
if (StringUtils.isNotBlank(databaseUrlAppend)) {
databaseUrl += databaseUrlAppend;
}
// create the pool
connectionPool = JdbcConnectionPool.create(databaseUrl, NF_USERNAME_PASSWORD, NF_USERNAME_PASSWORD);
connectionPool.setMaxConnections(MAX_CONNECTIONS);
Connection connection = null;
ResultSet rs = null;
Statement statement = null;
try {
// get a connection
connection = connectionPool.getConnection();
connection.setAutoCommit(false);
// create a statement for initializing the database
statement = connection.createStatement();
// determine if the tables need to be created
rs = connection.getMetaData().getTables(null, null, "KEY", null);
if (!rs.next()) {
logger.info("Database not built for repository: " + databaseUrl + ". Building now...");
RepositoryUtils.closeQuietly(rs);
// action table
statement.execute(CREATE_KEY_TABLE);
} else {
logger.info("Existing database found and connected to at: " + databaseUrl);
}
// commit any changes
connection.commit();
} catch (SQLException sqle) {
RepositoryUtils.rollback(connection, logger);
throw sqle;
} finally {
RepositoryUtils.closeQuietly(rs);
RepositoryUtils.closeQuietly(statement);
RepositoryUtils.closeQuietly(connection);
}
}
return connectionPool;
}
@Override
public Class getObjectType() {
return JdbcConnectionPool.class;
}
@Override
public boolean isSingleton() {
return true;
}
public void setProperties(NiFiProperties properties) {
this.properties = properties;
}
/**
* Disposes resources.
*/
public void shutdown() {
// shutdown the connection pool
if (connectionPool != null) {
try {
connectionPool.dispose();
} catch (Exception e) {
logger.warn("Unable to dispose of connection pool: " + e.getMessage());
if (logger.isDebugEnabled()) {
logger.warn(StringUtils.EMPTY, e);
}
}
}
}
}

View File

@ -21,8 +21,11 @@ import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.Authority;
import org.h2.jdbcx.JdbcConnectionPool;
import org.apache.nifi.user.NiFiUser;
import org.apache.nifi.util.NiFiProperties;
@ -41,8 +44,8 @@ public class UserDataSourceFactoryBean implements FactoryBean {
private static final String CREATE_USER_TABLE = "CREATE TABLE USER ("
+ "ID VARCHAR2(100) NOT NULL PRIMARY KEY, "
+ "DN VARCHAR2(255) NOT NULL UNIQUE, "
+ "USER_NAME VARCHAR2(100) NOT NULL, "
+ "IDENTITY VARCHAR2(4096) NOT NULL UNIQUE, "
+ "USER_NAME VARCHAR2(4096) NOT NULL, "
+ "USER_GROUP VARCHAR2(100), "
+ "CREATION TIMESTAMP NOT NULL, "
+ "LAST_ACCESSED TIMESTAMP, "
@ -60,57 +63,30 @@ public class UserDataSourceFactoryBean implements FactoryBean {
+ ")";
private static final String INSERT_ANONYMOUS_USER = "INSERT INTO USER ("
+ "ID, DN, USER_NAME, CREATION, LAST_VERIFIED, JUSTIFICATION, STATUS"
+ "ID, IDENTITY, USER_NAME, CREATION, LAST_VERIFIED, JUSTIFICATION, STATUS"
+ ") VALUES ("
+ "'" + UUID.randomUUID().toString() + "', "
+ "'" + NiFiUser.ANONYMOUS_USER_DN + "', "
+ "'" + NiFiUser.ANONYMOUS_USER_DN + "', "
+ "'" + NiFiUser.ANONYMOUS_USER_IDENTITY + "', "
+ "'" + NiFiUser.ANONYMOUS_USER_IDENTITY + "', "
+ "NOW(), "
+ "NOW(), "
+ "'Anonymous user needs no justification', "
+ "'ACTIVE'"
+ ")";
private static final String INSERT_ANONYMOUS_MONITOR_AUTHORITY = "INSERT INTO AUTHORITY ("
private static final String INSERT_ANONYMOUS_AUTHORITY = "INSERT INTO AUTHORITY ("
+ "USER_ID, ROLE"
+ ") VALUES ("
+ "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), "
+ "'ROLE_MONITOR'"
+ "(SELECT ID FROM USER WHERE IDENTITY = '" + NiFiUser.ANONYMOUS_USER_IDENTITY + "'), "
+ "'%s'"
+ ")";
private static final String INSERT_ANONYMOUS_DFM_AUTHORITY = "INSERT INTO AUTHORITY ("
+ "USER_ID, ROLE"
+ ") VALUES ("
+ "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), "
+ "'ROLE_DFM'"
+ ")";
private static final String DELETE_ANONYMOUS_AUTHORITIES = "DELETE FROM AUTHORITY "
+ "WHERE USER_ID = (SELECT ID FROM USER WHERE IDENTITY = '" + NiFiUser.ANONYMOUS_USER_IDENTITY + "')";
private static final String INSERT_ANONYMOUS_ADMIN_AUTHORITY = "INSERT INTO AUTHORITY ("
+ "USER_ID, ROLE"
+ ") VALUES ("
+ "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), "
+ "'ROLE_ADMIN'"
+ ")";
private static final String INSERT_ANONYMOUS_NIFI_AUTHORITY = "INSERT INTO AUTHORITY ("
+ "USER_ID, ROLE"
+ ") VALUES ("
+ "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), "
+ "'ROLE_NIFI'"
+ ")";
private static final String INSERT_ANONYMOUS_PROVENANCE_AUTHORITY = "INSERT INTO AUTHORITY ("
+ "USER_ID, ROLE"
+ ") VALUES ("
+ "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), "
+ "'ROLE_PROVENANCE'"
+ ")";
private static final String SELECT_ANONYMOUS_PROVENANCE_AUTHORITY = "SELECT * FROM AUTHORITY "
+ "WHERE "
+ "USER_ID = (SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "') "
+ "AND "
+ "ROLE = 'ROLE_PROVENANCE'";
private static final String RENAME_DN_COLUMN = "ALTER TABLE USER ALTER COLUMN DN RENAME TO IDENTITY";
private static final String RESIZE_IDENTITY_COLUMN = "ALTER TABLE USER MODIFY IDENTITY VARCHAR(4096)";
private static final String RESIZE_USER_NAME_COLUMN = "ALTER TABLE USER MODIFY USER_NAME VARCHAR(4096)";
private JdbcConnectionPool connectionPool;
@ -128,6 +104,17 @@ public class UserDataSourceFactoryBean implements FactoryBean {
throw new NullPointerException("Database directory must be specified.");
}
// get the roles being granted to anonymous users
final Set<String> rawAnonymousAuthorities = new HashSet<>(properties.getAnonymousAuthorities());
final Set<Authority> anonymousAuthorities = Authority.convertRawAuthorities(rawAnonymousAuthorities);
// ensure every authorities was recognized
if (rawAnonymousAuthorities.size() != anonymousAuthorities.size()) {
final Set<String> validAuthorities = Authority.convertAuthorities(anonymousAuthorities);
rawAnonymousAuthorities.removeAll(validAuthorities);
throw new IllegalStateException("Invalid authorities specified: " + StringUtils.join(rawAnonymousAuthorities, ", "));
}
// create a handle to the repository directory
File repositoryDirectory = new File(repositoryDirectoryPath);
@ -161,21 +148,25 @@ public class UserDataSourceFactoryBean implements FactoryBean {
// seed the anonymous user
statement.execute(INSERT_ANONYMOUS_USER);
statement.execute(INSERT_ANONYMOUS_MONITOR_AUTHORITY);
statement.execute(INSERT_ANONYMOUS_DFM_AUTHORITY);
statement.execute(INSERT_ANONYMOUS_ADMIN_AUTHORITY);
statement.execute(INSERT_ANONYMOUS_NIFI_AUTHORITY);
} else {
logger.info("Existing database found and connected to at: " + databaseUrl);
RepositoryUtils.closeQuietly(rs);
// if the DN column exists, transform the table
rs = connection.getMetaData().getColumns(null, null, "USER", "DN");
if (rs.next()) {
statement.execute(RENAME_DN_COLUMN);
statement.execute(RESIZE_IDENTITY_COLUMN);
statement.execute(RESIZE_USER_NAME_COLUMN);
}
// remove all authorities for the anonymous user
statement.execute(DELETE_ANONYMOUS_AUTHORITIES);
}
// close the previous result set
RepositoryUtils.closeQuietly(rs);
// merge in the provenance role to handle existing databases
rs = statement.executeQuery(SELECT_ANONYMOUS_PROVENANCE_AUTHORITY);
if (!rs.next()) {
statement.execute(INSERT_ANONYMOUS_PROVENANCE_AUTHORITY);
// add all authorities for the anonymous user
for (final Authority authority : anonymousAuthorities) {
statement.execute(String.format(INSERT_ANONYMOUS_AUTHORITY, authority.name()));
}
// commit any changes

View File

@ -26,4 +26,6 @@ public interface DAOFactory {
ActionDAO getActionDAO();
AuthorityDAO getAuthorityDAO();
KeyDAO getKeyDAO();
}

View File

@ -0,0 +1,49 @@
/*
* 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.admin.dao;
import org.apache.nifi.key.Key;
/**
* Key data access.
*/
public interface KeyDAO {
/**
* Gets the key for the specified user identity. Returns null if no key exists for the key id.
*
* @param id The key id
* @return The key or null
*/
Key findKeyById(int id);
/**
* Gets the latest key for the specified identity. Returns null if no key exists for the user identity.
*
* @param identity The identity
* @return The key or null
*/
Key findLatestKeyByIdentity(String identity);
/**
* Creates a key for the specified user identity.
*
* @param identity The user identity
* @return The key
*/
Key createKey(String identity);
}

View File

@ -20,6 +20,7 @@ import java.sql.Connection;
import org.apache.nifi.admin.dao.ActionDAO;
import org.apache.nifi.admin.dao.AuthorityDAO;
import org.apache.nifi.admin.dao.DAOFactory;
import org.apache.nifi.admin.dao.KeyDAO;
import org.apache.nifi.admin.dao.UserDAO;
/**
@ -48,4 +49,9 @@ public class DAOFactoryImpl implements DAOFactory {
return new StandardUserDAO(connection);
}
@Override
public KeyDAO getKeyDAO() {
return new StandardKeyDAO(connection);
}
}

View File

@ -63,7 +63,7 @@ public class StandardActionDAO implements ActionDAO {
// action table
// ------------
private static final String INSERT_ACTION = "INSERT INTO ACTION ("
+ "USER_DN, USER_NAME, SOURCE_ID, SOURCE_NAME, SOURCE_TYPE, OPERATION, ACTION_TIMESTAMP"
+ "IDENTITY, USER_NAME, SOURCE_ID, SOURCE_NAME, SOURCE_TYPE, OPERATION, ACTION_TIMESTAMP"
+ ") VALUES ("
+ "?, "
+ "?, "
@ -216,8 +216,8 @@ public class StandardActionDAO implements ActionDAO {
try {
// obtain a statement to insert to the action table
statement = connection.prepareStatement(INSERT_ACTION, Statement.RETURN_GENERATED_KEYS);
statement.setString(1, StringUtils.left(action.getUserIdentity(), 255));
statement.setString(2, StringUtils.left(action.getUserName(), 100));
statement.setString(1, StringUtils.left(action.getUserIdentity(), 4096));
statement.setString(2, StringUtils.left(action.getUserName(), 4096));
statement.setString(3, action.getSourceId());
statement.setString(4, StringUtils.left(action.getSourceName(), 1000));
statement.setString(5, action.getSourceType().toString());
@ -561,7 +561,7 @@ public class StandardActionDAO implements ActionDAO {
FlowChangeAction action = new FlowChangeAction();
action.setId(actionId);
action.setUserIdentity(rs.getString("USER_DN"));
action.setUserIdentity(rs.getString("IDENTITY"));
action.setUserName(rs.getString("USER_NAME"));
action.setOperation(Operation.valueOf(rs.getString("OPERATION")));
action.setTimestamp(new Date(rs.getTimestamp("ACTION_TIMESTAMP").getTime()));
@ -635,7 +635,7 @@ public class StandardActionDAO implements ActionDAO {
// populate the action
action = new FlowChangeAction();
action.setId(rs.getInt("ID"));
action.setUserIdentity(rs.getString("USER_DN"));
action.setUserIdentity(rs.getString("IDENTITY"));
action.setUserName(rs.getString("USER_NAME"));
action.setOperation(operation);
action.setTimestamp(new Date(rs.getTimestamp("ACTION_TIMESTAMP").getTime()));

View File

@ -0,0 +1,154 @@
/*
* 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.admin.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.UUID;
import org.apache.nifi.admin.RepositoryUtils;
import org.apache.nifi.admin.dao.DataAccessException;
import org.apache.nifi.admin.dao.KeyDAO;
import org.apache.nifi.key.Key;
/**
*
*/
public class StandardKeyDAO implements KeyDAO {
private static final String SELECT_KEY_FOR_USER_BY_ID = "SELECT ID, IDENTITY, KEY "
+ "FROM KEY "
+ "WHERE ID = ?";
private static final String SELECT_KEY_FOR_USER_BY_IDENTITY = "SELECT ID, IDENTITY, KEY "
+ "FROM KEY "
+ "WHERE IDENTITY = ?";
private static final String INSERT_KEY = "INSERT INTO KEY ("
+ "IDENTITY, KEY"
+ ") VALUES ("
+ "?, ?"
+ ")";
private final Connection connection;
public StandardKeyDAO(Connection connection) {
this.connection = connection;
}
@Override
public Key findKeyById(int id) {
Key key = null;
PreparedStatement statement = null;
ResultSet rs = null;
try {
// add each authority for the specified user
statement = connection.prepareStatement(SELECT_KEY_FOR_USER_BY_ID);
statement.setInt(1, id);
// execute the query
rs = statement.executeQuery();
// if the key was found, add it
if (rs.next()) {
key = new Key();
key.setId(rs.getInt("ID"));
key.setIdentity(rs.getString("IDENTITY"));
key.setKey(rs.getString("KEY"));
}
} catch (SQLException sqle) {
throw new DataAccessException(sqle);
} finally {
RepositoryUtils.closeQuietly(rs);
RepositoryUtils.closeQuietly(statement);
}
return key;
}
@Override
public Key findLatestKeyByIdentity(String identity) {
if (identity == null) {
throw new IllegalArgumentException("Specified identity cannot be null.");
}
Key key = null;
PreparedStatement statement = null;
ResultSet rs = null;
try {
// add each authority for the specified user
statement = connection.prepareStatement(SELECT_KEY_FOR_USER_BY_IDENTITY);
statement.setString(1, identity);
// execute the query
rs = statement.executeQuery();
// if the key was found, add it
if (rs.next()) {
key = new Key();
key.setId(rs.getInt("ID"));
key.setIdentity(rs.getString("IDENTITY"));
key.setKey(rs.getString("KEY"));
}
} catch (SQLException sqle) {
throw new DataAccessException(sqle);
} finally {
RepositoryUtils.closeQuietly(rs);
RepositoryUtils.closeQuietly(statement);
}
return key;
}
@Override
public Key createKey(final String identity) {
PreparedStatement statement = null;
ResultSet rs = null;
try {
final String keyValue = UUID.randomUUID().toString();
// add each authority for the specified user
statement = connection.prepareStatement(INSERT_KEY, Statement.RETURN_GENERATED_KEYS);
statement.setString(1, identity);
statement.setString(2, keyValue);
// insert the key
int updateCount = statement.executeUpdate();
rs = statement.getGeneratedKeys();
// verify the results
if (updateCount == 1 && rs.next()) {
final Key key = new Key();
key.setId(rs.getInt(1));
key.setIdentity(identity);
key.setKey(keyValue);
return key;
} else {
throw new DataAccessException("Unable to add key for user.");
}
} catch (SQLException sqle) {
throw new DataAccessException(sqle);
} finally {
RepositoryUtils.closeQuietly(rs);
RepositoryUtils.closeQuietly(statement);
}
}
}

View File

@ -45,9 +45,9 @@ public class StandardUserDAO implements UserDAO {
+ "FROM USER U "
+ "WHERE U.STATUS = 'PENDING'";
private static final String SELECT_USER_BY_DN = "SELECT "
private static final String SELECT_USER_BY_USER = "SELECT "
+ "U.ID, "
+ "U.DN, "
+ "U.IDENTITY, "
+ "U.USER_NAME, "
+ "U.USER_GROUP, "
+ "U.CREATION, "
@ -59,11 +59,11 @@ public class StandardUserDAO implements UserDAO {
+ "FROM USER U "
+ "LEFT JOIN AUTHORITY A " // ensures that users without authorities are still matched
+ "ON U.ID = A.USER_ID "
+ "WHERE U.DN = ?";
+ "WHERE U.IDENTITY = ?";
private static final String SELECT_USER_BY_ID = "SELECT "
+ "U.ID, "
+ "U.DN, "
+ "U.IDENTITY, "
+ "U.USER_NAME, "
+ "U.USER_GROUP, "
+ "U.CREATION, "
@ -79,7 +79,7 @@ public class StandardUserDAO implements UserDAO {
private static final String SELECT_USERS = "SELECT "
+ "U.ID, "
+ "U.DN, "
+ "U.IDENTITY, "
+ "U.USER_NAME, "
+ "U.USER_GROUP, "
+ "U.CREATION, "
@ -91,7 +91,7 @@ public class StandardUserDAO implements UserDAO {
+ "FROM USER U "
+ "LEFT JOIN AUTHORITY A " // ensures that users without authorities are still matched
+ "ON U.ID = A.USER_ID "
+ "WHERE U.DN <> ?";
+ "WHERE U.IDENTITY <> ?";
private static final String SELECT_USER_GROUPS = "SELECT DISTINCT "
+ "U.USER_GROUP "
@ -99,7 +99,7 @@ public class StandardUserDAO implements UserDAO {
private static final String SELECT_USER_GROUP = "SELECT "
+ "U.ID, "
+ "U.DN, "
+ "U.IDENTITY, "
+ "U.USER_NAME, "
+ "U.USER_GROUP, "
+ "U.CREATION, "
@ -111,10 +111,10 @@ public class StandardUserDAO implements UserDAO {
+ "FROM USER U "
+ "LEFT JOIN AUTHORITY A " // ensures that users without authorities are still matched
+ "ON U.ID = A.USER_ID "
+ "WHERE U.DN <> ? AND U.USER_GROUP = ?";
+ "WHERE U.IDENTITY <> ? AND U.USER_GROUP = ?";
private static final String INSERT_USER = "INSERT INTO USER ("
+ "ID, DN, USER_NAME, USER_GROUP, CREATION, LAST_VERIFIED, JUSTIFICATION, STATUS"
+ "ID, IDENTITY, USER_NAME, USER_GROUP, CREATION, LAST_VERIFIED, JUSTIFICATION, STATUS"
+ ") VALUES ("
+ "?, "
+ "?, "
@ -127,7 +127,7 @@ public class StandardUserDAO implements UserDAO {
+ ")";
private static final String UPDATE_USER = "UPDATE USER SET "
+ "DN = ?, "
+ "IDENTITY = ?, "
+ "USER_NAME = ?, "
+ "USER_GROUP = ?, "
+ "LAST_ACCESSED = ?, "
@ -194,7 +194,7 @@ public class StandardUserDAO implements UserDAO {
try {
// create the connection and obtain a statement
statement = connection.prepareStatement(SELECT_USERS);
statement.setString(1, NiFiUser.ANONYMOUS_USER_DN);
statement.setString(1, NiFiUser.ANONYMOUS_USER_IDENTITY);
// execute the query
rs = statement.executeQuery();
@ -211,7 +211,7 @@ public class StandardUserDAO implements UserDAO {
if (user == null || !userId.equals(user.getId())) {
user = new NiFiUser();
user.setId(userId);
user.setDn(rs.getString("DN"));
user.setIdentity(rs.getString("IDENTITY"));
user.setUserName(rs.getString("USER_NAME"));
user.setUserGroup(rs.getString("USER_GROUP"));
user.setJustification(rs.getString("JUSTIFICATION"));
@ -287,7 +287,7 @@ public class StandardUserDAO implements UserDAO {
try {
// create the connection and obtain a statement
statement = connection.prepareStatement(SELECT_USER_GROUP);
statement.setString(1, NiFiUser.ANONYMOUS_USER_DN);
statement.setString(1, NiFiUser.ANONYMOUS_USER_IDENTITY);
statement.setString(2, group);
// execute the query
@ -305,7 +305,7 @@ public class StandardUserDAO implements UserDAO {
if (user == null || !userId.equals(user.getId())) {
user = new NiFiUser();
user.setId(userId);
user.setDn(rs.getString("DN"));
user.setIdentity(rs.getString("IDENTITY"));
user.setUserName(rs.getString("USER_NAME"));
user.setUserGroup(rs.getString("USER_GROUP"));
user.setJustification(rs.getString("JUSTIFICATION"));
@ -366,7 +366,7 @@ public class StandardUserDAO implements UserDAO {
if (user == null) {
user = new NiFiUser();
user.setId(rs.getString("ID"));
user.setDn(rs.getString("DN"));
user.setIdentity(rs.getString("IDENTITY"));
user.setUserName(rs.getString("USER_NAME"));
user.setUserGroup(rs.getString("USER_GROUP"));
user.setJustification(rs.getString("JUSTIFICATION"));
@ -409,7 +409,7 @@ public class StandardUserDAO implements UserDAO {
ResultSet rs = null;
try {
// create the connection and obtain a statement
statement = connection.prepareStatement(SELECT_USER_BY_DN);
statement = connection.prepareStatement(SELECT_USER_BY_USER);
statement.setString(1, dn);
// execute the query
@ -424,7 +424,7 @@ public class StandardUserDAO implements UserDAO {
if (user == null) {
user = new NiFiUser();
user.setId(rs.getString("ID"));
user.setDn(rs.getString("DN"));
user.setIdentity(rs.getString("IDENTITY"));
user.setUserName(rs.getString("USER_NAME"));
user.setUserGroup(rs.getString("USER_GROUP"));
user.setJustification(rs.getString("JUSTIFICATION"));
@ -463,20 +463,25 @@ public class StandardUserDAO implements UserDAO {
@Override
public NiFiUser createUser(NiFiUser user) throws DataAccessException {
if (user.getDn() == null) {
throw new IllegalArgumentException("User dn must be specified.");
if (user.getIdentity() == null) {
throw new IllegalArgumentException("User identity must be specified.");
}
// ensure the user identity is not too lengthy
if (user.getIdentity().length() > 4096) {
throw new IllegalArgumentException("User identity must be less than 4096 characters.");
}
PreparedStatement statement = null;
ResultSet rs = null;
try {
final String id = UUID.nameUUIDFromBytes(user.getDn().getBytes(StandardCharsets.UTF_8)).toString();
final String id = UUID.nameUUIDFromBytes(user.getIdentity().getBytes(StandardCharsets.UTF_8)).toString();
// create a statement
statement = connection.prepareStatement(INSERT_USER, Statement.RETURN_GENERATED_KEYS);
statement.setString(1, id);
statement.setString(2, StringUtils.left(user.getDn(), 255));
statement.setString(3, StringUtils.left(user.getUserName(), 100));
statement.setString(2, StringUtils.left(user.getIdentity(), 4096));
statement.setString(3, StringUtils.left(user.getUserName(), 4096));
statement.setString(4, StringUtils.left(user.getUserGroup(), 100));
if (user.getLastVerified() != null) {
statement.setTimestamp(5, new java.sql.Timestamp(user.getLastVerified().getTime()));
@ -531,8 +536,8 @@ public class StandardUserDAO implements UserDAO {
try {
// create a statement
statement = connection.prepareStatement(UPDATE_USER);
statement.setString(1, StringUtils.left(user.getDn(), 255));
statement.setString(2, StringUtils.left(user.getUserName(), 100));
statement.setString(1, StringUtils.left(user.getIdentity(), 4096));
statement.setString(2, StringUtils.left(user.getUserName(), 4096));
statement.setString(3, StringUtils.left(user.getUserGroup(), 100));
statement.setString(6, StringUtils.left(user.getJustification(), 500));
statement.setString(7, user.getStatus().toString());

View File

@ -0,0 +1,42 @@
/*
* 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.admin.service;
import org.apache.nifi.key.Key;
/**
* Supports retrieving and issues keys for signing user tokens.
*/
public interface KeyService {
/**
* Gets a key for the specified user identity. Returns null if the user has not had a key issued
*
* @param id The key id
* @return The key or null
*/
Key getKey(int id);
/**
* Gets a key for the specified user identity. If a key does not exist, one will be created.
*
* @param identity The user identity
* @return The key
* @throws AdministrationException if it failed to get/create the key
*/
Key getOrCreateKey(String identity);
}

View File

@ -76,14 +76,14 @@ public abstract class AbstractUserAction<T> implements AdministrationAction<T> {
*/
protected void verifyAccount(AuthorityProvider authorityProvider, NiFiUser user) {
// load the roles for the user
Set<Authority> authorities = authorityProvider.getAuthorities(user.getDn());
Set<Authority> authorities = authorityProvider.getAuthorities(user.getIdentity());
// update the user's authorities
user.getAuthorities().clear();
user.getAuthorities().addAll(authorities);
// get the user group
user.setUserGroup(authorityProvider.getGroupForUser(user.getDn()));
user.setUserGroup(authorityProvider.getGroupForUser(user.getIdentity()));
// update the users status in case they were previously pending or disabled
user.setStatus(AccountStatus.ACTIVE);

View File

@ -37,11 +37,11 @@ import org.apache.nifi.user.NiFiUser;
*/
public class AuthorizeUserAction extends AbstractUserAction<NiFiUser> {
private final String dn;
private final String identity;
private final int cacheDurationSeconds;
public AuthorizeUserAction(String dn, int cacheDurationSeconds) {
this.dn = dn;
public AuthorizeUserAction(String identity, int cacheDurationSeconds) {
this.identity = identity;
this.cacheDurationSeconds = cacheDurationSeconds;
}
@ -50,14 +50,14 @@ public class AuthorizeUserAction extends AbstractUserAction<NiFiUser> {
UserDAO userDao = daoFactory.getUserDAO();
// get the user
NiFiUser user = userDao.findUserByDn(dn);
NiFiUser user = userDao.findUserByDn(identity);
// verify the user was found
if (user == null) {
// determine whether this users exists
boolean doesDnExist = false;
try {
doesDnExist = authorityProvider.doesDnExist(dn);
doesDnExist = authorityProvider.doesDnExist(identity);
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access authority details: %s", aae.getMessage()), aae);
}
@ -66,8 +66,8 @@ public class AuthorizeUserAction extends AbstractUserAction<NiFiUser> {
if (doesDnExist) {
// create the user
user = new NiFiUser();
user.setDn(dn);
user.setUserName(CertificateUtils.extractUsername(dn));
user.setIdentity(identity);
user.setUserName(CertificateUtils.extractUsername(identity));
user.setJustification("User details specified by authority provider.");
try {
@ -86,12 +86,12 @@ public class AuthorizeUserAction extends AbstractUserAction<NiFiUser> {
createUser.execute(daoFactory, authorityProvider);
} catch (UnknownIdentityException uie) {
// strange since the provider just reported this dn existed but handleing anyways...
throw new AccountNotFoundException(String.format("Unable to verify access for %s.", dn));
throw new AccountNotFoundException(String.format("Unable to verify access for %s.", identity));
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access authority details: %s", aae.getMessage()), aae);
}
} else {
throw new AccountNotFoundException(String.format("Unable to verify access for %s.", dn));
throw new AccountNotFoundException(String.format("Unable to verify access for %s.", identity));
}
} else {
Throwable providerError = null;
@ -134,7 +134,7 @@ public class AuthorizeUserAction extends AbstractUserAction<NiFiUser> {
updateUserAuthorities.execute(daoFactory, authorityProvider);
if (providerError != null) {
throw new AccountDisabledException(String.format("User credentials for %s were not found. This account has been disabled.", user.getDn()), providerError);
throw new AccountDisabledException(String.format("User credentials for %s were not found. This account has been disabled.", user.getIdentity()), providerError);
}
}
@ -165,9 +165,9 @@ public class AuthorizeUserAction extends AbstractUserAction<NiFiUser> {
*/
private void checkAccountStatus(NiFiUser user) {
if (AccountStatus.DISABLED.equals(user.getStatus())) {
throw new AccountDisabledException(String.format("Account for %s is disabled.", user.getDn()));
throw new AccountDisabledException(String.format("The account for %s has been disabled.", user.getIdentity()));
} else if (AccountStatus.PENDING.equals(user.getStatus())) {
throw new AccountPendingException(String.format("Account for %s is pending.", user.getDn()));
throw new AccountPendingException(String.format("The account for %s is currently pending approval.", user.getIdentity()));
}
}
}

View File

@ -63,12 +63,12 @@ public class DisableUserAction implements AdministrationAction<NiFiUser> {
try {
// revoke the user in the authority provider
authorityProvider.revokeUser(user.getDn());
authorityProvider.revokeUser(user.getIdentity());
} catch (UnknownIdentityException uie) {
// user identity is not known
logger.info(String.format("User %s has already been removed from the authority provider.", user.getDn()));
logger.info(String.format("User %s has already been removed from the authority provider.", user.getIdentity()));
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to revoke user '%s': %s", user.getDn(), aae.getMessage()), aae);
throw new AdministrationException(String.format("Unable to revoke user '%s': %s", user.getIdentity(), aae.getMessage()), aae);
}
return user;

View File

@ -0,0 +1,42 @@
/*
* 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.admin.service.action;
import org.apache.nifi.admin.dao.DAOFactory;
import org.apache.nifi.authorization.AuthorityProvider;
import org.apache.nifi.admin.dao.KeyDAO;
import org.apache.nifi.key.Key;
/**
* Gets a key for the specified key id.
*/
public class GetKeyByIdAction implements AdministrationAction<Key> {
private final int id;
public GetKeyByIdAction(int id) {
this.id = id;
}
@Override
public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
final KeyDAO keyDao = daoFactory.getKeyDAO();
return keyDao.findKeyById(id);
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.admin.service.action;
import org.apache.nifi.admin.dao.DAOFactory;
import org.apache.nifi.authorization.AuthorityProvider;
import org.apache.nifi.admin.dao.KeyDAO;
import org.apache.nifi.key.Key;
/**
* Gets a key for the specified key id.
*/
public class GetKeyByIdentityAction implements AdministrationAction<Key> {
private final String identity;
public GetKeyByIdentityAction(String identity) {
this.identity = identity;
}
@Override
public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
final KeyDAO keyDao = daoFactory.getKeyDAO();
return keyDao.findLatestKeyByIdentity(identity);
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.admin.service.action;
import org.apache.nifi.admin.dao.DAOFactory;
import org.apache.nifi.authorization.AuthorityProvider;
import org.apache.nifi.admin.dao.KeyDAO;
import org.apache.nifi.key.Key;
/**
* Gets a key for the specified user identity.
*/
public class GetOrCreateKeyAction implements AdministrationAction<Key> {
private final String identity;
public GetOrCreateKeyAction(String identity) {
this.identity = identity;
}
@Override
public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
final KeyDAO keyDao = daoFactory.getKeyDAO();
Key key = keyDao.findLatestKeyByIdentity(identity);
if (key == null) {
key = keyDao.createKey(identity);
}
return key;
}
}

View File

@ -30,11 +30,11 @@ import org.apache.nifi.user.NiFiUser;
*/
public class RequestUserAccountAction implements AdministrationAction<NiFiUser> {
private final String dn;
private final String identity;
private final String justification;
public RequestUserAccountAction(String dn, String justification) {
this.dn = dn;
public RequestUserAccountAction(String identity, String justification) {
this.identity = identity;
this.justification = justification;
}
@ -43,15 +43,15 @@ public class RequestUserAccountAction implements AdministrationAction<NiFiUser>
UserDAO userDao = daoFactory.getUserDAO();
// determine if this user already exists
NiFiUser user = userDao.findUserByDn(dn);
NiFiUser user = userDao.findUserByDn(identity);
if (user != null) {
throw new IllegalArgumentException(String.format("User account for %s already exists.", dn));
throw new IllegalArgumentException(String.format("User account for %s already exists.", identity));
}
// create the user
user = new NiFiUser();
user.setDn(dn);
user.setUserName(CertificateUtils.extractUsername(dn));
user.setIdentity(identity);
user.setUserName(CertificateUtils.extractUsername(identity));
user.setJustification(justification);
user.setStatus(AccountStatus.PENDING);

View File

@ -46,7 +46,7 @@ public class SeedUserAccountsAction extends AbstractUserAction<Void> {
@Override
public Void execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) throws DataAccessException {
UserDAO userDao = daoFactory.getUserDAO();
Set<String> authorizedDns = new HashSet<>();
Set<String> authorizedIdentities = new HashSet<>();
// get the current user cache
final Set<NiFiUser> existingUsers;
@ -62,7 +62,7 @@ public class SeedUserAccountsAction extends AbstractUserAction<Void> {
try {
// all users for all roles
for (final Authority authority : Authority.values()) {
authorizedDns.addAll(authorityProvider.getUsers(authority));
authorizedIdentities.addAll(authorityProvider.getUsers(authority));
}
} catch (AuthorityAccessException aae) {
// unable to access the authority provider... honor the cache
@ -73,25 +73,25 @@ public class SeedUserAccountsAction extends AbstractUserAction<Void> {
final Set<NiFiUser> accountsToRevoke = new HashSet<>(existingUsers);
// persist the users
for (String dn : authorizedDns) {
for (String identity : authorizedIdentities) {
NiFiUser user = null;
try {
// locate the user for this dn
user = userDao.findUserByDn(dn);
user = userDao.findUserByDn(identity);
boolean newAccount = false;
// if the user does not exist, create a new account
if (user == null) {
logger.info(String.format("Creating user account: %s", dn));
logger.info(String.format("Creating user account: %s", identity));
newAccount = true;
// create the user
user = new NiFiUser();
user.setDn(dn);
user.setUserName(CertificateUtils.extractUsername(dn));
user.setIdentity(identity);
user.setUserName(CertificateUtils.extractUsername(identity));
user.setJustification("User details specified by authority provider.");
} else {
logger.info(String.format("User account already created: %s. Updating authorities...", dn));
logger.info(String.format("User account already created: %s. Updating authorities...", identity));
}
// verify the account
@ -142,7 +142,7 @@ public class SeedUserAccountsAction extends AbstractUserAction<Void> {
}
try {
logger.info(String.format("User not authorized with configured provider: %s. Disabling account...", user.getDn()));
logger.info(String.format("User not authorized with configured provider: %s. Disabling account...", user.getIdentity()));
// disable the account and reset its last verified timestamp since it was not found
// in the current configured authority provider

View File

@ -56,11 +56,11 @@ public class UngroupUserAction extends AbstractUserAction<Void> {
try {
// update the authority provider
authorityProvider.ungroupUser(user.getDn());
authorityProvider.ungroupUser(user.getIdentity());
} catch (UnknownIdentityException uie) {
throw new AccountNotFoundException(String.format("Unable to ungroup user '%s': %s", user.getDn(), uie.getMessage()), uie);
throw new AccountNotFoundException(String.format("Unable to ungroup user '%s': %s", user.getIdentity(), uie.getMessage()), uie);
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to ungroup user '%s': %s", user.getDn(), aae.getMessage()), aae);
throw new AdministrationException(String.format("Unable to ungroup user '%s': %s", user.getIdentity(), aae.getMessage()), aae);
}
return null;

View File

@ -61,41 +61,41 @@ public class UpdateUserAction extends AbstractUserAction<NiFiUser> {
}
// determine whether this users exists
boolean doesDnExist = false;
boolean doesIdentityExist = false;
try {
doesDnExist = authorityProvider.doesDnExist(user.getDn());
doesIdentityExist = authorityProvider.doesDnExist(user.getIdentity());
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access authority details: %s", aae.getMessage()), aae);
}
// if the user already doesn't exist, add them
if (!doesDnExist) {
if (!doesIdentityExist) {
try {
// add the account account and group if necessary
authorityProvider.addUser(user.getDn(), user.getUserGroup());
authorityProvider.addUser(user.getIdentity(), user.getUserGroup());
} catch (final IdentityAlreadyExistsException iaee) {
logger.warn(String.format("User '%s' already exists in the authority provider. Continuing with user update.", user.getDn()));
logger.warn(String.format("User '%s' already exists in the authority provider. Continuing with user update.", user.getIdentity()));
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access authorities for '%s': %s", user.getDn(), aae.getMessage()), aae);
throw new AdministrationException(String.format("Unable to access authorities for '%s': %s", user.getIdentity(), aae.getMessage()), aae);
}
}
try {
// update the authority provider as approprivate
authorityProvider.setAuthorities(user.getDn(), authorities);
authorityProvider.setAuthorities(user.getIdentity(), authorities);
} catch (UnknownIdentityException uie) {
throw new AccountNotFoundException(String.format("Unable to modify authorities for '%s': %s.", user.getDn(), uie.getMessage()), uie);
throw new AccountNotFoundException(String.format("Unable to modify authorities for '%s': %s.", user.getIdentity(), uie.getMessage()), uie);
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getDn(), aae.getMessage()), aae);
throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getIdentity(), aae.getMessage()), aae);
}
try {
// get the user group
user.setUserGroup(authorityProvider.getGroupForUser(user.getDn()));
user.setUserGroup(authorityProvider.getGroupForUser(user.getIdentity()));
} catch (UnknownIdentityException uie) {
throw new AccountNotFoundException(String.format("Unable to determine the group for '%s': %s.", user.getDn(), uie.getMessage()), uie);
throw new AccountNotFoundException(String.format("Unable to determine the group for '%s': %s.", user.getIdentity(), uie.getMessage()), uie);
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access the group for '%s': %s.", user.getDn(), aae.getMessage()), aae);
throw new AdministrationException(String.format("Unable to access the group for '%s': %s.", user.getIdentity(), aae.getMessage()), aae);
}
// since all the authorities were updated accordingly, set the authorities

View File

@ -61,7 +61,7 @@ public class UpdateUserGroupAction extends AbstractUserAction<Void> {
// record the new users being added to this group
final Set<NiFiUser> newUsers = new HashSet<>();
final Set<String> newUserDns = new HashSet<>();
final Set<String> newUserIdentities = new HashSet<>();
// if the user ids have been specified we need to create/update a group using the specified group name
if (userIds != null) {
@ -81,13 +81,13 @@ public class UpdateUserGroupAction extends AbstractUserAction<Void> {
try {
// if the user is unknown to the authority provider we cannot continue
if (!authorityProvider.doesDnExist(user.getDn()) || AccountStatus.DISABLED.equals(user.getStatus())) {
throw new IllegalStateException(String.format("Unable to group these users because access for '%s' is not %s.", user.getDn(), AccountStatus.ACTIVE.toString()));
if (!authorityProvider.doesDnExist(user.getIdentity()) || AccountStatus.DISABLED.equals(user.getStatus())) {
throw new IllegalStateException(String.format("Unable to group these users because access for '%s' is not %s.", user.getIdentity(), AccountStatus.ACTIVE.toString()));
}
// record the user being added to this group
newUsers.add(user);
newUserDns.add(user.getDn());
newUserIdentities.add(user.getIdentity());
} catch (final AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access authority details: %s", aae.getMessage()), aae);
}
@ -95,11 +95,11 @@ public class UpdateUserGroupAction extends AbstractUserAction<Void> {
try {
// update the authority provider
authorityProvider.setUsersGroup(newUserDns, group);
authorityProvider.setUsersGroup(newUserIdentities, group);
} catch (UnknownIdentityException uie) {
throw new AccountNotFoundException(String.format("Unable to set user group '%s': %s", StringUtils.join(newUserDns, ", "), uie.getMessage()), uie);
throw new AccountNotFoundException(String.format("Unable to set user group '%s': %s", StringUtils.join(newUserIdentities, ", "), uie.getMessage()), uie);
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to set user group '%s': %s", StringUtils.join(newUserDns, ", "), aae.getMessage()), aae);
throw new AdministrationException(String.format("Unable to set user group '%s': %s", StringUtils.join(newUserIdentities, ", "), aae.getMessage()), aae);
}
}
@ -118,35 +118,35 @@ public class UpdateUserGroupAction extends AbstractUserAction<Void> {
if (authorities != null) {
try {
// update the authority provider as approprivate
authorityProvider.setAuthorities(user.getDn(), authorities);
authorityProvider.setAuthorities(user.getIdentity(), authorities);
// since all the authorities were updated accordingly, set the authorities
user.getAuthorities().clear();
user.getAuthorities().addAll(authorities);
} catch (UnknownIdentityException uie) {
throw new AccountNotFoundException(String.format("Unable to modify authorities for '%s': %s.", user.getDn(), uie.getMessage()), uie);
throw new AccountNotFoundException(String.format("Unable to modify authorities for '%s': %s.", user.getIdentity(), uie.getMessage()), uie);
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getDn(), aae.getMessage()), aae);
throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getIdentity(), aae.getMessage()), aae);
}
} else {
try {
// refresh the authorities according to the provider
user.getAuthorities().clear();
user.getAuthorities().addAll(authorityProvider.getAuthorities(user.getDn()));
user.getAuthorities().addAll(authorityProvider.getAuthorities(user.getIdentity()));
} catch (UnknownIdentityException uie) {
throw new AccountNotFoundException(String.format("Unable to determine the authorities for '%s': %s.", user.getDn(), uie.getMessage()), uie);
throw new AccountNotFoundException(String.format("Unable to determine the authorities for '%s': %s.", user.getIdentity(), uie.getMessage()), uie);
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getDn(), aae.getMessage()), aae);
throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getIdentity(), aae.getMessage()), aae);
}
}
try {
// get the user group
user.setUserGroup(authorityProvider.getGroupForUser(user.getDn()));
user.setUserGroup(authorityProvider.getGroupForUser(user.getIdentity()));
} catch (UnknownIdentityException uie) {
throw new AccountNotFoundException(String.format("Unable to determine the group for '%s': %s.", user.getDn(), uie.getMessage()), uie);
throw new AccountNotFoundException(String.format("Unable to determine the group for '%s': %s.", user.getIdentity(), uie.getMessage()), uie);
} catch (AuthorityAccessException aae) {
throw new AdministrationException(String.format("Unable to access the group for '%s': %s.", user.getDn(), aae.getMessage()), aae);
throw new AdministrationException(String.format("Unable to access the group for '%s': %s.", user.getIdentity(), aae.getMessage()), aae);
}
// update the users status in case they were previously pending or disabled

View File

@ -0,0 +1,126 @@
/*
* 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.admin.service.impl;
import org.apache.nifi.admin.dao.DataAccessException;
import org.apache.nifi.admin.service.AdministrationException;
import org.apache.nifi.admin.service.KeyService;
import org.apache.nifi.admin.service.action.GetKeyByIdAction;
import org.apache.nifi.admin.service.action.GetOrCreateKeyAction;
import org.apache.nifi.admin.service.transaction.Transaction;
import org.apache.nifi.admin.service.transaction.TransactionBuilder;
import org.apache.nifi.admin.service.transaction.TransactionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.nifi.key.Key;
/**
*
*/
public class StandardKeyService implements KeyService {
private static final Logger logger = LoggerFactory.getLogger(StandardKeyService.class);
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
private TransactionBuilder transactionBuilder;
@Override
public Key getKey(int id) {
Transaction transaction = null;
Key key = null;
readLock.lock();
try {
// start the transaction
transaction = transactionBuilder.start();
// get the key
GetKeyByIdAction addActions = new GetKeyByIdAction(id);
key = transaction.execute(addActions);
// commit the transaction
transaction.commit();
} catch (TransactionException | DataAccessException te) {
rollback(transaction);
throw new AdministrationException(te);
} catch (Throwable t) {
rollback(transaction);
throw t;
} finally {
closeQuietly(transaction);
readLock.unlock();
}
return key;
}
@Override
public Key getOrCreateKey(String identity) {
Transaction transaction = null;
Key key = null;
writeLock.lock();
try {
// start the transaction
transaction = transactionBuilder.start();
// seed the accounts
GetOrCreateKeyAction addActions = new GetOrCreateKeyAction(identity);
key = transaction.execute(addActions);
// commit the transaction
transaction.commit();
} catch (TransactionException | DataAccessException te) {
rollback(transaction);
throw new AdministrationException(te);
} catch (Throwable t) {
rollback(transaction);
throw t;
} finally {
closeQuietly(transaction);
writeLock.unlock();
}
return key;
}
private void rollback(Transaction transaction) {
if (transaction != null) {
transaction.rollback();
}
}
private void closeQuietly(final Transaction transaction) {
if (transaction != null) {
try {
transaction.close();
} catch (final IOException ioe) {
}
}
}
public void setTransactionBuilder(TransactionBuilder transactionBuilder) {
this.transactionBuilder = transactionBuilder;
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.key;
import java.io.Serializable;
/**
* An signing key for a NiFi user.
*/
public class Key implements Serializable {
private int id;
private String identity;
private String key;
/**
* The key id.
*
* @return the id
*/
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
/**
* The identity of the user this key is associated with.
*
* @return the identity
*/
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
/**
* The signing key.
*
* @return the signing key
*/
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}

View File

@ -29,10 +29,10 @@ import org.apache.commons.lang3.StringUtils;
*/
public class NiFiUser implements Serializable {
public static final String ANONYMOUS_USER_DN = "anonymous";
public static final String ANONYMOUS_USER_IDENTITY = "anonymous";
private String id;
private String dn;
private String identity;
private String userName;
private String userGroup;
private String justification;
@ -55,12 +55,12 @@ public class NiFiUser implements Serializable {
this.creation = creation;
}
public String getDn() {
return dn;
public String getIdentity() {
return identity;
}
public void setDn(String dn) {
this.dn = dn;
public void setIdentity(String identity) {
this.identity = identity;
}
public String getUserName() {
@ -143,7 +143,7 @@ public class NiFiUser implements Serializable {
return false;
}
final NiFiUser other = (NiFiUser) obj;
if (!Objects.equals(this.dn, other.dn)) {
if (!Objects.equals(this.identity, other.identity)) {
return false;
}
return true;
@ -152,13 +152,13 @@ public class NiFiUser implements Serializable {
@Override
public int hashCode() {
int hash = 7;
hash = 53 * hash + Objects.hashCode(this.dn);
hash = 53 * hash + Objects.hashCode(this.identity);
return hash;
}
@Override
public String toString() {
return String.format("dn[%s], userName[%s], justification[%s], authorities[%s]", getDn(), getUserName(), getJustification(), StringUtils.join(getAuthorities(), ", "));
return String.format("identity[%s], userName[%s], justification[%s], authorities[%s]", getIdentity(), getUserName(), getJustification(), StringUtils.join(getAuthorities(), ", "));
}
}

View File

@ -36,6 +36,11 @@
<bean id="auditDataSource" class="org.apache.nifi.admin.AuditDataSourceFactoryBean" destroy-method="shutdown" depends-on="userDataSource">
<property name="properties" ref="nifiProperties"/>
</bean>
<!-- initialize the data source -->
<bean id="keyDataSource" class="org.apache.nifi.admin.KeyDataSourceFactoryBean" destroy-method="shutdown">
<property name="properties" ref="nifiProperties"/>
</bean>
<!-- initialize the user transaction builder -->
<bean id="userTransactionBuilder" class="org.apache.nifi.admin.service.transaction.impl.StandardTransactionBuilder">
@ -48,6 +53,12 @@
<property name="authorityProvider" ref="authorityProvider"/>
<property name="dataSource" ref="auditDataSource"/>
</bean>
<!-- initialize the key transaction builder -->
<bean id="keyTransactionBuilder" class="org.apache.nifi.admin.service.transaction.impl.StandardTransactionBuilder">
<property name="authorityProvider" ref="authorityProvider"/>
<property name="dataSource" ref="keyDataSource"/>
</bean>
<!-- administration service -->
<bean id="userService" class="org.apache.nifi.admin.service.impl.StandardUserService" init-method="seedUserAccounts">
@ -59,4 +70,9 @@
<bean id="auditService" class="org.apache.nifi.admin.service.impl.StandardAuditService">
<property name="transactionBuilder" ref="auditTransactionBuilder"/>
</bean>
<!-- key service -->
<bean id="keyService" class="org.apache.nifi.admin.service.impl.StandardKeyService">
<property name="transactionBuilder" ref="keyTransactionBuilder"/>
</bean>
</beans>

View File

@ -54,17 +54,17 @@ public class AuthorizeUserActionTest {
private static final String USER_ID_10 = "10";
private static final String USER_ID_11 = "11";
private static final String USER_DN_1 = "authority access exception while searching for user";
private static final String USER_DN_2 = "unknown user";
private static final String USER_DN_3 = "user removed after checking existence";
private static final String USER_DN_4 = "access exception getting authorities";
private static final String USER_DN_5 = "error creating user account";
private static final String USER_DN_6 = "create user general sequence";
private static final String USER_DN_7 = "existing user requires verification";
private static final String USER_DN_8 = "existing user does not require verification";
private static final String USER_DN_9 = "existing pending user";
private static final String USER_DN_10 = "existing disabled user";
private static final String USER_DN_11 = "existing user is now unknown in the authority provider";
private static final String USER_IDENTITY_1 = "authority access exception while searching for user";
private static final String USER_IDENTITY_2 = "unknown user";
private static final String USER_IDENTITY_3 = "user removed after checking existence";
private static final String USER_IDENTITY_4 = "access exception getting authorities";
private static final String USER_IDENTITY_5 = "error creating user account";
private static final String USER_IDENTITY_6 = "create user general sequence";
private static final String USER_IDENTITY_7 = "existing user requires verification";
private static final String USER_IDENTITY_8 = "existing user does not require verification";
private static final String USER_IDENTITY_9 = "existing pending user";
private static final String USER_IDENTITY_10 = "existing disabled user";
private static final String USER_IDENTITY_11 = "existing user is now unknown in the authority provider";
private DAOFactory daoFactory;
private UserDAO userDao;
@ -85,18 +85,18 @@ public class AuthorizeUserActionTest {
if (USER_ID_7.equals(id)) {
user = new NiFiUser();
user.setId(USER_ID_7);
user.setDn(USER_DN_7);
user.setIdentity(USER_IDENTITY_7);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
} else if (USER_ID_8.equals(id)) {
user = new NiFiUser();
user.setId(USER_ID_8);
user.setDn(USER_DN_8);
user.setIdentity(USER_IDENTITY_8);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
user.setLastVerified(new Date());
} else if (USER_ID_11.equals(id)) {
user = new NiFiUser();
user.setId(USER_ID_11);
user.setDn(USER_DN_11);
user.setIdentity(USER_IDENTITY_11);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
user.setStatus(AccountStatus.ACTIVE);
}
@ -112,35 +112,35 @@ public class AuthorizeUserActionTest {
NiFiUser user = null;
switch (dn) {
case USER_DN_7:
case USER_IDENTITY_7:
user = new NiFiUser();
user.setId(USER_ID_7);
user.setDn(USER_DN_7);
user.setIdentity(USER_IDENTITY_7);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
break;
case USER_DN_8:
case USER_IDENTITY_8:
user = new NiFiUser();
user.setId(USER_ID_8);
user.setDn(USER_DN_8);
user.setIdentity(USER_IDENTITY_8);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
user.setLastVerified(new Date());
break;
case USER_DN_9:
case USER_IDENTITY_9:
user = new NiFiUser();
user.setId(USER_ID_9);
user.setDn(USER_DN_9);
user.setIdentity(USER_IDENTITY_9);
user.setStatus(AccountStatus.PENDING);
break;
case USER_DN_10:
case USER_IDENTITY_10:
user = new NiFiUser();
user.setId(USER_ID_10);
user.setDn(USER_DN_10);
user.setIdentity(USER_IDENTITY_10);
user.setStatus(AccountStatus.DISABLED);
break;
case USER_DN_11:
case USER_IDENTITY_11:
user = new NiFiUser();
user.setId(USER_ID_11);
user.setDn(USER_DN_11);
user.setIdentity(USER_IDENTITY_11);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
user.setStatus(AccountStatus.ACTIVE);
break;
@ -154,10 +154,10 @@ public class AuthorizeUserActionTest {
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
NiFiUser user = (NiFiUser) args[0];
switch (user.getDn()) {
case USER_DN_5:
switch (user.getIdentity()) {
case USER_IDENTITY_5:
throw new DataAccessException();
case USER_DN_6:
case USER_IDENTITY_6:
user.setId(USER_ID_6);
break;
}
@ -215,9 +215,9 @@ public class AuthorizeUserActionTest {
Object[] args = invocation.getArguments();
String dn = (String) args[0];
switch (dn) {
case USER_DN_1:
case USER_IDENTITY_1:
throw new AuthorityAccessException(StringUtils.EMPTY);
case USER_DN_2:
case USER_IDENTITY_2:
return false;
}
@ -231,21 +231,21 @@ public class AuthorizeUserActionTest {
String dn = (String) args[0];
Set<Authority> authorities = EnumSet.noneOf(Authority.class);
switch (dn) {
case USER_DN_3:
case USER_IDENTITY_3:
throw new UnknownIdentityException(StringUtils.EMPTY);
case USER_DN_4:
case USER_IDENTITY_4:
throw new AuthorityAccessException(StringUtils.EMPTY);
case USER_DN_6:
case USER_IDENTITY_6:
authorities.add(Authority.ROLE_MONITOR);
break;
case USER_DN_7:
case USER_IDENTITY_7:
authorities.add(Authority.ROLE_DFM);
break;
case USER_DN_9:
case USER_IDENTITY_9:
throw new UnknownIdentityException(StringUtils.EMPTY);
case USER_DN_10:
case USER_IDENTITY_10:
throw new UnknownIdentityException(StringUtils.EMPTY);
case USER_DN_11:
case USER_IDENTITY_11:
throw new UnknownIdentityException(StringUtils.EMPTY);
}
@ -272,7 +272,7 @@ public class AuthorizeUserActionTest {
*/
@Test(expected = AdministrationException.class)
public void testAuthorityAccessExceptionInDoesDnExist() throws Exception {
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_1, 0);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_1, 0);
authorizeUser.execute(daoFactory, authorityProvider);
}
@ -283,7 +283,7 @@ public class AuthorizeUserActionTest {
*/
@Test(expected = AccountNotFoundException.class)
public void testUnknownUser() throws Exception {
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_2, 0);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_2, 0);
authorizeUser.execute(daoFactory, authorityProvider);
}
@ -294,7 +294,7 @@ public class AuthorizeUserActionTest {
*/
@Test(expected = AccountNotFoundException.class)
public void testUserRemovedAfterCheckingExistence() throws Exception {
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_3, 0);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_3, 0);
authorizeUser.execute(daoFactory, authorityProvider);
}
@ -305,7 +305,7 @@ public class AuthorizeUserActionTest {
*/
@Test(expected = AdministrationException.class)
public void testAuthorityAccessException() throws Exception {
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_4, 0);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_4, 0);
authorizeUser.execute(daoFactory, authorityProvider);
}
@ -316,7 +316,7 @@ public class AuthorizeUserActionTest {
*/
@Test(expected = DataAccessException.class)
public void testErrorCreatingUserAccount() throws Exception {
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_5, 0);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_5, 0);
authorizeUser.execute(daoFactory, authorityProvider);
}
@ -327,11 +327,11 @@ public class AuthorizeUserActionTest {
*/
@Test
public void testAccountCreation() throws Exception {
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_6, 0);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_6, 0);
NiFiUser user = authorizeUser.execute(daoFactory, authorityProvider);
// verify the user
Assert.assertEquals(USER_DN_6, user.getDn());
Assert.assertEquals(USER_IDENTITY_6, user.getIdentity());
Assert.assertEquals(1, user.getAuthorities().size());
Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_MONITOR));
@ -347,11 +347,11 @@ public class AuthorizeUserActionTest {
*/
@Test
public void testExistingUserRequiresVerification() throws Exception {
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_7, 0);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_7, 0);
NiFiUser user = authorizeUser.execute(daoFactory, authorityProvider);
// verify the user
Assert.assertEquals(USER_DN_7, user.getDn());
Assert.assertEquals(USER_IDENTITY_7, user.getIdentity());
Assert.assertEquals(1, user.getAuthorities().size());
Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_DFM));
@ -369,11 +369,11 @@ public class AuthorizeUserActionTest {
@Test
public void testExistingUserNoVerification() throws Exception {
// disabling verification by passing in a large cache duration
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_8, Integer.MAX_VALUE);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_8, Integer.MAX_VALUE);
NiFiUser user = authorizeUser.execute(daoFactory, authorityProvider);
// verify the user
Assert.assertEquals(USER_DN_8, user.getDn());
Assert.assertEquals(USER_IDENTITY_8, user.getIdentity());
Assert.assertEquals(1, user.getAuthorities().size());
Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_MONITOR));
@ -391,7 +391,7 @@ public class AuthorizeUserActionTest {
@Test(expected = AccountPendingException.class)
public void testExistingPendingUser() throws Exception {
// disabling verification by passing in a large cache duration
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_9, Integer.MAX_VALUE);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_9, Integer.MAX_VALUE);
authorizeUser.execute(daoFactory, authorityProvider);
}
@ -403,7 +403,7 @@ public class AuthorizeUserActionTest {
@Test(expected = AccountDisabledException.class)
public void testExistingDisabledUser() throws Exception {
// disabling verification by passing in a large cache duration
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_10, Integer.MAX_VALUE);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_10, Integer.MAX_VALUE);
authorizeUser.execute(daoFactory, authorityProvider);
}
@ -416,7 +416,7 @@ public class AuthorizeUserActionTest {
@Test
public void testExistingActiveUserNotFoundInProvider() throws Exception {
try {
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_11, 0);
AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_11, 0);
authorizeUser.execute(daoFactory, authorityProvider);
Assert.fail();

View File

@ -40,8 +40,8 @@ public class CreateUserActionTest {
private final String USER_ID_2 = "2";
private final String USER_ID_3 = "3";
private final String USER_DN_1 = "data access exception when creating user";
private final String USER_DN_3 = "general create user case";
private final String USER_IDENTITY_1 = "data access exception when creating user";
private final String USER_IDENTITY_3 = "general create user case";
private DAOFactory daoFactory;
private UserDAO userDao;
@ -57,9 +57,9 @@ public class CreateUserActionTest {
Object[] args = invocation.getArguments();
NiFiUser user = (NiFiUser) args[0];
if (USER_DN_1.equals(user.getDn())) {
if (USER_IDENTITY_1.equals(user.getIdentity())) {
throw new DataAccessException();
} else if (USER_DN_3.equals(user.getDn())) {
} else if (USER_IDENTITY_3.equals(user.getIdentity())) {
user.setId(USER_ID_3);
}
@ -100,7 +100,7 @@ public class CreateUserActionTest {
@Test(expected = DataAccessException.class)
public void testExceptionCreatingUser() throws Exception {
NiFiUser user = new NiFiUser();
user.setDn(USER_DN_1);
user.setIdentity(USER_IDENTITY_1);
CreateUserAction createUser = new CreateUserAction(user);
createUser.execute(daoFactory, null);
@ -128,7 +128,7 @@ public class CreateUserActionTest {
@Test
public void testCreateUserAccount() throws Exception {
NiFiUser user = new NiFiUser();
user.setDn(USER_DN_3);
user.setIdentity(USER_IDENTITY_3);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_DFM, Authority.ROLE_ADMIN));
CreateUserAction createUser = new CreateUserAction(user);

View File

@ -40,8 +40,8 @@ public class DisableUserActionTest {
private static final String USER_ID_3 = "3";
private static final String USER_ID_4 = "4";
private static final String USER_DN_3 = "authority access exception";
private static final String USER_DN_4 = "general disable user case";
private static final String USER_IDENTITY_3 = "authority access exception";
private static final String USER_IDENTITY_4 = "general disable user case";
private DAOFactory daoFactory;
private UserDAO userDao;
@ -66,11 +66,11 @@ public class DisableUserActionTest {
} else if (USER_ID_3.equals(id)) {
user = new NiFiUser();
user.setId(id);
user.setDn(USER_DN_3);
user.setIdentity(USER_IDENTITY_3);
} else if (USER_ID_4.equals(id)) {
user = new NiFiUser();
user.setId(id);
user.setDn(USER_DN_4);
user.setIdentity(USER_IDENTITY_4);
user.setStatus(AccountStatus.ACTIVE);
}
return user;
@ -103,7 +103,7 @@ public class DisableUserActionTest {
Object[] args = invocation.getArguments();
String dn = (String) args[0];
if (USER_DN_3.equals(dn)) {
if (USER_IDENTITY_3.equals(dn)) {
throw new AuthorityAccessException(StringUtils.EMPTY);
}
@ -158,11 +158,11 @@ public class DisableUserActionTest {
// verify the user
Assert.assertEquals(USER_ID_4, user.getId());
Assert.assertEquals(USER_DN_4, user.getDn());
Assert.assertEquals(USER_IDENTITY_4, user.getIdentity());
Assert.assertEquals(AccountStatus.DISABLED, user.getStatus());
// verify the interaction with the dao and provider
Mockito.verify(userDao, Mockito.times(1)).updateUser(user);
Mockito.verify(authorityProvider, Mockito.times(1)).revokeUser(USER_DN_4);
Mockito.verify(authorityProvider, Mockito.times(1)).revokeUser(USER_IDENTITY_4);
}
}

View File

@ -36,9 +36,9 @@ public class RequestUserAccountActionTest {
private static final String USER_ID_3 = "3";
private static final String USER_DN_1 = "existing user account dn";
private static final String USER_DN_2 = "data access exception";
private static final String USER_DN_3 = "new account request";
private static final String USER_IDENTITY_1 = "existing user account";
private static final String USER_IDENTITY_2 = "data access exception";
private static final String USER_IDENTITY_3 = "new account request";
private DAOFactory daoFactory;
private UserDAO userDao;
@ -54,7 +54,7 @@ public class RequestUserAccountActionTest {
String dn = (String) args[0];
NiFiUser user = null;
if (USER_DN_1.equals(dn)) {
if (USER_IDENTITY_1.equals(dn)) {
user = new NiFiUser();
}
return user;
@ -65,10 +65,10 @@ public class RequestUserAccountActionTest {
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
NiFiUser user = (NiFiUser) args[0];
switch (user.getDn()) {
case USER_DN_2:
switch (user.getIdentity()) {
case USER_IDENTITY_2:
throw new DataAccessException();
case USER_DN_3:
case USER_IDENTITY_3:
user.setId(USER_ID_3);
break;
}
@ -90,7 +90,7 @@ public class RequestUserAccountActionTest {
*/
@Test(expected = IllegalArgumentException.class)
public void testExistingAccount() throws Exception {
RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_DN_1, StringUtils.EMPTY);
RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_IDENTITY_1, StringUtils.EMPTY);
requestUserAccount.execute(daoFactory, null);
}
@ -102,7 +102,7 @@ public class RequestUserAccountActionTest {
*/
@Test(expected = DataAccessException.class)
public void testDataAccessException() throws Exception {
RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_DN_2, StringUtils.EMPTY);
RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_IDENTITY_2, StringUtils.EMPTY);
requestUserAccount.execute(daoFactory, null);
}
@ -113,12 +113,12 @@ public class RequestUserAccountActionTest {
*/
@Test
public void testRequestUserAccountAction() throws Exception {
RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_DN_3, StringUtils.EMPTY);
RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_IDENTITY_3, StringUtils.EMPTY);
NiFiUser user = requestUserAccount.execute(daoFactory, null);
// verfiy the user
Assert.assertEquals(USER_ID_3, user.getId());
Assert.assertEquals(USER_DN_3, user.getDn());
Assert.assertEquals(USER_IDENTITY_3, user.getIdentity());
Assert.assertEquals(AccountStatus.PENDING, user.getStatus());
// verify interaction with dao

View File

@ -44,10 +44,10 @@ public class SeedUserAccountsActionTest {
private static final String USER_ID_3 = "3";
private static final String USER_ID_4 = "4";
private static final String USER_DN_1 = "user dn 1 - active user - remove monitor and operator, add dfm";
private static final String USER_DN_2 = "user dn 2 - active user - no action";
private static final String USER_DN_3 = "user dn 3 - pending user - add operator";
private static final String USER_DN_4 = "user dn 4 - new user - add monitor";
private static final String USER_IDENTITY_1 = "user 1 - active user - remove monitor and operator, add dfm";
private static final String USER_IDENTITY_2 = "user 2 - active user - no action";
private static final String USER_IDENTITY_3 = "user 3 - pending user - add operator";
private static final String USER_IDENTITY_4 = "user 4 - new user - add monitor";
private DAOFactory daoFactory;
private UserDAO userDao;
@ -68,19 +68,19 @@ public class SeedUserAccountsActionTest {
if (USER_ID_1.equals(id)) {
user = new NiFiUser();
user.setId(USER_ID_1);
user.setDn(USER_DN_1);
user.setIdentity(USER_IDENTITY_1);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
user.setStatus(AccountStatus.ACTIVE);
} else if (USER_ID_2.equals(id)) {
user = new NiFiUser();
user.setId(USER_ID_2);
user.setDn(USER_DN_2);
user.setIdentity(USER_IDENTITY_2);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_ADMIN));
user.setStatus(AccountStatus.ACTIVE);
} else if (USER_ID_3.equals(id)) {
user = new NiFiUser();
user.setId(USER_ID_3);
user.setDn(USER_DN_3);
user.setIdentity(USER_IDENTITY_3);
user.setStatus(AccountStatus.PENDING);
}
return user;
@ -93,22 +93,22 @@ public class SeedUserAccountsActionTest {
String dn = (String) args[0];
NiFiUser user = null;
if (USER_DN_1.equals(dn)) {
if (USER_IDENTITY_1.equals(dn)) {
user = new NiFiUser();
user.setId(USER_ID_1);
user.setDn(USER_DN_1);
user.setIdentity(USER_IDENTITY_1);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
user.setStatus(AccountStatus.ACTIVE);
} else if (USER_DN_2.equals(dn)) {
} else if (USER_IDENTITY_2.equals(dn)) {
user = new NiFiUser();
user.setId(USER_ID_2);
user.setDn(USER_DN_2);
user.setIdentity(USER_IDENTITY_2);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_ADMIN));
user.setStatus(AccountStatus.ACTIVE);
} else if (USER_DN_3.equals(dn)) {
} else if (USER_IDENTITY_3.equals(dn)) {
user = new NiFiUser();
user.setId(USER_ID_3);
user.setDn(USER_DN_3);
user.setIdentity(USER_IDENTITY_3);
user.setStatus(AccountStatus.PENDING);
}
return user;
@ -120,7 +120,7 @@ public class SeedUserAccountsActionTest {
Object[] args = invocation.getArguments();
NiFiUser user = (NiFiUser) args[0];
if (USER_DN_4.equals(user.getDn())) {
if (USER_IDENTITY_4.equals(user.getIdentity())) {
user.setId(USER_ID_4);
}
@ -141,13 +141,13 @@ public class SeedUserAccountsActionTest {
Set<String> users = new HashSet<>();
if (Authority.ROLE_DFM.equals(role)) {
users.add(USER_DN_1);
users.add(USER_IDENTITY_1);
} else if (Authority.ROLE_ADMIN.equals(role)) {
users.add(USER_DN_2);
users.add(USER_IDENTITY_2);
} else if (Authority.ROLE_PROXY.equals(role)) {
users.add(USER_DN_3);
users.add(USER_IDENTITY_3);
} else if (Authority.ROLE_MONITOR.equals(role)) {
users.add(USER_DN_4);
users.add(USER_IDENTITY_4);
}
return users;
}
@ -160,16 +160,16 @@ public class SeedUserAccountsActionTest {
Set<Authority> authorities = EnumSet.noneOf(Authority.class);
switch (dn) {
case USER_DN_1:
case USER_IDENTITY_1:
authorities.add(Authority.ROLE_DFM);
break;
case USER_DN_2:
case USER_IDENTITY_2:
authorities.add(Authority.ROLE_ADMIN);
break;
case USER_DN_3:
case USER_IDENTITY_3:
authorities.add(Authority.ROLE_PROXY);
break;
case USER_DN_4:
case USER_IDENTITY_4:
authorities.add(Authority.ROLE_MONITOR);
break;
}

View File

@ -46,8 +46,8 @@ public class SetUserAuthoritiesActionTest {
private static final String USER_ID_2 = "2";
private static final String USER_ID_3 = "3";
private static final String USER_DN_2 = "user dn 2";
private static final String USER_DN_3 = "user dn 3";
private static final String USER_IDENTITY_2 = "user 2";
private static final String USER_IDENTITY_3 = "user 3";
private DAOFactory daoFactory;
private UserDAO userDao;
@ -70,11 +70,11 @@ public class SetUserAuthoritiesActionTest {
} else if (USER_ID_2.equals(id)) {
user = new NiFiUser();
user.setId(USER_ID_2);
user.setDn(USER_DN_2);
user.setIdentity(USER_IDENTITY_2);
} else if (USER_ID_3.equals(id)) {
user = new NiFiUser();
user.setId(USER_ID_3);
user.setDn(USER_DN_3);
user.setIdentity(USER_IDENTITY_3);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
user.setStatus(AccountStatus.ACTIVE);
}
@ -88,10 +88,10 @@ public class SetUserAuthoritiesActionTest {
String dn = (String) args[0];
NiFiUser user = null;
if (USER_DN_3.equals(dn)) {
if (USER_IDENTITY_3.equals(dn)) {
user = new NiFiUser();
user.setId(USER_ID_3);
user.setDn(USER_DN_3);
user.setIdentity(USER_IDENTITY_3);
user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR));
user.setStatus(AccountStatus.ACTIVE);
}
@ -148,7 +148,7 @@ public class SetUserAuthoritiesActionTest {
String dn = (String) args[0];
Set<Authority> authorities = EnumSet.noneOf(Authority.class);
if (USER_DN_3.equals(dn)) {
if (USER_IDENTITY_3.equals(dn)) {
authorities.add(Authority.ROLE_DFM);
}
@ -162,7 +162,7 @@ public class SetUserAuthoritiesActionTest {
String dn = (String) args[0];
Set<Authority> authorites = (Set<Authority>) args[1];
if (USER_DN_2.equals(dn)) {
if (USER_IDENTITY_2.equals(dn)) {
throw new AuthorityAccessException(StringUtils.EMPTY);
}
@ -218,6 +218,6 @@ public class SetUserAuthoritiesActionTest {
Set<Authority> authoritiesAddedToProvider = EnumSet.of(Authority.ROLE_ADMIN);
// verify interaction with provider
Mockito.verify(authorityProvider, Mockito.times(1)).setAuthorities(USER_DN_3, authoritiesAddedToProvider);
Mockito.verify(authorityProvider, Mockito.times(1)).setAuthorities(USER_IDENTITY_3, authoritiesAddedToProvider);
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.XmlType;
/**
* Details for the access configuration.
*/
@XmlType(name = "accessConfig")
public class AccessConfigurationDTO {
private Boolean supportsLogin;
private Boolean supportsAnonymous;
/**
* @return Indicates whether or not this NiFi supports user login.
*/
@ApiModelProperty(
value = "Indicates whether or not this NiFi supports user login.",
readOnly = true
)
public Boolean getSupportsLogin() {
return supportsLogin;
}
public void setSupportsLogin(Boolean 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;
}
}

View File

@ -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;
}
}

View File

@ -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.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 AccessConfigurationDTO.
*/
@XmlRootElement(name = "accessConfigurationEntity")
public class AccessConfigurationEntity extends Entity {
private AccessConfigurationDTO config;
/**
* The AccessConfigurationDTO that is being serialized.
*
* @return The AccessConfigurationDTO object
*/
public AccessConfigurationDTO getConfig() {
return config;
}
public void setConfig(AccessConfigurationDTO config) {
this.config = config;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,52 @@
/*
* 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;
/**
* 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 identity.
*/
@XmlRootElement(name = "identityEntity")
public class IdentityEntity extends Entity {
private String userId;
private String identity;
/**
* @return current user id
*/
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
/**
* @return the user identity being serialized
*/
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
}

View File

@ -22,6 +22,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authorization.AuthorityProvider;
import org.apache.nifi.controller.ControllerService;
@ -38,9 +39,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Scans through the classpath to load all FlowFileProcessors,
* FlowFileComparators, and ReportingTasks using the service provider API and
* running through all classloaders (root, NARs).
* Scans through the classpath to load all FlowFileProcessors, FlowFileComparators, and ReportingTasks using the service provider API and running through all classloaders (root, NARs).
*
* @ThreadSafe - is immutable
*/
@ -60,6 +59,7 @@ public class ExtensionManager {
definitionMap.put(ReportingTask.class, new HashSet<Class>());
definitionMap.put(ControllerService.class, new HashSet<Class>());
definitionMap.put(AuthorityProvider.class, new HashSet<Class>());
definitionMap.put(LoginIdentityProvider.class, new HashSet<Class>());
definitionMap.put(ProvenanceEventRepository.class, new HashSet<Class>());
definitionMap.put(ComponentStatusRepository.class, new HashSet<Class>());
definitionMap.put(FlowFileRepository.class, new HashSet<Class>());
@ -68,9 +68,7 @@ public class ExtensionManager {
}
/**
* Loads all FlowFileProcessor, FlowFileComparator, ReportingTask class
* types that can be found on the bootstrap classloader and by creating
* classloaders for all NARs found within the classpath.
* Loads all FlowFileProcessor, FlowFileComparator, ReportingTask class types that can be found on the bootstrap classloader and by creating classloaders for all NARs found within the classpath.
*/
public static void discoverExtensions() {
final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
@ -113,8 +111,7 @@ public class ExtensionManager {
}
/**
* Registers extension for the specified type from the specified
* ClassLoader.
* Registers extension for the specified type from the specified ClassLoader.
*
* @param type the extension type
* @param classloaderMap mapping of classname to classloader
@ -152,9 +149,7 @@ public class ExtensionManager {
}
/**
* Determines the effective classloader for classes of the given type. If
* returns null it indicates the given type is not known or was not
* detected.
* Determines the effective classloader for classes of the given type. If returns null it indicates the given type is not known or was not detected.
*
* @param classType to lookup the classloader of
* @return String of fully qualified class name; null if not a detected type

View File

@ -23,6 +23,7 @@ import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authorization.AuthorityProvider;
import org.apache.nifi.components.Validator;
@ -58,6 +59,7 @@ public class NarThreadContextClassLoader extends URLClassLoader {
narSpecificClasses.add(StreamCallback.class);
narSpecificClasses.add(ControllerService.class);
narSpecificClasses.add(AuthorityProvider.class);
narSpecificClasses.add(LoginIdentityProvider.class);
narSpecificClasses.add(ProvenanceEventRepository.class);
narSpecificClasses.add(ComponentStatusRepository.class);
narSpecificClasses.add(FlowFileRepository.class);

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
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.
-->
<!--
This file lists the login identity providers to use when running securely. In order
to use a specific provider it must be configured here and it's identifier
must be specified in the nifi.properties file.
-->
<loginIdentityProviders>
<!--
Identity Provider for users logging in with username/password against an LDAP server.
'Authentication Strategy' - How the connection to the LDAP server is authenticated. Possible
values are ANONYMOUS, SIMPLE, or START_TLS.
'Manager DN' - The DN of the manager that is used to bind to the LDAP server to search for users.
'Manager Password' - The password of the manager that is used to bind to the LDAP server to
search for users.
'TLS - Keystore' - Path to the Keystore that is used when connecting to LDAP using START_TLS.
'TLS - Keystore Password' - Password for the Keystore that is used when connecting to LDAP
using START_TLS.
'TLS - Keystore Type' - Type of the Keystore that is used when connecting to LDAP using
START_TLS (i.e. JKS or PKCS12).
'TLS - Truststore' - Path to the Truststore that is used when connecting to LDAP using START_TLS.
'TLS - Truststore Password' - Password for the Truststore that is used when connecting to
LDAP using START_TLS.
'TLS - Truststore Type' - Type of the Truststore that is used when connecting to LDAP using
START_TLS (i.e. JKS or PKCS12).
'TLS - Client Auth' - Client authentication policy when connecting to LDAP using START_TLS.
Possible values are REQUIRED, WANT, NONE.
'TLS - Protocol' - Protocol to use when connecting to LDAP using START_TLS. (i.e. TLS,
TLSv1.1, TLSv1.2, etc).
'TLS - Shutdown Gracefully' - Specifies whether the TLS should be shut down gracefully
before the target context is closed. Defaults to false.
'Referral Strategy' - Strategy for handling referrals. Possible values are FOLLOW, IGNORE, THROW.
'Connect Timeout' - Duration of connect timeout. (i.e. 10 secs).
'Read Timeout' - Duration of read timeout. (i.e. 10 secs).
'Url' - Url of the LDAP servier (i.e. ldap://<hostname>:<port>).
'User Search Base' - Base DN for searching for users (i.e. CN=Users,DC=example,DC=com).
'User Search Filter' - Filter for searching for users against the 'User Search Base'.
(i.e. sAMAccountName={0}). The user specified name is inserted into '{0}'.
'Authentication Expiration' - The duration of how long the user authentication is valid
for. If the user never logs out, they will be required to log back in following
this duration.
-->
<!-- To enable the ldap-provider remove 2 lines. This is 1 of 2.
<provider>
<identifier>ldap-provider</identifier>
<class>org.apache.nifi.ldap.LdapProvider</class>
<property name="Authentication Strategy">START_TLS</property>
<property name="Manager DN"></property>
<property name="Manager Password"></property>
<property name="TLS - Keystore"></property>
<property name="TLS - Keystore Password"></property>
<property name="TLS - Keystore Type"></property>
<property name="TLS - Truststore"></property>
<property name="TLS - Truststore Password"></property>
<property name="TLS - Truststore Type"></property>
<property name="TLS - Client Auth"></property>
<property name="TLS - Protocol"></property>
<property name="TLS - Shutdown Gracefully"></property>
<property name="Referral Strategy">FOLLOW</property>
<property name="Connect Timeout">10 secs</property>
<property name="Read Timeout">10 secs</property>
<property name="Url"></property>
<property name="User Search Base"></property>
<property name="User Search Filter"></property>
<property name="Expiration Duration">12 hours</property>
</provider>
To enable the ldap-provider remove 2 lines. This is 2 of 2. -->
</loginIdentityProviders>

View File

@ -25,6 +25,7 @@ nifi.administrative.yield.duration=${nifi.administrative.yield.duration}
nifi.bored.yield.duration=${nifi.bored.yield.duration}
nifi.authority.provider.configuration.file=${nifi.authority.provider.configuration.file}
nifi.login.identity.provider.configuration.file=${nifi.login.identity.provider.configuration.file}
nifi.templates.directory=${nifi.templates.directory}
nifi.ui.banner.text=${nifi.ui.banner.text}
nifi.ui.autorefresh.interval=${nifi.ui.autorefresh.interval}
@ -124,7 +125,9 @@ nifi.security.truststorePasswd=${nifi.security.truststorePasswd}
nifi.security.needClientAuth=${nifi.security.needClientAuth}
nifi.security.user.credential.cache.duration=${nifi.security.user.credential.cache.duration}
nifi.security.user.authority.provider=${nifi.security.user.authority.provider}
nifi.security.user.login.identity.provider=${nifi.security.user.login.identity.provider}
nifi.security.support.new.account.requests=${nifi.security.support.new.account.requests}
nifi.security.anonymous.authorities=${nifi.security.anonymous.authorities}
nifi.security.ocsp.responder.url=${nifi.security.ocsp.responder.url}
nifi.security.ocsp.responder.certificate=${nifi.security.ocsp.responder.certificate}

View File

@ -615,8 +615,12 @@ public class JettyServer implements NiFiServer {
private SslContextFactory createSslContextFactory() {
final SslContextFactory contextFactory = new SslContextFactory();
// need client auth
contextFactory.setNeedClientAuth(props.getNeedClientAuth());
// require client auth when not supporting login or anonymous access
if (StringUtils.isBlank(props.getProperty(NiFiProperties.SECURITY_USER_LOGIN_IDENTITY_PROVIDER)) && props.getAnonymousAuthorities().isEmpty()) {
contextFactory.setNeedClientAuth(true);
} else {
contextFactory.setWantClientAuth(true);
}
/* below code sets JSSE system properties when values are provided */
// keystore properties

View File

@ -78,7 +78,7 @@ public class ControllerAuditor extends NiFiAuditor {
// create the config action
FlowChangeAction configAction = new FlowChangeAction();
configAction.setUserIdentity(user.getDn());
configAction.setUserIdentity(user.getIdentity());
configAction.setUserName(user.getUserName());
configAction.setOperation(Operation.Configure);
configAction.setTimestamp(new Date());
@ -131,7 +131,7 @@ public class ControllerAuditor extends NiFiAuditor {
// create the config action
FlowChangeAction configAction = new FlowChangeAction();
configAction.setUserIdentity(user.getDn());
configAction.setUserIdentity(user.getIdentity());
configAction.setUserName(user.getUserName());
configAction.setOperation(Operation.Configure);
configAction.setTimestamp(new Date());
@ -184,7 +184,7 @@ public class ControllerAuditor extends NiFiAuditor {
// create the config action
FlowChangeAction configAction = new FlowChangeAction();
configAction.setUserIdentity(user.getDn());
configAction.setUserIdentity(user.getIdentity());
configAction.setUserName(user.getUserName());
configAction.setOperation(Operation.Configure);
configAction.setTimestamp(new Date());
@ -237,7 +237,7 @@ public class ControllerAuditor extends NiFiAuditor {
// create the config action
FlowChangeAction configAction = new FlowChangeAction();
configAction.setUserIdentity(user.getDn());
configAction.setUserIdentity(user.getIdentity());
configAction.setUserName(user.getUserName());
configAction.setOperation(Operation.Configure);
configAction.setTimestamp(new Date());

View File

@ -167,7 +167,7 @@ public class ControllerServiceAuditor extends NiFiAuditor {
// create a configuration action
FlowChangeAction configurationAction = new FlowChangeAction();
configurationAction.setUserIdentity(user.getDn());
configurationAction.setUserIdentity(user.getIdentity());
configurationAction.setUserName(user.getUserName());
configurationAction.setOperation(operation);
configurationAction.setTimestamp(actionTimestamp);
@ -187,7 +187,7 @@ public class ControllerServiceAuditor extends NiFiAuditor {
if (isDisabled != updateIsDisabled) {
// create a controller service action
FlowChangeAction serviceAction = new FlowChangeAction();
serviceAction.setUserIdentity(user.getDn());
serviceAction.setUserIdentity(user.getIdentity());
serviceAction.setUserName(user.getUserName());
serviceAction.setTimestamp(new Date());
serviceAction.setSourceId(controllerService.getIdentifier());
@ -271,7 +271,7 @@ public class ControllerServiceAuditor extends NiFiAuditor {
// create a processor action
FlowChangeAction processorAction = new FlowChangeAction();
processorAction.setUserIdentity(user.getDn());
processorAction.setUserIdentity(user.getIdentity());
processorAction.setUserName(user.getUserName());
processorAction.setTimestamp(new Date());
processorAction.setSourceId(processor.getIdentifier());
@ -289,7 +289,7 @@ public class ControllerServiceAuditor extends NiFiAuditor {
// create a reporting task action
FlowChangeAction reportingTaskAction = new FlowChangeAction();
reportingTaskAction.setUserIdentity(user.getDn());
reportingTaskAction.setUserIdentity(user.getIdentity());
reportingTaskAction.setUserName(user.getUserName());
reportingTaskAction.setTimestamp(new Date());
reportingTaskAction.setSourceId(reportingTask.getIdentifier());
@ -307,7 +307,7 @@ public class ControllerServiceAuditor extends NiFiAuditor {
// create a controller service action
FlowChangeAction serviceAction = new FlowChangeAction();
serviceAction.setUserIdentity(user.getDn());
serviceAction.setUserIdentity(user.getIdentity());
serviceAction.setUserName(user.getUserName());
serviceAction.setTimestamp(new Date());
serviceAction.setSourceId(controllerService.getIdentifier());
@ -387,7 +387,7 @@ public class ControllerServiceAuditor extends NiFiAuditor {
// create the controller service action for adding this controller service
action = new FlowChangeAction();
action.setUserIdentity(user.getDn());
action.setUserIdentity(user.getIdentity());
action.setUserName(user.getUserName());
action.setOperation(operation);
action.setTimestamp(new Date());

View File

@ -121,7 +121,7 @@ public class FunnelAuditor extends NiFiAuditor {
if (user != null) {
// create the action for adding this funnel
action = new FlowChangeAction();
action.setUserIdentity(user.getDn());
action.setUserIdentity(user.getIdentity());
action.setUserName(user.getUserName());
action.setOperation(operation);
action.setTimestamp(new Date());

View File

@ -205,7 +205,7 @@ public class PortAuditor extends NiFiAuditor {
for (ActionDetails detail : configurationDetails) {
// create the port action for updating the name
FlowChangeAction portAction = new FlowChangeAction();
portAction.setUserIdentity(user.getDn());
portAction.setUserIdentity(user.getIdentity());
portAction.setUserName(user.getUserName());
portAction.setOperation(Operation.Configure);
portAction.setTimestamp(timestamp);
@ -225,7 +225,7 @@ public class PortAuditor extends NiFiAuditor {
if (scheduledState != updatedScheduledState) {
// create a processor action
FlowChangeAction processorAction = new FlowChangeAction();
processorAction.setUserIdentity(user.getDn());
processorAction.setUserIdentity(user.getIdentity());
processorAction.setUserName(user.getUserName());
processorAction.setTimestamp(new Date());
processorAction.setSourceId(updatedPort.getIdentifier());
@ -323,7 +323,7 @@ public class PortAuditor extends NiFiAuditor {
// create the port action for adding this processor
action = new FlowChangeAction();
action.setUserIdentity(user.getDn());
action.setUserIdentity(user.getIdentity());
action.setUserName(user.getUserName());
action.setOperation(operation);
action.setTimestamp(new Date());

View File

@ -140,7 +140,7 @@ public class ProcessGroupAuditor extends NiFiAuditor {
// create the port action for updating the name
FlowChangeAction processGroupAction = new FlowChangeAction();
processGroupAction.setUserIdentity(user.getDn());
processGroupAction.setUserIdentity(user.getIdentity());
processGroupAction.setUserName(user.getUserName());
processGroupAction.setOperation(operation);
processGroupAction.setTimestamp(timestamp);
@ -157,7 +157,7 @@ public class ProcessGroupAuditor extends NiFiAuditor {
if (processGroupDTO.isRunning() != null) {
// create a process group action
FlowChangeAction processGroupAction = new FlowChangeAction();
processGroupAction.setUserIdentity(user.getDn());
processGroupAction.setUserIdentity(user.getIdentity());
processGroupAction.setUserName(user.getUserName());
processGroupAction.setSourceId(processGroup.getIdentifier());
processGroupAction.setSourceName(processGroup.getName());
@ -242,7 +242,7 @@ public class ProcessGroupAuditor extends NiFiAuditor {
// create the process group action for adding this process group
action = new FlowChangeAction();
action.setUserIdentity(user.getDn());
action.setUserIdentity(user.getIdentity());
action.setUserName(user.getUserName());
action.setOperation(operation);
action.setTimestamp(new Date());

View File

@ -177,7 +177,7 @@ public class ProcessorAuditor extends NiFiAuditor {
// create a configuration action
FlowChangeAction configurationAction = new FlowChangeAction();
configurationAction.setUserIdentity(user.getDn());
configurationAction.setUserIdentity(user.getIdentity());
configurationAction.setUserName(user.getUserName());
configurationAction.setOperation(operation);
configurationAction.setTimestamp(actionTimestamp);
@ -197,7 +197,7 @@ public class ProcessorAuditor extends NiFiAuditor {
if (scheduledState != updatedScheduledState) {
// create a processor action
FlowChangeAction processorAction = new FlowChangeAction();
processorAction.setUserIdentity(user.getDn());
processorAction.setUserIdentity(user.getIdentity());
processorAction.setUserName(user.getUserName());
processorAction.setTimestamp(new Date());
processorAction.setSourceId(processor.getIdentifier());
@ -294,7 +294,7 @@ public class ProcessorAuditor extends NiFiAuditor {
// create the processor action for adding this processor
action = new FlowChangeAction();
action.setUserIdentity(user.getDn());
action.setUserIdentity(user.getIdentity());
action.setUserName(user.getUserName());
action.setOperation(operation);
action.setTimestamp(new Date());

View File

@ -188,7 +188,7 @@ public class RelationshipAuditor extends NiFiAuditor {
// create a configuration action
FlowChangeAction configurationAction = new FlowChangeAction();
configurationAction.setUserIdentity(user.getDn());
configurationAction.setUserIdentity(user.getIdentity());
configurationAction.setUserName(user.getUserName());
configurationAction.setOperation(Operation.Configure);
configurationAction.setTimestamp(actionTimestamp);
@ -353,7 +353,7 @@ public class RelationshipAuditor extends NiFiAuditor {
// create a new relationship action
action = new FlowChangeAction();
action.setUserIdentity(user.getDn());
action.setUserIdentity(user.getIdentity());
action.setUserName(user.getUserName());
action.setOperation(operation);
action.setTimestamp(actionTimestamp);

View File

@ -246,7 +246,7 @@ public class RemoteProcessGroupAuditor extends NiFiAuditor {
for (ActionDetails detail : details) {
// create the port action for updating the name
FlowChangeAction remoteProcessGroupAction = new FlowChangeAction();
remoteProcessGroupAction.setUserIdentity(user.getDn());
remoteProcessGroupAction.setUserIdentity(user.getIdentity());
remoteProcessGroupAction.setUserName(user.getUserName());
remoteProcessGroupAction.setOperation(Operation.Configure);
remoteProcessGroupAction.setTimestamp(timestamp);
@ -267,7 +267,7 @@ public class RemoteProcessGroupAuditor extends NiFiAuditor {
if (transmissionState != updatedTransmissionState) {
// create a processor action
FlowChangeAction remoteProcessGroupAction = new FlowChangeAction();
remoteProcessGroupAction.setUserIdentity(user.getDn());
remoteProcessGroupAction.setUserIdentity(user.getIdentity());
remoteProcessGroupAction.setUserName(user.getUserName());
remoteProcessGroupAction.setTimestamp(new Date());
remoteProcessGroupAction.setSourceId(updatedRemoteProcessGroup.getIdentifier());
@ -356,7 +356,7 @@ public class RemoteProcessGroupAuditor extends NiFiAuditor {
// create the remote process group action
action = new FlowChangeAction();
action.setUserIdentity(user.getDn());
action.setUserIdentity(user.getIdentity());
action.setUserName(user.getUserName());
action.setOperation(operation);
action.setTimestamp(new Date());

View File

@ -160,7 +160,7 @@ public class ReportingTaskAuditor extends NiFiAuditor {
// create a configuration action
FlowChangeAction configurationAction = new FlowChangeAction();
configurationAction.setUserIdentity(user.getDn());
configurationAction.setUserIdentity(user.getIdentity());
configurationAction.setUserName(user.getUserName());
configurationAction.setOperation(operation);
configurationAction.setTimestamp(actionTimestamp);
@ -180,7 +180,7 @@ public class ReportingTaskAuditor extends NiFiAuditor {
if (scheduledState != updatedScheduledState) {
// create a reporting task action
FlowChangeAction taskAction = new FlowChangeAction();
taskAction.setUserIdentity(user.getDn());
taskAction.setUserIdentity(user.getIdentity());
taskAction.setUserName(user.getUserName());
taskAction.setTimestamp(new Date());
taskAction.setSourceId(reportingTask.getIdentifier());
@ -276,7 +276,7 @@ public class ReportingTaskAuditor extends NiFiAuditor {
// create the reporting task action for adding this reporting task
action = new FlowChangeAction();
action.setUserIdentity(user.getDn());
action.setUserIdentity(user.getIdentity());
action.setUserName(user.getUserName());
action.setOperation(operation);
action.setTimestamp(new Date());

View File

@ -232,7 +232,7 @@ public class SnippetAuditor extends NiFiAuditor {
if (user != null) {
// create the action for adding this funnel
action = new FlowChangeAction();
action.setUserIdentity(user.getDn());
action.setUserIdentity(user.getIdentity());
action.setUserName(user.getUserName());
action.setOperation(operation);
action.setTimestamp(timestamp);

View File

@ -1266,6 +1266,13 @@ public interface NiFiServiceFacade {
*/
Collection<UserDTO> getUsers(Boolean grouped);
/**
* Creates a new account request.
*
* @return user
*/
UserDTO createUser();
/**
* Updates the specified user accordingly.
*

View File

@ -0,0 +1,40 @@
/*
* 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;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
/**
*
*/
@Configuration
@Import({NiFiWebApiSecurityConfiguration.class})
@ImportResource({"classpath:nifi-context.xml",
"classpath:nifi-administration-context.xml",
"classpath:nifi-cluster-manager-context.xml",
"classpath:nifi-cluster-protocol-context.xml",
"classpath:nifi-web-security-context.xml",
"classpath:nifi-web-api-context.xml"})
public class NiFiWebApiConfiguration {
public NiFiWebApiConfiguration() {
super();
}
}

View File

@ -0,0 +1,179 @@
/*
* 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;
import org.apache.nifi.admin.service.UserService;
import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.NiFiAuthenticationProvider;
import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter;
import org.apache.nifi.web.security.NiFiAuthenticationEntryPoint;
import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter;
import org.apache.nifi.web.security.jwt.JwtService;
import org.apache.nifi.web.security.node.NodeAuthorizedUserFilter;
import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
import org.apache.nifi.web.security.x509.X509AuthenticationFilter;
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
import org.apache.nifi.web.security.x509.X509IdentityProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
/**
* NiFi Web Api Spring security
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
private NiFiProperties properties;
private UserService userService;
private AuthenticationUserDetailsService userDetailsService;
private JwtService jwtService;
private X509CertificateExtractor certificateExtractor;
private X509IdentityProvider certificateIdentityProvider;
private LoginIdentityProvider loginIdentityProvider;
public NiFiWebApiSecurityConfiguration() {
super(true); // disable defaults
}
@Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity
.ignoring()
.antMatchers("/access/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe().disable()
.exceptionHandling()
.authenticationEntryPoint(new NiFiAuthenticationEntryPoint(properties))
.and()
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// cluster authorized user
http.addFilterBefore(buildNodeAuthorizedUserFilter(), AnonymousAuthenticationFilter.class);
// anonymous
http.anonymous().authenticationFilter(buildAnonymousFilter());
// x509
http.addFilterAfter(buildX509Filter(), AnonymousAuthenticationFilter.class);
// jwt - consider when configured for log in
if (loginIdentityProvider != null) {
http.addFilterAfter(buildJwtFilter(), AnonymousAuthenticationFilter.class);
}
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// override xxxBean method so the authentication manager is available in app context (necessary for the method level security)
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new NiFiAuthenticationProvider(userDetailsService));
}
private NodeAuthorizedUserFilter buildNodeAuthorizedUserFilter() {
final NodeAuthorizedUserFilter nodeFilter = new NodeAuthorizedUserFilter();
nodeFilter.setProperties(properties);
nodeFilter.setCertificateExtractor(certificateExtractor);
nodeFilter.setCertificateIdentityProvider(certificateIdentityProvider);
return nodeFilter;
}
private JwtAuthenticationFilter buildJwtFilter() throws Exception {
final JwtAuthenticationFilter jwtFilter = new JwtAuthenticationFilter();
jwtFilter.setProperties(properties);
jwtFilter.setJwtService(jwtService);
jwtFilter.setAuthenticationManager(authenticationManager());
return jwtFilter;
}
private X509AuthenticationFilter buildX509Filter() throws Exception {
final X509AuthenticationFilter x509Filter = new X509AuthenticationFilter();
x509Filter.setProperties(properties);
x509Filter.setCertificateExtractor(certificateExtractor);
x509Filter.setCertificateIdentityProvider(certificateIdentityProvider);
x509Filter.setAuthenticationManager(authenticationManager());
return x509Filter;
}
private AnonymousAuthenticationFilter buildAnonymousFilter() {
final NiFiAnonymousUserFilter anonymousFilter = new NiFiAnonymousUserFilter();
anonymousFilter.setUserService(userService);
return anonymousFilter;
}
@Autowired
public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@Autowired
public void setProperties(NiFiProperties properties) {
this.properties = properties;
}
@Autowired
public void setJwtService(JwtService jwtService) {
this.jwtService = jwtService;
}
@Autowired
public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) {
this.loginIdentityProvider = loginIdentityProvider;
}
@Autowired
public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
this.certificateExtractor = certificateExtractor;
}
@Autowired
public void setCertificateIdentityProvider(X509IdentityProvider certificateIdentityProvider) {
this.certificateIdentityProvider = certificateIdentityProvider;
}
}

View File

@ -170,6 +170,7 @@ import org.apache.nifi.web.api.dto.status.ClusterProcessGroupStatusDTO;
import org.apache.nifi.web.api.dto.status.NodeProcessGroupStatusDTO;
import org.apache.nifi.web.dao.ControllerServiceDAO;
import org.apache.nifi.web.dao.ReportingTaskDAO;
import org.apache.nifi.web.security.user.NewAccountRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
@ -762,7 +763,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
if (user == null) {
throw new WebApplicationException(new Throwable("Unable to access details for current user."));
}
final String userDn = user.getDn();
final String userDn = user.getIdentity();
if (Node.Status.CONNECTING.name().equalsIgnoreCase(nodeDTO.getStatus())) {
clusterManager.requestReconnection(nodeDTO.getNodeId(), userDn);
@ -1782,7 +1783,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
// create a purge action to record that records are being removed
FlowChangeAction purgeAction = new FlowChangeAction();
purgeAction.setUserIdentity(user.getDn());
purgeAction.setUserIdentity(user.getIdentity());
purgeAction.setUserName(user.getUserName());
purgeAction.setOperation(Operation.Purge);
purgeAction.setTimestamp(new Date());
@ -1819,6 +1820,23 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
}
}
@Override
public UserDTO createUser() {
NewAccountRequest newAccountRequest = NiFiUserUtils.getNewAccountRequest();
// log the new user account request
logger.info("Requesting new user account for " + newAccountRequest.getUsername());
// get the justification
String justification = newAccountRequest.getJustification();
if (justification == null) {
justification = StringUtils.EMPTY;
}
// create the pending user account
return dtoFactory.createUserDTO(userService.createPendingUserAccount(newAccountRequest.getUsername(), justification));
}
@Override
public UserDTO updateUser(UserDTO userDto) {
NiFiUser user;
@ -2256,7 +2274,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
}
final Set<String> allowedUsers = port.getUserAccessControl();
if (allowedUsers.contains(user.getDn())) {
if (allowedUsers.contains(user.getIdentity())) {
return true;
}
@ -2632,7 +2650,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final UserDTO groupedUser = groupedUserDTOs.get(user.getUserGroup());
groupedUser.setId(groupedUser.getId() + "," + String.valueOf(user.getId()));
groupedUser.setUserName(groupedUser.getUserName() + ", " + user.getUserName());
groupedUser.setDn(groupedUser.getDn() + ", " + user.getDn());
groupedUser.setDn(groupedUser.getDn() + ", " + user.getIdentity());
groupedUser.setCreation(getOldestDate(groupedUser.getCreation(), user.getCreation()));
groupedUser.setLastAccessed(getNewestDate(groupedUser.getLastAccessed(), user.getLastAccessed()));
groupedUser.setLastVerified(getNewestDate(groupedUser.getLastVerified(), user.getLastVerified()));
@ -2731,7 +2749,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
throw new WebApplicationException(new Throwable("Unable to access details for current user."));
}
final String userDn = user.getDn();
final String userDn = user.getIdentity();
clusterManager.deleteNode(nodeId, userDn);
}

View File

@ -158,19 +158,19 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
@Override
public String getCurrentUserDn() {
String userDn = NiFiUser.ANONYMOUS_USER_DN;
String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
final NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user != null) {
userDn = user.getDn();
userIdentity = user.getIdentity();
}
return userDn;
return userIdentity;
}
@Override
public String getCurrentUserName() {
String userName = NiFiUser.ANONYMOUS_USER_DN;
String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
final NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user != null) {

View File

@ -129,19 +129,19 @@ public class StandardNiFiWebContext implements NiFiWebContext {
@Override
public String getCurrentUserDn() {
String userDn = NiFiUser.ANONYMOUS_USER_DN;
String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY;
final NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user != null) {
userDn = user.getDn();
userIdentity = user.getIdentity();
}
return userDn;
return userIdentity;
}
@Override
public String getCurrentUserName() {
String userName = NiFiUser.ANONYMOUS_USER_DN;
String userName = NiFiUser.ANONYMOUS_USER_IDENTITY;
final NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user != null) {

View File

@ -0,0 +1,424 @@
/*
* 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 io.jsonwebtoken.JwtException;
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.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
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.commons.lang3.StringUtils;
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.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.InvalidAuthenticationException;
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.X509IdentityProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AccountStatusException;
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;
/**
* 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 static final Logger logger = LoggerFactory.getLogger(AccessResource.class);
private static final String AUTHORIZATION = "Authorization";
private NiFiProperties properties;
private LoginIdentityProvider loginIdentityProvider;
private X509CertificateExtractor certificateExtractor;
private X509IdentityProvider certificateIdentityProvider;
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 = 401, message = "Unable to determine access status because the client could not be authenticated."),
@ApiResponse(code = 403, message = "Unable to determine access status because the client is not authorized to make this request."),
@ApiResponse(code = 409, message = "Unable to determine access status because NiFi is not in the appropriate state."),
@ApiResponse(code = 500, message = "Unable to determine access status because an unexpected error occurred.")
}
)
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 {
final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(httpServletRequest);
// if there is not certificate, consider a token
if (certificates == null) {
// look for an authorization token
final String authorization = httpServletRequest.getHeader(AUTHORIZATION);
// if there is no authorization header, we don't know the user
if (authorization == null) {
accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name());
accessStatus.setMessage("No credentials supplied, unknown user.");
} else {
try {
// Extract the Base64 encoded token from the Authorization header
final String token = StringUtils.substringAfterLast(authorization, " ");
final String principal = jwtService.getAuthenticationFromToken(token);
// 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.setMessage("Account is active and authorized");
} catch (JwtException e) {
throw new InvalidAuthenticationException(e.getMessage(), e);
}
}
} else {
try {
final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificates);
// get the proxy chain and ensure its populated
final List<String> proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(httpServletRequest, authenticationResponse.getIdentity());
if (proxyChain.isEmpty()) {
logger.error(String.format("Unable to parse the proxy chain %s from the incoming request.", authenticationResponse.getIdentity()));
throw new IllegalArgumentException("Unable to determine the user from the incoming request.");
}
// set the user identity
accessStatus.setIdentity(proxyChain.get(0));
accessStatus.setUsername(CertificateUtils.extractUsername(proxyChain.get(0)));
// ensure the proxy chain is authorized
checkAuthorization(proxyChain);
// no issues with authorization
accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
accessStatus.setMessage("Account is active and authorized");
} catch (final IllegalArgumentException iae) {
throw new InvalidAuthenticationException(iae.getMessage(), iae);
}
}
} 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 = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 409, message = "Unable to create access token because NiFi is not in the appropriate state. (i.e. may not be configured to support username/password login."),
@ApiResponse(code = 500, message = "Unable to create access token because an unexpected error occurred.")
}
)
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;
final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(httpServletRequest);
// if there is not certificate, consider login credentials
if (certificates == null) {
// ensure we have login credentials
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
throw new IllegalArgumentException("The username and password must be specified.");
}
try {
// attempt to authenticate
final AuthenticationResponse authenticationResponse = loginIdentityProvider.authenticate(new LoginCredentials(username, password));
final long maxExpiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS);
final long minExpiration = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
long expiration = authenticationResponse.getExpiration();
if (expiration > maxExpiration) {
expiration = maxExpiration;
logger.warn(String.format("Max token expiration exceeded. Setting expiration to %s from %s for %s", expiration,
authenticationResponse.getExpiration(), authenticationResponse.getIdentity()));
} else if (expiration < minExpiration) {
expiration = minExpiration;
logger.warn(String.format("Min token expiration not met. Setting expiration to %s from %s for %s", expiration,
authenticationResponse.getExpiration(), authenticationResponse.getIdentity()));
}
// create the authentication token
loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, authenticationResponse.getIssuer());
} 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);
}
} else {
// consider a certificate
final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificates);
// get the proxy chain and ensure its populated
final List<String> proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(httpServletRequest, authenticationResponse.getIdentity());
if (proxyChain.isEmpty()) {
logger.error(String.format("Unable to parse the proxy chain %s from the incoming request.", authenticationResponse.getIdentity()));
throw new IllegalArgumentException("Unable to determine the user from the incoming request.");
}
// authorize the proxy if necessary
authorizeProxyIfNecessary(proxyChain);
// create the authentication token
loginAuthenticationToken = new LoginAuthenticationToken(proxyChain.get(0), authenticationResponse.getExpiration(), authenticationResponse.getIssuer());
}
// 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 token to the end user which they will use to identify themselves
// when they enter a new account request
} catch (final AuthenticationServiceException ase) {
// throw an administration exception which will return a 500
throw new AdministrationException(ase.getMessage(), ase);
} catch (final Exception e) {
// any other issue we're going to treat as access denied exception which will return 403
throw new AccessDeniedException(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 setCertificateExtractor(X509CertificateExtractor certificateExtractor) {
this.certificateExtractor = certificateExtractor;
}
public void setCertificateIdentityProvider(X509IdentityProvider certificateIdentityProvider) {
this.certificateIdentityProvider = certificateIdentityProvider;
}
public void setUserDetailsService(AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) {
this.userDetailsService = userDetailsService;
}
}

View File

@ -23,7 +23,6 @@ import com.sun.jersey.server.impl.model.method.dispatch.FormDispatchProvider;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
@ -45,9 +44,8 @@ import org.apache.nifi.action.Operation;
import org.apache.nifi.cluster.context.ClusterContext;
import org.apache.nifi.cluster.context.ClusterContextThreadLocal;
import org.apache.nifi.cluster.manager.impl.WebClusterManager;
import org.apache.nifi.web.security.DnUtils;
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
import org.apache.nifi.web.security.user.NiFiUserDetails;
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.api.entity.Entity;
import org.apache.nifi.web.api.request.ClientIdParameter;
@ -55,6 +53,8 @@ import org.apache.nifi.web.util.WebUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.nifi.user.NiFiUser;
import org.apache.nifi.web.security.user.NiFiUserUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
@ -362,14 +362,12 @@ public abstract class ApplicationResource {
result.put(PROXY_SCHEME_HTTP_HEADER, httpServletRequest.getScheme());
}
// if this is a secure request, add the custom headers for proxying user requests
final X509Certificate cert = new X509CertificateExtractor().extractClientCertificate(httpServletRequest);
if (cert != null) {
if (httpServletRequest.isSecure()) {
// add the certificate DN to the proxy chain
final String xProxiedEntitiesChain = DnUtils.getXProxiedEntitiesChain(httpServletRequest);
if (StringUtils.isNotBlank(xProxiedEntitiesChain)) {
result.put(PROXIED_ENTITIES_CHAIN_HTTP_HEADER, xProxiedEntitiesChain);
final NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user != null) {
result.put(PROXIED_ENTITIES_CHAIN_HTTP_HEADER, ProxiedEntitiesUtils.buildProxiedEntitiesChainString(user));
}
// add the user's authorities (if any) to the headers

View File

@ -79,6 +79,7 @@ import org.apache.nifi.web.api.request.IntegerParameter;
import org.apache.nifi.web.api.request.LongParameter;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.web.api.entity.ControllerServiceTypesEntity;
import org.apache.nifi.web.api.entity.IdentityEntity;
import org.apache.nifi.web.api.entity.ReportingTaskTypesEntity;
import org.springframework.security.access.prepost.PreAuthorize;
@ -239,7 +240,7 @@ public class ControllerResource extends ApplicationResource {
public ProcessGroupResource getGroupResource(
@ApiParam(
value = "The id of the process group that is the parent of the requested resource(s). If the desired process group is "
+ "the root group an alias 'root' may be used as the process-group-id.",
+ "the root group an alias 'root' may be used as the process-group-id.",
required = true
)
@PathParam("process-group-id") String groupId) {
@ -454,13 +455,13 @@ public class ControllerResource extends ApplicationResource {
@ApiOperation(
value = "Gets the current revision of this NiFi",
notes = "NiFi employs an optimistic locking strategy where the client must include a revision in their request when "
+ "performing an update. If the specified revision does not match the current base revision a 409 status code "
+ "is returned. The revision is comprised of a clientId and a version number. The version is a simple integer "
+ "value that is incremented with each change. Including the most recent version tells NiFi that your working "
+ "with the most recent flow. In addition to the version the client who is performing the updates is recorded. "
+ "This allows the same client to submit multiple requests without having to wait for the previously ones to "
+ "return. Invoking this endpoint will return the current base revision. It is also available when retrieving "
+ "a process group and in the response of all mutable requests.",
+ "performing an update. If the specified revision does not match the current base revision a 409 status code "
+ "is returned. The revision is comprised of a clientId and a version number. The version is a simple integer "
+ "value that is incremented with each change. Including the most recent version tells NiFi that your working "
+ "with the most recent flow. In addition to the version the client who is performing the updates is recorded. "
+ "This allows the same client to submit multiple requests without having to wait for the previously ones to "
+ "return. Invoking this endpoint will return the current base revision. It is also available when retrieving "
+ "a process group and in the response of all mutable requests.",
response = Entity.class,
authorizations = {
@Authorization(value = "Read Only", type = "ROLE_MONITOR"),
@ -844,6 +845,47 @@ public class ControllerResource extends ApplicationResource {
return clusterContext(generateOkResponse(entity)).build();
}
/**
* Retrieves the identity of the user making the 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 An identityEntity
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/identity")
@ApiOperation(
value = "Retrieves the user identity of the user making the request",
response = IdentityEntity.class
)
public Response getIdentity(
@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) {
// note that the cluster manager will handle this request directly
final NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user == null) {
throw new WebApplicationException(new Throwable("Unable to access details for current user."));
}
// create the revision
final RevisionDTO revision = new RevisionDTO();
revision.setClientId(clientId.getClientId());
// create the response entity
IdentityEntity entity = new IdentityEntity();
entity.setRevision(revision);
entity.setUserId(user.getId());
entity.setIdentity(user.getUserName());
// generate the response
return clusterContext(generateOkResponse(entity)).build();
}
/**
* Retrieves the user details, including the authorities, about the user making the request.
*
@ -854,14 +896,17 @@ public class ControllerResource extends ApplicationResource {
@Consumes(MediaType.WILDCARD)
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("/authorities")
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
@PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN', 'ROLE_PROXY', 'ROLE_NIFI', 'ROLE_PROVENANCE')")
@ApiOperation(
value = "Retrieves the user details, including the authorities, about the user making the request",
response = AuthorityEntity.class,
authorizations = {
@Authorization(value = "Read Only", type = "ROLE_MONITOR"),
@Authorization(value = "Data Flow Manager", type = "ROLE_DFM"),
@Authorization(value = "Administrator", type = "ROLE_ADMIN")
@Authorization(value = "Administrator", type = "ROLE_ADMIN"),
@Authorization(value = "Proxy", type = "ROLE_PROXY"),
@Authorization(value = "NiFi", type = "ROLE_NIFI"),
@Authorization(value = "Provenance", type = "ROLE_PROVENANCE")
}
)
@ApiResponses(

View File

@ -16,12 +16,14 @@
*/
package org.apache.nifi.web.api;
import com.sun.jersey.api.Responses;
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 com.wordnik.swagger.annotations.Authorization;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -39,6 +41,7 @@ import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@ -59,9 +62,11 @@ import org.apache.nifi.web.api.entity.UserSearchResultsEntity;
import org.apache.nifi.web.api.entity.UsersEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.user.NiFiUser;
import org.apache.nifi.web.NiFiServiceFacade;
import static org.apache.nifi.web.api.ApplicationResource.CLIENT_ID;
import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.security.user.NiFiUserUtils;
import org.springframework.security.access.prepost.PreAuthorize;
/**
@ -83,12 +88,35 @@ public class UserResource extends ApplicationResource {
private NiFiProperties properties;
private NiFiServiceFacade serviceFacade;
@POST
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.TEXT_PLAIN)
@Path("") // necessary due to a bug in swagger
@ApiOperation(
value = "Creates a user",
response = String.class
)
public Response createUser() {
if (!properties.getSupportNewAccountRequests()) {
return Responses.notFound().entity("This NiFi does not support new account requests.").build();
}
final NiFiUser nifiUser = NiFiUserUtils.getNiFiUser();
if (nifiUser != null) {
throw new IllegalArgumentException("User account already created " + nifiUser.getIdentity());
}
// create an account request for the current user
final UserDTO user = serviceFacade.createUser();
final String uri = generateResourceUri("controller", "templates", user.getId());
return generateCreatedResponse(URI.create(uri), "Not authorized. User account created. Authorization pending.").build();
}
/**
* Gets all users that are registered within this Controller.
*
* @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.
* @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.
* @param grouped Whether to return the users in their groups.
* @return A usersEntity.
*/
@ -144,9 +172,7 @@ public class UserResource extends ApplicationResource {
/**
* Gets the details for the specified user.
*
* @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.
* @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.
* @param id The user id.
* @return A userEntity.
*/
@ -315,12 +341,9 @@ public class UserResource extends ApplicationResource {
* Updates the specified user.
*
* @param httpServletRequest 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.
* @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.
* @param id The id of the user to update.
* @param rawAuthorities Array of authorities to assign to the specified
* user.
* @param rawAuthorities Array of authorities to assign to the specified user.
* @param status The status of the specified users account.
* @param formParams form params
* @return A userEntity
@ -491,9 +514,7 @@ public class UserResource extends ApplicationResource {
*
* @param httpServletRequest request
* @param id The user id
* @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.
* @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 userEntity.
*/
@DELETE

View File

@ -39,7 +39,7 @@ public class AccessDeniedExceptionMapper implements ExceptionMapper<AccessDenied
// get the current user
NiFiUser user = NiFiUserUtils.getNiFiUser();
if (user != null) {
logger.info(String.format("%s does not have permission to access the requested resource. Returning %s response.", user.getDn(), Response.Status.FORBIDDEN));
logger.info(String.format("%s does not have permission to access the requested resource. Returning %s response.", user.getIdentity(), Response.Status.FORBIDDEN));
} else {
logger.info(String.format("User does not have permission to access the requested resource. Returning %s response.", Response.Status.FORBIDDEN));
}

View File

@ -0,0 +1,44 @@
/*
* 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.config;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Maps access denied exceptions into a client response.
*/
@Provider
public class InvalidAuthenticationExceptionMapper implements ExceptionMapper<InvalidAuthenticationException> {
private static final Logger logger = LoggerFactory.getLogger(InvalidAuthenticationExceptionMapper.class);
@Override
public Response toResponse(InvalidAuthenticationException exception) {
if (logger.isDebugEnabled()) {
logger.debug(StringUtils.EMPTY, exception);
}
return Response.status(Response.Status.UNAUTHORIZED).entity(exception.getMessage()).type("text/plain").build();
}
}

View File

@ -2382,7 +2382,7 @@ public final class DtoFactory {
// create the user
UserDTO userDTO = new UserDTO();
userDTO.setId(String.valueOf(user.getId()));
userDTO.setDn(user.getDn());
userDTO.setDn(user.getIdentity());
userDTO.setUserName(user.getUserName());
userDTO.setUserGroup(user.getUserGroup());
userDTO.setJustification(user.getJustification());

View File

@ -113,6 +113,7 @@ import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
import org.apache.nifi.web.api.dto.status.ControllerStatusDTO;
import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
import org.apache.nifi.web.security.user.NiFiUserUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -822,17 +823,7 @@ public class ControllerFacade {
final Map<String, String> attributes = event.getAttributes();
// calculate the dn chain
final List<String> dnChain = new ArrayList<>();
// build the dn chain
NiFiUser chainedUser = user;
do {
// add the entry for this user
dnChain.add(chainedUser.getDn());
// go to the next user in the chain
chainedUser = chainedUser.getChain();
} while (chainedUser != null);
final List<String> dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user);
// ensure the users in this chain are allowed to download this content
final DownloadAuthorization downloadAuthorization = userService.authorizeDownload(dnChain, attributes);
@ -850,7 +841,7 @@ public class ControllerFacade {
final String type = event.getAttributes().get(CoreAttributes.MIME_TYPE.key());
// get the content
final InputStream content = flowController.getContent(event, contentDirection, user.getDn(), uri);
final InputStream content = flowController.getContent(event, contentDirection, user.getIdentity(), uri);
return new DownloadableContent(filename, type, content);
} catch (final ContentNotFoundException cnfe) {
throw new ResourceNotFoundException("Unable to find the specified content.");
@ -880,7 +871,7 @@ public class ControllerFacade {
}
// replay the flow file
final ProvenanceEventRecord event = flowController.replayFlowFile(originalEvent, user.getDn());
final ProvenanceEventRecord event = flowController.replayFlowFile(originalEvent, user.getIdentity());
// convert the event record
return createProvenanceEventDto(event);

View File

@ -321,7 +321,7 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO
throw new WebApplicationException(new Throwable("Unable to access details for current user."));
}
return queue.dropFlowFiles(dropRequestId, user.getDn());
return queue.dropFlowFiles(dropRequestId, user.getIdentity());
}
@Override

View File

@ -52,13 +52,13 @@ public class RequestLogger implements Filter {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
// get the user details for the log message
String dn = "<no user found>";
String identity = "<no user found>";
if (user != null) {
dn = user.getDn();
identity = user.getIdentity();
}
// log the request attempt - response details will be logged later
logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", dn, request.getMethod(),
logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", identity, request.getMethod(),
request.getRequestURL().toString(), request.getRemoteAddr()));
}

View File

@ -241,12 +241,21 @@
<bean id="systemDiagnosticsResource" class="org.apache.nifi.web.api.SystemDiagnosticsResource" scope="singleton">
<property name="serviceFacade" ref="serviceFacade"/>
</bean>
<bean id="accessResource" class="org.apache.nifi.web.api.AccessResource" scope="singleton">
<property name="properties" ref="nifiProperties"/>
<property name="certificateExtractor" ref="certificateExtractor"/>
<property name="certificateIdentityProvider" ref="certificateIdentityProvider"/>
<property name="loginIdentityProvider" ref="loginIdentityProvider"/>
<property name="jwtService" ref="jwtService"/>
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
<!-- configuration for jaxb serialization -->
<bean class="org.apache.nifi.web.util.ObjectMapperResolver" scope="singleton"/>
<!-- exception mapping -->
<bean class="org.apache.nifi.web.api.config.AccessDeniedExceptionMapper" scope="singleton"/>
<bean class="org.apache.nifi.web.api.config.InvalidAuthenticationExceptionMapper" scope="singleton"/>
<bean class="org.apache.nifi.web.api.config.AuthenticationCredentialsNotFoundExceptionMapper" scope="singleton"/>
<bean class="org.apache.nifi.web.api.config.AccountNotFoundExceptionMapper" scope="singleton"/>
<bean class="org.apache.nifi.web.api.config.AdministrationExceptionMapper" scope="singleton"/>

View File

@ -15,16 +15,13 @@
-->
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>nifi-api</display-name>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:nifi-context.xml
classpath:nifi-web-api-context.xml
classpath:nifi-web-security-context.xml
classpath:nifi-administration-context.xml
classpath:nifi-cluster-manager-context.xml
classpath:nifi-cluster-protocol-context.xml
</param-value>
<param-value>org.apache.nifi.web</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

View File

@ -0,0 +1,292 @@
/*
* 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.integration.accesscontrol;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;
import org.apache.commons.io.FileUtils;
import org.apache.nifi.integration.util.NiFiTestServer;
import org.apache.nifi.integration.util.NiFiTestUser;
import org.apache.nifi.integration.util.SourceTestProcessor;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarClassLoaders;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
import org.apache.nifi.web.api.dto.AccessStatusDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.entity.AccessConfigurationEntity;
import org.apache.nifi.web.api.entity.AccessStatusEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.util.WebUtils;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Access token endpoint test.
*/
public class AccessTokenEndpointTest {
private static final String CLIENT_ID = "token-endpoint-id";
private static final String CONTEXT_PATH = "/nifi-api";
private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-admin.xml";
private static NiFiTestServer SERVER;
private static NiFiTestUser TOKEN_USER;
private static String BASE_URL;
@BeforeClass
public static void setup() throws Exception {
// configure the location of the nifi properties
File nifiPropertiesFile = new File("src/test/resources/access-control/nifi.properties");
System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath());
// update the flow.xml property
NiFiProperties props = NiFiProperties.getInstance();
props.setProperty("nifi.flow.configuration.file", FLOW_XML_PATH);
// delete the database directory to avoid issues with re-registration in testRequestAccessUsingToken
FileUtils.deleteDirectory(props.getDatabaseRepositoryPath().toFile());
// load extensions
NarClassLoaders.load(props);
ExtensionManager.discoverExtensions();
// start the server
SERVER = new NiFiTestServer("src/main/webapp", CONTEXT_PATH);
SERVER.startServer();
SERVER.loadFlow();
// get the base url
BASE_URL = SERVER.getBaseUrl() + CONTEXT_PATH;
// create the user
final Client client = WebUtils.createClient(null, createTrustContext(props));
TOKEN_USER = new NiFiTestUser(client, null);
}
private static SSLContext createTrustContext(final NiFiProperties props) throws Exception {
return SslContextFactory.createTrustSslContext(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE),
props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray(),
props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE), "TLS");
}
// -----------
// LOGIN CONIG
// -----------
/**
* Test getting access configuration.
*
* @throws Exception ex
*/
@Test
public void testGetAccessConfig() throws Exception {
String url = BASE_URL + "/access/config";
ClientResponse response = TOKEN_USER.testGet(url);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
// extract the process group
AccessConfigurationEntity accessConfigEntity = response.getEntity(AccessConfigurationEntity.class);
// ensure there is content
Assert.assertNotNull(accessConfigEntity);
// extract the process group dto
AccessConfigurationDTO accessConfig = accessConfigEntity.getConfig();
// verify config
Assert.assertTrue(accessConfig.getSupportsLogin());
Assert.assertFalse(accessConfig.getSupportsAnonymous());
}
/**
* Obtains a token and creates a processor using it.
*
* @throws Exception ex
*/
@Test
public void testCreateProcessorUsingToken() throws Exception {
String url = BASE_URL + "/access/token";
ClientResponse response = TOKEN_USER.testCreateToken(url, "user@nifi", "whateve");
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
// get the token
String token = response.getEntity(String.class);
// attempt to create a processor with it
createProcessor(token);
}
private ProcessorDTO createProcessor(final String token) throws Exception {
String url = BASE_URL + "/controller/process-groups/root/processors";
// authorization header
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + token);
// create the processor
ProcessorDTO processor = new ProcessorDTO();
processor.setName("Copy");
processor.setType(SourceTestProcessor.class.getName());
// create the revision
final RevisionDTO revision = new RevisionDTO();
revision.setClientId(CLIENT_ID);
revision.setVersion(NiFiTestUser.REVISION);
// create the entity body
ProcessorEntity entity = new ProcessorEntity();
entity.setRevision(revision);
entity.setProcessor(processor);
// perform the request
ClientResponse response = TOKEN_USER.testPostWithHeaders(url, entity, headers);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
// get the entity body
entity = response.getEntity(ProcessorEntity.class);
// verify creation
processor = entity.getProcessor();
Assert.assertEquals("Copy", processor.getName());
Assert.assertEquals("org.apache.nifi.integration.util.SourceTestProcessor", processor.getType());
return processor;
}
/**
* Verifies the response when bad credentials are specified.
*
* @throws Exception ex
*/
@Test
public void testInvalidCredentials() throws Exception {
String url = BASE_URL + "/access/token";
ClientResponse response = TOKEN_USER.testCreateToken(url, "user@nifi", "not a real password");
// ensure the request is successful
Assert.assertEquals(400, response.getStatus());
}
/**
* Verifies the response when the user is known.
*
* @throws Exception ex
*/
@Test
public void testUnkownUser() throws Exception {
String url = BASE_URL + "/access/token";
ClientResponse response = TOKEN_USER.testCreateToken(url, "not a real user", "not a real password");
// ensure the request is successful
Assert.assertEquals(400, response.getStatus());
}
/**
* Request access using access token.
*
* @throws Exception ex
*/
@Test
public void testRequestAccessUsingToken() throws Exception {
String accessStatusUrl = BASE_URL + "/access";
String accessTokenUrl = BASE_URL + "/access/token";
String registrationUrl = BASE_URL + "/controller/users";
ClientResponse response = TOKEN_USER.testGet(accessStatusUrl);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
AccessStatusEntity accessStatusEntity = response.getEntity(AccessStatusEntity.class);
AccessStatusDTO accessStatus = accessStatusEntity.getAccessStatus();
// verify unknown
Assert.assertEquals("UNKNOWN", accessStatus.getStatus());
response = TOKEN_USER.testCreateToken(accessTokenUrl, "unregistered-user@nifi", "password");
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
// get the token
String token = response.getEntity(String.class);
// authorization header
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + token);
// check the status with the token
response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
accessStatusEntity = response.getEntity(AccessStatusEntity.class);
accessStatus = accessStatusEntity.getAccessStatus();
// verify unregistered
Assert.assertEquals("UNREGISTERED", accessStatus.getStatus());
response = TOKEN_USER.testRegisterUser(registrationUrl, "Gimme access", headers);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());
// check the status with the token
response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers);
// ensure the request is successful
Assert.assertEquals(200, response.getStatus());
accessStatusEntity = response.getEntity(AccessStatusEntity.class);
accessStatus = accessStatusEntity.getAccessStatus();
// verify unregistered
Assert.assertEquals("NOT_ACTIVE", accessStatus.getStatus());
}
@AfterClass
public static void cleanup() throws Exception {
// shutdown the server
SERVER.shutdownServer();
SERVER = null;
// look for the flow.xml
File flow = new File(FLOW_XML_PATH);
if (flow.exists()) {
flow.delete();
}
}
}

View File

@ -37,7 +37,7 @@ import org.apache.nifi.authorization.DownloadAuthorization;
*/
public class NiFiTestAuthorizationProvider implements AuthorityProvider {
private Map<String, Set<Authority>> users;
private final Map<String, Set<Authority>> users;
/**
* Creates a new FileAuthorizationProvider.
@ -48,6 +48,7 @@ public class NiFiTestAuthorizationProvider implements AuthorityProvider {
users.put("CN=Lastname Firstname Middlename monitor, OU=Unknown, OU=Unknown, OU=Unknown, O=Unknown, C=Unknown", EnumSet.of(Authority.ROLE_MONITOR));
users.put("CN=Lastname Firstname Middlename dfm, OU=Unknown, OU=Unknown, OU=Unknown, O=Unknown, C=Unknown", EnumSet.of(Authority.ROLE_DFM));
users.put("CN=Lastname Firstname Middlename admin, OU=Unknown, OU=Unknown, OU=Unknown, O=Unknown, C=Unknown", EnumSet.of(Authority.ROLE_ADMIN));
users.put("user@nifi", EnumSet.of(Authority.ROLE_DFM));
}
@Override

View File

@ -0,0 +1,75 @@
/*
* 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.integration.util;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.nifi.authorization.exception.ProviderCreationException;
import org.apache.nifi.authentication.AuthenticationResponse;
import org.apache.nifi.authentication.LoginCredentials;
import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext;
import org.apache.nifi.authentication.LoginIdentityProviderInitializationContext;
import org.apache.nifi.authentication.exception.IdentityAccessException;
import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
/**
*
*/
public class NiFiTestLoginIdentityProvider implements LoginIdentityProvider {
private final Map<String, String> users;
/**
* Creates a new FileAuthorizationProvider.
*/
public NiFiTestLoginIdentityProvider() {
users = new HashMap<>();
users.put("user@nifi", "whateve");
users.put("unregistered-user@nifi", "password");
}
private void checkUser(final String user, final String password) {
if (!users.containsKey(user)) {
throw new InvalidLoginCredentialsException("Unknown user");
}
if (!users.get(user).equals(password)) {
throw new InvalidLoginCredentialsException("Invalid password");
}
}
@Override
public AuthenticationResponse authenticate(LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException {
checkUser(credentials.getUsername(), credentials.getPassword());
return new AuthenticationResponse(credentials.getUsername(), credentials.getUsername(), TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS), getClass().getSimpleName());
}
@Override
public void initialize(LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException {
}
@Override
public void onConfigured(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException {
}
@Override
public void preDestruction() {
}
}

View File

@ -78,8 +78,12 @@ public class NiFiTestServer {
private void createSecureConnector() {
org.eclipse.jetty.util.ssl.SslContextFactory contextFactory = new org.eclipse.jetty.util.ssl.SslContextFactory();
// need client auth
contextFactory.setNeedClientAuth(properties.getNeedClientAuth());
// require client auth when not supporting login or anonymous access
if (StringUtils.isBlank(properties.getProperty(NiFiProperties.SECURITY_USER_LOGIN_IDENTITY_PROVIDER)) && properties.getAnonymousAuthorities().isEmpty()) {
contextFactory.setNeedClientAuth(true);
} else {
contextFactory.setWantClientAuth(true);
}
/* below code sets JSSE system properties when values are provided */
// keystore properties
@ -163,7 +167,6 @@ public class NiFiTestServer {
}
public Client getClient() {
// create the client
return WebUtils.createClient(null, SslContextFactory.createSslContext(properties));
}

View File

@ -22,8 +22,7 @@ import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import org.apache.nifi.web.security.DnUtils;
import org.apache.nifi.web.security.x509.X509AuthenticationFilter;
import org.apache.nifi.web.security.ProxiedEntitiesUtils;
/**
*
@ -35,9 +34,27 @@ public class NiFiTestUser {
private final Client client;
private final String proxyDn;
public NiFiTestUser(Client client, String dn) {
public NiFiTestUser(Client client, String proxyDn) {
this.client = client;
this.proxyDn = DnUtils.formatProxyDn(dn);
if (proxyDn != null) {
this.proxyDn = ProxiedEntitiesUtils.formatProxyDn(proxyDn);
} else {
this.proxyDn = null;
}
}
/**
* Conditionally adds the proxied entities chain.
*
* @param builder the resource builder
* @return the resource builder
*/
private WebResource.Builder addProxiedEntities(final WebResource.Builder builder) {
if (proxyDn == null) {
return builder;
} else {
return builder.header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn);
}
}
/**
@ -59,6 +76,18 @@ public class NiFiTestUser {
* @return response
*/
public ClientResponse testGet(String url, Map<String, String> queryParams) {
return testGetWithHeaders(url, queryParams, null);
}
/**
* Performs a GET using the specified url and query parameters.
*
* @param url url
* @param queryParams params
* @param headers http headers
* @return response
*/
public ClientResponse testGetWithHeaders(String url, Map<String, String> queryParams, Map<String, String> headers) {
// get the resource
WebResource resource = client.resource(url);
@ -69,8 +98,18 @@ public class NiFiTestUser {
}
}
// get the builder
WebResource.Builder builder = addProxiedEntities(resource.accept(MediaType.APPLICATION_JSON));
// append any headers
if (headers != null && !headers.isEmpty()) {
for (String key : headers.keySet()) {
builder = builder.header(key, headers.get(key));
}
}
// perform the query
return resource.accept(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn).get(ClientResponse.class);
return builder.get(ClientResponse.class);
}
/**
@ -93,14 +132,34 @@ public class NiFiTestUser {
* @throws Exception ex
*/
public ClientResponse testPost(String url, Object entity) throws Exception {
return testPostWithHeaders(url, entity, null);
}
/**
* Performs a POST using the specified url and entity body.
*
* @param url url
* @param entity entity
* @param headers http headers
* @return response
* @throws Exception ex
*/
public ClientResponse testPostWithHeaders(String url, Object entity, Map<String, String> headers) throws Exception {
// get the resource
WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn);
WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON));
// include the request entity
if (entity != null) {
resourceBuilder = resourceBuilder.entity(entity);
}
// append any headers
if (headers != null && !headers.isEmpty()) {
for (String key : headers.keySet()) {
resourceBuilder = resourceBuilder.header(key, headers.get(key));
}
}
// perform the request
return resourceBuilder.post(ClientResponse.class);
}
@ -110,18 +169,38 @@ public class NiFiTestUser {
*
* @param url url
* @param entity entity
* @return repsonse
* @return response
* @throws Exception ex
*/
public ClientResponse testPostMultiPart(String url, Object entity) throws Exception {
return testPostMultiPartWithHeaders(url, entity, null);
}
/**
* Performs a POST using the specified url and entity body.
*
* @param url url
* @param entity entity
* @param headers http headers
* @return response
* @throws Exception ex
*/
public ClientResponse testPostMultiPartWithHeaders(String url, Object entity, Map<String, String> headers) throws Exception {
// get the resource
WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_XML).type(MediaType.MULTIPART_FORM_DATA).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn);
WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_XML).type(MediaType.MULTIPART_FORM_DATA));
// include the request entity
if (entity != null) {
resourceBuilder = resourceBuilder.entity(entity);
}
// append any headers
if (headers != null && !headers.isEmpty()) {
for (String key : headers.keySet()) {
resourceBuilder = resourceBuilder.header(key, headers.get(key));
}
}
// perform the request
return resourceBuilder.post(ClientResponse.class);
}
@ -135,6 +214,19 @@ public class NiFiTestUser {
* @throws java.lang.Exception ex
*/
public ClientResponse testPost(String url, Map<String, String> formData) throws Exception {
return testPostWithHeaders(url, formData, null);
}
/**
* Performs a POST using the specified url and form data.
*
* @param url url
* @param formData form data
* @param headers http headers
* @return response
* @throws java.lang.Exception ex
*/
public ClientResponse testPostWithHeaders(String url, Map<String, String> formData, Map<String, String> headers) throws Exception {
// convert the form data
MultivaluedMapImpl entity = new MultivaluedMapImpl();
for (String key : formData.keySet()) {
@ -142,14 +234,20 @@ public class NiFiTestUser {
}
// get the resource
WebResource.Builder resourceBuilder
= client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn);
WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED));
// add the form data if necessary
if (!entity.isEmpty()) {
resourceBuilder = resourceBuilder.entity(entity);
}
// append any headers
if (headers != null && !headers.isEmpty()) {
for (String key : headers.keySet()) {
resourceBuilder = resourceBuilder.header(key, headers.get(key));
}
}
// perform the request
return resourceBuilder.post(ClientResponse.class);
}
@ -163,14 +261,34 @@ public class NiFiTestUser {
* @throws java.lang.Exception ex
*/
public ClientResponse testPut(String url, Object entity) throws Exception {
return testPutWithHeaders(url, entity, null);
}
/**
* Performs a PUT using the specified url and entity body.
*
* @param url url
* @param entity entity
* @param headers http headers
* @return response
* @throws java.lang.Exception ex
*/
public ClientResponse testPutWithHeaders(String url, Object entity, Map<String, String> headers) throws Exception {
// get the resource
WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn);
WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON));
// include the request entity
if (entity != null) {
resourceBuilder = resourceBuilder.entity(entity);
}
// append any headers
if (headers != null && !headers.isEmpty()) {
for (String key : headers.keySet()) {
resourceBuilder = resourceBuilder.header(key, headers.get(key));
}
}
// perform the request
return resourceBuilder.put(ClientResponse.class);
}
@ -184,6 +302,19 @@ public class NiFiTestUser {
* @throws java.lang.Exception ex
*/
public ClientResponse testPut(String url, Map<String, String> formData) throws Exception {
return testPutWithHeaders(url, formData, null);
}
/**
* Performs a PUT using the specified url and form data.
*
* @param url url
* @param formData form data
* @param headers http headers
* @return response
* @throws java.lang.Exception ex
*/
public ClientResponse testPutWithHeaders(String url, Map<String, String> formData, Map<String, String> headers) throws Exception {
// convert the form data
MultivaluedMapImpl entity = new MultivaluedMapImpl();
for (String key : formData.keySet()) {
@ -191,14 +322,20 @@ public class NiFiTestUser {
}
// get the resource
WebResource.Builder resourceBuilder
= client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn);
WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED));
// add the form data if necessary
if (!entity.isEmpty()) {
resourceBuilder = resourceBuilder.entity(entity);
}
// append any headers
if (headers != null && !headers.isEmpty()) {
for (String key : headers.keySet()) {
resourceBuilder = resourceBuilder.header(key, headers.get(key));
}
}
// perform the request
return resourceBuilder.put(ClientResponse.class);
}
@ -211,24 +348,26 @@ public class NiFiTestUser {
* @throws java.lang.Exception ex
*/
public ClientResponse testDelete(String url) throws Exception {
return testDelete(url, (Object) null);
return testDelete(url, null);
}
/**
* Performs a DELETE using the specified url and entity.
*
* @param url url
* @param entity entity
* @return repsonse
* @param headers http headers
* @return response
* @throws java.lang.Exception ex
*/
public ClientResponse testDelete(String url, Object entity) throws Exception {
public ClientResponse testDeleteWithHeaders(String url, Map<String, String> headers) throws Exception {
// get the resource
WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn);
WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON));
// append any query parameters
if (entity != null) {
resourceBuilder = resourceBuilder.entity(entity);
// append any headers
if (headers != null && !headers.isEmpty()) {
for (String key : headers.keySet()) {
resourceBuilder = resourceBuilder.header(key, headers.get(key));
}
}
// perform the query
@ -255,7 +394,56 @@ public class NiFiTestUser {
}
// perform the request
return resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn).delete(ClientResponse.class);
return addProxiedEntities(resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED)).delete(ClientResponse.class);
}
/**
* Attempts to create a token with the specified username and password.
*
* @param url the url
* @param username the username
* @param password the password
* @return response
* @throws Exception ex
*/
public ClientResponse testCreateToken(String url, String username, String password) throws Exception {
// convert the form data
MultivaluedMapImpl entity = new MultivaluedMapImpl();
entity.add("username", username);
entity.add("password", password);
// get the resource
WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.TEXT_PLAIN).type(MediaType.APPLICATION_FORM_URLENCODED)).entity(entity);
// perform the request
return resourceBuilder.post(ClientResponse.class);
}
/**
* Attempts to create a token with the specified username and password.
*
* @param url the url
* @param justification justification
* @param headers http headers
* @return response
* @throws Exception ex
*/
public ClientResponse testRegisterUser(String url, String justification, Map<String, String> headers) throws Exception {
// convert the form data
MultivaluedMapImpl entity = new MultivaluedMapImpl();
entity.add("justification", justification);
// get the resource
WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.TEXT_PLAIN).type(MediaType.APPLICATION_FORM_URLENCODED)).entity(entity);
// append any headers
if (headers != null && !headers.isEmpty()) {
for (String key : headers.keySet()) {
resourceBuilder = resourceBuilder.header(key, headers.get(key));
}
}
// perform the request
return resourceBuilder.post(ClientResponse.class);
}
}

View File

@ -0,0 +1,15 @@
# 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.
org.apache.nifi.integration.util.NiFiTestLoginIdentityProvider

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
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.
-->
<!--
This file lists all authority providers to use when running securely.
-->
<loginIdentityProviders>
<provider>
<identifier>test-provider</identifier>
<class>org.apache.nifi.integration.util.NiFiTestLoginIdentityProvider</class>
</provider>
</loginIdentityProviders>

View File

@ -14,16 +14,15 @@
# limitations under the License.
# Core Properties #
nifi.version=nifi 0.2.1-SNAPSHOT
nifi.version=nifi version
nifi.flow.configuration.file=
nifi.flow.configuration.archive.dir=target/archive
nifi.flowcontroller.autoResumeState=true
nifi.flowcontroller.graceful.shutdown.period=10 sec
nifi.flowservice.writedelay.interval=2 sec
nifi.reporting.task.configuration.file=target/test-classes/access-control/reporting-tasks.xml
nifi.controller.service.configuration.file=target/test-classes/access-control/controller-services.xml
nifi.authority.provider.configuration.file=target/test-classes/access-control/authority-providers.xml
nifi.login.identity.provider.configuration.file=target/test-classes/access-control/login-identity-providers.xml
nifi.templates.directory=target/test-classes/access-control/templates
nifi.ui.banner.text=TEST BANNER
nifi.ui.autorefresh.interval=30 sec
@ -93,10 +92,11 @@ nifi.security.truststoreType=JKS
nifi.security.truststorePasswd=localtest
nifi.security.needClientAuth=true
nifi.security.user.authority.provider=test-provider
nifi.security.user.login.identity.provider=test-provider
nifi.security.authorizedUsers.file=target/test-classes/access-control/users.xml
nifi.security.user.credential.cache.duration=1 hr
nifi.security.support.new.account.requests=
nifi.security.default.user.roles=
nifi.security.anonymous.authorities=
# cluster common properties (cluster manager and nodes must have same values) #
nifi.cluster.protocol.heartbeat.interval=5 sec

View File

@ -22,11 +22,49 @@
</parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-web-security</artifactId>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/xsd</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<executions>
<execution>
<id>current</id>
<goals>
<goal>xjc</goal>
</goals>
<configuration>
<packageName>org.apache.nifi.authentication.generated</packageName>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<excludes>**/authentication/generated/*.java,</excludes>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-administration</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-nar-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
@ -39,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>

View File

@ -1,85 +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.security.cert.X509Certificate;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.apache.nifi.web.security.x509.SubjectDnX509PrincipalExtractor;
import org.apache.nifi.web.security.x509.X509CertificateExtractor;
import org.apache.commons.lang3.StringUtils;
/**
*
*/
public class DnUtils {
private static final Pattern proxyChainPattern = Pattern.compile("<(.*?)>");
/**
* @param request http request
* @return the X-ProxiedEntitiesChain from the specified request
*/
public static String getXProxiedEntitiesChain(final HttpServletRequest request) {
String xProxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain");
final X509Certificate cert = new X509CertificateExtractor().extractClientCertificate(request);
if (cert != null) {
final SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
final String extractedPrincipal = principalExtractor.extractPrincipal(cert).toString();
final String formattedPrincipal = formatProxyDn(extractedPrincipal);
if (StringUtils.isBlank(xProxiedEntitiesChain)) {
xProxiedEntitiesChain = formattedPrincipal;
} else {
xProxiedEntitiesChain += formattedPrincipal;
}
}
return xProxiedEntitiesChain;
}
/**
* Formats the specified DN to be set as a HTTP header using well known
* conventions.
*
* @param dn raw dn
* @return the dn formatted as an HTTP header
*/
public static String formatProxyDn(String dn) {
return "<" + dn + ">";
}
/**
* Tokenizes the specified proxy chain.
*
* @param rawProxyChain raw chain
* @return tokenized proxy chain
*/
public static Deque<String> tokenizeProxyChain(String rawProxyChain) {
final Deque<String> dnList = new ArrayDeque<>();
// parse the proxy chain
final Matcher rawProxyChainMatcher = proxyChainPattern.matcher(rawProxyChain);
while (rawProxyChainMatcher.find()) {
dnList.push(rawProxyChainMatcher.group(1));
}
return dnList;
}
}

View File

@ -0,0 +1,35 @@
/*
* 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 org.springframework.security.core.AuthenticationException;
/**
* Thrown if the authentication of a given request is invalid. For instance,
* an expired certificate or token.
*/
public class InvalidAuthenticationException extends AuthenticationException {
public InvalidAuthenticationException(String msg) {
super(msg);
}
public InvalidAuthenticationException(String msg, Throwable t) {
super(msg, t);
}
}

View File

@ -14,28 +14,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.security.authentication;
package org.apache.nifi.web.security;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.WebAttributes;
/**
* This is our own implementation of
* org.springframework.security.web.AuthenticationEntryPoint that allows us to
* send the response to the client exactly how we want to and log the results.
* This is our own implementation of org.springframework.security.web.AuthenticationEntryPoint that allows us to send the response to the client exactly how we want to and log the results.
*/
public class NiFiAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(NiFiAuthenticationEntryPoint.class);
private final NiFiProperties properties;
public NiFiAuthenticationEntryPoint(NiFiProperties properties) {
this.properties = properties;
}
/**
* Always returns a 403 error code to the client.
*
@ -47,23 +52,20 @@ public class NiFiAuthenticationEntryPoint implements AuthenticationEntryPoint {
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException, ServletException {
// get the last exception - the exception that is being passed in is a generic no credentials found
// exception because the authentication could not be found in the security context. the actual cause
// of the problem is stored in the session as the authentication_exception
Object authenticationException = request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
// if the content type is not set, mark as access denied
if (StringUtils.isBlank(response.getContentType())) {
// write the response message
PrintWriter out = response.getWriter();
response.setContentType("text/plain");
// log request result
if (authenticationException instanceof AuthenticationException) {
ae = (AuthenticationException) authenticationException;
logger.info(String.format("Rejecting access to web api: %s", ae.getMessage()));
// return authorized if the request is secure and this nifi supports new account requests
if (request.isSecure() && properties.getSupportNewAccountRequests()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
out.println("Not authorized.");
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
out.println("Access is denied.");
}
}
// set the response status
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("text/plain");
// write the response message
PrintWriter out = response.getWriter();
out.println("Access is denied.");
}
}

View File

@ -0,0 +1,209 @@
/*
* 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 javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.user.NiFiUser;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken;
import org.apache.nifi.web.security.user.NiFiUserUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
*
*/
public abstract class NiFiAuthenticationFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(NiFiAuthenticationFilter.class);
private AuthenticationManager authenticationManager;
private NiFiProperties properties;
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Checking secure context token: " + SecurityContextHolder.getContext().getAuthentication());
}
if (requiresAuthentication((HttpServletRequest) request)) {
authenticate((HttpServletRequest) request, (HttpServletResponse) response);
}
chain.doFilter(request, response);
}
private boolean requiresAuthentication(final HttpServletRequest request) {
// continue attempting authorization if the user is anonymous
if (isAnonymousUser()) {
return true;
}
// or there is no user yet
return NiFiUserUtils.getNiFiUser() == null && NiFiUserUtils.getNewAccountRequest() == null;
}
private boolean isAnonymousUser() {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
return user != null && NiFiUser.ANONYMOUS_USER_IDENTITY.equals(user.getIdentity());
}
private void authenticate(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
try {
final NiFiAuthenticationRequestToken authenticated = attemptAuthentication(request, response);
if (authenticated != null) {
// log the request attempt - response details will be logged later
logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)",
ProxiedEntitiesUtils.formatProxyDn(StringUtils.join(authenticated.getChain(), "><")), request.getMethod(),
request.getRequestURL().toString(), request.getRemoteAddr()));
final Authentication authorized = authenticationManager.authenticate(authenticated);
successfulAuthorization(request, response, authorized);
}
} catch (final AuthenticationException ae) {
if (!isAnonymousUser()) {
unsuccessfulAuthorization(request, response, ae);
}
}
}
public abstract NiFiAuthenticationRequestToken attemptAuthentication(HttpServletRequest request, HttpServletResponse response);
protected void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
ProxiedEntitiesUtils.successfulAuthorization(request, response, authResult);
}
protected void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException {
// populate the response
ProxiedEntitiesUtils.unsuccessfulAuthorization(request, response, ae);
// 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 InvalidAuthenticationException) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
out.println(ae.getMessage());
} 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);
}
}
/**
* Determines if the specified request is attempting to register a new user account.
*
* @param request http request
* @return true if new user
*/
protected final boolean isNewAccountRequest(HttpServletRequest request) {
if ("POST".equalsIgnoreCase(request.getMethod())) {
String path = request.getPathInfo();
if (StringUtils.isNotBlank(path)) {
if ("/controller/users".equals(path)) {
return true;
}
}
}
return false;
}
/**
* Extracts the justification from the specified request.
*
* @param request The request
* @return The justification
*/
protected final String getJustification(HttpServletRequest request) {
// get the justification
String justification = request.getParameter("justification");
if (justification == null) {
justification = StringUtils.EMPTY;
}
return justification;
}
@Override
public void destroy() {
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setProperties(NiFiProperties properties) {
this.properties = properties;
}
}

Some files were not shown because too many files have changed in this diff Show More