CAMEL-6424: camel-netty-http added support for basic auth. Work in progress.


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/3493d980
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/3493d980
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/3493d980

Branch: refs/heads/master
Commit: 3493d980679fee253a364612148fde925b8afefd
Parents: bf1f5f0
Author: Claus Ibsen <davscl...@apache.org>
Authored: Mon Jul 15 10:09:36 2013 +0200
Committer: Claus Ibsen <davscl...@apache.org>
Committed: Mon Jul 15 13:48:13 2013 +0200

----------------------------------------------------------------------
 .../netty/http/HttpBasicAuthSubject.java        | 45 +++++++++++++
 .../netty/http/NettyHttpComponent.java          | 13 ++++
 .../component/netty/http/NettyHttpEndpoint.java |  9 +++
 .../http/NettyHttpSecurityConfiguration.java    | 57 +++++++++++++++++
 .../http/handlers/HttpServerChannelHandler.java | 67 ++++++++++++++++++++
 .../http/NettyHttpSimpleBasicAuthTest.java      | 49 ++++++++++++++
 .../src/test/resources/log4j.properties         |  2 +-
 7 files changed, 241 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/3493d980/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpBasicAuthSubject.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpBasicAuthSubject.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpBasicAuthSubject.java
new file mode 100644
index 0000000..2809c84
--- /dev/null
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpBasicAuthSubject.java
@@ -0,0 +1,45 @@
+/**
+ * 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.camel.component.netty.http;
+
+import java.io.Serializable;
+
+public final class HttpBasicAuthSubject implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    private final String username;
+    private final String password;
+
+    public HttpBasicAuthSubject(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    @Override
+    public String toString() {
+        // do not display the password
+        return "HttpBasicAuthSubject[" + username + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/3493d980/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
index 6d5c4c3..177ce94 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
@@ -46,6 +46,8 @@ public class NettyHttpComponent extends NettyComponent 
implements HeaderFilterSt
     private final Map<String, HttpServerBootstrapFactory> bootstrapFactories = 
new HashMap<String, HttpServerBootstrapFactory>();
     private NettyHttpBinding nettyHttpBinding;
     private HeaderFilterStrategy headerFilterStrategy;
+    // TODO: make it easy to configure this
+    private NettyHttpSecurityConfiguration nettyHttpSecurityConfiguration;// = 
new NettyHttpSecurityConfiguration();
 
     public NettyHttpComponent() {
         // use the http configuration and filter strategy
@@ -102,6 +104,9 @@ public class NettyHttpComponent extends NettyComponent 
implements HeaderFilterSt
         if (answer.getHeaderFilterStrategy() == null) {
             answer.setHeaderFilterStrategy(getHeaderFilterStrategy());
         }
+        if (answer.getNettyHttpSecurityConfiguration() == null) {
+            
answer.setNettyHttpSecurityConfiguration(getNettyHttpSecurityConfiguration());
+        }
 
         answer.setNettySharedHttpServer(shared);
         return answer;
@@ -141,6 +146,14 @@ public class NettyHttpComponent extends NettyComponent 
implements HeaderFilterSt
         this.headerFilterStrategy = headerFilterStrategy;
     }
 
+    public NettyHttpSecurityConfiguration getNettyHttpSecurityConfiguration() {
+        return nettyHttpSecurityConfiguration;
+    }
+
+    public void 
setNettyHttpSecurityConfiguration(NettyHttpSecurityConfiguration 
nettyHttpSecurityConfiguration) {
+        this.nettyHttpSecurityConfiguration = nettyHttpSecurityConfiguration;
+    }
+
     public synchronized HttpServerConsumerChannelFactory 
getMultiplexChannelHandler(int port) {
         HttpServerConsumerChannelFactory answer = 
multiplexChannelHandlers.get(port);
         if (answer == null) {

http://git-wip-us.apache.org/repos/asf/camel/blob/3493d980/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpEndpoint.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpEndpoint.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpEndpoint.java
index e481d4b..1039fcf 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpEndpoint.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpEndpoint.java
@@ -46,6 +46,7 @@ public class NettyHttpEndpoint extends NettyEndpoint 
implements HeaderFilterStra
     private boolean traceEnabled;
     private String httpMethodRestrict;
     private NettySharedHttpServer nettySharedHttpServer;
+    private NettyHttpSecurityConfiguration nettyHttpSecurityConfiguration;
 
     public NettyHttpEndpoint(String endpointUri, NettyHttpComponent component, 
NettyConfiguration configuration) {
         super(endpointUri, component, configuration);
@@ -171,6 +172,14 @@ public class NettyHttpEndpoint extends NettyEndpoint 
implements HeaderFilterStra
         this.nettySharedHttpServer = nettySharedHttpServer;
     }
 
+    public NettyHttpSecurityConfiguration getNettyHttpSecurityConfiguration() {
+        return nettyHttpSecurityConfiguration;
+    }
+
+    public void 
setNettyHttpSecurityConfiguration(NettyHttpSecurityConfiguration 
nettyHttpSecurityConfiguration) {
+        this.nettyHttpSecurityConfiguration = nettyHttpSecurityConfiguration;
+    }
+
     @Override
     protected void doStart() throws Exception {
         super.doStart();

http://git-wip-us.apache.org/repos/asf/camel/blob/3493d980/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpSecurityConfiguration.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpSecurityConfiguration.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpSecurityConfiguration.java
new file mode 100644
index 0000000..e04c497
--- /dev/null
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpSecurityConfiguration.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.netty.http;
+
+public class NettyHttpSecurityConfiguration {
+
+    private boolean authenticate = true;
+    private String constraint = "BASIC";
+    private String realm = "Camel";
+    private ContextPathMatcher contextPathMatcher;
+
+    public boolean isAuthenticate() {
+        return authenticate;
+    }
+
+    public void setAuthenticate(boolean authenticate) {
+        this.authenticate = authenticate;
+    }
+
+    public String getConstraint() {
+        return constraint;
+    }
+
+    public void setConstraint(String constraint) {
+        this.constraint = constraint;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public void setRealm(String realm) {
+        this.realm = realm;
+    }
+
+    public ContextPathMatcher getContextPathMatcher() {
+        return contextPathMatcher;
+    }
+
+    public void setContextPathMatcher(ContextPathMatcher contextPathMatcher) {
+        this.contextPathMatcher = contextPathMatcher;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/3493d980/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java
index 95ca27d..ccc2415 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerChannelHandler.java
@@ -18,17 +18,23 @@ package org.apache.camel.component.netty.http.handlers;
 
 import java.net.SocketAddress;
 import java.nio.channels.ClosedChannelException;
+import java.nio.charset.Charset;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.component.netty.NettyConsumer;
 import org.apache.camel.component.netty.NettyHelper;
 import org.apache.camel.component.netty.handlers.ServerChannelHandler;
+import org.apache.camel.component.netty.http.HttpBasicAuthSubject;
 import org.apache.camel.component.netty.http.NettyHttpConsumer;
+import org.apache.camel.component.netty.http.NettyHttpSecurityConfiguration;
+import org.apache.camel.util.ObjectHelper;
+import org.jboss.netty.buffer.ChannelBuffer;
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.jboss.netty.channel.ChannelFutureListener;
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.jboss.netty.channel.ExceptionEvent;
 import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.handler.codec.base64.Base64;
 import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
 import org.jboss.netty.handler.codec.http.HttpRequest;
 import org.jboss.netty.handler.codec.http.HttpResponse;
@@ -40,6 +46,7 @@ import static 
org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive;
 import static org.jboss.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
 import static 
org.jboss.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
 import static 
org.jboss.netty.handler.codec.http.HttpResponseStatus.SERVICE_UNAVAILABLE;
+import static 
org.jboss.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED;
 import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
 
 /**
@@ -100,10 +107,70 @@ public class HttpServerChannelHandler extends 
ServerChannelHandler {
             return;
         }
 
+        // is basic auth configured
+        NettyHttpSecurityConfiguration security = 
consumer.getEndpoint().getNettyHttpSecurityConfiguration();
+        if (security != null) {
+            String url = request.getUri();
+
+            // is it a restricted resource?
+            boolean restricted = security.getContextPathMatcher() == null || 
security.getContextPathMatcher().matches(url);
+            if (restricted) {
+                // basic auth subject
+                HttpBasicAuthSubject subject = 
extractBasicAuthSubject(request);
+                boolean authenticated = subject != null && 
authenticate(subject);
+                if (subject == null || !authenticated) {
+                    if (subject == null) {
+                        LOG.debug("Http Basic Auth required for resource: {}", 
url);
+                    } else {
+                        LOG.debug("Http Basic Auth not authorized for 
username: {}", subject.getUsername());
+                    }
+                    // restricted resource, so send back 401 to require valid 
username/password
+                    HttpResponse response = new DefaultHttpResponse(HTTP_1_1, 
UNAUTHORIZED);
+                    response.setHeader("WWW-Authenticate", "Basic realm=\"" + 
security.getRealm() + "\"");
+                    response.setHeader(Exchange.CONTENT_TYPE, "text/plain");
+                    response.setHeader(Exchange.CONTENT_LENGTH, 0);
+                    response.setContent(ChannelBuffers.copiedBuffer(new 
byte[]{}));
+                    messageEvent.getChannel().write(response);
+                    return;
+                } else {
+                    LOG.debug("Http Basic Auth authorized for username: {}", 
subject.getUsername());
+                }
+            }
+        }
+
         // let Camel process this message
         super.messageReceived(ctx, messageEvent);
     }
 
+    /**
+     * Authenticates the http basic auth subject.
+     *
+     * @param subject  the subject
+     * @return <tt>true</tt> if username and password is valid, <tt>false</tt> 
if not
+     */
+    protected boolean authenticate(HttpBasicAuthSubject subject) {
+        // TODO: an api for authentication
+        return subject.getPassword().equals("secret");
+        //return true;
+    }
+
+    protected static HttpBasicAuthSubject extractBasicAuthSubject(HttpRequest 
request) {
+        String auth = request.getHeader("Authorization");
+        if (auth != null) {
+            String constraint = ObjectHelper.before(auth, " ");
+            String decoded = ObjectHelper.after(auth, " ");
+            // the decoded part is base64 encoded, so we need to decode that
+            ChannelBuffer buf = 
ChannelBuffers.copiedBuffer(decoded.getBytes());
+            ChannelBuffer out = Base64.decode(buf);
+            String userAndPw = out.toString(Charset.defaultCharset());
+            String username = ObjectHelper.before(userAndPw, ":");
+            String password = ObjectHelper.after(userAndPw, ":");
+            HttpBasicAuthSubject subject = new HttpBasicAuthSubject(username, 
password);
+            return subject;
+        }
+        return null;
+    }
+
     @Override
     protected void beforeProcess(Exchange exchange, MessageEvent messageEvent) 
{
         if (consumer.getConfiguration().isBridgeEndpoint()) {

http://git-wip-us.apache.org/repos/asf/camel/blob/3493d980/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthTest.java
 
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthTest.java
new file mode 100644
index 0000000..048f1b4
--- /dev/null
+++ 
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSimpleBasicAuthTest.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.camel.component.netty.http;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Ignore;
+import org.junit.Test;
+
+@Ignore
+public class NettyHttpSimpleBasicAuthTest extends BaseNettyTest {
+
+    @Test
+    public void testHttpSimple() throws Exception {
+//        getMockEndpoint("mock:input").expectedBodiesReceived("Hello World");
+//
+//        String out = 
template.requestBody("netty-http:http://localhost:{{port}}/foo";, "Hello World", 
String.class);
+//        assertEquals("Bye World", out);
+//
+//        assertMockEndpointsSatisfied();
+//        Thread.sleep(9999999);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("netty-http:http://0.0.0.0:{{port}}/foo";)
+                    .to("mock:input")
+                    .transform().constant("Bye World");
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/3493d980/components/camel-netty-http/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/components/camel-netty-http/src/test/resources/log4j.properties 
b/components/camel-netty-http/src/test/resources/log4j.properties
index 7d3c19c..0bada7f 100644
--- a/components/camel-netty-http/src/test/resources/log4j.properties
+++ b/components/camel-netty-http/src/test/resources/log4j.properties
@@ -16,7 +16,7 @@
 ## ------------------------------------------------------------------------
 
 #
-# The logging properties used for eclipse testing, We want to see debug output 
on the console.
+# The logging properties used for testing
 #
 log4j.rootLogger=INFO, file
 

Reply via email to