nifi-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ald...@apache.org
Subject [1/5] nifi-minifi git commit: MINIFI-238 - MiNiFi Initial Command and Control Server Implementation
Date Fri, 31 Mar 2017 16:22:54 GMT
Repository: nifi-minifi
Updated Branches:
  refs/heads/master 7ecf80e47 -> f89f41504


http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/C2AuthenticationToken.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/C2AuthenticationToken.java
b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/C2AuthenticationToken.java
new file mode 100644
index 0000000..d6f85ec
--- /dev/null
+++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/C2AuthenticationToken.java
@@ -0,0 +1,51 @@
+/*
+ * 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.minifi.c2.security.authentication;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+
+public class C2AuthenticationToken extends AbstractAuthenticationToken {
+    private final String principal;
+    private Object credentials;
+
+    public C2AuthenticationToken(String principal, Object credentials, Collection<? extends
GrantedAuthority> authorities) {
+        super(authorities);
+        setAuthenticated(true);
+        this.principal = principal;
+        this.credentials = credentials;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return credentials;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return principal;
+    }
+
+    @Override
+    public void eraseCredentials() {
+        super.eraseCredentials();
+        credentials = null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationFilter.java
b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationFilter.java
new file mode 100644
index 0000000..63c743b
--- /dev/null
+++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationFilter.java
@@ -0,0 +1,70 @@
+/*
+ * 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.minifi.c2.security.authentication;
+
+import org.apache.nifi.minifi.c2.util.HttpRequestUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.GenericFilterBean;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+
+public class X509AuthenticationFilter extends GenericFilterBean {
+    private static final Logger logger = LoggerFactory.getLogger(X509AuthenticationFilter.class);
+    private AuthenticationManager authenticationManager;
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
+        authenticateIfPossible(request);
+        chain.doFilter(request, response);
+    }
+
+    private void authenticateIfPossible(ServletRequest request) {
+        if (!request.isSecure()) {
+            return;
+        }
+
+        X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+
+        if (certs == null || certs.length == 0) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Unable to get certificates in request from " + HttpRequestUtil.getClientString(request));
+            }
+            return;
+        }
+
+        Authentication authentication = authenticationManager.authenticate(new X509AuthenticationToken(certs));
+        if (authentication.isAuthenticated()) {
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+        }
+    }
+
+    @Autowired
+    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
+        this.authenticationManager = authenticationManager;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationProvider.java
b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationProvider.java
new file mode 100644
index 0000000..c297947
--- /dev/null
+++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationProvider.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.minifi.c2.security.authentication;
+
+import org.apache.nifi.minifi.c2.api.security.authorization.AuthorityGranter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+
+public class X509AuthenticationProvider implements AuthenticationProvider {
+    private static final Logger logger = LoggerFactory.getLogger(X509AuthenticationProvider.class);
+    private final AuthorityGranter authorityGranter;
+
+    public X509AuthenticationProvider(AuthorityGranter authorityGranter) {
+        this.authorityGranter = authorityGranter;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
+        X509AuthenticationToken x509AuthenticationToken = (X509AuthenticationToken) authentication;
+        if (logger.isDebugEnabled()) {
+            logger.debug("Authenticating " + X509AuthenticationToken.class.getSimpleName()
+ " with principal " +  x509AuthenticationToken.getPrincipal());
+        }
+        return new C2AuthenticationToken(x509AuthenticationToken.getPrincipal(), x509AuthenticationToken.getCredentials(),
+                authorityGranter.grantAuthorities(authentication));
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return X509AuthenticationToken.class.isAssignableFrom(authentication);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationToken.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationToken.java
b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationToken.java
new file mode 100644
index 0000000..1faa0b3
--- /dev/null
+++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authentication/X509AuthenticationToken.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.minifi.c2.security.authentication;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class X509AuthenticationToken extends AbstractAuthenticationToken {
+    private final X509Certificate[] x509Certificates;
+    private final String subjectDn;
+
+    public X509AuthenticationToken(X509Certificate[] x509Certificates) {
+        this(x509Certificates, null);
+        setAuthenticated(false);
+    }
+
+    protected X509AuthenticationToken(X509Certificate[] x509Certificates, Collection<GrantedAuthority>
grantedAuthorities) {
+        super(grantedAuthorities);
+        this.x509Certificates = Arrays.copyOf(x509Certificates, x509Certificates.length,
X509Certificate[].class);
+        X509Certificate x509Certificate = x509Certificates[0];
+        this.subjectDn = x509Certificate.getSubjectDN().getName().trim();
+    }
+
+    @Override
+    public X509Certificate[] getCredentials() {
+        return x509Certificates;
+    }
+
+    @Override
+    public String getPrincipal() {
+        return subjectDn;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/GrantedAuthorityAuthorizer.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/GrantedAuthorityAuthorizer.java
b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/GrantedAuthorityAuthorizer.java
new file mode 100644
index 0000000..71f3fa4
--- /dev/null
+++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/GrantedAuthorityAuthorizer.java
@@ -0,0 +1,138 @@
+/*
+ * 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.minifi.c2.security.authorization;
+
+import org.apache.nifi.minifi.c2.api.security.authorization.AuthorizationException;
+import org.apache.nifi.minifi.c2.api.security.authorization.Authorizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.Resource;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.yaml.snakeyaml.Yaml;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class GrantedAuthorityAuthorizer implements Authorizer {
+    private static final Logger logger = LoggerFactory.getLogger(GrantedAuthorityAuthorizer.class);
+
+    public static final String DENY = "deny";
+    public static final String ALLOW = "allow";
+    public static final String DEFAULT_ACTION = "Default Action";
+    private final Map<String, Object> grantedAuthorityMap;
+
+    public GrantedAuthorityAuthorizer(Resource configYaml) throws IOException {
+        try (InputStream inputStream = configYaml.getInputStream()) {
+            grantedAuthorityMap = as(Map.class, new Yaml().load(inputStream), o -> new
IllegalArgumentException("Expected yaml map for root of configuration but was " + o));
+        }
+    }
+
+    @Override
+    public void authorize(Authentication authentication, UriInfo uriInfo) throws AuthorizationException
{
+        if (authentication == null) {
+            throw new AuthorizationException("null authentication object provided.");
+        }
+
+        if (!authentication.isAuthenticated()) {
+            throw new AuthorizationException(authentication + " not authenticated.");
+        }
+
+        Set<String> authorities = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
+
+        String defaultAction = as(String.class, grantedAuthorityMap.getOrDefault(DEFAULT_ACTION,
DENY));
+        String path = uriInfo.getAbsolutePath().getPath();
+        Map<String, Object> pathAuthorizations = as(Map.class, grantedAuthorityMap.get("Paths"));
+        if (pathAuthorizations == null && !ALLOW.equalsIgnoreCase(defaultAction))
{
+            throw new AuthorizationException("Didn't find authorizations for " + path + "
and default policy is " + defaultAction + " instead of allow");
+        }
+
+        Map<String, Object> pathAuthorization = as(Map.class, pathAuthorizations.get(path));
+        if (pathAuthorization == null && !ALLOW.equalsIgnoreCase(defaultAction))
{
+            throw new AuthorizationException("Didn't find authorizations for " + path + "
and default policy is " + defaultAction + " instead of allow");
+        }
+        defaultAction = as(String.class, pathAuthorization.getOrDefault(DEFAULT_ACTION, defaultAction));
+        List<Map<String, Object>> actions = as(List.class, pathAuthorization.get("Actions"));
+        MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters();
+        for (Map<String, Object> action : actions) {
+            String ruleAction = as(String.class, action.get("Action"));
+            if (ruleAction == null || !(ALLOW.equalsIgnoreCase(ruleAction) || DENY.equalsIgnoreCase(ruleAction)))
{
+                throw new AuthorizationException("Expected Action key of allow or deny for
" + action);
+            }
+            String authorization = as(String.class, action.get("Authorization"));
+            if (authorization != null && !authorities.contains(authorization)) {
+                continue;
+            }
+            Map<String, Object> parameters = as(Map.class, action.get("Query Parameters"));
+            if (parameters != null) {
+                boolean foundParameterMismatch = false;
+                for (Map.Entry<String, Object> parameter : parameters.entrySet()) {
+                    Object value = parameter.getValue();
+                    if (value instanceof String) {
+                        value = Arrays.asList((String)value);
+                    }
+                    if (!Objects.equals(queryParameters.get(parameter.getKey()), value))
{
+                        foundParameterMismatch = true;
+                        break;
+                    }
+                }
+                if (foundParameterMismatch) {
+                    continue;
+                }
+            }
+            if (ALLOW.equalsIgnoreCase(ruleAction)) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Action " + action + "matched which resulted in " + ruleAction);
+                }
+                return;
+            } else {
+                throw new AuthorizationException("Action " + action + " matched which resulted
in " + ruleAction);
+            }
+        }
+        if (ALLOW.equalsIgnoreCase(defaultAction)) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Found no matching actions so falling back to default action
" + defaultAction);
+            }
+        } else {
+            throw new AuthorizationException("Didn't find authorizations for " + path + "
and default policy is " + defaultAction + " instead of allow");
+        }
+    }
+
+    private static <T> T as(Class<T> clazz, Object object) throws AuthorizationException
{
+        return as(clazz, object, o -> new AuthorizationException("Expected " + clazz +
" but was " + o));
+    }
+
+    private static <T, E extends Throwable> T as(Class<T> clazz, Object object,
Function<Object, E> exceptionSupplier) throws E {
+        if (object == null) {
+            return null;
+        }
+        if (!clazz.isInstance(object)) {
+            throw exceptionSupplier.apply(object);
+        }
+        return clazz.cast(object);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/PrincipalStringAuthorityGranter.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/PrincipalStringAuthorityGranter.java
b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/PrincipalStringAuthorityGranter.java
new file mode 100644
index 0000000..24e1ffa
--- /dev/null
+++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/security/authorization/PrincipalStringAuthorityGranter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.minifi.c2.security.authorization;
+
+import org.apache.nifi.minifi.c2.api.security.authorization.AuthorityGranter;
+import org.springframework.core.io.Resource;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class PrincipalStringAuthorityGranter implements AuthorityGranter {
+    private final Map<String, List<String>> grantedAuthorityMap;
+
+    public PrincipalStringAuthorityGranter(Resource configYaml) throws IOException {
+        try (InputStream inputStream = configYaml.getInputStream()) {
+            Object yaml = new Yaml().load(inputStream);
+            if (!(yaml instanceof Map)) {
+                throw new IllegalArgumentException("Expected authority map of Principal ->
Authority list");
+            }
+            grantedAuthorityMap = (Map<String, List<String>>) yaml;
+        }
+    }
+    @Override
+    public Collection<GrantedAuthority> grantAuthorities(Authentication authentication)
{
+        List<String> authorities = grantedAuthorityMap.get(authentication.getPrincipal().toString());
+        if (authorities == null) {
+            return null;
+        }
+        return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java
b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java
new file mode 100644
index 0000000..e57089e
--- /dev/null
+++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/service/ConfigService.java
@@ -0,0 +1,167 @@
+/*
+ * 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.minifi.c2.service;
+
+import com.wordnik.swagger.annotations.Api;
+import org.apache.nifi.minifi.c2.api.Configuration;
+import org.apache.nifi.minifi.c2.api.ConfigurationProvider;
+import org.apache.nifi.minifi.c2.api.InvalidParameterException;
+import org.apache.nifi.minifi.c2.api.security.authorization.AuthorizationException;
+import org.apache.nifi.minifi.c2.api.security.authorization.Authorizer;
+import org.apache.nifi.minifi.c2.api.util.Pair;
+import org.apache.nifi.minifi.c2.util.HttpRequestUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Path("/config")
+@Api(
+        value = "/config",
+        description = "Provides configuration for MiNiFi instances"
+)
+public class ConfigService {
+    private static final Logger logger = LoggerFactory.getLogger(ConfigService.class);
+    private final List<Pair<MediaType, ConfigurationProvider>> configurationProviders;
+    private final Authorizer authorizer;
+
+    public ConfigService(List<ConfigurationProvider> configurationProviders, Authorizer
authorizer) {
+        this.authorizer = authorizer;
+        if (configurationProviders == null || configurationProviders.size() == 0) {
+            throw new IllegalArgumentException("Expected at least one configuration provider");
+        }
+        this.configurationProviders = configurationProviders.stream().map(c -> new Pair<>(MediaType.valueOf(c.getContentType()),
c)).collect(Collectors.toList());
+    }
+
+    @GET
+    public Response getConfig(@Context HttpServletRequest request, @Context HttpHeaders httpHeaders,
@Context UriInfo uriInfo) {
+        try {
+            authorizer.authorize(SecurityContextHolder.getContext().getAuthentication(),
uriInfo);
+        } catch (AuthorizationException e) {
+            logger.warn(HttpRequestUtil.getClientString(request) + " not authorized to access
" + uriInfo, e);
+            return Response.status(403).build();
+        }
+        Map<String, List<String>> parameters = new HashMap<>();
+        for (Map.Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet())
{
+            parameters.put(entry.getKey(), entry.getValue());
+        }
+        List<MediaType> acceptValues = httpHeaders.getAcceptableMediaTypes();
+        boolean defaultAccept = false;
+        if (acceptValues.size() == 0) {
+            acceptValues = Arrays.asList(MediaType.WILDCARD_TYPE);
+            defaultAccept = true;
+        }
+        if (logger.isDebugEnabled()) {
+            StringBuilder builder = new StringBuilder("Handling request from ")
+                    .append(HttpRequestUtil.getClientString(request))
+                    .append(" with parameters ")
+                    .append(parameters)
+                    .append(" and Accept");
+            if (defaultAccept) {
+                builder = builder.append(" default value");
+            }
+            builder = builder.append(": ")
+                    .append(acceptValues.stream().map(Object::toString).collect(Collectors.joining(",
")));
+            logger.debug(builder.toString());
+        }
+        Pair<MediaType, ConfigurationProvider> providerPair = getProvider(acceptValues);
+
+        try {
+            Integer version = null;
+            List<String> versionList = parameters.get("version");
+            if (versionList != null && versionList.size() > 0) {
+                try {
+                    version = Integer.parseInt(versionList.get(0));
+                } catch (NumberFormatException e) {
+                    throw new InvalidParameterException("Unable to parse " + version + "
as integer.", e);
+                }
+            }
+            Response.ResponseBuilder ok = Response.ok();
+            Configuration configuration = providerPair.getSecond().getConfiguration(version,
parameters);
+            ok = ok.header("X-Content-Version", configuration.getVersion());
+            ok = ok.type(providerPair.getFirst());
+            byte[] buffer = new byte[1024];
+            int read;
+            try (InputStream inputStream = configuration.getInputStream();
+                 ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+                MessageDigest md5 = MessageDigest.getInstance("MD5");
+                MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+                while((read = inputStream.read(buffer)) >= 0) {
+                    outputStream.write(buffer, 0, read);
+                    md5.update(buffer, 0, read);
+                    sha256.update(buffer, 0, read);
+                }
+                ok = ok.header("Content-MD5", bytesToHex(md5.digest()));
+                ok = ok.header("X-Content-SHA-256", bytesToHex(sha256.digest()));
+                ok = ok.entity(outputStream.toByteArray());
+            } catch (IOException|NoSuchAlgorithmException e) {
+                logger.error("Error reading or checksumming configuration file", e);
+                throw new WebApplicationException(500);
+            }
+            return ok.build();
+        } catch (InvalidParameterException e) {
+            logger.info(HttpRequestUtil.getClientString(request) + " made invalid request
with " + HttpRequestUtil.getQueryString(request), e);
+            return Response.status(400).entity("Invalid request.").build();
+        } catch (Throwable t) {
+            logger.error(HttpRequestUtil.getClientString(request) + " made request with "
+ HttpRequestUtil.getQueryString(request) + " that caused error in " + providerPair.getSecond(),
t);
+            return Response.status(500).entity("Internal error").build();
+        }
+    }
+
+    // see: http://stackoverflow.com/questions/15429257/how-to-convert-byte-array-to-hexstring-in-java#answer-15429408
+    protected static String bytesToHex(byte[] in) {
+        final StringBuilder builder = new StringBuilder();
+        for(byte b : in) {
+            builder.append(String.format("%02x", b));
+        }
+        return builder.toString();
+    }
+
+    private Pair<MediaType, ConfigurationProvider> getProvider(List<MediaType>
acceptValues) {
+        for (MediaType accept : acceptValues) {
+            for (Pair<MediaType, ConfigurationProvider> configurationProviderPair :
configurationProviders) {
+                if (accept.isCompatible(configurationProviderPair.getFirst())) {
+                    return configurationProviderPair;
+                }
+            }
+        }
+
+        throw new WebApplicationException(Response.status(406).entity("Unable to find configuration
provider for " +
+                "\"Accept: " + acceptValues.stream().map(Object::toString).collect(Collectors.joining(",
")) + "\" supported media types are " +
+                configurationProviders.stream().map(Pair::getFirst).map(Object::toString).collect(Collectors.joining(",
"))).build());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/util/HttpRequestUtil.java
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/util/HttpRequestUtil.java
b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/util/HttpRequestUtil.java
new file mode 100644
index 0000000..395eb73
--- /dev/null
+++ b/minifi-c2/minifi-c2-service/src/main/java/org/apache/nifi/minifi/c2/util/HttpRequestUtil.java
@@ -0,0 +1,41 @@
+/*
+ * 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.minifi.c2.util;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+public class HttpRequestUtil {
+    public static String getQueryString(HttpServletRequest request) {
+        String queryString = request.getQueryString();
+        if (queryString == null) {
+            return "no query string";
+        }
+        return "query string \"" + queryString + "\"";
+    }
+
+    public static String getClientString(ServletRequest request) {
+        String remoteHost = request.getRemoteHost();
+        String remoteAddr = request.getRemoteAddr();
+        String result =  "Client " + remoteHost;
+        if (!remoteAddr.equals(remoteHost)) {
+            result = result + " (" + remoteAddr + ")";
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/minifi-c2-service/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/minifi-c2/minifi-c2-service/src/main/webapp/WEB-INF/web.xml b/minifi-c2/minifi-c2-service/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..7be7364
--- /dev/null
+++ b/minifi-c2/minifi-c2-service/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,51 @@
+<?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.
+  -->
+<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
+    <display-name>minifi-c2</display-name>
+    <context-param>
+        <param-name>contextClass</param-name>
+        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
+    </context-param>
+    <context-param>
+        <param-name>contextConfigLocation</param-name>
+        <param-value>org.apache.nifi.minifi.c2.configuration</param-value>
+    </context-param>
+    <listener>
+        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+    </listener>
+    <servlet>
+        <servlet-name>jerseySpring</servlet-name>
+        <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+        <init-param>
+            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
+            <param-value>true</param-value>
+        </init-param>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>jerseySpring</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+    <filter>
+        <filter-name>springSecurityFilterChain</filter-name>
+        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>springSecurityFilterChain</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+</web-app>

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-c2/pom.xml
----------------------------------------------------------------------
diff --git a/minifi-c2/pom.xml b/minifi-c2/pom.xml
new file mode 100644
index 0000000..f4d5add
--- /dev/null
+++ b/minifi-c2/pom.xml
@@ -0,0 +1,38 @@
+<?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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>minifi</artifactId>
+        <groupId>org.apache.nifi.minifi</groupId>
+        <version>0.2.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>minifi-c2</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>minifi-c2-api</module>
+        <module>minifi-c2-cache</module>
+        <module>minifi-c2-provider</module>
+        <module>minifi-c2-service</module>
+        <module>minifi-c2-jetty</module>
+        <module>minifi-c2-assembly</module>
+        <module>minifi-c2-docker</module>
+        <module>minifi-c2-integration-tests</module>
+    </modules>
+</project>

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf
----------------------------------------------------------------------
diff --git a/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf
b/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf
index 537536e..85204cc 100644
--- a/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf
+++ b/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-resources/src/main/resources/conf/bootstrap.conf
@@ -54,6 +54,10 @@ nifi.minifi.config=./conf/config.yml
 #nifi.minifi.notifier.ingestors.pull.http.hostname=localhost
 # Port on which to pull configurations from
 #nifi.minifi.notifier.ingestors.pull.http.port=4567
+# Path to pull configurations from
+#nifi.minifi.notifier.ingestors.pull.http.path=/c2/config
+# Query string to pull configurations with
+#nifi.minifi.notifier.ingestors.pull.http.query=class=raspi3
 # Period on which to pull configurations from, defaults to 5 minutes if commented out
 #nifi.minifi.notifier.ingestors.pull.http.period.ms=300000
 

http://git-wip-us.apache.org/repos/asf/nifi-minifi/blob/f89f4150/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 3727ce4..3fa1f2a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,7 @@ limitations under the License.
         <module>minifi-assembly</module>
         <module>minifi-toolkit</module>
         <module>minifi-docker</module>
+        <module>minifi-c2</module>
     </modules>
 
     <url>http://nifi.apache.org/minifi</url>
@@ -402,6 +403,11 @@ limitations under the License.
             </dependency>
             <dependency>
                 <groupId>org.apache.nifi</groupId>
+                <artifactId>nifi-toolkit-tls</artifactId>
+                <version>${org.apache.nifi.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.nifi</groupId>
                 <artifactId>nifi-resources</artifactId>
                 <version>${org.apache.nifi.version}</version>
                 <classifier>resources</classifier>


Mime
View raw message