http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java index 52f4522..621dc09 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java @@ -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); + } }
http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider new file mode 100644 index 0000000..4b42e4f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider @@ -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 \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/controller-services.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/controller-services.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/controller-services.xml deleted file mode 100644 index f5bd96a..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/controller-services.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<!-- - 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. ---> -<services> - -</services> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml new file mode 100644 index 0000000..04120c9 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml @@ -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> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties index 0aa5a14..10db651 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties @@ -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 http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/reporting-tasks.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/reporting-tasks.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/reporting-tasks.xml deleted file mode 100644 index 251735e..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/reporting-tasks.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.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. ---> -<tasks> -</tasks> http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml index daeef5c..ee18bff 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml @@ -22,6 +22,40 @@ </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> @@ -29,6 +63,10 @@ </dependency> <dependency> <groupId>org.apache.nifi</groupId> + <artifactId>nifi-nar-utils</artifactId> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> <artifactId>nifi-api</artifactId> </dependency> <dependency> @@ -40,6 +78,11 @@ <artifactId>nifi-framework-core</artifactId> </dependency> <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt</artifactId> + <version>0.6.0</version> + </dependency> + <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> </dependency> http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java deleted file mode 100644 index f3bd11e..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java +++ /dev/null @@ -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; - } -} http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java new file mode 100644 index 0000000..1065152 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java @@ -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); + } + +} http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.java new file mode 100644 index 0000000..ef1dfb2 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.java @@ -0,0 +1,71 @@ +/* + * 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.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; + +/** + * 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. + * + * @param request request + * @param response response + * @param ae ae + * @throws java.io.IOException ex + * @throws javax.servlet.ServletException ex + */ + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException, ServletException { + // 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"); + + // 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."); + } + } + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java new file mode 100644 index 0000000..f09d610 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java @@ -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; + } + +} http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java new file mode 100644 index 0000000..eb0684b --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java @@ -0,0 +1,73 @@ +/* + * 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.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NewAccountAuthenticationToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NiFiAuthorizationToken; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * + */ +public class NiFiAuthenticationProvider implements AuthenticationProvider { + + private final AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService; + + public NiFiAuthenticationProvider(final AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + final NiFiAuthenticationRequestToken request = (NiFiAuthenticationRequestToken) authentication; + + try { + // defer to the nifi user details service to authorize the user + final UserDetails userDetails = userDetailsService.loadUserDetails(request); + + // build an authentication for accesing nifi + final NiFiAuthorizationToken result = new NiFiAuthorizationToken(userDetails); + result.setDetails(request.getDetails()); + return result; + } catch (final UsernameNotFoundException unfe) { + // if the authentication request is for a new account and it could not be authorized because the user was not found, + // return the token so the new account could be created. this must go here toe nsure that any proxies have been authorized + if (isNewAccountAuthenticationToken(request)) { + return new NewAccountAuthenticationToken(((NewAccountAuthenticationRequestToken) authentication).getNewAccountRequest()); + } else { + throw unfe; + } + } + } + + private boolean isNewAccountAuthenticationToken(final Authentication authentication) { + return NewAccountAuthenticationRequestToken.class.isAssignableFrom(authentication.getClass()); + } + + @Override + public boolean supports(Class<?> authentication) { + return NiFiAuthenticationRequestToken.class.isAssignableFrom(authentication); + } + +} http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java new file mode 100644 index 0000000..1b2f28a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java @@ -0,0 +1,147 @@ +/* + * 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.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.user.NiFiUser; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * + */ +public class ProxiedEntitiesUtils { + + public static final String PROXY_ENTITIES_CHAIN = "X-ProxiedEntitiesChain"; + public static final String PROXY_ENTITIES_ACCEPTED = "X-ProxiedEntitiesAccepted"; + public static final String PROXY_ENTITIES_DETAILS = "X-ProxiedEntitiesDetails"; + + private static final Pattern proxyChainPattern = Pattern.compile("<(.*?)>"); + + /** + * 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 List<String> tokenizeProxiedEntitiesChain(String rawProxyChain) { + final List<String> proxyChain = new ArrayList<>(); + final Matcher rawProxyChainMatcher = proxyChainPattern.matcher(rawProxyChain); + while (rawProxyChainMatcher.find()) { + proxyChain.add(rawProxyChainMatcher.group(1)); + } + + return proxyChain; + } + + /** + * Builds the proxy chain for the specified user. + * + * @param user The current user + * @return The proxy chain for that user in String form + */ + public static String buildProxiedEntitiesChainString(final NiFiUser user) { + // calculate the dn chain + final List<String> proxyChain = buildProxiedEntitiesChain(user); + return formatProxyDn(StringUtils.join(proxyChain, "><")); + } + + /** + * Builds the proxy chain for the specified user. + * + * @param user The current user + * @return The proxy chain for that user in List form + */ + public static List<String> buildProxiedEntitiesChain(final NiFiUser user) { + // calculate the dn chain + final List<String> proxyChain = new ArrayList<>(); + + // build the dn chain + NiFiUser chainedUser = user; + do { + // add the entry for this user + proxyChain.add(chainedUser.getIdentity()); + + // go to the next user in the chain + chainedUser = chainedUser.getChain(); + } while (chainedUser != null); + + return proxyChain; + } + + /** + * Builds the proxy chain from the specified request and user. + * + * @param request the request + * @param username the username + * @return the proxy chain in list form + */ + public static List<String> buildProxiedEntitiesChain(final HttpServletRequest request, final String username) { + final String chain = buildProxiedEntitiesChainString(request, username); + return tokenizeProxiedEntitiesChain(chain); + } + + /** + * Builds the dn chain from the specified request and user. + * + * @param request the request + * @param username the username + * @return the dn chain in string form + */ + public static String buildProxiedEntitiesChainString(final HttpServletRequest request, final String username) { + String principal; + if (username.startsWith("<") && username.endsWith(">")) { + principal = username; + } else { + principal = formatProxyDn(username); + } + + // look for a proxied user + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + principal = request.getHeader(PROXY_ENTITIES_CHAIN) + principal; + } + return principal; + } + + public static void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) { + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + response.setHeader(PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString()); + } + } + + public static void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage()); + } + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/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/NiFiAnonymousUserFilter.java index 295f09c..e67ed2c 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/NiFiAnonymousUserFilter.java @@ -16,25 +16,22 @@ */ package org.apache.nifi.web.security.anonymous; -import java.util.ArrayList; -import java.util.List; +import java.util.EnumSet; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.admin.service.AdministrationException; import org.apache.nifi.admin.service.UserService; +import org.apache.nifi.authorization.Authority; import org.apache.nifi.user.NiFiUser; import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.token.NiFiAuthorizationToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; /** - * Custom AnonymouseAuthenticationFilter used to grant additional authorities - * depending on the current operating mode. + * Custom AnonymouseAuthenticationFilter used to grant additional authorities depending on the current operating mode. */ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter { @@ -42,7 +39,6 @@ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter { private static final String ANONYMOUS_KEY = "anonymousNifiKey"; - private NiFiProperties properties; private UserService userService; public NiFiAnonymousUserFilter() { @@ -51,51 +47,37 @@ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter { @Override protected Authentication createAuthentication(HttpServletRequest request) { - Authentication authentication; + Authentication authentication = null; + try { // load the anonymous user from the database - NiFiUser user = userService.getUserByDn(NiFiUser.ANONYMOUS_USER_DN); - NiFiUserDetails userDetails = new NiFiUserDetails(user); + NiFiUser user = userService.getUserByDn(NiFiUser.ANONYMOUS_USER_IDENTITY); + + // if this is an unsecure request allow full access + if (!request.isSecure()) { + user.getAuthorities().addAll(EnumSet.allOf(Authority.class)); + } + + // only create an authentication token if the anonymous user has some authorities + if (!user.getAuthorities().isEmpty()) { + NiFiUserDetails userDetails = new NiFiUserDetails(user); - // get the granted authorities - List<GrantedAuthority> authorities = new ArrayList<>(userDetails.getAuthorities()); - authentication = new AnonymousAuthenticationToken(ANONYMOUS_KEY, userDetails, authorities); + // get the granted authorities + authentication = new NiFiAuthorizationToken(userDetails); + } } catch (AdministrationException ase) { // record the issue anonymousUserFilterLogger.warn("Unable to load anonymous user from accounts database: " + ase.getMessage()); if (anonymousUserFilterLogger.isDebugEnabled()) { anonymousUserFilterLogger.warn(StringUtils.EMPTY, ase); } - - // defer to the base implementation - authentication = super.createAuthentication(request); } return authentication; } - /** - * Only supports anonymous users for non-secure requests or one way ssl. - * - * @param request request - * @return true if allowed - */ - @Override - protected boolean applyAnonymousForThisRequest(HttpServletRequest request) { - // anonymous for non secure requests - if ("http".equalsIgnoreCase(request.getScheme())) { - return true; - } - - return !properties.getNeedClientAuth(); - } - /* setters */ public void setUserService(UserService userService) { this.userService = userService; } - public void setProperties(NiFiProperties properties) { - this.properties = properties; - } - } http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.java deleted file mode 100644 index cd5f1ac..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.java +++ /dev/null @@ -1,69 +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.authentication; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -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. - */ -public class NiFiAuthenticationEntryPoint implements AuthenticationEntryPoint { - - private static final Logger logger = LoggerFactory.getLogger(NiFiAuthenticationEntryPoint.class); - - /** - * Always returns a 403 error code to the client. - * - * @param request request - * @param response response - * @param ae ae - * @throws java.io.IOException ex - * @throws javax.servlet.ServletException ex - */ - @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); - - // log request result - if (authenticationException instanceof AuthenticationException) { - ae = (AuthenticationException) authenticationException; - logger.info(String.format("Rejecting access to web api: %s", ae.getMessage())); - } - - // 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."); - } -} http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java index 95b4669..23d9e61 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java @@ -16,34 +16,35 @@ */ package org.apache.nifi.web.security.authorization; -import java.util.Deque; -import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import org.apache.commons.lang3.StringUtils; import org.apache.nifi.admin.service.AccountDisabledException; import org.apache.nifi.admin.service.AccountNotFoundException; import org.apache.nifi.admin.service.AccountPendingException; import org.apache.nifi.admin.service.AdministrationException; import org.apache.nifi.admin.service.UserService; import org.apache.nifi.authorization.Authority; -import org.apache.nifi.web.security.DnUtils; import org.apache.nifi.user.NiFiUser; -import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.UntrustedProxyException; import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; 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.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * UserDetailsService that will verify user identity and grant user authorities. */ -public class NiFiAuthorizationService implements UserDetailsService { +public class NiFiAuthorizationService implements AuthenticationUserDetailsService<NiFiAuthenticationRequestToken> { private static final Logger logger = LoggerFactory.getLogger(NiFiAuthorizationService.class); @@ -53,35 +54,33 @@ public class NiFiAuthorizationService implements UserDetailsService { /** * Loads the user details for the specified dn. * - * Synchronizing because we want each request to be authorized atomically - * since each may contain any number of DNs. We wanted an access decision - * made for each individual request as a whole (without other request - * potentially impacting it). + * Synchronizing because we want each request to be authorized atomically since each may contain any number of DNs. We wanted an access decision made for each individual request as a whole + * (without other request potentially impacting it). * - * @param rawProxyChain proxy chain + * @param request request * @return user details * @throws UsernameNotFoundException ex * @throws org.springframework.dao.DataAccessException ex */ @Override - public synchronized UserDetails loadUserByUsername(String rawProxyChain) throws UsernameNotFoundException, DataAccessException { + public synchronized UserDetails loadUserDetails(NiFiAuthenticationRequestToken request) throws UsernameNotFoundException, DataAccessException { NiFiUserDetails userDetails = null; - final Deque<String> dnList = DnUtils.tokenizeProxyChain(rawProxyChain); + final List<String> chain = new ArrayList<>(request.getChain()); // ensure valid input - if (dnList.size() == 0) { - logger.warn("Malformed proxy chain: " + rawProxyChain); + if (chain.isEmpty()) { + logger.warn("Malformed proxy chain: " + StringUtils.join(request.getChain())); throw new UntrustedProxyException("Malformed proxy chain."); } NiFiUser proxy = null; // process each part of the proxy chain - for (final Iterator<String> dnIter = dnList.iterator(); dnIter.hasNext();) { - final String dn = dnIter.next(); + for (final ListIterator<String> chainIter = request.getChain().listIterator(chain.size()); chainIter.hasPrevious();) { + final String dn = chainIter.previous(); // if there is another dn after this one, this dn is a proxy for the request - if (dnIter.hasNext()) { + if (chainIter.hasPrevious()) { try { // get the user details for the proxy final NiFiUserDetails proxyDetails = getNiFiUserDetails(dn); @@ -108,9 +107,6 @@ public class NiFiAuthorizationService implements UserDetailsService { // attempt to create a new user account for the proxying client userService.createPendingUserAccount(dn, "Automatic account request generated for unknown proxy."); - - // propagate the exception to return the appropriate response - throw new UsernameNotFoundException(String.format("An account request was generated for the proxy '%s'.", dn)); } catch (AdministrationException ae) { throw new AuthenticationServiceException(String.format("Unable to create an account request for '%s': %s", dn, ae.getMessage()), ae); } catch (IllegalArgumentException iae) { @@ -121,10 +117,10 @@ public class NiFiAuthorizationService implements UserDetailsService { throw new AccountStatusException(message) { }; } - } else { - logger.warn(String.format("Untrusted proxy '%s' must be authorized with '%s' authority: %s", dn, Authority.ROLE_PROXY.toString(), unfe.getMessage())); - throw new UntrustedProxyException(String.format("Untrusted proxy '%s' must be authorized with '%s'.", dn, Authority.ROLE_PROXY.toString())); } + + logger.warn(String.format("Untrusted proxy '%s' must be authorized with '%s' authority: %s", dn, Authority.ROLE_PROXY.toString(), unfe.getMessage())); + throw new UntrustedProxyException(String.format("Untrusted proxy '%s' must be authorized with '%s'.", dn, Authority.ROLE_PROXY.toString())); } catch (AuthenticationException ae) { logger.warn(String.format("Untrusted proxy '%s' must be authorized with '%s' authority: %s", dn, Authority.ROLE_PROXY.toString(), ae.getMessage())); throw new UntrustedProxyException(String.format("Untrusted proxy '%s' must be authorized with '%s'.", dn, Authority.ROLE_PROXY.toString())); http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.java deleted file mode 100644 index 80feed7..0000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.java +++ /dev/null @@ -1,128 +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.authorization; - -import java.io.IOException; -import java.io.Serializable; -import java.security.cert.X509Certificate; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import org.apache.nifi.controller.FlowController; -import org.apache.commons.lang3.StringUtils; -import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.web.security.x509.SubjectDnX509PrincipalExtractor; -import org.apache.nifi.web.security.x509.X509CertificateExtractor; -import org.apache.nifi.user.NiFiUser; -import org.apache.nifi.util.NiFiProperties; -import org.apache.nifi.web.util.WebUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; -import org.springframework.web.context.support.WebApplicationContextUtils; -import org.springframework.web.filter.GenericFilterBean; - -/** - * Custom filter to extract a user's authorities from the request where the user - * was authenticated by the cluster manager and populate the threadlocal with - * the authorized user. If the request contains the appropriate header with - * authorities and the application instance is a node connected to the cluster, - * then the authentication/authorization steps remaining in the filter chain are - * skipped. - * - * Checking if the application instance is a connected node is important because - * it prevents external clients from faking the request headers and bypassing - * the authentication processing chain. - */ -public class NodeAuthorizedUserFilter extends GenericFilterBean { - - private static final Logger LOGGER = LoggerFactory.getLogger(NodeAuthorizedUserFilter.class); - - public static final String PROXY_USER_DETAILS = "X-ProxiedEntityUserDetails"; - - private NiFiProperties properties; - private final AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private final X509CertificateExtractor certificateExtractor = new X509CertificateExtractor(); - private final X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - final HttpServletRequest httpServletRequest = (HttpServletRequest) request; - - // get the proxied user's authorities - final String hexEncodedUserDetails = httpServletRequest.getHeader(PROXY_USER_DETAILS); - - // check if the request has the necessary header information and this instance is configured as a node - if (StringUtils.isNotBlank(hexEncodedUserDetails) && properties.isNode()) { - - // get the flow controller from the Spring context - final ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); - final FlowController flowController = ctx.getBean("flowController", FlowController.class); - - // check that we are connected to the cluster - if (flowController.getNodeId() != null) { - try { - // get the DN from the cert in the request - final X509Certificate certificate = certificateExtractor.extractClientCertificate((HttpServletRequest) request); - if (certificate != null) { - // extract the principal from the certificate - final Object certificatePrincipal = principalExtractor.extractPrincipal(certificate); - final String dn = certificatePrincipal.toString(); - - // only consider the pre-authorized user when the request came from the NCM according to the DN in the certificate - final String clusterManagerDN = flowController.getClusterManagerDN(); - if (clusterManagerDN != null && clusterManagerDN.equals(dn)) { - // deserialize hex encoded object - final Serializable userDetailsObj = WebUtils.deserializeHexToObject(hexEncodedUserDetails); - - // if we have a valid object, set the authentication token and bypass the remaining authentication processing chain - if (userDetailsObj instanceof NiFiUserDetails) { - final NiFiUserDetails userDetails = (NiFiUserDetails) userDetailsObj; - final NiFiUser user = userDetails.getNiFiUser(); - - // log the request attempt - response details will be logged later - logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", user.getDn(), httpServletRequest.getMethod(), - httpServletRequest.getRequestURL().toString(), request.getRemoteAddr())); - - // we do not create the authentication token with the X509 certificate because the certificate is from the sending system, not the proxied user - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - token.setDetails(authenticationDetailsSource.buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(token); - } - } - } - } catch (final ClassNotFoundException cnfe) { - LOGGER.warn("Classpath issue detected because failed to deserialize authorized user in request header due to: " + cnfe, cnfe); - } - } - } - - chain.doFilter(request, response); - } - - /* setters */ - public void setProperties(NiFiProperties properties) { - this.properties = properties; - } -} http://git-wip-us.apache.org/repos/asf/nifi/blob/aaf14c45/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..20675fb --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,80 @@ +/* + * 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.jwt; + +import io.jsonwebtoken.JwtException; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.web.security.NiFiAuthenticationFilter; +import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.user.NewAccountRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Arrays; +import org.apache.nifi.web.security.InvalidAuthenticationException; + +/** + */ +public class JwtAuthenticationFilter extends NiFiAuthenticationFilter { + + private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); + + private static final String AUTHORIZATION = "Authorization"; + + private JwtService jwtService; + + @Override + public NiFiAuthenticationRequestToken attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { + // only suppport jwt login when running securely + if (!request.isSecure()) { + return null; + } + + // TODO: Refactor request header extraction logic to shared utility as it is duplicated in AccessResource + + // get the principal out of the user token + final String authorization = request.getHeader(AUTHORIZATION); + + // if there is no authorization header, we don't know the user + if (authorization == null) { + return null; + } else { + // Extract the Base64 encoded token from the Authorization header + final String token = StringUtils.substringAfterLast(authorization, " "); + + try { + final String jwtPrincipal = jwtService.getAuthenticationFromToken(token); + + if (isNewAccountRequest(request)) { + return new NewAccountAuthenticationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request))); + } else { + return new NiFiAuthenticationRequestToken(Arrays.asList(jwtPrincipal)); + } + } catch (JwtException e) { + throw new InvalidAuthenticationException(e.getMessage(), e); + } + } + } + + public void setJwtService(JwtService jwtService) { + this.jwtService = jwtService; + } + +}
