This is an automated email from the ASF dual-hosted git repository. thenatog pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/master by this push: new e81960f NIFI-7170: - Adding a flag to nifi.properties to disable anonymous authentication. e81960f is described below commit e81960f8e8a6bd446bb766a5fb2e00c7e2e973f8 Author: Matt Gilman <matt.c.gil...@gmail.com> AuthorDate: Wed Feb 19 15:49:44 2020 -0500 NIFI-7170: - Adding a flag to nifi.properties to disable anonymous authentication. NIFI-7170: - Fixing checkstyle issues. NIFI-7170: - Adding missing license header. NIFI-7170: - Initial PR feedback. NIFI-7170: - Fixing broken integration tests. - Creating new integration tests for verifying allowing and preventing anonymous access. NIFI-7170: - Ensuring the new anonymous authentication property is considered for proxied requests. NIFI-7170 - Fixed comment. Signed-off-by: Nathan Gough <thena...@gmail.com> This closes #4099. --- .../java/org/apache/nifi/util/NiFiProperties.java | 15 +- .../src/main/asciidoc/administration-guide.adoc | 12 +- .../tasks/NiFiPropertiesDiagnosticTask.java | 1 + .../nifi-framework/nifi-resources/pom.xml | 1 + .../src/main/resources/conf/nifi.properties | 1 + .../nifi/web/NiFiWebApiSecurityConfiguration.java | 25 +++- .../accesscontrol/AccessControlHelper.java | 10 +- .../accesscontrol/ITAccessTokenEndpoint.java | 166 ++++++--------------- ...lper.java => OneWaySslAccessControlHelper.java} | 97 ++++-------- .../anonymous/AbstractAnonymousUserTest.java | 57 +++++++ .../anonymous/ITAllowDirectAnonymousAccess.java | 66 ++++++++ .../anonymous/ITAllowProxiedAnonymousAccess.java | 66 ++++++++ .../anonymous/ITPreventDirectAnonymousAccess.java | 56 +++++++ .../anonymous/ITPreventProxiedAnonymousAccess.java | 57 +++++++ .../nifi/integration/util/NiFiTestAuthorizer.java | 5 + .../nifi-anonymous-allowed.properties | 139 +++++++++++++++++ ...java => NiFiAnonymousAuthenticationFilter.java} | 25 ++-- .../NiFiAnonymousAuthenticationProvider.java | 56 +++++++ .../NiFiAnonymousAuthenticationRequestToken.java | 60 ++++++++ .../security/x509/X509AuthenticationProvider.java | 7 + .../main/resources/nifi-web-security-context.xml | 6 + .../NiFiAnonymousAuthenticationProviderTest.java | 91 +++++++++++ .../x509/X509AuthenticationProviderTest.java | 50 ++++++- 23 files changed, 853 insertions(+), 216 deletions(-) diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java index f0e8e6b..214d178 100644 --- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java +++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java @@ -153,6 +153,7 @@ public abstract class NiFiProperties { public static final String SECURITY_TRUSTSTORE_TYPE = "nifi.security.truststoreType"; public static final String SECURITY_TRUSTSTORE_PASSWD = "nifi.security.truststorePasswd"; public static final String SECURITY_USER_AUTHORIZER = "nifi.security.user.authorizer"; + public static final String SECURITY_ANONYMOUS_AUTHENTICATION = "nifi.security.allow.anonymous.authentication"; public static final String SECURITY_USER_LOGIN_IDENTITY_PROVIDER = "nifi.security.user.login.identity.provider"; 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"; @@ -920,9 +921,18 @@ public abstract class NiFiProperties { } /** + * @return True if property value is 'true'; False otherwise. + */ + public Boolean isAnonymousAuthenticationAllowed() { + final String anonymousAuthenticationAllowed = getProperty(SECURITY_ANONYMOUS_AUTHENTICATION, "false"); + + return "true".equalsIgnoreCase(anonymousAuthenticationAllowed); + } + + /** * Returns whether an OpenId Connect (OIDC) URL is set. * - * @return whether an OpenId Connection URL is set + * @return whether an OpenId Connect URL is set */ public boolean isOidcEnabled() { return !StringUtils.isBlank(getOidcDiscoveryUrl()); @@ -1066,12 +1076,13 @@ public abstract class NiFiProperties { * - Kerberos service support is not enabled * - openid connect is not enabled * - knox sso is not enabled + * - anonymous authentication is not enabled * </p> * * @return true if client certificates are required for access to the REST API */ public boolean isClientAuthRequiredForRestApi() { - return !isLoginIdentityProviderEnabled() && !isKerberosSpnegoSupportEnabled() && !isOidcEnabled() && !isKnoxSsoEnabled(); + return !isLoginIdentityProviderEnabled() && !isKerberosSpnegoSupportEnabled() && !isOidcEnabled() && !isKnoxSsoEnabled() && !isAnonymousAuthenticationAllowed(); } public InetSocketAddress getNodeApiAddress() { diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc index 0b941bc..a3864f0 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -231,7 +231,16 @@ token during authentication. NOTE: NiFi can only be configured for username/password, OpenId Connect, or Apache Knox at a given time. It does not support running each of these concurrently. NiFi will require client certificates for authenticating users over HTTPS if none of these are configured. -A secured instance of NiFi cannot be accessed anonymously unless configured to use an <<ldap_login_identity_provider>> or <<kerberos_login_identity_provider>> Login Identity Provider, which in turn must be configured to explicitly allow anonymous access. Anonymous access is not currently possible by the default FileAuthorizer (see <<authorizer-configuration>>), but is a future effort (link:https://issues.apache.org/jira/browse/NIFI-2730[NIFI-2730^]). +A user cannot anonymously authenticate with a secured instance of NiFi unless `nifi.security.allow.anonymous.authentication` is set to `true`. +If this is the case, NiFi must also be configured with an Authorizer that supports authorizing an anonymous user. Currently, NiFi does not ship +with any Authorizers that support this. There is a feature request here to help support it (link:https://issues.apache.org/jira/browse/NIFI-2730[NIFI-2730^]). + +There are three scenarios to consider when setting `nifi.security.allow.anonymous.authentication`. When the user is directly calling an endpoint +with no attempted authentication then `nifi.security.allow.anonymous.authentication` will control whether the request is authenticated or rejected. +The other two scenarios are when the request is proxied. This could either be proxied by a NiFi node (e.g. a node in the NiFi cluster) or by a separate +proxy that is proxying a request for an anonymous user. In these proxy scenarios `nifi.security.allow.anonymous.authentication` will control whether the +request is authenticated or rejected. In all three of these scenarios if the request is authenticated it will subsequently be subjected to normal +authorization based on the requested resource. NOTE: NiFi does not perform user authentication over HTTP. Using HTTP, all users will be granted all roles. @@ -3289,6 +3298,7 @@ These properties pertain to various security features in NiFi. Many of these pro |`nifi.security.truststoreType`|The truststore type. It is blank by default. |`nifi.security.truststorePasswd`|The truststore password. It is blank by default. |`nifi.security.user.authorizer`|Specifies which of the configured Authorizers in the _authorizers.xml_ file to use. By default, it is set to `file-provider`. +|`nifi.security.allow.anonymous.authentication`|Whether anonymous authentication is allowed when running over HTTPS. If set to true, client certificates are not required to connect via TLS. |`nifi.security.user.login.identity.provider`|This indicates what type of login identity provider to use. The default value is blank, can be set to the identifier from a provider in the file specified in `nifi.login.identity.provider.configuration.file`. Setting this property will trigger NiFi to support username/password authentication. |`nifi.security.ocsp.responder.url`|This is the URL for the Online Certificate Status Protocol (OCSP) responder if one is being used. It is blank by default. diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/diagnostics/bootstrap/tasks/NiFiPropertiesDiagnosticTask.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/diagnostics/bootstrap/tasks/NiFiPropertiesDiagnosticTask.java index 0443cc0..22cbe44 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/diagnostics/bootstrap/tasks/NiFiPropertiesDiagnosticTask.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/diagnostics/bootstrap/tasks/NiFiPropertiesDiagnosticTask.java @@ -35,6 +35,7 @@ public class NiFiPropertiesDiagnosticTask implements DiagnosticTask { "nifi.ui.autorefresh.interval", "nifi.cluster.node.protocol.max.threads", "nifi.cluster.node.protocol.threads", + "nifi.security.allow.anonymous.authentication", "nifi.security.user.login.identity.provider", "nifi.security.user.authorizer", "nifi.provenance.repository.implementation", diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml index 702ffe8..58b250c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml @@ -155,6 +155,7 @@ <nifi.security.truststoreType /> <nifi.security.truststorePasswd /> <nifi.security.user.authorizer>managed-authorizer</nifi.security.user.authorizer> + <nifi.security.allow.anonymous.authentication>false</nifi.security.allow.anonymous.authentication> <nifi.security.user.login.identity.provider /> <nifi.security.x509.principal.extractor /> <nifi.security.ocsp.responder.url /> diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties index 2d55730..82174eb 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties @@ -170,6 +170,7 @@ nifi.security.truststore=${nifi.security.truststore} nifi.security.truststoreType=${nifi.security.truststoreType} nifi.security.truststorePasswd=${nifi.security.truststorePasswd} nifi.security.user.authorizer=${nifi.security.user.authorizer} +nifi.security.allow.anonymous.authentication=${nifi.security.allow.anonymous.authentication} nifi.security.user.login.identity.provider=${nifi.security.user.login.identity.provider} nifi.security.ocsp.responder.url=${nifi.security.ocsp.responder.url} nifi.security.ocsp.responder.certificate=${nifi.security.ocsp.responder.certificate} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java index 647e6c8..b8d35e9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java @@ -17,7 +17,8 @@ package org.apache.nifi.web; import org.apache.nifi.util.NiFiProperties; -import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter; +import org.apache.nifi.web.security.anonymous.NiFiAnonymousAuthenticationFilter; +import org.apache.nifi.web.security.anonymous.NiFiAnonymousAuthenticationProvider; import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter; import org.apache.nifi.web.security.jwt.JwtAuthenticationProvider; import org.apache.nifi.web.security.knox.KnoxAuthenticationFilter; @@ -76,7 +77,8 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte private KnoxAuthenticationFilter knoxAuthenticationFilter; private KnoxAuthenticationProvider knoxAuthenticationProvider; - private NiFiAnonymousUserFilter anonymousAuthenticationFilter; + private NiFiAnonymousAuthenticationFilter anonymousAuthenticationFilter; + private NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider; public NiFiWebApiSecurityConfiguration() { super(true); // disable defaults @@ -118,7 +120,10 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte http.addFilterBefore(knoxFilterBean(), AnonymousAuthenticationFilter.class); // anonymous - http.anonymous().authenticationFilter(anonymousFilterBean()); + http.addFilterAfter(anonymousFilterBean(), AnonymousAuthenticationFilter.class); + + // disable default anonymous handling because it doesn't handle conditional authentication well + http.anonymous().disable(); } @@ -144,7 +149,8 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte .authenticationProvider(x509AuthenticationProvider) .authenticationProvider(jwtAuthenticationProvider) .authenticationProvider(otpAuthenticationProvider) - .authenticationProvider(knoxAuthenticationProvider); + .authenticationProvider(knoxAuthenticationProvider) + .authenticationProvider(anonymousAuthenticationProvider); } @Bean @@ -190,9 +196,11 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte } @Bean - public NiFiAnonymousUserFilter anonymousFilterBean() throws Exception { + public NiFiAnonymousAuthenticationFilter anonymousFilterBean() throws Exception { if (anonymousAuthenticationFilter == null) { - anonymousAuthenticationFilter = new NiFiAnonymousUserFilter(); + anonymousAuthenticationFilter = new NiFiAnonymousAuthenticationFilter(); + anonymousAuthenticationFilter.setProperties(properties); + anonymousAuthenticationFilter.setAuthenticationManager(authenticationManager()); } return anonymousAuthenticationFilter; } @@ -218,6 +226,11 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte } @Autowired + public void setAnonymousAuthenticationProvider(NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider) { + this.anonymousAuthenticationProvider = anonymousAuthenticationProvider; + } + + @Autowired public void setX509AuthenticationProvider(X509AuthenticationProvider x509AuthenticationProvider) { this.x509AuthenticationProvider = x509AuthenticationProvider; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java index 2571866..b56f708 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java @@ -28,6 +28,7 @@ import org.apache.nifi.nar.NarUnpacker; import org.apache.nifi.nar.StandardExtensionDiscoveringManager; import org.apache.nifi.nar.SystemBundle; import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.StringUtils; import javax.ws.rs.core.Response; import java.io.File; @@ -52,6 +53,7 @@ public class AccessControlHelper { private NiFiTestUser noneUser; private NiFiTestUser privilegedUser; private NiFiTestUser executeCodeUser; + private NiFiTestUser anonymousUser; private static final String CONTEXT_PATH = "/nifi-api"; @@ -67,7 +69,8 @@ public class AccessControlHelper { // configure the location of the nifi properties File nifiPropertiesFile = new File(nifiPropertiesPath); System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath()); - NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null); + + NiFiProperties props = NiFiProperties.createBasicNiFiProperties(nifiPropertiesPath, null); flowXmlPath = props.getProperty(NiFiProperties.FLOW_CONFIGURATION_FILE); final File libTargetDir = new File("target/test-classes/access-control/lib"); @@ -103,6 +106,7 @@ public class AccessControlHelper { noneUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.NONE_USER_DN); privilegedUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.PRIVILEGED_USER_DN); executeCodeUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.EXECUTED_CODE_USER_DN); + anonymousUser = new NiFiTestUser(server.getClient(), StringUtils.EMPTY); // populate the initial data flow NiFiWebApiTest.populateFlow(server.getClient(), baseUrl, readWriteUser, READ_WRITE_CLIENT_ID); @@ -132,6 +136,10 @@ public class AccessControlHelper { return executeCodeUser; } + public NiFiTestUser getAnonymousUser() { + return anonymousUser; + } + public void testGenericGetUri(final String uri) throws Exception { Response response; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java index 0dc359f..5904929 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java @@ -16,21 +16,8 @@ */ package org.apache.nifi.integration.accesscontrol; -import org.apache.nifi.web.security.jwt.JwtServiceTest; import net.minidev.json.JSONObject; -import org.apache.commons.io.FileUtils; -import org.apache.nifi.bundle.Bundle; -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.ExtensionDiscoveringManager; -import org.apache.nifi.nar.ExtensionManagerHolder; -import org.apache.nifi.nar.NarClassLoadersHolder; -import org.apache.nifi.nar.NarUnpacker; -import org.apache.nifi.nar.StandardExtensionDiscoveringManager; -import org.apache.nifi.nar.SystemBundle; -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; @@ -38,18 +25,13 @@ 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.apache.nifi.web.security.jwt.JwtServiceTest; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import javax.net.ssl.SSLContext; -import javax.ws.rs.client.Client; import javax.ws.rs.core.Response; -import java.io.File; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; import java.util.Calendar; import java.util.HashMap; import java.util.LinkedHashMap; @@ -61,63 +43,15 @@ import java.util.StringJoiner; */ public class ITAccessTokenEndpoint { + private static OneWaySslAccessControlHelper helper; + private final String user = "unregistered-user@nifi"; private final String password = "password"; private static final String CLIENT_ID = "token-endpoint-id"; - private static final String CONTEXT_PATH = "/nifi-api"; - - private static String flowXmlPath; - 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()); - - NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null); - flowXmlPath = props.getProperty(NiFiProperties.FLOW_CONFIGURATION_FILE); - - // delete the database directory to avoid issues with re-registration in testRequestAccessUsingToken - FileUtils.deleteDirectory(props.getDatabaseRepositoryPath().toFile()); - - final File libTargetDir = new File("target/test-classes/access-control/lib"); - libTargetDir.mkdirs(); - - final File libSourceDir = new File("src/test/resources/lib"); - for (final File libFile : libSourceDir.listFiles()) { - final File libDestFile = new File(libTargetDir, libFile.getName()); - Files.copy(libFile.toPath(), libDestFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - - final Bundle systemBundle = SystemBundle.create(props); - NarUnpacker.unpackNars(props, systemBundle); - NarClassLoadersHolder.getInstance().init(props.getFrameworkWorkingDirectory(), props.getExtensionsWorkingDirectory()); - - // load extensions - final ExtensionDiscoveringManager extensionManager = new StandardExtensionDiscoveringManager(); - extensionManager.discoverExtensions(systemBundle, NarClassLoadersHolder.getInstance().getBundles()); - ExtensionManagerHolder.init(extensionManager); - - // start the server - SERVER = new NiFiTestServer("src/main/webapp", CONTEXT_PATH, props); - 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"); + helper = new OneWaySslAccessControlHelper(); } // ----------- @@ -130,9 +64,9 @@ public class ITAccessTokenEndpoint { */ @Test public void testGetAccessConfig() throws Exception { - String url = BASE_URL + "/access/config"; + String url = helper.getBaseUrl() + "/access/config"; - Response response = TOKEN_USER.testGet(url); + Response response = helper.getUser().testGet(url); // ensure the request is successful Assert.assertEquals(200, response.getStatus()); @@ -157,9 +91,9 @@ public class ITAccessTokenEndpoint { */ @Test public void testCreateProcessorUsingToken() throws Exception { - String url = BASE_URL + "/access/token"; + String url = helper.getBaseUrl() + "/access/token"; - Response response = TOKEN_USER.testCreateToken(url, "user@nifi", "whatever"); + Response response = helper.getUser().testCreateToken(url, "user@nifi", "whatever"); // ensure the request is successful Assert.assertEquals(201, response.getStatus()); @@ -172,7 +106,7 @@ public class ITAccessTokenEndpoint { } private ProcessorDTO createProcessor(final String token) throws Exception { - String url = BASE_URL + "/process-groups/root/processors"; + String url = helper.getBaseUrl() + "/process-groups/root/processors"; // authorization header Map<String, String> headers = new HashMap<>(); @@ -194,7 +128,7 @@ public class ITAccessTokenEndpoint { entity.setComponent(processor); // perform the request - Response response = TOKEN_USER.testPostWithHeaders(url, entity, headers); + Response response = helper.getUser().testPostWithHeaders(url, entity, headers); // ensure the request is successful Assert.assertEquals(201, response.getStatus()); @@ -217,9 +151,9 @@ public class ITAccessTokenEndpoint { */ @Test public void testInvalidCredentials() throws Exception { - String url = BASE_URL + "/access/token"; + String url = helper.getBaseUrl() + "/access/token"; - Response response = TOKEN_USER.testCreateToken(url, "user@nifi", "not a real password"); + Response response = helper.getUser().testCreateToken(url, "user@nifi", "not a real password"); // ensure the request is successful Assert.assertEquals(400, response.getStatus()); @@ -232,9 +166,9 @@ public class ITAccessTokenEndpoint { */ @Test public void testUnknownUser() throws Exception { - String url = BASE_URL + "/access/token"; + String url = helper.getBaseUrl() + "/access/token"; - Response response = TOKEN_USER.testCreateToken(url, "not a real user", "not a real password"); + Response response = helper.getUser().testCreateToken(url, "not a real user", "not a real password"); // ensure the request is successful Assert.assertEquals(400, response.getStatus()); @@ -247,10 +181,10 @@ public class ITAccessTokenEndpoint { */ @Test public void testRequestAccessUsingToken() throws Exception { - String accessStatusUrl = BASE_URL + "/access"; - String accessTokenUrl = BASE_URL + "/access/token"; + String accessStatusUrl = helper.getBaseUrl() + "/access"; + String accessTokenUrl = helper.getBaseUrl() + "/access/token"; - Response response = TOKEN_USER.testGet(accessStatusUrl); + Response response = helper.getUser().testGet(accessStatusUrl); // ensure the request is successful Assert.assertEquals(200, response.getStatus()); @@ -261,7 +195,7 @@ public class ITAccessTokenEndpoint { // verify unknown Assert.assertEquals("UNKNOWN", accessStatus.getStatus()); - response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password); + response = helper.getUser().testCreateToken(accessTokenUrl, user, password); // ensure the request is successful Assert.assertEquals(201, response.getStatus()); @@ -274,7 +208,7 @@ public class ITAccessTokenEndpoint { headers.put("Authorization", "Bearer " + token); // check the status with the token - response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers); + response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers); // ensure the request is successful Assert.assertEquals(200, response.getStatus()); @@ -288,11 +222,11 @@ public class ITAccessTokenEndpoint { @Test public void testLogOutSuccess() throws Exception { - String accessStatusUrl = BASE_URL + "/access"; - String accessTokenUrl = BASE_URL + "/access/token"; - String logoutUrl = BASE_URL + "/access/logout"; + String accessStatusUrl = helper.getBaseUrl() + "/access"; + String accessTokenUrl = helper.getBaseUrl() + "/access/token"; + String logoutUrl = helper.getBaseUrl() + "/access/logout"; - Response response = TOKEN_USER.testGet(accessStatusUrl); + Response response = helper.getUser().testGet(accessStatusUrl); // ensure the request is successful Assert.assertEquals(200, response.getStatus()); @@ -303,7 +237,7 @@ public class ITAccessTokenEndpoint { // verify unknown Assert.assertEquals("UNKNOWN", accessStatus.getStatus()); - response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password); + response = helper.getUser().testCreateToken(accessTokenUrl, user, password); // ensure the request is successful Assert.assertEquals(201, response.getStatus()); @@ -316,7 +250,7 @@ public class ITAccessTokenEndpoint { headers.put("Authorization", "Bearer " + token); // check the status with the token - response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers); + response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers); // ensure the request is successful Assert.assertEquals(200, response.getStatus()); @@ -329,21 +263,21 @@ public class ITAccessTokenEndpoint { // log out - response = TOKEN_USER.testGetWithHeaders(logoutUrl, null, headers); + response = helper.getUser().testDeleteWithHeaders(logoutUrl, headers); Assert.assertEquals(200, response.getStatus()); // ensure we can no longer use our token - response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers); + response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers); Assert.assertEquals(401, response.getStatus()); } @Test public void testLogOutNoTokenHeader() throws Exception { - String accessStatusUrl = BASE_URL + "/access"; - String accessTokenUrl = BASE_URL + "/access/token"; - String logoutUrl = BASE_URL + "/access/logout"; + String accessStatusUrl = helper.getBaseUrl() + "/access"; + String accessTokenUrl = helper.getBaseUrl() + "/access/token"; + String logoutUrl = helper.getBaseUrl() + "/access/logout"; - Response response = TOKEN_USER.testGet(accessStatusUrl); + Response response = helper.getUser().testGet(accessStatusUrl); // ensure the request is successful Assert.assertEquals(200, response.getStatus()); @@ -354,7 +288,7 @@ public class ITAccessTokenEndpoint { // verify unknown Assert.assertEquals("UNKNOWN", accessStatus.getStatus()); - response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password); + response = helper.getUser().testCreateToken(accessTokenUrl, user, password); // ensure the request is successful Assert.assertEquals(201, response.getStatus()); @@ -367,7 +301,7 @@ public class ITAccessTokenEndpoint { headers.put("Authorization", "Bearer " + token); // check the status with the token - response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers); + response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers); // ensure the request is successful Assert.assertEquals(200, response.getStatus()); @@ -380,8 +314,8 @@ public class ITAccessTokenEndpoint { // log out should fail as we provided no token for logout to use - response = TOKEN_USER.testGetWithHeaders(logoutUrl, null, null); - Assert.assertEquals(500, response.getStatus()); + response = helper.getUser().testDeleteWithHeaders(logoutUrl, null); + Assert.assertEquals(401, response.getStatus()); } @Test @@ -405,11 +339,11 @@ public class ITAccessTokenEndpoint { claims.put("iat", TOKEN_ISSUED_AT); final String EXPECTED_PAYLOAD = new JSONObject(claims).toString(); - String accessStatusUrl = BASE_URL + "/access"; - String accessTokenUrl = BASE_URL + "/access/token"; - String logoutUrl = BASE_URL + "/access/logout"; + String accessStatusUrl = helper.getBaseUrl() + "/access"; + String accessTokenUrl = helper.getBaseUrl() + "/access/token"; + String logoutUrl = helper.getBaseUrl() + "/access/logout"; - Response response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password); + Response response = helper.getUser().testCreateToken(accessTokenUrl, user, password); // ensure the request is successful Assert.assertEquals(201, response.getStatus()); @@ -419,7 +353,7 @@ public class ITAccessTokenEndpoint { Map<String, String> headers = new HashMap<>(); headers.put("Authorization", "Bearer " + token); // check the status with the token - response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers); + response = helper.getUser().testGetWithHeaders(accessStatusUrl, null, headers); Assert.assertEquals(200, response.getStatus()); // Generate a token that will not match signatures with the generated token. @@ -428,7 +362,7 @@ public class ITAccessTokenEndpoint { badHeaders.put("Authorization", "Bearer " + UNKNOWN_USER_TOKEN); // Log out should fail as we provide a bad token to use, signatures will mismatch - response = TOKEN_USER.testGetWithHeaders(logoutUrl, null, badHeaders); + response = helper.getUser().testGetWithHeaders(logoutUrl, null, badHeaders); Assert.assertEquals(401, response.getStatus()); } @@ -442,10 +376,10 @@ public class ITAccessTokenEndpoint { final long TOKEN_ISSUED_AT = currentTime; final long TOKEN_EXPIRATION_SECONDS = currentTime + EXPIRATION_SECONDS; - String accessTokenUrl = BASE_URL + "/access/token"; - String logoutUrl = BASE_URL + "/access/logout"; + String accessTokenUrl = helper.getBaseUrl() + "/access/token"; + String logoutUrl = helper.getBaseUrl() + "/access/logout"; - Response response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password); + Response response = helper.getUser().testCreateToken(accessTokenUrl, user, password); // ensure the request is successful Assert.assertEquals(201, response.getStatus()); // replace the user in the token with an unknown user @@ -477,20 +411,12 @@ public class ITAccessTokenEndpoint { badHeaders.put("Authorization", "Bearer " + splicedUserToken); // Log out should fail as we provide a bad token to use, signatures will mismatch - response = TOKEN_USER.testGetWithHeaders(logoutUrl, null, badHeaders); + response = helper.getUser().testGetWithHeaders(logoutUrl, null, badHeaders); Assert.assertEquals(401, response.getStatus()); } @AfterClass public static void cleanup() throws Exception { - // shutdown the server - SERVER.shutdownServer(); - SERVER = null; - - // look for the flow.xml - File flow = new File(flowXmlPath); - if (flow.exists()) { - flow.delete(); - } + helper.cleanup(); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/OneWaySslAccessControlHelper.java similarity index 57% copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/OneWaySslAccessControlHelper.java index 2571866..e5942e8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/OneWaySslAccessControlHelper.java @@ -16,9 +16,8 @@ */ package org.apache.nifi.integration.accesscontrol; +import org.apache.commons.io.FileUtils; import org.apache.nifi.bundle.Bundle; -import org.apache.nifi.integration.NiFiWebApiTest; -import org.apache.nifi.integration.util.NiFiTestAuthorizer; import org.apache.nifi.integration.util.NiFiTestServer; import org.apache.nifi.integration.util.NiFiTestUser; import org.apache.nifi.nar.ExtensionDiscoveringManager; @@ -27,31 +26,22 @@ import org.apache.nifi.nar.NarClassLoadersHolder; import org.apache.nifi.nar.NarUnpacker; import org.apache.nifi.nar.StandardExtensionDiscoveringManager; import org.apache.nifi.nar.SystemBundle; +import org.apache.nifi.security.util.SslContextFactory; import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.util.WebUtils; -import javax.ws.rs.core.Response; +import javax.net.ssl.SSLContext; +import javax.ws.rs.client.Client; import java.io.File; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import static org.junit.Assert.assertEquals; - /** * Access control test for the dfm user. */ -public class AccessControlHelper { - - public static final String NONE_CLIENT_ID = "client-id"; - public static final String READ_CLIENT_ID = "r-client-id"; - public static final String WRITE_CLIENT_ID = "w-client-id"; - public static final String READ_WRITE_CLIENT_ID = "rw-client-id"; +public class OneWaySslAccessControlHelper { - private NiFiTestUser readUser; - private NiFiTestUser writeUser; - private NiFiTestUser readWriteUser; - private NiFiTestUser noneUser; - private NiFiTestUser privilegedUser; - private NiFiTestUser executeCodeUser; + private NiFiTestUser user; private static final String CONTEXT_PATH = "/nifi-api"; @@ -59,17 +49,21 @@ public class AccessControlHelper { private String baseUrl; private String flowXmlPath; - public AccessControlHelper() throws Exception { + public OneWaySslAccessControlHelper() throws Exception { this("src/test/resources/access-control/nifi.properties"); } - public AccessControlHelper(final String nifiPropertiesPath) throws Exception { + public OneWaySslAccessControlHelper(final String nifiPropertiesPath) throws Exception { // configure the location of the nifi properties File nifiPropertiesFile = new File(nifiPropertiesPath); System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath()); - NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null); + + NiFiProperties props = NiFiProperties.createBasicNiFiProperties(nifiPropertiesPath, null); flowXmlPath = props.getProperty(NiFiProperties.FLOW_CONFIGURATION_FILE); + // delete the database directory to avoid issues with re-registration in testRequestAccessUsingToken + FileUtils.deleteDirectory(props.getDatabaseRepositoryPath().toFile()); + final File libTargetDir = new File("target/test-classes/access-control/lib"); libTargetDir.mkdirs(); @@ -96,66 +90,25 @@ public class AccessControlHelper { // get the base url baseUrl = server.getBaseUrl() + CONTEXT_PATH; - // create the users - user purposefully decoupled from clientId (same user different browsers tabs) - readUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.READ_USER_DN); - writeUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.WRITE_USER_DN); - readWriteUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.READ_WRITE_USER_DN); - noneUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.NONE_USER_DN); - privilegedUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.PRIVILEGED_USER_DN); - executeCodeUser = new NiFiTestUser(server.getClient(), NiFiTestAuthorizer.EXECUTED_CODE_USER_DN); - - // populate the initial data flow - NiFiWebApiTest.populateFlow(server.getClient(), baseUrl, readWriteUser, READ_WRITE_CLIENT_ID); - } - - public NiFiTestUser getReadUser() { - return readUser; - } - - public NiFiTestUser getWriteUser() { - return writeUser; + // create the user + final Client client = WebUtils.createClient(null, createTrustContext(props)); + user = new NiFiTestUser(client, null); } - public NiFiTestUser getReadWriteUser() { - return readWriteUser; - } - - public NiFiTestUser getNoneUser() { - return noneUser; - } - - public NiFiTestUser getPrivilegedUser() { - return privilegedUser; - } - - public NiFiTestUser getExecuteCodeUser() { - return executeCodeUser; - } - - public void testGenericGetUri(final String uri) throws Exception { - Response response; - - // read - response = getReadUser().testGet(uri); - assertEquals(200, response.getStatus()); - - // read/write - response = getReadWriteUser().testGet(uri); - assertEquals(200, response.getStatus()); - - // write - response = getWriteUser().testGet(uri); - assertEquals(403, response.getStatus()); - - // none - response = getNoneUser().testGet(uri); - assertEquals(403, response.getStatus()); + public NiFiTestUser getUser() { + return user; } public String getBaseUrl() { return baseUrl; } + 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"); + } + public void cleanup() throws Exception { // shutdown the server server.shutdownServer(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/AbstractAnonymousUserTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/AbstractAnonymousUserTest.java new file mode 100644 index 0000000..66a7fac --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/AbstractAnonymousUserTest.java @@ -0,0 +1,57 @@ +/* + * 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.anonymous; + +import org.apache.nifi.integration.util.NiFiTestUser; +import org.apache.nifi.integration.util.SourceTestProcessor; +import org.apache.nifi.web.api.dto.ProcessorDTO; +import org.apache.nifi.web.api.dto.RevisionDTO; +import org.apache.nifi.web.api.entity.ProcessorEntity; + +import javax.ws.rs.core.Response; + +public abstract class AbstractAnonymousUserTest { + + private static final String CLIENT_ID = "anonymous-client-id"; + + /** + * Attempt to create a processor anonymously. + * + * @throws Exception ex + */ + protected Response testCreateProcessor(final String baseUrl, final NiFiTestUser niFiTestUser) throws Exception { + final String url = baseUrl + "/process-groups/root/processors"; + + // create the processor + final 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(0l); + + // create the entity body + final ProcessorEntity entity = new ProcessorEntity(); + entity.setRevision(revision); + entity.setComponent(processor); + + // perform the request + return niFiTestUser.testPost(url, entity); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITAllowDirectAnonymousAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITAllowDirectAnonymousAccess.java new file mode 100644 index 0000000..004388c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITAllowDirectAnonymousAccess.java @@ -0,0 +1,66 @@ +/* + * 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.anonymous; + +import org.apache.nifi.integration.accesscontrol.OneWaySslAccessControlHelper; +import org.apache.nifi.web.api.dto.ProcessorDTO; +import org.apache.nifi.web.api.entity.ProcessorEntity; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.ws.rs.core.Response; + +/** + * Integration test for allowing direct anonymous access. + */ +public class ITAllowDirectAnonymousAccess extends AbstractAnonymousUserTest { + + private static OneWaySslAccessControlHelper helper; + + @BeforeClass + public static void setup() throws Exception { + helper = new OneWaySslAccessControlHelper("src/test/resources/access-control/nifi-anonymous-allowed.properties"); + } + + /** + * Attempt to create a processor anonymously. + * + * @throws Exception ex + */ + @Test + public void testDirectAnonymousAccess() throws Exception { + final Response response = super.testCreateProcessor(helper.getBaseUrl(), helper.getUser()); + + // ensure the request is successful + Assert.assertEquals(201, response.getStatus()); + + // get the entity body + final ProcessorEntity entity = response.readEntity(ProcessorEntity.class); + + // verify creation + final ProcessorDTO processor = entity.getComponent(); + Assert.assertEquals("Copy", processor.getName()); + Assert.assertEquals("org.apache.nifi.integration.util.SourceTestProcessor", processor.getType()); + } + + @AfterClass + public static void cleanup() throws Exception { + helper.cleanup(); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITAllowProxiedAnonymousAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITAllowProxiedAnonymousAccess.java new file mode 100644 index 0000000..25c34a1 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITAllowProxiedAnonymousAccess.java @@ -0,0 +1,66 @@ +/* + * 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.anonymous; + +import org.apache.nifi.integration.accesscontrol.AccessControlHelper; +import org.apache.nifi.web.api.dto.ProcessorDTO; +import org.apache.nifi.web.api.entity.ProcessorEntity; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.ws.rs.core.Response; + +/** + * Integration test for allowing proxied anonymous access. + */ +public class ITAllowProxiedAnonymousAccess extends AbstractAnonymousUserTest { + + private static AccessControlHelper helper; + + @BeforeClass + public static void setup() throws Exception { + helper = new AccessControlHelper("src/test/resources/access-control/nifi-anonymous-allowed.properties"); + } + + /** + * Attempt to create a processor anonymously. + * + * @throws Exception ex + */ + @Test + public void testProxiedAnonymousAccess() throws Exception { + final Response response = super.testCreateProcessor(helper.getBaseUrl(), helper.getAnonymousUser()); + + // ensure the request is successful + Assert.assertEquals(201, response.getStatus()); + + // get the entity body + final ProcessorEntity entity = response.readEntity(ProcessorEntity.class); + + // verify creation + final ProcessorDTO processor = entity.getComponent(); + Assert.assertEquals("Copy", processor.getName()); + Assert.assertEquals("org.apache.nifi.integration.util.SourceTestProcessor", processor.getType()); + } + + @AfterClass + public static void cleanup() throws Exception { + helper.cleanup(); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITPreventDirectAnonymousAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITPreventDirectAnonymousAccess.java new file mode 100644 index 0000000..1ab4bfe --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITPreventDirectAnonymousAccess.java @@ -0,0 +1,56 @@ +/* + * 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.anonymous; + +import org.apache.nifi.integration.accesscontrol.OneWaySslAccessControlHelper; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.ws.rs.core.Response; + +/** + * Integration test for preventing direct anonymous access. + */ +public class ITPreventDirectAnonymousAccess extends AbstractAnonymousUserTest { + + private static OneWaySslAccessControlHelper helper; + + @BeforeClass + public static void setup() throws Exception { + helper = new OneWaySslAccessControlHelper(); + } + + /** + * Attempt to create a processor anonymously. + * + * @throws Exception ex + */ + @Test + public void testDirectAnonymousAccess() throws Exception { + final Response response = super.testCreateProcessor(helper.getBaseUrl(), helper.getUser()); + + // ensure the request is not successful + Assert.assertEquals(401, response.getStatus()); + } + + @AfterClass + public static void cleanup() throws Exception { + helper.cleanup(); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITPreventProxiedAnonymousAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITPreventProxiedAnonymousAccess.java new file mode 100644 index 0000000..281b882 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/anonymous/ITPreventProxiedAnonymousAccess.java @@ -0,0 +1,57 @@ +/* + * 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.anonymous; + +import org.apache.nifi.integration.accesscontrol.AccessControlHelper; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.ws.rs.core.Response; + +/** + * Integration test for preventing proxied anonymous access. + */ +public class ITPreventProxiedAnonymousAccess extends AbstractAnonymousUserTest { + + private static AccessControlHelper helper; + + @BeforeClass + public static void setup() throws Exception { + System.out.println(ITPreventProxiedAnonymousAccess.class.getName() + " setup()"); + helper = new AccessControlHelper(); + } + + /** + * Attempt to create a processor anonymously. + * + * @throws Exception ex + */ + @Test + public void testProxiedAnonymousAccess() throws Exception { + final Response response = super.testCreateProcessor(helper.getBaseUrl(), helper.getAnonymousUser()); + + // ensure the request is not successful + Assert.assertEquals(401, response.getStatus()); + } + + @AfterClass + public static void cleanup() throws Exception { + helper.cleanup(); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java index fc3ed79..3d56591 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java @@ -77,6 +77,11 @@ public class NiFiTestAuthorizer implements Authorizer { return AuthorizationResult.resourceNotFound(); } + // allow the anonymous user + if (request.isAnonymous()) { + return AuthorizationResult.approved(); + } + // allow the token user if (TOKEN_USER.equals(request.getIdentity())) { return AuthorizationResult.approved(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi-anonymous-allowed.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi-anonymous-allowed.properties new file mode 100644 index 0000000..8ac6b3a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi-anonymous-allowed.properties @@ -0,0 +1,139 @@ +# 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. + +# Core Properties # +nifi.flow.configuration.file=target/test-classes/access-control/flow.xml.gz +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.authorizer.configuration.file=target/test-classes/access-control/authorizers.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 +nifi.nar.library.directory=target/test-classes/access-control/lib +nifi.nar.working.directory=target/test-classes/access-control/nar + +nifi.state.management.configuration.file=target/test-classes/access-control/state-management.xml +nifi.state.management.embedded.zookeeper.start=false +nifi.state.management.embedded.zookeeper.properties= +nifi.state.management.embedded.zookeeper.max.instances=3 +nifi.state.management.provider.local=local-provider +nifi.state.management.provider.cluster= + +# H2 Settings +nifi.database.directory=target/test-classes/database_repository +nifi.h2.url.append=;LOCK_TIMEOUT=25000;WRITE_DELAY=0;AUTO_SERVER=FALSE + +# FlowFile Repository +nifi.provenance.repository.implementation=org.apache.nifi.provenance.VolatileProvenanceRepository +nifi.flowfile.repository.directory=target/test-classes/flowfile_repository +nifi.flowfile.repository.partitions=256 +nifi.flowfile.repository.checkpoint.interval=2 mins +nifi.queue.swap.threshold=20000 +nifi.swap.storage.directory=target/test-classes/flowfile_repository/swap +nifi.swap.in.period=5 sec +nifi.swap.in.threads=1 +nifi.swap.out.period=5 sec +nifi.swap.out.threads=4 + +# Content Repository +nifi.content.claim.max.appendable.size=10 MB +nifi.content.claim.max.flow.files=100 +nifi.content.repository.directory.default=target/test-classes/content_repository +nifi.content.repository.archive.enabled=false + +# Provenance Repository Properties +nifi.provenance.repository.directory.default=./target/provenance_repository +nifi.provenance.repository.query.threads=2 +nifi.provenance.repository.max.storage.time=24 hours +nifi.provenance.repository.max.storage.size=1 GB +nifi.provenance.repository.rollover.time=30 secs +nifi.provenance.repository.rollover.size=100 MB + +# Component Status Repository +nifi.components.status.repository.implementation=org.apache.nifi.controller.status.history.VolatileComponentStatusRepository +nifi.components.status.repository.buffer.size=288 +nifi.components.status.snapshot.frequency=10 secs + +# Site to Site properties +#For testing purposes. Default value should actually be empty! +nifi.remote.input.host= +nifi.remote.input.socket.port= +nifi.remote.input.secure=false + +# web properties # +nifi.web.war.directory=target/test-classes/lib +nifi.web.http.host= +nifi.web.http.port= +nifi.web.https.host= +nifi.web.https.port=8443 +nifi.web.jetty.working.directory=target/test-classes/access-control/jetty + +# security properties # +nifi.sensitive.props.key=REPLACE_ME +nifi.sensitive.props.algorithm=PBEWITHMD5AND256BITAES-CBC-OPENSSL +nifi.sensitive.props.provider=BC + +nifi.security.keystore=target/test-classes/access-control/keystore.jks +nifi.security.keystoreType=JKS +nifi.security.keystorePasswd=passwordpassword +nifi.security.keyPasswd= +nifi.security.truststore=target/test-classes/access-control/truststore.jks +nifi.security.truststoreType=JKS +nifi.security.truststorePasswd=passwordpassword +nifi.security.user.login.identity.provider=test-provider +nifi.security.user.authorizer=test-provider +nifi.security.allow.anonymous.authentication=true + +# cluster common properties (cluster manager and nodes must have same values) # +nifi.cluster.protocol.heartbeat.interval=5 sec +nifi.cluster.protocol.is.secure=false +nifi.cluster.protocol.socket.timeout=30 sec +nifi.cluster.protocol.connection.handshake.timeout=45 sec +# if multicast is used, then nifi.cluster.protocol.multicast.xxx properties must be configured # +nifi.cluster.protocol.use.multicast=false +nifi.cluster.protocol.multicast.address= +nifi.cluster.protocol.multicast.port= +nifi.cluster.protocol.multicast.service.broadcast.delay=500 ms +nifi.cluster.protocol.multicast.service.locator.attempts=3 +nifi.cluster.protocol.multicast.service.locator.attempts.delay=1 sec + +# cluster node properties (only configure for cluster nodes) # +nifi.cluster.is.node=false +nifi.cluster.node.address= +nifi.cluster.node.protocol.port= +nifi.cluster.node.protocol.threads=2 +# if multicast is not used, nifi.cluster.node.unicast.xxx must have same values as nifi.cluster.manager.xxx # +nifi.cluster.node.unicast.manager.address= +nifi.cluster.node.unicast.manager.protocol.port= +nifi.cluster.node.unicast.manager.authority.provider.port= + +# cluster manager properties (only configure for cluster manager) # +nifi.cluster.is.manager=false +nifi.cluster.manager.address= +nifi.cluster.manager.protocol.port= +nifi.cluster.manager.authority.provider.port= +nifi.cluster.manager.authority.provider.threads=10 +nifi.cluster.manager.node.firewall.file= +nifi.cluster.manager.node.event.history.size=10 +nifi.cluster.manager.node.api.connection.timeout=30 sec +nifi.cluster.manager.node.api.read.timeout=30 sec +nifi.cluster.manager.node.api.request.threads=10 +nifi.cluster.manager.flow.retrieval.delay=5 sec +nifi.cluster.manager.protocol.threads=10 +nifi.cluster.manager.safemode.duration=0 sec diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationFilter.java similarity index 59% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationFilter.java index da9c52e..2565a58 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationFilter.java @@ -16,25 +16,24 @@ */ package org.apache.nifi.web.security.anonymous; -import javax.servlet.http.HttpServletRequest; - -import org.apache.nifi.authorization.user.NiFiUserDetails; -import org.apache.nifi.authorization.user.StandardNiFiUser; -import org.apache.nifi.web.security.token.NiFiAuthenticationToken; +import org.apache.nifi.web.security.NiFiAuthenticationFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; -public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter { +import javax.servlet.http.HttpServletRequest; - private static final String ANONYMOUS_KEY = "anonymousNifiKey"; +/** + * Extracts an anonymous authentication request from a specified servlet request. + */ +public class NiFiAnonymousAuthenticationFilter extends NiFiAuthenticationFilter { - public NiFiAnonymousUserFilter() { - super(ANONYMOUS_KEY); - } + private static final Logger logger = LoggerFactory.getLogger(NiFiAnonymousAuthenticationFilter.class); @Override - protected Authentication createAuthentication(HttpServletRequest request) { - return new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS)); + public Authentication attemptAuthentication(final HttpServletRequest request) { + // return the anonymous authentication request for this http request + return new NiFiAnonymousAuthenticationRequestToken(request.isSecure(), request.getRemoteAddr()); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationProvider.java new file mode 100644 index 0000000..7e107d0 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationProvider.java @@ -0,0 +1,56 @@ +/* + * 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.anonymous; + +import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.user.NiFiUserDetails; +import org.apache.nifi.authorization.user.StandardNiFiUser; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.InvalidAuthenticationException; +import org.apache.nifi.web.security.NiFiAuthenticationProvider; +import org.apache.nifi.web.security.token.NiFiAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * + */ +public class NiFiAnonymousAuthenticationProvider extends NiFiAuthenticationProvider { + + final NiFiProperties properties; + + public NiFiAnonymousAuthenticationProvider(NiFiProperties nifiProperties, Authorizer authorizer) { + super(nifiProperties, authorizer); + this.properties = nifiProperties; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + final NiFiAnonymousAuthenticationRequestToken request = (NiFiAnonymousAuthenticationRequestToken) authentication; + + if (request.isSecureRequest() && !properties.isAnonymousAuthenticationAllowed()) { + throw new InvalidAuthenticationException("Anonymous authentication has not been configured."); + } + + return new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.populateAnonymousUser(null, request.getClientAddress()))); + } + + @Override + public boolean supports(Class<?> authentication) { + return NiFiAnonymousAuthenticationRequestToken.class.isAssignableFrom(authentication); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationRequestToken.java new file mode 100644 index 0000000..c0f0c93 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationRequestToken.java @@ -0,0 +1,60 @@ +/* + * 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.anonymous; + +import org.apache.nifi.web.security.NiFiAuthenticationRequestToken; + +import static org.apache.nifi.authorization.user.StandardNiFiUser.ANONYMOUS_IDENTITY; + +/** + * This is an authentication request for an anonymous user. + */ +public class NiFiAnonymousAuthenticationRequestToken extends NiFiAuthenticationRequestToken { + + final boolean secureRequest; + + /** + * Creates a representation of the anonymous authentication request for a user. + * + * @param clientAddress the address of the client making the request + */ + public NiFiAnonymousAuthenticationRequestToken(final boolean secureRequest, final String clientAddress) { + super(clientAddress); + setAuthenticated(false); + this.secureRequest = secureRequest; + } + + @Override + public Object getCredentials() { + return null; + } + + public boolean isSecureRequest() { + return secureRequest; + } + + @Override + public Object getPrincipal() { + return ANONYMOUS_IDENTITY; + } + + @Override + public String toString() { + return "<anonymous>"; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java index 564171f..a56f9dd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java @@ -64,11 +64,13 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider { private X509IdentityProvider certificateIdentityProvider; private Authorizer authorizer; + final NiFiProperties properties; public X509AuthenticationProvider(final X509IdentityProvider certificateIdentityProvider, final Authorizer authorizer, final NiFiProperties nifiProperties) { super(nifiProperties, authorizer); this.certificateIdentityProvider = certificateIdentityProvider; this.authorizer = authorizer; + this.properties = nifiProperties; } @Override @@ -99,6 +101,11 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider { // determine if the user is anonymous final boolean isAnonymous = StringUtils.isBlank(identity); if (isAnonymous) { + // prevent anonymous users unless it's been explicitly configured + if (!properties.isAnonymousAuthenticationAllowed()) { + throw new InvalidAuthenticationException("Anonymous authentication has not been configured."); + } + identity = StandardNiFiUser.ANONYMOUS_IDENTITY; } else { identity = mapIdentity(identity); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml index 32c20c8..d37062a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml @@ -100,4 +100,10 @@ <constructor-arg ref="oidcProvider"/> </bean> + <!-- anonymous --> + <bean id="anonymousAuthenticationProvider" class="org.apache.nifi.web.security.anonymous.NiFiAnonymousAuthenticationProvider"> + <constructor-arg ref="nifiProperties" index="0"/> + <constructor-arg ref="authorizer" index="1"/> + </bean> + </beans> diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationProviderTest.java new file mode 100644 index 0000000..2422e8c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousAuthenticationProviderTest.java @@ -0,0 +1,91 @@ +/* + * 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.anonymous; + +import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.user.NiFiUserDetails; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.StringUtils; +import org.apache.nifi.web.security.InvalidAuthenticationException; +import org.apache.nifi.web.security.token.NiFiAuthenticationToken; +import org.junit.Test; +import org.mockito.Mockito; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class NiFiAnonymousAuthenticationProviderTest { + + private static final Logger logger = LoggerFactory.getLogger(NiFiAnonymousAuthenticationProviderTest.class); + + @Test + public void testAnonymousDisabledNotSecure() throws Exception { + final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class); + when(nifiProperties.isAnonymousAuthenticationAllowed()).thenReturn(false); + + final NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider = new NiFiAnonymousAuthenticationProvider(nifiProperties, mock(Authorizer.class)); + + final NiFiAnonymousAuthenticationRequestToken authenticationRequest = new NiFiAnonymousAuthenticationRequestToken(false, StringUtils.EMPTY); + + final NiFiAuthenticationToken authentication = (NiFiAuthenticationToken) anonymousAuthenticationProvider.authenticate(authenticationRequest); + final NiFiUserDetails userDetails = (NiFiUserDetails) authentication.getDetails(); + assertTrue(userDetails.getNiFiUser().isAnonymous()); + } + + @Test + public void testAnonymousEnabledNotSecure() throws Exception { + final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class); + when(nifiProperties.isAnonymousAuthenticationAllowed()).thenReturn(true); + + final NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider = new NiFiAnonymousAuthenticationProvider(nifiProperties, mock(Authorizer.class)); + + final NiFiAnonymousAuthenticationRequestToken authenticationRequest = new NiFiAnonymousAuthenticationRequestToken(false, StringUtils.EMPTY); + + final NiFiAuthenticationToken authentication = (NiFiAuthenticationToken) anonymousAuthenticationProvider.authenticate(authenticationRequest); + final NiFiUserDetails userDetails = (NiFiUserDetails) authentication.getDetails(); + assertTrue(userDetails.getNiFiUser().isAnonymous()); + } + + @Test(expected = InvalidAuthenticationException.class) + public void testAnonymousDisabledSecure() throws Exception { + final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class); + when(nifiProperties.isAnonymousAuthenticationAllowed()).thenReturn(false); + + final NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider = new NiFiAnonymousAuthenticationProvider(nifiProperties, mock(Authorizer.class)); + + final NiFiAnonymousAuthenticationRequestToken authenticationRequest = new NiFiAnonymousAuthenticationRequestToken(true, StringUtils.EMPTY); + + anonymousAuthenticationProvider.authenticate(authenticationRequest); + } + + @Test + public void testAnonymousEnabledSecure() throws Exception { + final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class); + when(nifiProperties.isAnonymousAuthenticationAllowed()).thenReturn(true); + + final NiFiAnonymousAuthenticationProvider anonymousAuthenticationProvider = new NiFiAnonymousAuthenticationProvider(nifiProperties, mock(Authorizer.class)); + + final NiFiAnonymousAuthenticationRequestToken authenticationRequest = new NiFiAnonymousAuthenticationRequestToken(true, StringUtils.EMPTY); + + final NiFiAuthenticationToken authentication = (NiFiAuthenticationToken) anonymousAuthenticationProvider.authenticate(authenticationRequest); + final NiFiUserDetails userDetails = (NiFiUserDetails) authentication.getDetails(); + assertTrue(userDetails.getNiFiUser().isAnonymous()); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java index d61b29d..fc4cf5f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java @@ -34,7 +34,9 @@ import org.junit.Test; import java.security.Principal; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -132,6 +134,27 @@ public class X509AuthenticationProviderTest { @Test public void testAnonymousWithOneProxy() { + // override the setting to enable anonymous authentication + final Map<String, String> additionalProperties = new HashMap<String, String>() {{ + put(NiFiProperties.SECURITY_ANONYMOUS_AUTHENTICATION, Boolean.TRUE.toString()); + }}; + final NiFiProperties properties = NiFiProperties.createBasicNiFiProperties(null, additionalProperties); + x509AuthenticationProvider = new X509AuthenticationProvider(certificateIdentityProvider, authorizer, properties); + + final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(ANONYMOUS), PROXY_1)); + final NiFiUser user = ((NiFiUserDetails) auth.getDetails()).getNiFiUser(); + + assertNotNull(user); + assertEquals(StandardNiFiUser.ANONYMOUS_IDENTITY, user.getIdentity()); + assertTrue(user.isAnonymous()); + + assertNotNull(user.getChain()); + assertEquals(PROXY_1, user.getChain().getIdentity()); + assertFalse(user.getChain().isAnonymous()); + } + + @Test(expected = InvalidAuthenticationException.class) + public void testAnonymousWithOneProxyWhileAnonymousAuthenticationPrevented() { final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(ANONYMOUS), PROXY_1)); final NiFiUser user = ((NiFiUserDetails) auth.getDetails()).getNiFiUser(); @@ -169,6 +192,31 @@ public class X509AuthenticationProviderTest { @Test public void testAnonymousProxyInChain() { + // override the setting to enable anonymous authentication + final Map<String, String> additionalProperties = new HashMap<String, String>() {{ + put(NiFiProperties.SECURITY_ANONYMOUS_AUTHENTICATION, Boolean.TRUE.toString()); + }}; + final NiFiProperties properties = NiFiProperties.createBasicNiFiProperties(null, additionalProperties); + x509AuthenticationProvider = new X509AuthenticationProvider(certificateIdentityProvider, authorizer, properties); + + final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(IDENTITY_1, ANONYMOUS), PROXY_1)); + final NiFiUser user = ((NiFiUserDetails) auth.getDetails()).getNiFiUser(); + + assertNotNull(user); + assertEquals(IDENTITY_1, user.getIdentity()); + assertFalse(user.isAnonymous()); + + assertNotNull(user.getChain()); + assertEquals(StandardNiFiUser.ANONYMOUS_IDENTITY, user.getChain().getIdentity()); + assertTrue(user.getChain().isAnonymous()); + + assertNotNull(user.getChain().getChain()); + assertEquals(PROXY_1, user.getChain().getChain().getIdentity()); + assertFalse(user.getChain().getChain().isAnonymous()); + } + + @Test(expected = InvalidAuthenticationException.class) + public void testAnonymousProxyInChainWhileAnonymousAuthenticationPrevented() { final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(IDENTITY_1, ANONYMOUS), PROXY_1)); final NiFiUser user = ((NiFiUserDetails) auth.getDetails()).getNiFiUser(); @@ -274,4 +322,4 @@ public class X509AuthenticationProviderTest { return certificate; } -} \ No newline at end of file +}