You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by rg...@apache.org on 2014/07/30 15:07:51 UTC

svn commit: r1614652 - in /qpid/trunk/qpid/java: broker-core/src/main/java/org/apache/qpid/server/security/ broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/ broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/d...

Author: rgodfrey
Date: Wed Jul 30 13:07:51 2014
New Revision: 1614652

URL: http://svn.apache.org/r1614652
Log:
QPID-5946 : [Java Broker] Add alternative KeyStore implementation that can use standard crt/pem rather than jks files

Added:
    qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStore.java
    qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java
    qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/
    qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/data/
    qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/data/Handler.java
Modified:
    qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
    qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java
    qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java

Added: qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStore.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStore.java?rev=1614652&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStore.java (added)
+++ qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStore.java Wed Jul 30 13:07:51 2014
@@ -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.qpid.server.security;
+
+import org.apache.qpid.server.model.DerivedAttribute;
+import org.apache.qpid.server.model.KeyStore;
+import org.apache.qpid.server.model.ManagedAttribute;
+import org.apache.qpid.server.model.ManagedObject;
+
+@ManagedObject( category = false, type = "NonJavaKeyStore" )
+public interface NonJavaKeyStore<X extends NonJavaKeyStore<X>> extends KeyStore<X>
+{
+
+    @ManagedAttribute( mandatory = true, secure = true )
+    String getPrivateKeyUrl();
+
+    @ManagedAttribute( mandatory = true )
+    String getCertificateUrl();
+
+    @ManagedAttribute
+    String getIntermediateCertificateUrl();
+
+    @DerivedAttribute
+    String getSubjectName();
+
+    @DerivedAttribute
+    public long getCertificateValidEnd();
+
+    @DerivedAttribute
+    public long getCertificateValidStart();
+}

Added: qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java?rev=1614652&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java (added)
+++ qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/NonJavaKeyStoreImpl.java Wed Jul 30 13:07:51 2014
@@ -0,0 +1,404 @@
+/*
+ *
+ * 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.qpid.server.security;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.AccessControlException;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.xml.bind.DatatypeConverter;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.server.model.AbstractConfiguredObject;
+import org.apache.qpid.server.model.Broker;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.IntegrityViolationException;
+import org.apache.qpid.server.model.KeyStore;
+import org.apache.qpid.server.model.ManagedAttributeField;
+import org.apache.qpid.server.model.ManagedObject;
+import org.apache.qpid.server.model.ManagedObjectFactoryConstructor;
+import org.apache.qpid.server.model.Port;
+import org.apache.qpid.server.model.State;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.util.urlstreamhandler.data.Handler;
+
+@ManagedObject( category = false )
+public class NonJavaKeyStoreImpl extends AbstractConfiguredObject<NonJavaKeyStoreImpl> implements NonJavaKeyStore<NonJavaKeyStoreImpl>
+{
+    private static final Logger LOGGER = Logger.getLogger(NonJavaKeyStoreImpl.class);
+
+    private final Broker<?> _broker;
+
+    @ManagedAttributeField( afterSet = "updateKeyManagers" )
+    private String _privateKeyUrl;
+    @ManagedAttributeField( afterSet = "updateKeyManagers" )
+    private String _certificateUrl;
+    @ManagedAttributeField( afterSet = "updateKeyManagers" )
+    private String _intermediateCertificateUrl;
+
+    private volatile KeyManager[] _keyManagers = new KeyManager[0];
+
+    private static final SecureRandom RANDOM = new SecureRandom();
+
+    static
+    {
+        Handler.register();
+    }
+
+    private X509Certificate _certificate;
+
+    @ManagedObjectFactoryConstructor
+    public NonJavaKeyStoreImpl(final Map<String, Object> attributes, Broker<?> broker)
+    {
+        super(parentsMap(broker), attributes);
+        _broker = broker;
+    }
+
+    @Override
+    public String getPrivateKeyUrl()
+    {
+        return _privateKeyUrl;
+    }
+
+    @Override
+    public String getCertificateUrl()
+    {
+        return _certificateUrl;
+    }
+
+    @Override
+    public String getIntermediateCertificateUrl()
+    {
+        return _intermediateCertificateUrl;
+    }
+
+    @Override
+    public String getSubjectName()
+    {
+        if(_certificate != null)
+        {
+            try
+            {
+                String dn = _certificate.getSubjectX500Principal().getName();
+                LdapName ldapDN = new LdapName(dn);
+                String name = dn;
+                for (Rdn rdn : ldapDN.getRdns())
+                {
+                    if (rdn.getType().equalsIgnoreCase("CN"))
+                    {
+                        name = String.valueOf(rdn.getValue());
+                        break;
+                    }
+                }
+                return name;
+            }
+            catch (InvalidNameException e)
+            {
+                LOGGER.error("Error getting subject name from certificate");
+                return null;
+            }
+        }
+        else
+        {
+            return null;
+        }
+    }
+
+    @Override
+    public long getCertificateValidEnd()
+    {
+        return _certificate == null ? 0 : _certificate.getNotAfter().getTime();
+    }
+
+    @Override
+    public long getCertificateValidStart()
+    {
+        return _certificate == null ? 0 : _certificate.getNotBefore().getTime();
+    }
+
+
+    @Override
+    public KeyManager[] getKeyManagers() throws GeneralSecurityException
+    {
+
+        return _keyManagers;
+    }
+
+    @Override
+    public void onValidate()
+    {
+        super.onValidate();
+        validateKeyStoreAttributes(this);
+    }
+
+    @Override
+    public State getState()
+    {
+        return State.ACTIVE;
+    }
+
+    @Override
+    public Object getAttribute(String name)
+    {
+        if (KeyStore.STATE.equals(name))
+        {
+            return getState();
+        }
+
+        return super.getAttribute(name);
+    }
+
+    @Override
+    protected boolean setState(State desiredState)
+    {
+        if (desiredState == State.DELETED)
+        {
+            // verify that it is not in use
+            String storeName = getName();
+
+            Collection<Port> ports = new ArrayList<Port>(_broker.getPorts());
+            for (Port port : ports)
+            {
+                if (port.getKeyStore() == this)
+                {
+                    throw new IntegrityViolationException("Key store '"
+                                                          + storeName
+                                                          + "' can't be deleted as it is in use by a port:"
+                                                          + port.getName());
+                }
+            }
+            deleted();
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    protected void authoriseSetDesiredState(State desiredState) throws AccessControlException
+    {
+        if (desiredState == State.DELETED)
+        {
+            if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), KeyStore.class, Operation.DELETE))
+            {
+                throw new AccessControlException("Deletion of key store is denied");
+            }
+        }
+    }
+
+    @Override
+    protected void authoriseSetAttributes(ConfiguredObject<?> modified, Set<String> attributes)
+            throws AccessControlException
+    {
+        if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), KeyStore.class, Operation.UPDATE))
+        {
+            throw new AccessControlException("Setting key store attributes is denied");
+        }
+    }
+
+    @Override
+    protected void validateChange(final ConfiguredObject<?> proxyForValidation, final Set<String> changedAttributes)
+    {
+        super.validateChange(proxyForValidation, changedAttributes);
+        NonJavaKeyStore changedStore = (NonJavaKeyStore) proxyForValidation;
+        if (changedAttributes.contains(NAME) && !getName().equals(changedStore.getName()))
+        {
+            throw new IllegalConfigurationException("Changing the key store name is not allowed");
+        }
+        validateKeyStoreAttributes(changedStore);
+    }
+
+    private void validateKeyStoreAttributes(NonJavaKeyStore<?> keyStore)
+    {
+        try
+        {
+            getUrlFromString(keyStore.getPrivateKeyUrl()).openStream();
+            getUrlFromString(keyStore.getCertificateUrl()).openStream();
+            if(keyStore.getIntermediateCertificateUrl() != null)
+            {
+                getUrlFromString(keyStore.getIntermediateCertificateUrl()).openStream();
+            }
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private void updateKeyManagers()
+    {
+        try
+        {
+            if (_privateKeyUrl != null && _certificateUrl != null)
+            {
+                PrivateKey privateKey = readPrivateKey(getUrlFromString(_privateKeyUrl));
+                X509Certificate[] certs = readCertificates(getUrlFromString(_certificateUrl));
+                if(_intermediateCertificateUrl != null)
+                {
+                    List<X509Certificate> allCerts = new ArrayList<>(Arrays.asList(certs));
+                    allCerts.addAll(Arrays.asList(readCertificates(getUrlFromString(_intermediateCertificateUrl))));
+                    certs = allCerts.toArray(new X509Certificate[allCerts.size()]);
+                }
+
+                java.security.KeyStore inMemoryKeyStore = java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType());
+
+                byte[] bytes = new byte[64];
+                char[] chars = new char[64];
+                RANDOM.nextBytes(bytes);
+                StandardCharsets.US_ASCII.decode(ByteBuffer.wrap(bytes)).get(chars);
+                inMemoryKeyStore.load(null, chars);
+                inMemoryKeyStore.setKeyEntry("1", privateKey, chars, certs);
+
+
+                KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+                kmf.init(inMemoryKeyStore, chars);
+                _keyManagers = kmf.getKeyManagers();
+                _certificate = certs[0];
+            }
+
+        }
+        catch (IOException | GeneralSecurityException e)
+        {
+            LOGGER.error("Error attempting to create KeyStore from private key and certificates", e);
+            _keyManagers = new KeyManager[0];
+        }
+    }
+
+    private URL getUrlFromString(String urlString) throws MalformedURLException
+    {
+        URL url;
+
+        try
+        {
+            url = new URL(urlString);
+        }
+        catch (MalformedURLException e)
+        {
+            File file = new File(urlString);
+            url = file.toURI().toURL();
+
+        }
+        return url;
+    }
+
+    public static X509Certificate[] readCertificates(URL certFile)
+            throws IOException, GeneralSecurityException
+    {
+        List<X509Certificate> crt = new ArrayList<>();
+        try (InputStream is = certFile.openStream())
+        {
+            do
+            {
+                CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                crt.add( (X509Certificate) cf.generateCertificate(is));
+            } while(is.available() != 0);
+        }
+        catch(CertificateException e)
+        {
+            if(crt.isEmpty())
+            {
+                throw e;
+            }
+        }
+        return crt.toArray(new X509Certificate[crt.size()]);
+    }
+
+    private static PrivateKey readPrivateKey(final URL url)
+            throws IOException, GeneralSecurityException
+    {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        try (InputStream urlStream = url.openStream())
+        {
+            byte[] tmp = new byte[1024];
+            int read;
+            while((read = urlStream.read(tmp)) != -1)
+            {
+                buffer.write(tmp,0,read);
+            }
+        }
+
+        byte[] content = buffer.toByteArray();
+        String contentAsString = new String(content, StandardCharsets.US_ASCII);
+        if(contentAsString.contains("-----BEGIN ") && contentAsString.contains(" PRIVATE KEY-----"))
+        {
+            BufferedReader lineReader = new BufferedReader(new StringReader(contentAsString));
+
+            String line;
+            do
+            {
+                line = lineReader.readLine();
+            } while(line != null && !(line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")));
+
+            if(line != null)
+            {
+                StringBuilder keyBuilder = new StringBuilder();
+
+                while((line = lineReader.readLine()) != null)
+                {
+                    if(line.startsWith("-----END ") && line.endsWith(" PRIVATE KEY-----"))
+                    {
+                        break;
+                    }
+                    keyBuilder.append(line);
+                }
+
+                content = DatatypeConverter.parseBase64Binary(keyBuilder.toString());
+            }
+        }
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(content);
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        PrivateKey key = kf.generatePrivate(keySpec);
+        return key;
+    }
+
+
+
+}

Added: qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/data/Handler.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/data/Handler.java?rev=1614652&view=auto
==============================================================================
--- qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/data/Handler.java (added)
+++ qpid/trunk/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/urlstreamhandler/data/Handler.java Wed Jul 30 13:07:51 2014
@@ -0,0 +1,148 @@
+/*
+ *
+ * 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.qpid.server.util.urlstreamhandler.data;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.net.URLStreamHandler;
+import java.nio.charset.StandardCharsets;
+
+import javax.xml.bind.DatatypeConverter;
+
+public class Handler extends URLStreamHandler
+{
+    public static final String PROTOCOL_HANDLER_PROPERTY = "java.protocol.handler.pkgs";
+    private static boolean _registered;
+
+    @Override
+    protected URLConnection openConnection(final URL u) throws IOException
+    {
+        return new DataUrlConnection(u);
+    }
+
+    public synchronized static void register()
+    {
+        if(!_registered)
+        {
+            String registeredPackages = System.getProperty(PROTOCOL_HANDLER_PROPERTY);
+            String thisPackage = Handler.class.getPackage().getName();
+            String packageToRegister = thisPackage.substring(0, thisPackage.lastIndexOf('.') );
+            System.setProperty(PROTOCOL_HANDLER_PROPERTY,
+                               registeredPackages == null
+                                       ? packageToRegister
+                                       : packageToRegister + "|" + registeredPackages);
+
+            _registered = true;
+        }
+
+
+
+    }
+
+    private static class DataUrlConnection extends URLConnection
+    {
+        private final byte[] _content;
+        private final String _contentType;
+        private final boolean _base64;
+
+        public DataUrlConnection(final URL u) throws IOException
+        {
+            super(u);
+            String externalForm = u.toExternalForm();
+            if(externalForm.startsWith("data:"))
+            {
+                String[] parts = externalForm.substring(5).split(",",2);
+                _base64 = parts[0].endsWith(";base64");
+                if(_base64)
+                {
+                    _content = DatatypeConverter.parseBase64Binary(parts[1]);
+                }
+                else
+                {
+                    try
+                    {
+                        _content = URLDecoder.decode(parts[1], StandardCharsets.US_ASCII.name()).getBytes(StandardCharsets.US_ASCII);
+                    }
+                    catch (UnsupportedEncodingException e)
+                    {
+                        throw new IOException(e);
+                    }
+                }
+                String mediaType = (_base64
+                        ? parts[0].substring(0,parts[0].length()-";base64".length())
+                        : parts[0]).split(";")[0];
+
+                _contentType = "".equals(mediaType) ? "text/plain" : mediaType;
+            }
+            else
+            {
+                throw new MalformedURLException("'"+externalForm+"' does not start with 'data:'");
+            }
+        }
+
+
+
+        @Override
+        public void connect() throws IOException
+        {
+
+        }
+
+        @Override
+        public int getContentLength()
+        {
+            return _content.length;
+        }
+
+        @Override
+        public String getContentType()
+        {
+            return _contentType;
+        }
+
+        @Override
+        public String getContentEncoding()
+        {
+            return _base64 ? "base64" : null;
+        }
+
+        @Override
+        public InputStream getInputStream() throws IOException
+        {
+            return new ByteArrayInputStream(_content);
+        }
+    }
+
+    public static void main(String[] args) throws IOException
+    {
+        register();
+        URL url = new URL("data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7");
+        InputStream is = url.openStream();
+        url = new URL("data:,A%20brief%20note");
+        is = url.openStream();
+    }
+}

Modified: qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java?rev=1614652&r1=1614651&r2=1614652&view=diff
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java (original)
+++ qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java Wed Jul 30 13:07:51 2014
@@ -34,6 +34,7 @@ import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
 import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
 
 import org.apache.log4j.Logger;
 import org.eclipse.jetty.server.Connector;
@@ -385,8 +386,14 @@ public class HttpManagement extends Abst
 
     private void addRestServlet(ServletContextHandler root, String name, Class<? extends ConfiguredObject>... hierarchy)
     {
-        root.addServlet(new ServletHolder(name, new RestServlet(hierarchy)), "/api/latest/" + name + "/*");
-        root.addServlet(new ServletHolder(name, new RestServlet(hierarchy)), "/api/v" + BrokerModel.MODEL_MAJOR_VERSION + "/" + name + "/*");
+        ServletHolder servletHolder = new ServletHolder(name, new RestServlet(hierarchy));
+        servletHolder.getRegistration().setMultipartConfig(
+                new MultipartConfigElement("",
+                                           getContextValue(Long.class, MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME),
+                                           -1l,
+                                           getContextValue(Integer.class, MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME)));
+        root.addServlet(servletHolder, "/api/latest/" + name + "/*");
+        root.addServlet(servletHolder, "/api/v" + BrokerModel.MODEL_MAJOR_VERSION + "/" + name + "/*");
     }
 
     private void logOperationalListenMessages(Server server)

Modified: qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java?rev=1614652&r1=1614651&r2=1614652&view=diff
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java (original)
+++ qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java Wed Jul 30 13:07:51 2014
@@ -24,6 +24,7 @@ import java.net.SocketAddress;
 
 import org.apache.qpid.server.model.AuthenticationProvider;
 import org.apache.qpid.server.model.ManagedAttribute;
+import org.apache.qpid.server.model.ManagedContextDefault;
 import org.apache.qpid.server.model.Plugin;
 
 public interface HttpManagementConfiguration<X extends HttpManagementConfiguration<X>> extends Plugin<X>
@@ -43,5 +44,9 @@ public interface HttpManagementConfigura
     @ManagedAttribute( defaultValue = "600" )
     public int getSessionTimeout();
 
+    String MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME = "maxHttpFileUploadSize";
+    @ManagedContextDefault( name = MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME)
+    static final long DEFAULT_MAX_UPLOAD_SIZE = 100 * 1024;
+
     AuthenticationProvider getAuthenticationProvider(SocketAddress localAddress);
 }

Modified: qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java?rev=1614652&r1=1614651&r2=1614652&view=diff
==============================================================================
--- qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java (original)
+++ qpid/trunk/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java Wed Jul 30 13:07:51 2014
@@ -36,6 +36,8 @@ import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
+import javax.xml.bind.DatatypeConverter;
 
 import org.apache.log4j.Logger;
 import org.codehaus.jackson.map.ObjectMapper;
@@ -43,6 +45,7 @@ import org.codehaus.jackson.map.Serializ
 
 import org.apache.qpid.server.model.Broker;
 import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.util.urlstreamhandler.data.Handler;
 
 public class RestServlet extends AbstractServlet
 {
@@ -85,6 +88,7 @@ public class RestServlet extends Abstrac
         {
             doInitialization();
         }
+        Handler.register();
     }
 
     @SuppressWarnings("unchecked")
@@ -342,23 +346,56 @@ public class RestServlet extends Abstrac
     {
         response.setContentType("application/json");
 
-        ObjectMapper mapper = new ObjectMapper();
-        @SuppressWarnings("unchecked")
-        Map<String,Object> providedObject = mapper.readValue(request.getInputStream(), LinkedHashMap.class);
-
-
         List<String> names = new ArrayList<String>();
         String[] pathInfoElements = getPathInfoElements(request);
-        if(pathInfoElements != null )
+        if (pathInfoElements != null)
         {
-            if(pathInfoElements.length != _hierarchy.length)
+            if (pathInfoElements.length != _hierarchy.length)
             {
                 throw new IllegalArgumentException("Path to object to create must be fully specified. "
-                       + "Found " + names + " of size " + names.size() + " expecting " + _hierarchy.length);
+                                                   + "Found "
+                                                   + names
+                                                   + " of size "
+                                                   + names.size()
+                                                   + " expecting "
+                                                   + _hierarchy.length);
             }
             names.addAll(Arrays.asList(pathInfoElements));
         }
 
+        Map<String, Object> providedObject;
+
+        ArrayList<String> headers = Collections.list(request.getHeaderNames());
+        ObjectMapper mapper = new ObjectMapper();
+
+        if(headers.contains("Content-Type") && request.getHeader("Content-Type").startsWith("multipart/form-data"))
+        {
+            providedObject = new HashMap<>();
+            Map<String,String> fileUploads = new HashMap<>();
+            Collection<Part> parts = request.getParts();
+            for(Part part : parts)
+            {
+                if("data".equals(part.getName()) && "application/json".equals(part.getContentType()))
+                {
+                    providedObject = mapper.readValue(part.getInputStream(), LinkedHashMap.class);
+                }
+                else
+                {
+                    byte[] data = new byte[(int) part.getSize()];
+                    part.getInputStream().read(data);
+                    StringBuilder inlineURL = new StringBuilder("data:;base64,");
+                    inlineURL.append(DatatypeConverter.printBase64Binary(data));
+                    fileUploads.put(part.getName(),inlineURL.toString());
+                }
+            }
+            providedObject.putAll(fileUploads);
+        }
+        else
+        {
+
+            providedObject = mapper.readValue(request.getInputStream(), LinkedHashMap.class);
+        }
+
         if (names.isEmpty())
         {
             if (_hierarchy.length == 0)
@@ -368,7 +405,7 @@ public class RestServlet extends Abstrac
                     doUpdate(getBroker(), providedObject);
                     response.setStatus(HttpServletResponse.SC_OK);
                 }
-                catch(RuntimeException e)
+                catch (RuntimeException e)
                 {
                     setResponseStatus(response, e);
                 }
@@ -380,24 +417,24 @@ public class RestServlet extends Abstrac
             }
         }
 
-        providedObject.put("name", names.get(names.size()-1));
+        providedObject.put("name", names.get(names.size() - 1));
 
         @SuppressWarnings("unchecked")
         Collection<ConfiguredObject>[] objects = new Collection[_hierarchy.length];
-        if(_hierarchy.length == 1)
+        if (_hierarchy.length == 1)
         {
             createOrUpdate(providedObject, _hierarchy[0], getBroker(), null, response);
         }
         else
         {
-            for(int i = 0; i < _hierarchy.length-1; i++)
+            for (int i = 0; i < _hierarchy.length - 1; i++)
             {
                 objects[i] = new HashSet<ConfiguredObject>();
-                if(i == 0)
+                if (i == 0)
                 {
-                    for(ConfiguredObject object : getBroker().getChildren(_hierarchy[0]))
+                    for (ConfiguredObject object : getBroker().getChildren(_hierarchy[0]))
                     {
-                        if(object.getName().equals(names.get(0)))
+                        if (object.getName().equals(names.get(0)))
                         {
                             objects[0].add(object);
                             break;
@@ -406,15 +443,15 @@ public class RestServlet extends Abstrac
                 }
                 else
                 {
-                    for(int j = i-1; j >=0; j--)
+                    for (int j = i - 1; j >= 0; j--)
                     {
-                        if(getBroker().getModel().getChildTypes(_hierarchy[j]).contains(_hierarchy[i]))
+                        if (getBroker().getModel().getChildTypes(_hierarchy[j]).contains(_hierarchy[i]))
                         {
-                            for(ConfiguredObject<?> parent : objects[j])
+                            for (ConfiguredObject<?> parent : objects[j])
                             {
-                                for(ConfiguredObject<?> object : parent.getChildren(_hierarchy[i]))
+                                for (ConfiguredObject<?> object : parent.getChildren(_hierarchy[i]))
                                 {
-                                    if(object.getName().equals(names.get(i)))
+                                    if (object.getName().equals(names.get(i)))
                                     {
                                         objects[i].add(object);
                                     }
@@ -428,19 +465,20 @@ public class RestServlet extends Abstrac
             }
             List<ConfiguredObject> parents = new ArrayList<ConfiguredObject>();
             Class<? extends ConfiguredObject> objClass = getConfiguredClass();
-            Collection<Class<? extends ConfiguredObject>> parentClasses = getBroker().getModel().getParentTypes(objClass);
-            for(int i = _hierarchy.length-2; i >=0 ; i--)
+            Collection<Class<? extends ConfiguredObject>> parentClasses =
+                    getBroker().getModel().getParentTypes(objClass);
+            for (int i = _hierarchy.length - 2; i >= 0; i--)
             {
-                if(parentClasses.contains(_hierarchy[i]))
+                if (parentClasses.contains(_hierarchy[i]))
                 {
-                    if(objects[i].size() == 1)
+                    if (objects[i].size() == 1)
                     {
                         parents.add(objects[i].iterator().next());
                     }
                     else
                     {
                         throw new IllegalArgumentException("Cannot deduce parent of class "
-                                + _hierarchy[i].getSimpleName());
+                                                           + _hierarchy[i].getSimpleName());
                     }
                 }
 
@@ -450,6 +488,7 @@ public class RestServlet extends Abstrac
 
             createOrUpdate(providedObject, objClass, theParent, otherParents, response);
         }
+
     }
 
     private void createOrUpdate(Map<String, Object> providedObject, Class<? extends ConfiguredObject> objClass,



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org