diff --git a/.travis.yml b/.travis.yml index 4f44277028..3aef38608f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,15 @@ sudo: required language: Java jdk: oraclejdk8 +services: docker env: global: - secure: "eUJzrr3JIxDF1Cf6Qnlj+g1rtX0NOEoOKvMNw9V/0JYP++vtUW9GD11gJ0YwfRU4/za3R4WBcW8JgtvOiyDCZkIRd+1nTsHeKY/ERwFjG8S29X7R6DUEUHL00B03MGJ0NZyRE1lzDc/np4zqcuH8wHqzXwszu+iQsTqrzHegROM=" - secure: "F6oCHN9p/gAFymPD091Yq/7niP1swVRrox7wPUQ3g/ELpIl8GS5JD2GapfVhsdV5oOkIXn0JWmzNSAVrI9jeUVZUCtkUdr+OMdyzzS9iJHmDxQOGnz3/vefbV5VQTCIFZKzE2c7usVcaxkVj4E+Ao4pcrXoXOzbvx2In2D/7+B8=" before_install: certificates/add-to-cacerts.sh -install: mvn -q -B -U install -DskipTests -DskipITs +install: + - export DOCKER_HOST=tcp://127.0.0.1:2375 + - mvn -q -B -U install -DskipTests -DskipITs script: - > if [ ${TRAVIS_PULL_REQUEST} == "false" ]; then diff --git a/bom/pom.xml b/bom/pom.xml index 5aa0e71396..6b3d946e49 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -126,11 +126,6 @@ gateway.service.update.check.management ${project.version} - - org.kaazing - gateway.service.turn.proxy - ${project.version} - org.kaazing diff --git a/distribution/pom.xml b/distribution/pom.xml index bbd33c0ea9..5bb4531f6d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -109,10 +109,6 @@ org.kaazing gateway.service.update.check.management - - org.kaazing - gateway.service.turn.proxy - diff --git a/distribution/src/main/assembly/generic-bin.xml b/distribution/src/main/assembly/generic-bin.xml index aa22b58632..7ca5a1ea01 100644 --- a/distribution/src/main/assembly/generic-bin.xml +++ b/distribution/src/main/assembly/generic-bin.xml @@ -72,7 +72,6 @@ org.kaazing:gateway.service.amqp org.kaazing:gateway.service.update.check org.kaazing:gateway.service.turn.rest - org.kaazing:gateway.service.turn.proxy org.kaazing:gateway.security diff --git a/distribution/src/main/gateway/conf/gateway-config.xml b/distribution/src/main/gateway/conf/gateway-config.xml index c3c8cb4303..52d8715103 100644 --- a/distribution/src/main/gateway/conf/gateway-config.xml +++ b/distribution/src/main/gateway/conf/gateway-config.xml @@ -16,7 +16,7 @@ limitations under the License. --> - + + * @@ -93,13 +93,20 @@ demo - + * + + Update Checker + Checks to see if a newer version of the Gateway is available + update.check + + + diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/AbstractNioWorker.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/AbstractNioWorker.java index 51830dd485..c03044ead3 100644 --- a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/AbstractNioWorker.java +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/AbstractNioWorker.java @@ -68,6 +68,9 @@ import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.socket.Worker; +import org.jboss.netty.channel.socket.nio.NioWorker.ReadDispatcher; +import org.jboss.netty.channel.socket.nio.NioWorker.TcpReadDispatcher; +import org.jboss.netty.channel.socket.nio.NioWorker.UdpReadDispatcher; import org.jboss.netty.channel.socket.nio.SocketSendBufferPool.SendBuffer; import org.jboss.netty.logging.InternalLogger; import org.jboss.netty.logging.InternalLoggerFactory; @@ -603,6 +606,9 @@ public void deregister(final AbstractNioChannel channel) { @Override public void run() { channels.remove(channel.getId().intValue()); + if (channel instanceof NioChildDatagramChannel) { + return; + } SelectionKey key = channel.channel.keyFor(selector); if (key != null) { @@ -633,15 +639,19 @@ public void run() { try { channels.put(channel.getId().intValue(), channel); - // TODO some channels no need to register with selector + if (channel instanceof NioChildDatagramChannel) { + return; + } // ensure channel.writeSuspended cannot remain true due to race // note: setOpWrite is a no-op before selectionKey is registered w/ selector int rawInterestOps = channel.getRawInterestOps(); rawInterestOps |= SelectionKey.OP_WRITE; channel.setRawInterestOpsNow(rawInterestOps); - - channel.channel.register(selector, rawInterestOps, channel); + ReadDispatcher readDispatcher = channel instanceof NioSocketChannel + ? new TcpReadDispatcher((NioSocketChannel) channel) + : new UdpReadDispatcher((NioDatagramChannel) channel); + channel.channel.register(selector, rawInterestOps, readDispatcher); } catch (ClosedChannelException e) { close(channel, succeededFuture(channel)); diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioChildDatagramChannel.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioChildDatagramChannel.java index 120756d2a3..0311850cca 100644 --- a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioChildDatagramChannel.java +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioChildDatagramChannel.java @@ -133,10 +133,6 @@ public NioDatagramChannelConfig getConfig() { return config; } - DatagramChannel getDatagramChannel() { - return channel; - } - public ChannelFuture joinGroup(InetAddress multicastAddress) { try { return joinGroup( diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioChildDatagramPipelineSink.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioChildDatagramPipelineSink.java index 89d40ec57c..5a259382c4 100644 --- a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioChildDatagramPipelineSink.java +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioChildDatagramPipelineSink.java @@ -47,12 +47,6 @@ */ class NioChildDatagramPipelineSink extends AbstractNioChannelSink { - private final WorkerPool workerPool; - - NioChildDatagramPipelineSink(final WorkerPool workerPool) { - this.workerPool = workerPool; - } - /** * Handle downstream event. * @@ -99,10 +93,6 @@ public void eventSunk(final ChannelPipeline pipeline, final ChannelEvent e) } } - AbstractNioWorker nextWorker() { - return workerPool.nextWorker(); - } - private static final class ParentMessageEvent implements MessageEvent { private final MessageEvent delegate; diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramChannelFactory.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioClientDatagramChannelFactory.java similarity index 63% rename from mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramChannelFactory.java rename to mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioClientDatagramChannelFactory.java index ed1c4c7ae3..3ddac8b50c 100644 --- a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramChannelFactory.java +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioClientDatagramChannelFactory.java @@ -35,13 +35,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; -import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.channel.socket.DatagramChannel; import org.jboss.netty.channel.socket.DatagramChannelFactory; import org.jboss.netty.channel.socket.InternetProtocolFamily; -import org.jboss.netty.channel.socket.Worker; import org.jboss.netty.channel.socket.oio.OioDatagramChannelFactory; import org.jboss.netty.util.ExternalResourceReleasable; @@ -53,19 +51,19 @@ * *

How threads work

*

- * There is only one thread type in a {@link NioDatagramChannelFactory}; + * There is only one thread type in a {@link NioClientDatagramChannelFactory}; * worker threads. * *

Worker threads

*

- * One {@link NioDatagramChannelFactory} can have one or more worker + * One {@link NioClientDatagramChannelFactory} can have one or more worker * threads. A worker thread performs non-blocking read and write for one or * more {@link DatagramChannel}s in a non-blocking mode. * *

Life cycle of threads and graceful shutdown

*

* All worker threads are acquired from the {@link Executor} which was specified - * when a {@link NioDatagramChannelFactory} was created. Therefore, you should + * when a {@link NioClientDatagramChannelFactory} was created. Therefore, you should * make sure the specified {@link Executor} is able to lend the sufficient * number of threads. It is the best bet to specify * {@linkplain Executors#newCachedThreadPool() a cached thread pool}. @@ -89,56 +87,27 @@ *

* Multicast is not supported. Please use {@link OioDatagramChannelFactory} * instead. - * - * @apiviz.landmark */ -public class NioDatagramChannelFactory implements DatagramChannelFactory { +public class NioClientDatagramChannelFactory implements DatagramChannelFactory { private final NioDatagramPipelineSink sink; - private final NioChildDatagramPipelineSink childSink; - private final WorkerPool workerPool; - private final WorkerPool childPool; + private final WorkerPool workerPool; private final InternetProtocolFamily family; private boolean releasePool; - /** - * Create a new {@link NioDatagramChannelFactory} with a {@link Executors#newCachedThreadPool()} - * and without preferred {@link InternetProtocolFamily}. Please note that the {@link InternetProtocolFamily} - * of the channel will be platform (and possibly configuration) dependent and therefore - * unspecified. Use {@link #NioDatagramChannelFactory(InternetProtocolFamily)} if unsure. - * - * See {@link #NioDatagramChannelFactory(Executor)} - */ - public NioDatagramChannelFactory() { - this((InternetProtocolFamily) null); - } - - /** - * Create a new {@link NioDatagramChannelFactory} with a {@link Executors#newCachedThreadPool()}. - * - * See {@link #NioDatagramChannelFactory(Executor)} - */ - public NioDatagramChannelFactory(InternetProtocolFamily family) { - workerPool = new NioDatagramWorkerPool(Executors.newCachedThreadPool(), SelectorUtil.DEFAULT_IO_THREADS); - childPool = new NioWorkerPool(Executors.newCachedThreadPool(), SelectorUtil.DEFAULT_IO_THREADS); - this.family = family; - sink = new NioDatagramPipelineSink(workerPool); - childSink = new NioChildDatagramPipelineSink(childPool); + public NioClientDatagramChannelFactory(WorkerPool workerPool) { + this.workerPool = workerPool; + this.family = null; + sink = new NioDatagramPipelineSink(); releasePool = true; } public DatagramChannel newChannel(final ChannelPipeline pipeline) { - return new NioDatagramChannel(this, pipeline, sink, sink.nextWorker(), family); - } - - // mina.netty change - adding this to create child datagram channels - public NioChildDatagramChannel newChildChannel(Channel parent, final ChannelPipeline pipeline) { - return new NioChildDatagramChannel(parent, this, pipeline, childSink, childSink.nextWorker(), family); + return new NioDatagramChannel(this, pipeline, sink, workerPool.nextWorker(), family); } public void shutdown() { workerPool.shutdown(); - childPool.shutdown(); if (releasePool) { releasePool(); } @@ -146,7 +115,6 @@ public void shutdown() { public void releaseExternalResources() { workerPool.shutdown(); - childPool.shutdown(); releasePool(); } @@ -154,8 +122,5 @@ private void releasePool() { if (workerPool instanceof ExternalResourceReleasable) { ((ExternalResourceReleasable) workerPool).releaseExternalResources(); } - if (childPool instanceof ExternalResourceReleasable) { - ((ExternalResourceReleasable) childPool).releaseExternalResources(); - } } -} \ No newline at end of file +} diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramBossPool.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramBossPool.java new file mode 100644 index 0000000000..3578b5f21c --- /dev/null +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramBossPool.java @@ -0,0 +1,57 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.jboss.netty.channel.socket.nio; + +import org.jboss.netty.util.ThreadNameDeterminer; + +import java.util.concurrent.Executor; + + +/** + * Holds {@link NioServerDatagramBoss} instances to use + */ +public class NioDatagramBossPool extends AbstractNioBossPool { + private final ThreadNameDeterminer determiner; + + /** + * Create a new instance + * + * @param bossExecutor the {@link Executor} to use for server the {@link NioServerDatagramBoss} + * @param bossCount the number of {@link NioServerDatagramBoss} instances this {@link NioDatagramBossPool} will hold + * @param determiner the {@link ThreadNameDeterminer} to use for name the threads. Use {@code null} + * if you not want to set one explicit. + */ + public NioDatagramBossPool(Executor bossExecutor, int bossCount, ThreadNameDeterminer determiner) { + super(bossExecutor, bossCount, false); + this.determiner = determiner; + init(); + } + + /** + * Create a new instance using no {@link ThreadNameDeterminer} + * + * @param bossExecutor the {@link Executor} to use for server the {@link NioServerDatagramBoss} + * @param bossCount the number of {@link NioServerDatagramBoss} instances this {@link NioDatagramBossPool} will hold + */ + public NioDatagramBossPool(Executor bossExecutor, int bossCount) { + this(bossExecutor, bossCount, null); + } + + @Override + protected NioServerDatagramBoss newBoss(Executor executor) { + return new NioServerDatagramBoss(executor); + } +} diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramChannel.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramChannel.java index 7d0d15a245..8961b8d6e9 100644 --- a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramChannel.java +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramChannel.java @@ -70,7 +70,7 @@ public class NioDatagramChannel extends AbstractNioChannel NioDatagramChannel(final ChannelFactory factory, final ChannelPipeline pipeline, final ChannelSink sink, - final NioDatagramWorker worker, InternetProtocolFamily family) { + final AbstractNioWorker worker, InternetProtocolFamily family) { super(null, factory, pipeline, sink, worker, openNonBlockingChannel(family), true); config = new DefaultNioDatagramChannelConfig(channel); @@ -111,11 +111,6 @@ private static DatagramChannel openNonBlockingChannel(InternetProtocolFamily fam } } - @Override - public NioDatagramWorker getWorker() { - return (NioDatagramWorker) super.getWorker(); - } - public boolean isBound() { return isOpen() && channel.socket().isBound(); } diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramPipelineSink.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramPipelineSink.java index da33bb211b..ab8e972a25 100644 --- a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramPipelineSink.java +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramPipelineSink.java @@ -33,7 +33,6 @@ import static org.jboss.netty.channel.Channels.*; import java.net.InetSocketAddress; -import java.util.concurrent.Executor; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelFuture; @@ -49,23 +48,6 @@ */ class NioDatagramPipelineSink extends AbstractNioChannelSink { - private final WorkerPool workerPool; - - /** - * Creates a new {@link NioDatagramPipelineSink} with a the number of {@link NioDatagramWorker}s - * specified in workerCount. The {@link NioDatagramWorker}s take care of reading and writing - * for the {@link NioDatagramChannel}. - * - * @param workerExecutor - * the {@link Executor} that will run the {@link NioDatagramWorker}s - * for this sink - * @param workerCount - * the number of {@link NioDatagramWorker}s for this sink - */ - NioDatagramPipelineSink(final WorkerPool workerPool) { - this.workerPool = workerPool; - } - /** * Handle downstream event. * @@ -98,7 +80,7 @@ public void eventSunk(final ChannelPipeline pipeline, final ChannelEvent e) if (value != null) { connect(channel, future, (InetSocketAddress) value); } else { - NioDatagramWorker.disconnect(channel, future); + NioServerDatagramBoss.disconnect(channel, future); } break; case INTEREST_OPS: @@ -201,8 +183,4 @@ private static void connect( } } - NioDatagramWorker nextWorker() { - return workerPool.nextWorker(); - } - } diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramWorker.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioServerDatagramBoss.java similarity index 99% rename from mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramWorker.java rename to mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioServerDatagramBoss.java index f880fa90de..5c27adb672 100644 --- a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioDatagramWorker.java +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioServerDatagramBoss.java @@ -55,7 +55,7 @@ * A class responsible for registering channels with {@link Selector}. * It also implements the {@link Selector} loop. */ -public class NioDatagramWorker extends AbstractNioWorker { +public class NioServerDatagramBoss extends AbstractNioWorker implements Boss { /** * Sole constructor. @@ -63,7 +63,7 @@ public class NioDatagramWorker extends AbstractNioWorker { * @param executor the {@link Executor} used to execute {@link Runnable}s * such as {@link ChannelRegistionTask} */ - NioDatagramWorker(final Executor executor) { + NioServerDatagramBoss(final Executor executor) { super(executor); } diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioServerDatagramChannelFactory.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioServerDatagramChannelFactory.java new file mode 100644 index 0000000000..0d699818eb --- /dev/null +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioServerDatagramChannelFactory.java @@ -0,0 +1,117 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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. + */ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project 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.jboss.netty.channel.socket.nio; + +import java.util.concurrent.Executor; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.socket.DatagramChannel; +import org.jboss.netty.channel.socket.DatagramChannelFactory; +import org.jboss.netty.channel.socket.InternetProtocolFamily; +import org.jboss.netty.util.ExternalResourceReleasable; + +/** + * A {@link DatagramChannelFactory} that creates a NIO-based connectionless + * {@link DatagramChannel}. It utilizes the non-blocking I/O mode which + * was introduced with NIO to serve many number of concurrent connections + * efficiently. + * + *

How threads work

+ *

+ * There are two types of threads in a {@link NioServerDatagramChannelFactory}; + * one is boss thread and the other is worker thread. + * + *

Boss threads

+ *

+ * Each bound {@link NioDatagramChannel} has its own boss thread. + * For example, if you opened two server ports such as 80 and 443, you will + * have two boss threads. A boss thread creates child sessions based on + * the remote address of client. Once a child connection is created + * successfully, the boss thread passes the child {@link Channel} to one of + * the worker threads that the {@link NioServerDatagramChannelFactory} manages. + * + *

Worker threads

+ *

+ * One {@link NioServerDatagramChannelFactory} can have one or more worker + * threads. A worker thread performs non-blocking read and write for one or + * more {@link Channel}s in a non-blocking mode. + */ +public class NioServerDatagramChannelFactory implements DatagramChannelFactory { + + private final NioDatagramPipelineSink sink; + private final NioChildDatagramPipelineSink childSink; + private final BossPool bossPool; + private final WorkerPool workerPool; + private final InternetProtocolFamily family; + private boolean releasePool; + + public NioServerDatagramChannelFactory(Executor bossExecutor, int bossCount, WorkerPool workerPool) { + bossPool = new NioDatagramBossPool(bossExecutor, bossCount, null); + this.workerPool = workerPool; + this.family = null; + sink = new NioDatagramPipelineSink(); + childSink = new NioChildDatagramPipelineSink(); + releasePool = true; + } + + public DatagramChannel newChannel(final ChannelPipeline pipeline) { + return new NioDatagramChannel(this, pipeline, sink, bossPool.nextBoss(), family); + } + + // mina.netty change - adding this to create child datagram channels + public NioChildDatagramChannel newChildChannel(Channel parent, final ChannelPipeline pipeline) { + return new NioChildDatagramChannel(parent, this, pipeline, childSink, workerPool.nextWorker(), family); + } + + public void shutdown() { + workerPool.shutdown(); + bossPool.shutdown(); + if (releasePool) { + releasePool(); + } + } + + public void releaseExternalResources() { + workerPool.shutdown(); + bossPool.shutdown(); + releasePool(); + } + + private void releasePool() { + if (workerPool instanceof ExternalResourceReleasable) { + ((ExternalResourceReleasable) workerPool).releaseExternalResources(); + } + if (bossPool instanceof ExternalResourceReleasable) { + ((ExternalResourceReleasable) bossPool).releaseExternalResources(); + } + } +} diff --git a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioWorker.java b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioWorker.java index ae9723c9b1..14de230c81 100644 --- a/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioWorker.java +++ b/mina.netty/src/main/java/org/jboss/netty/channel/socket/nio/NioWorker.java @@ -30,6 +30,7 @@ */ package org.jboss.netty.channel.socket.nio; +import static org.jboss.netty.channel.Channels.fireWriteComplete; import static org.kaazing.mina.netty.config.InternalSystemProperty.MAXIMUM_PROCESS_TASKS_TIME; import static java.lang.String.format; import static org.jboss.netty.channel.Channels.fireChannelBound; @@ -41,10 +42,13 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousCloseException; import java.nio.channels.ClosedChannelException; +import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; +import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -53,6 +57,7 @@ import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelException; import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.ReceiveBufferSizePredictor; import org.jboss.netty.util.ThreadNameDeterminer; @@ -100,9 +105,12 @@ protected final long getMaximumProcessTaskQueueTimeNanos() { @Override protected boolean read(SelectionKey k) { - final SocketChannel ch = (SocketChannel) k.channel(); - final NioSocketChannel channel = (NioSocketChannel) k.attachment(); + ReadDispatcher dispatcher = (ReadDispatcher) k.attachment(); + return dispatcher.dispatch(this, k); + } + private boolean readTcp(SelectionKey k, NioSocketChannel channel) { + final SocketChannel ch = (SocketChannel) k.channel(); final ReceiveBufferSizePredictor predictor = channel.getConfig().getReceiveBufferSizePredictor(); final int predictedRecvBufSize = predictor.nextReceiveBufferSize(); @@ -176,16 +184,20 @@ protected int select(Selector selector, boolean quickSelect) throws IOException @Override protected Runnable createRegisterTask(Channel channel, ChannelFuture future) { - boolean server = !(channel instanceof NioClientSocketChannel); - return new RegisterTask((NioSocketChannel) channel, future, server); + if (channel instanceof NioSocketChannel) { + boolean server = !(channel instanceof NioClientSocketChannel); + return new TcpChannelRegisterTask((NioSocketChannel) channel, future, server); + } else { + return new UdpChannelRegistionTask((NioDatagramChannel) channel, future); + } } - private final class RegisterTask implements Runnable { + private final class TcpChannelRegisterTask implements Runnable { private final NioSocketChannel channel; private final ChannelFuture future; private final boolean server; - RegisterTask( + TcpChannelRegisterTask( NioSocketChannel channel, ChannelFuture future, boolean server) { this.channel = channel; @@ -212,7 +224,7 @@ public void run() { } channel.channel.register( - selector, channel.getRawInterestOps(), channel); + selector, channel.getRawInterestOps(), new TcpReadDispatcher(channel)); if (future != null) { channel.setConnected(); @@ -241,4 +253,330 @@ public void run() { super.run(); recvBufferPool.releaseExternalResources(); } + + @Override + protected void write0(final AbstractNioChannel channel) { + if (channel instanceof NioSocketChannel || channel instanceof NioChildDatagramChannel) { + super.write0(channel); + } else { + write0Udp(channel); + } + } + + @Override + void writeFromUserCode(final AbstractNioChannel channel) { + if (channel instanceof NioDatagramChannel) { + writeFromUserCodeUdp(channel); + } else { + super.writeFromUserCode(channel); + } + } + + @Override + protected void close(SelectionKey k) { + ReadDispatcher dispatcher = (ReadDispatcher) k.attachment(); + AbstractNioChannel ch = dispatcher.channel(); + close(ch, succeededFuture(ch)); + } + + @Override + void writeFromSelectorLoop(final SelectionKey k) { + ReadDispatcher dispatcher = (ReadDispatcher) k.attachment(); + AbstractNioChannel ch = dispatcher.channel(); + ch.writeSuspended = false; + write0(ch); + } + + // + // ---------------- UDP worker ----------------------- + // + private boolean readUdp(final SelectionKey key, NioDatagramChannel channel) { + ReceiveBufferSizePredictor predictor = + channel.getConfig().getReceiveBufferSizePredictor(); + final ChannelBufferFactory bufferFactory = channel.getConfig().getBufferFactory(); + final DatagramChannel nioChannel = (DatagramChannel) key.channel(); + final int predictedRecvBufSize = predictor.nextReceiveBufferSize(); + + final ByteBuffer byteBuffer = recvBufferPool.get(predictedRecvBufSize).order(bufferFactory.getDefaultOrder()); + + boolean failure = true; + SocketAddress remoteAddress = null; + try { + // Receive from the channel in a non blocking mode. We have already been notified that + // the channel is ready to receive. + remoteAddress = nioChannel.receive(byteBuffer); + failure = false; + } catch (ClosedChannelException e) { + // Can happen, and does not need a user attention. + } catch (Throwable t) { + fireExceptionCaught(channel, t); + } + + if (remoteAddress != null) { + // Flip the buffer so that we can wrap it. + byteBuffer.flip(); + + int readBytes = byteBuffer.remaining(); + if (readBytes > 0) { + // Update the predictor. + predictor.previousReceiveBufferSize(readBytes); + + final ChannelBuffer buffer = bufferFactory.getBuffer(readBytes); + buffer.setBytes(0, byteBuffer); + buffer.writerIndex(readBytes); + + // Update the predictor. + predictor.previousReceiveBufferSize(readBytes); + + // Notify the interested parties about the newly arrived message. + fireMessageReceived( + channel, buffer, remoteAddress); + } + } + + if (failure) { + key.cancel(); // Some JDK implementations run into an infinite loop without this. + close(channel, succeededFuture(channel)); + return false; + } + + return true; + } + + /** + * RegisterTask is a task responsible for registering a channel with a + * selector. + */ + private final class UdpChannelRegistionTask implements Runnable { + private final NioDatagramChannel channel; + + private final ChannelFuture future; + + UdpChannelRegistionTask(final NioDatagramChannel channel, + final ChannelFuture future) { + this.channel = channel; + this.future = future; + } + + /** + * This runnable's task. Does the actual registering by calling the + * underlying DatagramChannels peer DatagramSocket register method. + */ + public void run() { + final SocketAddress localAddress = channel.getLocalAddress(); + final SocketAddress remoteAddress = channel.getRemoteAddress(); + if (localAddress == null) { + if (future != null) { + future.setFailure(new ClosedChannelException()); + } + close(channel, succeededFuture(channel)); + return; + } + + try { + channel.getDatagramChannel().register( + selector, channel.getInternalInterestOps(), new UdpReadDispatcher(channel)); + + if (future != null) { + future.setSuccess(); + } + // mina.netty change - similar to tcp, connected event is fired here instead + // in NioDatagramPipelineSink. This means NioDatagramChannelIoSession is + // created in the correct i/o thread + fireChannelConnected(channel, remoteAddress); + } catch (final IOException e) { + if (future != null) { + future.setFailure(e); + } + close(channel, succeededFuture(channel)); + + if (!(e instanceof ClosedChannelException)) { + throw new ChannelException( + "Failed to register a socket to the selector.", e); + } + } + } + } + + void writeFromUserCodeUdp(final AbstractNioChannel channel) { + assert channel instanceof NioDatagramChannel; + + /* + * Note that we are not checking if the channel is connected. Connected + * has a different meaning in UDP and means that the channels socket is + * configured to only send and receive from a given remote peer. + */ + if (!channel.isBound()) { + cleanUpWriteBuffer(channel); + return; + } + + if (scheduleWriteIfNecessary(channel)) { + return; + } + + // From here, we are sure Thread.currentThread() == workerThread. + + if (channel.writeSuspended) { + return; + } + + if (channel.inWriteNowLoop) { + return; + } + + write0(channel); + } + + private void write0Udp(final AbstractNioChannel channel) { + assert channel instanceof NioDatagramChannel; + + boolean addOpWrite = false; + boolean removeOpWrite = false; + + long writtenBytes = 0; + + final SocketSendBufferPool sendBufferPool = this.sendBufferPool; + final DatagramChannel ch = ((NioDatagramChannel) channel).getDatagramChannel(); + final Queue writeBuffer = channel.writeBufferQueue; + final int writeSpinCount = channel.getConfig().getWriteSpinCount(); + synchronized (channel.writeLock) { + // inform the channel that write is in-progress + channel.inWriteNowLoop = true; + + // loop forever... + for (;;) { + MessageEvent evt = channel.currentWriteEvent; + SocketSendBufferPool.SendBuffer buf; + if (evt == null) { + if ((channel.currentWriteEvent = evt = writeBuffer.poll()) == null) { + removeOpWrite = true; + channel.writeSuspended = false; + break; + } + // mina.netty change - similar to mina.netty's AbstractNioWorker, passing channel as parameter + channel.currentWriteBuffer = buf = sendBufferPool.acquire(channel, evt.getMessage()); + } else { + buf = channel.currentWriteBuffer; + } + + try { + long localWrittenBytes = 0; + SocketAddress raddr = evt.getRemoteAddress(); + if (raddr == null) { + for (int i = writeSpinCount; i > 0; i --) { + localWrittenBytes = buf.transferTo(ch); + if (localWrittenBytes != 0) { + writtenBytes += localWrittenBytes; + break; + } + if (buf.finished()) { + break; + } + } + } else { + for (int i = writeSpinCount; i > 0; i --) { + localWrittenBytes = buf.transferTo(ch, raddr); + if (localWrittenBytes != 0) { + writtenBytes += localWrittenBytes; + break; + } + if (buf.finished()) { + break; + } + } + } + + if (localWrittenBytes > 0 || buf.finished()) { + // Successful write - proceed to the next message. + buf.release(); + ChannelFuture future = evt.getFuture(); + channel.currentWriteEvent = null; + channel.currentWriteBuffer = null; + evt = null; + buf = null; + future.setSuccess(); + } else { + // Not written at all - perhaps the kernel buffer is full. + addOpWrite = true; + channel.writeSuspended = true; + break; + } + } catch (final AsynchronousCloseException e) { + // Doesn't need a user attention - ignore. + } catch (final Throwable t) { + buf.release(); + ChannelFuture future = evt.getFuture(); + channel.currentWriteEvent = null; + channel.currentWriteBuffer = null; + // Mark the event object for garbage collection. + //noinspection UnusedAssignment + buf = null; + //noinspection UnusedAssignment + evt = null; + future.setFailure(t); + fireExceptionCaught(channel, t); + } + } + channel.inWriteNowLoop = false; + + // Initially, the following block was executed after releasing + // the writeLock, but there was a race condition, and it has to be + // executed before releasing the writeLock: + // + // https://issues.jboss.org/browse/NETTY-410 + // + if (addOpWrite) { + setOpWrite(channel); + } else if (removeOpWrite) { + clearOpWrite(channel); + } + } + + fireWriteComplete(channel, writtenBytes); + } + + interface ReadDispatcher { + AbstractNioChannel channel(); + boolean dispatch(NioWorker worker, SelectionKey key); + } + + static final class TcpReadDispatcher implements ReadDispatcher { + + private final NioSocketChannel channel; + + TcpReadDispatcher(NioSocketChannel channel) { + this.channel = channel; + } + + @Override + public AbstractNioChannel channel() { + return channel; + } + + @Override + public boolean dispatch(NioWorker worker, SelectionKey key) { + return worker.readTcp(key, channel); + } + } + + static final class UdpReadDispatcher implements ReadDispatcher { + + private final NioDatagramChannel channel; + + UdpReadDispatcher(NioDatagramChannel channel) { + this.channel = channel; + } + + @Override + public AbstractNioChannel channel() { + return channel; + } + + @Override + public boolean dispatch(NioWorker worker, SelectionKey key) { + return worker.readUdp(key, channel); + } + } + } diff --git a/mina.netty/src/main/java/org/kaazing/mina/netty/bootstrap/ConnectionlessServerBootstrap.java b/mina.netty/src/main/java/org/kaazing/mina/netty/bootstrap/ConnectionlessServerBootstrap.java index 39df45d4b8..f574cb4004 100644 --- a/mina.netty/src/main/java/org/kaazing/mina/netty/bootstrap/ConnectionlessServerBootstrap.java +++ b/mina.netty/src/main/java/org/kaazing/mina/netty/bootstrap/ConnectionlessServerBootstrap.java @@ -29,19 +29,16 @@ import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.channel.socket.nio.AbstractNioWorker; import org.jboss.netty.channel.socket.nio.NioChildDatagramChannel; -import org.jboss.netty.channel.socket.nio.NioDatagramChannel; -import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory; +import org.jboss.netty.channel.socket.nio.NioServerDatagramChannelFactory; import org.kaazing.mina.netty.IoAcceptorChannelHandler; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; import static org.jboss.netty.channel.Channels.fireChannelConnected; import static org.jboss.netty.channel.Channels.fireChannelOpen; -import static org.jboss.netty.channel.Channels.fireMessageReceived; import static org.jboss.netty.channel.Channels.pipeline; class ConnectionlessServerBootstrap extends ConnectionlessBootstrap implements ServerBootstrap { @@ -153,7 +150,7 @@ private NioChildDatagramChannel getChildChannel(Channel channel, SocketAddress r } ChannelFactory channelFactory = channel.getFactory(); - NioChildDatagramChannel childChannel = ((NioDatagramChannelFactory)channelFactory).newChildChannel(channel, childPipeline); + NioChildDatagramChannel childChannel = ((NioServerDatagramChannelFactory)channelFactory).newChildChannel(channel, childPipeline); childChannel.setLocalAddress((InetSocketAddress) channel.getLocalAddress()); childChannel.setRemoteAddress((InetSocketAddress) remoteAddress); fireChannelOpen(childChannel); diff --git a/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoAcceptor.java b/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoAcceptor.java index 85493f2305..6a242c032c 100644 --- a/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoAcceptor.java +++ b/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoAcceptor.java @@ -21,9 +21,8 @@ import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelConfig; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.channel.socket.DatagramChannelFactory; import org.jboss.netty.channel.socket.nio.NioChildDatagramChannel; -import org.jboss.netty.channel.socket.nio.NioDatagramChannel; -import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory; import org.kaazing.mina.core.service.IoProcessorEx; import org.kaazing.mina.netty.ChannelIoSession; import org.kaazing.mina.netty.socket.DatagramChannelIoAcceptor; @@ -37,11 +36,10 @@ public class NioDatagramChannelIoAcceptor extends DatagramChannelIoAcceptor { "Kaazing", "NioDatagramChannel", true, true, InetSocketAddress.class, DatagramSessionConfig.class, Object.class); - public NioDatagramChannelIoAcceptor(DatagramChannelIoSessionConfig sessionConfig) { - super(sessionConfig, new NioDatagramChannelFactory(), new SimpleChannelUpstreamHandler()); + public NioDatagramChannelIoAcceptor(DatagramChannelIoSessionConfig sessionConfig, DatagramChannelFactory channelFactory) { + super(sessionConfig, channelFactory, new SimpleChannelUpstreamHandler()); } - @Override public TransportMetadata getTransportMetadata() { return NIO_DATAGRAM_TRANSPORT_METADATA; diff --git a/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoConnector.java b/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoConnector.java index 0c144d3f84..391144a061 100644 --- a/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoConnector.java +++ b/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoConnector.java @@ -21,6 +21,7 @@ import org.apache.mina.core.service.TransportMetadata; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelConfig; +import org.jboss.netty.channel.socket.DatagramChannelFactory; import org.jboss.netty.channel.socket.nio.NioDatagramChannel; import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory; @@ -35,8 +36,8 @@ public class NioDatagramChannelIoConnector extends DatagramChannelIoConnector { "Kaazing", "NioDatagramChannel", true, true, InetSocketAddress.class, DatagramChannelIoSessionConfig.class, Object.class); - public NioDatagramChannelIoConnector(DatagramChannelIoSessionConfig sessionConfig) { - super(sessionConfig, new NioDatagramChannelFactory()); + public NioDatagramChannelIoConnector(DatagramChannelIoSessionConfig sessionConfig, DatagramChannelFactory channelFactory) { + super(sessionConfig, channelFactory); } @Override diff --git a/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoSession.java b/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoSession.java index f23bc94b88..b31a17a16b 100644 --- a/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoSession.java +++ b/mina.netty/src/main/java/org/kaazing/mina/netty/socket/nio/NioDatagramChannelIoSession.java @@ -73,9 +73,7 @@ else if (isClosedReceived()) { } else { AbstractNioWorker newWorker = ((WorkerExecutor) ioExecutor).worker; - channel.getWorker().deregister(channel); channel.setWorker(newWorker); - newWorker.register(channel); } } diff --git a/mina.netty/src/test/java/org/kaazing/mina/netty/NioDatagramChannelIoAcceptorIT.java b/mina.netty/src/test/java/org/kaazing/mina/netty/NioDatagramChannelIoAcceptorIT.java index 94d6deedae..0fd920f9a3 100644 --- a/mina.netty/src/test/java/org/kaazing/mina/netty/NioDatagramChannelIoAcceptorIT.java +++ b/mina.netty/src/test/java/org/kaazing/mina/netty/NioDatagramChannelIoAcceptorIT.java @@ -15,6 +15,7 @@ */ package org.kaazing.mina.netty; +import static java.util.concurrent.Executors.newCachedThreadPool; import static org.kaazing.mina.netty.PortUtil.nextPort; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -32,6 +33,8 @@ import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.logging.LoggingFilter; +import org.jboss.netty.channel.socket.nio.NioServerDatagramChannelFactory; +import org.jboss.netty.channel.socket.nio.NioWorkerPool; import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -48,7 +51,6 @@ /** * Integration test for mina.netty layer. Similar to IT, but for datagram transport. */ -@Ignore // Not yet working. gateway.server is still using Mina for UDP. public class NioDatagramChannelIoAcceptorIT { @Rule @@ -61,7 +63,9 @@ public class NioDatagramChannelIoAcceptorIT { public void initResources() throws Exception { DatagramChannelIoSessionConfig sessionConfig = new DefaultDatagramChannelIoSessionConfig(); sessionConfig.setReuseAddress(true); - acceptor = new NioDatagramChannelIoAcceptor(sessionConfig); + NioWorkerPool workerPool = new NioWorkerPool(newCachedThreadPool(), 4); + NioServerDatagramChannelFactory channelFactory = new NioServerDatagramChannelFactory(newCachedThreadPool(), 1, workerPool); + acceptor = new NioDatagramChannelIoAcceptor(sessionConfig, channelFactory); acceptor.getFilterChain().addLast("logger", new LoggingFilter()); socket = new DatagramSocket(); socket.setReuseAddress(true); diff --git a/mina.netty/src/test/java/org/kaazing/mina/netty/NioDatagramChannelIoConnectorIT.java b/mina.netty/src/test/java/org/kaazing/mina/netty/NioDatagramChannelIoConnectorIT.java index f4544d3e37..f8d01985d5 100644 --- a/mina.netty/src/test/java/org/kaazing/mina/netty/NioDatagramChannelIoConnectorIT.java +++ b/mina.netty/src/test/java/org/kaazing/mina/netty/NioDatagramChannelIoConnectorIT.java @@ -15,12 +15,15 @@ */ package org.kaazing.mina.netty; +import static java.util.concurrent.Executors.newCachedThreadPool; import static org.kaazing.mina.netty.PortUtil.nextPort; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.InputStream; import java.io.OutputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; @@ -39,6 +42,9 @@ import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.logging.LoggingFilter; +import org.jboss.netty.channel.socket.nio.NioClientDatagramChannelFactory; +import org.jboss.netty.channel.socket.nio.NioServerDatagramChannelFactory; +import org.jboss.netty.channel.socket.nio.NioWorkerPool; import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -63,7 +69,9 @@ public void initAcceptor() throws Exception { executor = Executors.newFixedThreadPool(1); acceptor = new ServerSocket(); acceptor.setReuseAddress(true); - connector = new NioDatagramChannelIoConnector(new DefaultDatagramChannelIoSessionConfig()); + NioWorkerPool workerPool = new NioWorkerPool(newCachedThreadPool(), 4); + NioClientDatagramChannelFactory channelFactory = new NioClientDatagramChannelFactory(workerPool); + connector = new NioDatagramChannelIoConnector(new DefaultDatagramChannelIoSessionConfig(), channelFactory); connector.getFilterChain().addLast("logger", new LoggingFilter()); } diff --git a/pom.xml b/pom.xml index b8ad43ab1b..ea4a736f8f 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 1.8 1.8 1.9.4.8 - 3.0.0-alpha-41 + 3.0.0-alpha-45 1.1 2.6.0 1.7.21 @@ -75,7 +75,6 @@ service/http.proxy service/proxy service/turn.rest - service/turn.proxy service/update.check service/update.check.management transport/spi @@ -227,6 +226,12 @@ ${k3po.version} test + + org.kaazing + netx.data + 0.4.0 + test + @@ -466,7 +471,6 @@ validate - true diff --git a/resource.address/http/src/main/java/org/kaazing/gateway/resource/address/http/HttpResourceAddress.java b/resource.address/http/src/main/java/org/kaazing/gateway/resource/address/http/HttpResourceAddress.java index a5dac8b9a2..06cc94cbd8 100644 --- a/resource.address/http/src/main/java/org/kaazing/gateway/resource/address/http/HttpResourceAddress.java +++ b/resource.address/http/src/main/java/org/kaazing/gateway/resource/address/http/HttpResourceAddress.java @@ -66,6 +66,8 @@ public final class HttpResourceAddress extends ResourceAddress { public static final HttpResourceOption SERVER_HEADER_ENABLED = new HttpServerHeaderOption(); public static final HttpResourceOption>> REALM_USER_PRINCIPAL_CLASSES = new HttpRealmAuthenticationUserPrincipalClassesOption(); + public static final ResourceOption MAX_AUTHENTICATION_ATTEMPTS = new MaxAuthenticationAttemptsOption(); + private Boolean serverHeaderEnabled = SERVER_HEADER_ENABLED.defaultValue(); private Boolean keepAlive = KEEP_ALIVE.defaultValue(); private Integer httpMaxRedirects = MAXIMUM_REDIRECTS.defaultValue(); @@ -85,6 +87,8 @@ public final class HttpResourceAddress extends ResourceAddress { private File tempDirectory; private GatewayHttpOriginSecurity gatewayOriginSecurity; private Collection balanceOrigins; + private Integer maxAuthenticationAttempts; + private String authenticationConnect; private String authenticationIdentifier; @@ -93,6 +97,7 @@ public final class HttpResourceAddress extends ResourceAddress { private Collection> realmUserPrincipalClasses; + HttpResourceAddress(ResourceAddressFactorySpi factory, String original, URI resource) { super(factory, original, resource); } @@ -149,6 +154,8 @@ protected V getOption0(ResourceOption option) { return (V) serviceDomain; case SERVER_HEADER: return (V) serverHeaderEnabled; + case MAX_AUTHENTICATION_ATTEMPTS: + return (V) maxAuthenticationAttempts; case REALM_USER_PRINCIPAL_CLASSES: return (V) realmUserPrincipalClasses; } @@ -235,6 +242,9 @@ protected void setOption0(ResourceOption option, V value) { case REALM_USER_PRINCIPAL_CLASSES: realmUserPrincipalClasses = (Collection>) value; return; + case MAX_AUTHENTICATION_ATTEMPTS: + maxAuthenticationAttempts = (Integer) value; + return; } } @@ -259,7 +269,7 @@ protected enum Kind { KEEP_ALIVE, KEEP_ALIVE_TIMEOUT, KEEP_ALIVE_CONNECTIONS, RE LOGIN_CONTEXT_FACTORY, INJECTABLE_HEADERS, ORIGIN_SECURITY, TEMP_DIRECTORY, GATEWAY_ORIGIN_SECURITY, BALANCE_ORIGINS, AUTHENTICATION_CONNECT, AUTHENTICATION_IDENTIFIER, ENCRYPTION_KEY_ALIAS, SERVICE_DOMAIN, SERVER_HEADER, - REALM_USER_PRINCIPAL_CLASSES ,MAX_REDIRECTS + REALM_USER_PRINCIPAL_CLASSES ,MAX_REDIRECTS, MAX_AUTHENTICATION_ATTEMPTS; } private static final Map> OPTION_NAMES = new HashMap<>(); @@ -287,6 +297,7 @@ private HttpKeepAliveConnectionsOption() { super(Kind.KEEP_ALIVE_CONNECTIONS, "keepalive.connections", DEFAULT_HTTP_KEEPALIVE_CONNECTIONS); } } + private static final class HttpMaxRedirectOption extends HttpResourceOption { private HttpMaxRedirectOption() { super(Kind.MAX_REDIRECTS, "maximum.redirects", 0); @@ -420,5 +431,11 @@ private HttpRealmAuthenticationUserPrincipalClassesOption() { super(Kind.REALM_USER_PRINCIPAL_CLASSES, "realmAuthenticationUserPrincipalClasses", new ArrayList<>()); } } + + private static final class MaxAuthenticationAttemptsOption extends HttpResourceOption { + private MaxAuthenticationAttemptsOption() { + super(Kind.MAX_AUTHENTICATION_ATTEMPTS, "max.authentication.attempts", 0); + } + } } diff --git a/resource.address/http/src/main/java/org/kaazing/gateway/resource/address/http/HttpResourceAddressFactorySpi.java b/resource.address/http/src/main/java/org/kaazing/gateway/resource/address/http/HttpResourceAddressFactorySpi.java index ad5973b7df..c5c5a5d6a6 100644 --- a/resource.address/http/src/main/java/org/kaazing/gateway/resource/address/http/HttpResourceAddressFactorySpi.java +++ b/resource.address/http/src/main/java/org/kaazing/gateway/resource/address/http/HttpResourceAddressFactorySpi.java @@ -27,9 +27,10 @@ import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.INJECTABLE_HEADERS; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.KEEP_ALIVE; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.KEEP_ALIVE_CONNECTIONS; -import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.MAXIMUM_REDIRECTS; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.KEEP_ALIVE_TIMEOUT; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.LOGIN_CONTEXT_FACTORY; +import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.MAXIMUM_REDIRECTS; +import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.MAX_AUTHENTICATION_ATTEMPTS; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.ORIGIN_SECURITY; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.REALM_AUTHENTICATION_COOKIE_NAMES; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.REALM_AUTHENTICATION_HEADER_NAMES; @@ -48,7 +49,6 @@ import java.io.File; import java.net.URI; import java.security.Principal; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -76,7 +76,7 @@ public class HttpResourceAddressFactorySpi extends ResourceAddressFactorySpi> RESOURCE_FACTORIES_BY_KEY = new HashMap<>(); - + static { // go backwards so we can set alternate addresses correctly List insecureAlternateResourceFactories = Collections.singletonList( @@ -243,6 +243,14 @@ protected void parseNamedOptions0(String location, ResourceOptions options, options.setOption(REALM_USER_PRINCIPAL_CLASSES, realmUserPrincipalClasses); } + Object maxAuthenticationAttempts = optionsByName.remove(MAX_AUTHENTICATION_ATTEMPTS.name()); + if (maxAuthenticationAttempts != null) { + if (maxAuthenticationAttempts instanceof String) { + maxAuthenticationAttempts = Integer.parseInt((String) maxAuthenticationAttempts); + } + options.setOption(MAX_AUTHENTICATION_ATTEMPTS, (Integer) maxAuthenticationAttempts); + } + IdentityResolver httpIdentityResolver = (IdentityResolver) optionsByName.remove(IDENTITY_RESOLVER.name()); if (httpIdentityResolver != null) { options.setOption(IDENTITY_RESOLVER, httpIdentityResolver); @@ -340,6 +348,7 @@ protected void setOptions(HttpResourceAddress address, ResourceOptions options, address.setOption0(SERVICE_DOMAIN, options.getOption(SERVICE_DOMAIN)); address.setOption0(SERVER_HEADER_ENABLED, options.getOption(SERVER_HEADER_ENABLED)); address.setOption0(REALM_USER_PRINCIPAL_CLASSES, options.getOption(REALM_USER_PRINCIPAL_CLASSES)); + address.setOption0(MAX_AUTHENTICATION_ATTEMPTS, options.getOption(MAX_AUTHENTICATION_ATTEMPTS)); if (address.getOption(IDENTITY_RESOLVER) == null) { Collection> realmUserPrincipalClasses = address.getOption(REALM_USER_PRINCIPAL_CLASSES); if (realmUserPrincipalClasses != null && realmUserPrincipalClasses.size() > 0) { diff --git a/resource.address/http/src/test/java/org/kaazing/gateway/resource/address/http/HttpResourceAddressFactorySpiTest.java b/resource.address/http/src/test/java/org/kaazing/gateway/resource/address/http/HttpResourceAddressFactorySpiTest.java index 9f44db2eed..0d447c9771 100644 --- a/resource.address/http/src/test/java/org/kaazing/gateway/resource/address/http/HttpResourceAddressFactorySpiTest.java +++ b/resource.address/http/src/test/java/org/kaazing/gateway/resource/address/http/HttpResourceAddressFactorySpiTest.java @@ -32,6 +32,7 @@ import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.KEEP_ALIVE_TIMEOUT; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.LOGIN_CONTEXT_FACTORY; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.MAXIMUM_REDIRECTS; +import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.MAX_AUTHENTICATION_ATTEMPTS; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.REALM_AUTHENTICATION_COOKIE_NAMES; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.REALM_AUTHENTICATION_HEADER_NAMES; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.REALM_AUTHENTICATION_PARAMETER_NAMES; @@ -95,6 +96,7 @@ public void before() { options.put("http.realmAuthenticationCookieNames",new String[] {"c1", "c2"}); options.put("http.loginContextFactory", loginContextFactory); options.put("http.serverHeaderEnabled", Boolean.FALSE); + options.put("http.max.authentication.attempts", 5); } @@ -139,6 +141,7 @@ public void shouldCreateAddressWithDefaultOptions() throws Exception { assertEmpty(address.getOption(REALM_AUTHENTICATION_COOKIE_NAMES)); assertNull(address.getOption(LOGIN_CONTEXT_FACTORY)); assertTrue(address.getOption(SERVER_HEADER_ENABLED)); + assertEquals(new Integer(0), address.getOption(MAX_AUTHENTICATION_ATTEMPTS)); } @Test @@ -162,6 +165,7 @@ public void shouldCreateAddressWithOptions() { assertArrayEquals(new String[]{"c1", "c2"}, address.getOption(REALM_AUTHENTICATION_COOKIE_NAMES)); assertEquals(loginContextFactory, address.getOption(LOGIN_CONTEXT_FACTORY)); assertFalse(address.getOption(SERVER_HEADER_ENABLED)); + assertEquals(new Integer(5), address.getOption(MAX_AUTHENTICATION_ATTEMPTS)); } @Test diff --git a/security/pom.xml b/security/pom.xml index 18b4f56dc8..7303fa427d 100644 --- a/security/pom.xml +++ b/security/pom.xml @@ -51,37 +51,4 @@ - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.11 - - - validate - validate - - UTF-8 - - true - true - true - - - check - - - - - - org.kaazing - code.quality - 1.1 - - - - - diff --git a/security/src/test/java/org/kaazing/gateway/security/auth/SimpleTestLoginModule.java b/security/src/test/java/org/kaazing/gateway/security/auth/SimpleTestLoginModule.java index 754da29f38..8679c2bec0 100644 --- a/security/src/test/java/org/kaazing/gateway/security/auth/SimpleTestLoginModule.java +++ b/security/src/test/java/org/kaazing/gateway/security/auth/SimpleTestLoginModule.java @@ -33,13 +33,12 @@ protected boolean doLogin() { try { handler.handle(new Callback[] {atc}); } catch (IOException e) { - // TODO: log exception - return false; - } - catch (UnsupportedCallbackException e) { - // TODO: log exception - return false; - } + // TODO: log exception + return false; + } catch (UnsupportedCallbackException e) { + // TODO: log exception + return false; + } String up = atc.getAuthenticationToken().get(); String name = up.substring(0, up.indexOf(':')); diff --git a/server.api/pom.xml b/server.api/pom.xml index 06ad602c62..6687b49e8a 100644 --- a/server.api/pom.xml +++ b/server.api/pom.xml @@ -36,38 +36,8 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.11 - - - validate - validate - - UTF-8 - - true - true - true - - - check - - - - - - org.kaazing - code.quality - 1.1 - - - maven-javadoc-plugin - 2.8.1 attach-javadocs diff --git a/server.api/src/main/java/org/kaazing/gateway/server/ExpiringState.java b/server.api/src/main/java/org/kaazing/gateway/server/ExpiringState.java new file mode 100644 index 0000000000..cb7552fdba --- /dev/null +++ b/server.api/src/main/java/org/kaazing/gateway/server/ExpiringState.java @@ -0,0 +1,57 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.server; + +import java.util.concurrent.TimeUnit; + +/** + * ExpiringState is accessible from the @javax.security.auth.spi.LoginModule + * via the Map options, under the name "ExpiringState". + * + */ +public interface ExpiringState { + + /** + * Puts an entry into this map with a given ttl (time to live) value + * if the specified key is not already associated with a value. + * Entry will expire and get evicted after the ttl. + * + * @param key key of the entry + * @param value value of the entry + * @param ttl maximum time for this entry to stay in the map + * @param timeunit time unit for the ttl + * @return null if absent, or returns the old value of the entry + */ + Object putIfAbsent(String key, Object value, long ttl, TimeUnit timeunit); + + /** + * Gets the given key. + * + * @param key of the entry + * @return value of the entry, or null + */ + Object get(String key); + + /** + * Removes the given key. + * + * @param key The key of the map entry to remove. + * @return A {@link java.util.concurrent.Future} from which the value + * removed from the map can be retrieved. + */ + Object remove(String key, Object value); +} diff --git a/server/src/main/java/org/kaazing/gateway/server/context/resolve/DefaultExpiringState.java b/server/src/main/java/org/kaazing/gateway/server/context/resolve/DefaultExpiringState.java new file mode 100644 index 0000000000..60fef83a07 --- /dev/null +++ b/server/src/main/java/org/kaazing/gateway/server/context/resolve/DefaultExpiringState.java @@ -0,0 +1,47 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.server.context.resolve; + +import java.util.concurrent.TimeUnit; + +import org.kaazing.gateway.server.ExpiringState; +import org.kaazing.gateway.service.messaging.collections.CollectionsFactory; + +import com.hazelcast.core.IMap; + +final class DefaultExpiringState implements ExpiringState { + private final IMap delegate; + + DefaultExpiringState(CollectionsFactory collectionsFactory, String expiringStateName) { + this.delegate = collectionsFactory.getMap(expiringStateName); + } + + @Override + public Object putIfAbsent(String key, Object value, long ttl, TimeUnit timeunit) { + return delegate.putIfAbsent(key, value); + } + + @Override + public Object get(String key) { + return delegate.get(key); + } + + @Override + public Object remove(String key, Object value) { + return delegate.remove(key, value); + } +} diff --git a/server/src/main/java/org/kaazing/gateway/server/context/resolve/GatewayContextResolver.java b/server/src/main/java/org/kaazing/gateway/server/context/resolve/GatewayContextResolver.java index 73e58c4faf..7ab4fd5333 100644 --- a/server/src/main/java/org/kaazing/gateway/server/context/resolve/GatewayContextResolver.java +++ b/server/src/main/java/org/kaazing/gateway/server/context/resolve/GatewayContextResolver.java @@ -25,6 +25,7 @@ import static org.kaazing.gateway.resource.address.uri.URIUtils.getScheme; import static org.kaazing.gateway.resource.address.uri.URIUtils.getUserInfo; import static org.kaazing.gateway.service.util.ServiceUtils.LIST_SEPARATOR; +import static org.kaazing.gateway.util.feature.EarlyAccessFeatures.LOGIN_MODULE_EXPIRING_STATE; import java.io.File; import java.lang.reflect.Method; @@ -221,12 +222,11 @@ public GatewayContext resolve(GatewayConfigDocument gatewayConfigDoc, Properties ClusterType[] clusterConfigs = gatewayConfig.getClusterArray(); ClusterType clusterConfig = (clusterConfigs.length > 0) ? clusterConfigs[clusterConfigs.length - 1] : null; - DefaultSecurityContext securityContext = securityResolver.resolve(securityConfig); - RealmsContext realmsContext = resolveRealms(securityConfig, securityContext, configuration); - DefaultServiceDefaultsContext serviceDefaultsContext = resolveServiceDefaults(serviceDefaults); - // Map sessionContexts = resolveSessions(sessionConfigs, securityContext, realmsContext); SchedulerProvider schedulerProvider = new SchedulerProvider(configuration); ClusterContext clusterContext = resolveCluster(clusterConfig, schedulerProvider); + DefaultSecurityContext securityContext = securityResolver.resolve(securityConfig); + RealmsContext realmsContext = resolveRealms(securityConfig, securityContext, configuration, clusterContext); + DefaultServiceDefaultsContext serviceDefaultsContext = resolveServiceDefaults(serviceDefaults); ServiceRegistry servicesByURI = new ServiceRegistry(); Map dependencyContexts = resolveDependencyContext(); ResourceAddressFactory resourceAddressFactory = resolveResourceAddressFactories(); @@ -937,7 +937,8 @@ private void validateAwsClusterDiscovery(String uri, } - private RealmsContext resolveRealms(SecurityType securityConfig, SecurityContext securityContext, Properties configuration) { + private RealmsContext resolveRealms(SecurityType securityConfig, SecurityContext securityContext, Properties configuration, + ClusterContext clusterContext) { Map realmContexts = new HashMap<>(); if (securityConfig != null) { @@ -976,12 +977,17 @@ private RealmsContext resolveRealms(SecurityType securityConfig, SecurityContext for (LoginModuleType loginModule : loginModulesArray) { String type = loginModule.getType(); String success = loginModule.getSuccess().toString(); - Map options = new HashMap<>(); + Map options = new HashMap<>(); // add the GATEWAY_CONFIG_DIRECTORY to the options so it can be used from various login modules // (see FileLoginModule for an example) options.put(Gateway.GATEWAY_CONFIG_DIRECTORY_PROPERTY, configuration .getProperty(Gateway.GATEWAY_CONFIG_DIRECTORY_PROPERTY)); + if (LOGIN_MODULE_EXPIRING_STATE.isEnabled(configuration)) { + final String expiringStateName = "ExpiringState"; + options.put(expiringStateName, + new DefaultExpiringState(clusterContext.getCollectionsFactory(), expiringStateName)); + } LoginModuleOptionsType rawOptions = loginModule.getOptions(); if (rawOptions != null) { @@ -1084,14 +1090,6 @@ private String resolveTimeIntervalValue(String value) { return String.valueOf(l); } - private String resolveAuthorizationMode(AuthenticationType.AuthorizationMode.Enum authorizationMode) { - if (authorizationMode == null) { - return AUTHORIZATION_MODE_CHALLENGE; - } else { - return authorizationMode.toString(); - } - } - // NOTE: Code between the previous and next methods was moved from here to SecurityContextResolver // (any changes pulled in from merges should be applied to that class) diff --git a/server/src/main/xsd/gateway-config-201606.xsd b/server/src/main/xsd/gateway-config-201606.xsd index 0370653ab7..8665fbfdeb 100644 --- a/server/src/main/xsd/gateway-config-201606.xsd +++ b/server/src/main/xsd/gateway-config-201606.xsd @@ -1168,76 +1168,6 @@ - - - - This service implements a proxy for STUN/TURN protcols. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The maximum redirects http connector follows + + + The number of attempts that the http connector + will try while authenticating (i.e responding to a 401) + + + @@ -1725,64 +1662,6 @@ - - - - Properties accepted by the turn.proxy service configuration. - - - - - - - - The alias in the certificate containing the shared secret - - - - - - The algorithm used for the HMAC computation of the password using the username and the - shared secret. If missing the default value is HmacSHA1. - - - - - - - Address and port that will override MAPPED-ADDRESS attribute and xored with the magic - cookie the attribute XOR-MAPPED-ADDRESS. - - The address can also be extracted from the source ip and port. For this, use the value - 'AUTO' in this property. - - Makes key.alias property mandatory. - - - - - - - If defined, will be used to mask the outgoing XOR-RELAYED-ADDRESS and unmask the - XOR-PEER-ADDRESS in order to protect the Turn server's IP addresses from exposure. - - Format: "0x%08X" - - Address is IPv4:PORT the mask will be performed by XOR-ing each byte of the expression with - each byte of the address. For IPv6:PORT the mask will be repeated 4 times and each byte - of the resulting expression will be xored with each bye of the address. - - The port will be xored with the first two bytes. - - Makes key.alias property mandatory. - - - - - - - - diff --git a/server/src/test/java/org/kaazing/gateway/server/context/resolve/AcceptOptionsTest.java b/server/src/test/java/org/kaazing/gateway/server/context/resolve/AcceptOptionsTest.java index 395440702f..fc37148e61 100644 --- a/server/src/test/java/org/kaazing/gateway/server/context/resolve/AcceptOptionsTest.java +++ b/server/src/test/java/org/kaazing/gateway/server/context/resolve/AcceptOptionsTest.java @@ -136,7 +136,7 @@ public void testWsInactivityTimeout() throws Exception { expectSuccess("ws.inactivity.timeout", "60", "ws.inactivityTimeout", 60000L, "http[http/1.1].keepAliveTimeout", 60); expectSuccess("ws.inactivity.timeout", "60s", "ws.inactivityTimeout", 60000L, "http[http/1.1].keepAliveTimeout", 60); //https://github.com/kaazing/gateway/issues/595 - //expectSuccess("ws.inactivity.timeout", "60000ms", "ws.inactivityTimeout", 60000L, "http[http/1.1].keepAliveTimeout", 60); + expectSuccess("ws.inactivity.timeout", "60000ms", "ws.inactivityTimeout", 60000L, "http[http/1.1].keepAliveTimeout", 60); } diff --git a/server/src/test/java/org/kaazing/gateway/server/util/ParseTimeIntervalTest.java b/server/src/test/java/org/kaazing/gateway/server/util/ParseTimeIntervalTest.java index 2809dbfc94..2f81dde1a2 100644 --- a/server/src/test/java/org/kaazing/gateway/server/util/ParseTimeIntervalTest.java +++ b/server/src/test/java/org/kaazing/gateway/server/util/ParseTimeIntervalTest.java @@ -26,6 +26,9 @@ public class ParseTimeIntervalTest { + private static final int NO_OF_DAYS_IN_A_YEAR = 365; + private static final int NO_OF_DAYS_IN_A_WEEK = 7; + @Test public void testNullInputAndUnit() throws Exception { assertEquals(0, Utils.parseTimeInterval(null, null, 0)); @@ -139,6 +142,81 @@ public void testHours() throws Exception { assertEquals(10, Utils.parseTimeInterval("10h", TimeUnit.HOURS, 0)); } + @Test + public void testDays() throws Exception { + assertEquals(0, Utils.parseTimeInterval("10", TimeUnit.DAYS, 0)); + assertEquals(10, Utils.parseTimeInterval("10d", TimeUnit.DAYS, 0)); + assertEquals(10, Utils.parseTimeInterval("10day", TimeUnit.DAYS, 0)); + assertEquals(10, Utils.parseTimeInterval("10days", TimeUnit.DAYS, 0)); + } + + @Test + public void testWeeks() throws Exception { + assertEquals(NO_OF_DAYS_IN_A_WEEK * 0, Utils.parseTimeInterval("10", TimeUnit.DAYS, 0)); + assertEquals(NO_OF_DAYS_IN_A_WEEK * 10, Utils.parseTimeInterval("10w", TimeUnit.DAYS, 0)); + assertEquals(NO_OF_DAYS_IN_A_WEEK * 10, Utils.parseTimeInterval("10week", TimeUnit.DAYS, 0)); + assertEquals(NO_OF_DAYS_IN_A_WEEK * 10, Utils.parseTimeInterval("10weeks", TimeUnit.DAYS, 0)); + } + + @Test + public void testYears() throws Exception { + assertEquals(NO_OF_DAYS_IN_A_YEAR * 0, Utils.parseTimeInterval("10", TimeUnit.DAYS, 0)); + assertEquals(NO_OF_DAYS_IN_A_YEAR * 10, Utils.parseTimeInterval("10y", TimeUnit.DAYS, 0)); + assertEquals(NO_OF_DAYS_IN_A_YEAR * 10, Utils.parseTimeInterval("10year", TimeUnit.DAYS, 0)); + assertEquals(NO_OF_DAYS_IN_A_YEAR * 10, Utils.parseTimeInterval("10years", TimeUnit.DAYS, 0)); + } + + @Test + public void testCastMilliseconds() throws Exception { + assertEquals(604800000, Utils.parseTimeInterval("604800000ms", TimeUnit.MILLISECONDS, 0)); + assertEquals(604800, Utils.parseTimeInterval("604800000ms", TimeUnit.SECONDS, 0)); + assertEquals(10080, Utils.parseTimeInterval("604800000ms", TimeUnit.MINUTES, 0)); + assertEquals(168, Utils.parseTimeInterval("604800000ms", TimeUnit.HOURS, 0)); + assertEquals(NO_OF_DAYS_IN_A_WEEK, Utils.parseTimeInterval("604800000ms", TimeUnit.DAYS, 0)); + } + + @Test + public void testCastSeconds() throws Exception { + assertEquals(31536000, Utils.parseTimeInterval("31536000s", TimeUnit.SECONDS, 0)); + assertEquals(525600, Utils.parseTimeInterval("31536000s", TimeUnit.MINUTES, 0)); + assertEquals(8760, Utils.parseTimeInterval("31536000s", TimeUnit.HOURS, 0)); + assertEquals(NO_OF_DAYS_IN_A_YEAR, Utils.parseTimeInterval("31536000s", TimeUnit.DAYS, 0)); + assertEquals(0, Utils.parseTimeInterval("1s", TimeUnit.MINUTES, 0)); + assertEquals(0, Utils.parseTimeInterval("3599s", TimeUnit.HOURS, 0)); + } + + @Test + public void testCastHours() throws Exception { + assertEquals(3600, Utils.parseTimeInterval("1h", TimeUnit.SECONDS, 0)); + assertEquals(60, Utils.parseTimeInterval("1h", TimeUnit.MINUTES, 0)); + assertEquals(1, Utils.parseTimeInterval("1h", TimeUnit.HOURS, 0)); + assertEquals(0, Utils.parseTimeInterval("1h", TimeUnit.DAYS, 0)); + } + + @Test + public void testCastDays() throws Exception { + assertEquals(86400, Utils.parseTimeInterval("1d", TimeUnit.SECONDS, 0)); + assertEquals(1440, Utils.parseTimeInterval("1d", TimeUnit.MINUTES, 0)); + assertEquals(24, Utils.parseTimeInterval("1d", TimeUnit.HOURS, 0)); + assertEquals(1, Utils.parseTimeInterval("1d", TimeUnit.DAYS, 0)); + } + + @Test + public void testCastWeeks() throws Exception { + assertEquals(604800, Utils.parseTimeInterval("1week", TimeUnit.SECONDS, 0)); + assertEquals(10080, Utils.parseTimeInterval("1week", TimeUnit.MINUTES, 0)); + assertEquals(168, Utils.parseTimeInterval("1week", TimeUnit.HOURS, 0)); + assertEquals(NO_OF_DAYS_IN_A_WEEK, Utils.parseTimeInterval("1week", TimeUnit.DAYS, 0)); + } + + @Test + public void testCastYears() throws Exception { + assertEquals(31536000, Utils.parseTimeInterval("1year", TimeUnit.SECONDS, 0)); + assertEquals(525600, Utils.parseTimeInterval("1year", TimeUnit.MINUTES, 0)); + assertEquals(8760, Utils.parseTimeInterval("1year", TimeUnit.HOURS, 0)); + assertEquals(NO_OF_DAYS_IN_A_YEAR, Utils.parseTimeInterval("1year", TimeUnit.DAYS, 0)); + } + @Test @Ignore // KG-6969: currently rounds down to 0 public void fractionalTimeIntervalSameOutputUnit() { diff --git a/service/http.directory/src/test/java/org/kaazing/gateway/service/http/directory/HttpDirectoryServiceIT.java b/service/http.directory/src/test/java/org/kaazing/gateway/service/http/directory/HttpDirectoryServiceIT.java index daed03e557..8c1c420282 100644 --- a/service/http.directory/src/test/java/org/kaazing/gateway/service/http/directory/HttpDirectoryServiceIT.java +++ b/service/http.directory/src/test/java/org/kaazing/gateway/service/http/directory/HttpDirectoryServiceIT.java @@ -18,12 +18,7 @@ import static org.kaazing.test.util.ITUtil.createRuleChain; import java.io.File; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.AfterClass; -import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -45,19 +40,6 @@ public class HttpDirectoryServiceIT { private static final String DIRECTORY_SERVICE_DOT_SLASH = "http://localhost:8006/"; private static final String DIRECTORY_SERVICE_NO_PATH = "http://localhost:8007/"; private static final String DIRECTORY_SERVICE_WRONG_PATH = "http://localhost:8008/"; - private static final String DIRECTORY_SERVICE_SYMLINK_INSIDE_FILE_FOLLOW = "http://localhost:8011/"; - private static final String DIRECTORY_SERVICE_SYMLINK_INSIDE_FILE_RESTRICTED = "http://localhost:8012/"; - private static final String DIRECTORY_SERVICE_SYMLINK_OUTSIDE_FILE_FOLLOW = "http://localhost:8015/"; - private static final String DIRECTORY_SERVICE_SYMLINK_OUTSIDE_FILE_RESTRICTED = "http://localhost:8016/"; - - private static final String BASE_DIR = "src/test/webapp/"; - private static final String LINK_INSIDE_FILE = "/public/indexSymInside.html"; - private static final String TARGET_INSIDE_FILE = "/public/insideDir/index.html"; - private static final String LINK_OUTSIDE_FILE = "/public/indexSymOutside.html"; - private static final String TARGET_OUTSIDE_FILE = "/outsideDir/index.html"; - - private static Path fileSymPathOutside = null; - private static Path fileSymPathInside = null; private final K3poRule robot = new K3poRule(); @@ -128,33 +110,6 @@ public class HttpDirectoryServiceIT { .property("directory", ".public") .property("welcome-file", "index.html") .done() - .service() - .accept(DIRECTORY_SERVICE_SYMLINK_INSIDE_FILE_FOLLOW) - .type("directory") - .property("directory", "/public/") - .property("welcome-file", "indexSymInside.html") - .property("symbolic-links", "follow") - .done() - .service() - .accept(DIRECTORY_SERVICE_SYMLINK_INSIDE_FILE_RESTRICTED) - .type("directory") - .property("directory", "/public/") - .property("welcome-file", "indexSymInside.html") - .property("symbolic-links", "restricted") - .done() - .service() - .accept(DIRECTORY_SERVICE_SYMLINK_OUTSIDE_FILE_FOLLOW) - .type("directory") - .property("directory", "/public/") - .property("welcome-file", "indexSymOutside.html") - .property("symbolic-links", "follow") - .done() - .service() - .accept(DIRECTORY_SERVICE_SYMLINK_OUTSIDE_FILE_RESTRICTED) - .type("directory") - .property("directory", "/public/") - .property("welcome-file", "indexSymOutside.html") - .done() .done(); // @formatter:on init(configuration); @@ -164,34 +119,6 @@ public class HttpDirectoryServiceIT { @Rule public TestRule chain = createRuleChain(gateway, robot); - @BeforeClass - public static void createResourcesBefore() { - fileSymPathInside = createSymlinkResources(LINK_INSIDE_FILE, TARGET_INSIDE_FILE); - fileSymPathOutside = createSymlinkResources(LINK_OUTSIDE_FILE, TARGET_OUTSIDE_FILE); - } - - private static Path createSymlinkResources(String linkPath, String targetPath) { - try { - Path newLink = new File(BASE_DIR, linkPath).getCanonicalFile().toPath(); - Path target = new File(BASE_DIR, targetPath).getCanonicalFile().toPath(); - return Files.createSymbolicLink(newLink, target); - } catch (FileAlreadyExistsException existsException) { - return new File(existsException.getFile()).toPath(); - } catch (Exception ex) { - return null; - } - } - - @AfterClass - public static void cleanResourcesAfter() { - try { - Files.delete(fileSymPathOutside); - Files.delete(fileSymPathInside); - } catch (Exception x) { - //no op - } - } - @Specification("get.index.check.status.code.200") @Test public void testGetIndexAndStatusCode200() throws Exception { @@ -472,29 +399,4 @@ public void shouldFindRootWithNoPathInDirectory() throws Exception { public void shouldNotFindResourceWithWrongPathInDirectory() throws Exception { robot.finish(); } - - @Specification("directory.service.symlink.follow.inside.file") - @Test - public void shouldFollowSymLinksInsideBaseFolder() throws Exception { - robot.finish(); - } - - @Specification("directory.service.symlink.restricted.inside.file") - @Test - public void shouldFollowSymLinksIfRestrictedInsideBaseFolder() throws Exception { - robot.finish(); - } - - @Specification("directory.service.symlink.follow.outside.file") - @Test - public void shouldFollowSymLinksOutsideBaseFolder() throws Exception { - robot.finish(); - } - - @Specification("directory.service.symlink.restricted.outside.file") - @Test - public void shouldNotFollowSymLinksIfRestrictedOutsideBaseFolder() throws Exception { - robot.finish(); - } - } diff --git a/service/http.directory/src/test/java/org/kaazing/gateway/service/http/directory/HttpDirectoryServiceSymlinkIT.java b/service/http.directory/src/test/java/org/kaazing/gateway/service/http/directory/HttpDirectoryServiceSymlinkIT.java new file mode 100644 index 0000000000..ffe7f46e97 --- /dev/null +++ b/service/http.directory/src/test/java/org/kaazing/gateway/service/http/directory/HttpDirectoryServiceSymlinkIT.java @@ -0,0 +1,153 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.service.http.directory; + +import static org.kaazing.test.util.ITUtil.createRuleChain; + +import java.io.File; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystemException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.kaazing.gateway.server.test.GatewayRule; +import org.kaazing.gateway.server.test.config.GatewayConfiguration; +import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class HttpDirectoryServiceSymlinkIT { + + private static final String DIRECTORY_SERVICE_SYMLINK_INSIDE_FILE_FOLLOW = "http://localhost:8011/"; + private static final String DIRECTORY_SERVICE_SYMLINK_INSIDE_FILE_RESTRICTED = "http://localhost:8012/"; + private static final String DIRECTORY_SERVICE_SYMLINK_OUTSIDE_FILE_FOLLOW = "http://localhost:8015/"; + private static final String DIRECTORY_SERVICE_SYMLINK_OUTSIDE_FILE_RESTRICTED = "http://localhost:8016/"; + + private static final String BASE_DIR = "src/test/webapp/"; + private static final String LINK_INSIDE_FILE = "/public/indexSymInside.html"; + private static final String TARGET_INSIDE_FILE = "/public/insideDir/index.html"; + private static final String LINK_OUTSIDE_FILE = "/public/indexSymOutside.html"; + private static final String TARGET_OUTSIDE_FILE = "/outsideDir/index.html"; + + private static Path fileSymPathOutside = null; + private static Path fileSymPathInside = null; + + private final K3poRule robot = new K3poRule(); + + private final GatewayRule gateway = new GatewayRule() { + { + // @formatter:off + GatewayConfiguration configuration = + new GatewayConfigurationBuilder() + .webRootDirectory(new File("src/test/webapp")) + .service() + .accept(DIRECTORY_SERVICE_SYMLINK_INSIDE_FILE_FOLLOW) + .type("directory") + .property("directory", "/public/") + .property("welcome-file", "indexSymInside.html") + .property("symbolic-links", "follow") + .done() + .service() + .accept(DIRECTORY_SERVICE_SYMLINK_INSIDE_FILE_RESTRICTED) + .type("directory") + .property("directory", "/public/") + .property("welcome-file", "indexSymInside.html") + .property("symbolic-links", "restricted") + .done() + .service() + .accept(DIRECTORY_SERVICE_SYMLINK_OUTSIDE_FILE_FOLLOW) + .type("directory") + .property("directory", "/public/") + .property("welcome-file", "indexSymOutside.html") + .property("symbolic-links", "follow") + .done() + .service() + .accept(DIRECTORY_SERVICE_SYMLINK_OUTSIDE_FILE_RESTRICTED) + .type("directory") + .property("directory", "/public/") + .property("welcome-file", "indexSymOutside.html") + .done() + .done(); + // @formatter:on + init(configuration); + } + }; + + @Rule + public TestRule chain = createRuleChain(gateway, robot); + + @BeforeClass + public static void createResourcesBefore() { + fileSymPathInside = createSymlinkResources(LINK_INSIDE_FILE, TARGET_INSIDE_FILE); + fileSymPathOutside = createSymlinkResources(LINK_OUTSIDE_FILE, TARGET_OUTSIDE_FILE); + } + + private static Path createSymlinkResources(String linkPath, String targetPath) { + try { + Path newLink = new File(BASE_DIR, linkPath).getCanonicalFile().toPath(); + Path target = new File(BASE_DIR, targetPath).getCanonicalFile().toPath(); + return Files.createSymbolicLink(newLink, target); + } catch (FileAlreadyExistsException existsException) { + return new File(existsException.getFile()).toPath(); + } catch (FileSystemException accessDeniedException) { + Assume.assumeTrue("User does not have OS access to create symlinks", false); + return null; + } catch (Exception ex) { + return null; + } + } + + @AfterClass + public static void cleanResourcesAfter() { + try { + Files.delete(fileSymPathOutside); + Files.delete(fileSymPathInside); + } catch (Exception x) { + // no op + } + } + + @Specification("directory.service.symlink.follow.inside.file") + @Test + public void shouldFollowSymLinksInsideBaseFolder() throws Exception { + robot.finish(); + } + + @Specification("directory.service.symlink.restricted.inside.file") + @Test + public void shouldFollowSymLinksIfRestrictedInsideBaseFolder() throws Exception { + robot.finish(); + } + + @Specification("directory.service.symlink.follow.outside.file") + @Test + public void shouldFollowSymLinksOutsideBaseFolder() throws Exception { + robot.finish(); + } + + @Specification("directory.service.symlink.restricted.outside.file") + @Test + public void shouldNotFollowSymLinksIfRestrictedOutsideBaseFolder() throws Exception { + robot.finish(); + } + +} diff --git a/service/http.proxy/src/main/java/org/kaazing/gateway/service/http/proxy/HttpProxyServiceHandler.java b/service/http.proxy/src/main/java/org/kaazing/gateway/service/http/proxy/HttpProxyServiceHandler.java index 4363cd37a9..0e3a2220c4 100644 --- a/service/http.proxy/src/main/java/org/kaazing/gateway/service/http/proxy/HttpProxyServiceHandler.java +++ b/service/http.proxy/src/main/java/org/kaazing/gateway/service/http/proxy/HttpProxyServiceHandler.java @@ -55,6 +55,7 @@ import org.kaazing.gateway.service.ServiceProperties; import org.kaazing.gateway.service.proxy.AbstractProxyAcceptHandler; import org.kaazing.gateway.service.proxy.AbstractProxyHandler; +import org.kaazing.gateway.transport.BridgeSession; import org.kaazing.gateway.transport.IoHandlerAdapter; import org.kaazing.gateway.transport.http.DefaultHttpSession; import org.kaazing.gateway.transport.http.HttpAcceptSession; @@ -107,6 +108,7 @@ class HttpProxyServiceHandler extends AbstractProxyAcceptHandler { private String connectURI; private String useForwarded; + private int remoteClientPort; private boolean rewriteCookieDomain; private boolean rewriteCookiePath; private boolean rewriteLocation; @@ -197,6 +199,9 @@ protected AbstractProxyHandler createConnectHandler() { @Override public void sessionOpened(IoSession session) { + // get the port number of the remote client + BridgeSession bridgeSession = (BridgeSession) session; + remoteClientPort = BridgeSession.REMOTE_ADDRESS.get(bridgeSession).getTransport().getResource().getPort(); if (!session.isClosing()) { final DefaultHttpSession acceptSession = (DefaultHttpSession) session; // final Subject subject = ((IoSessionEx) acceptSession).getSubject(); @@ -486,9 +491,9 @@ private void setupForwardedHeaders(HttpAcceptSession acceptSession, HttpConnectS } if (FORWARDED_INJECT.equalsIgnoreCase(useForwarded)) { - String remoteIpAddress = getResourceIpAddress(acceptSession, FORWARDED_FOR); - if (remoteIpAddress != null) { - connectSession.addWriteHeader(HEADER_X_FORWARDED_FOR, remoteIpAddress); + String remoteIpWithPort = format("%s:%d", getResourceIpAddress(acceptSession, FORWARDED_FOR), remoteClientPort); + if (remoteIpWithPort != null) { + connectSession.addWriteHeader(HEADER_X_FORWARDED_FOR, remoteIpWithPort); } String serverIpAddress = getResourceIpAddress(acceptSession, FORWARDED_BY); @@ -505,7 +510,7 @@ private void setupForwardedHeaders(HttpAcceptSession acceptSession, HttpConnectS connectSession.addWriteHeader(HEADER_X_FORWARDED_HOST, format("%s:%s", host, port)); connectSession.addWriteHeader(HEADER_FORWARDED, - format("%s=%s;%s=%s;%s=%s;%s=%s:%s", FORWARDED_FOR, remoteIpAddress, FORWARDED_BY, serverIpAddress, + format("%s=%s;%s=%s;%s=%s;%s=%s:%s", FORWARDED_FOR, remoteIpWithPort, FORWARDED_BY, serverIpAddress, FORWARDED_PROTO, protocol, FORWARDED_HOST, host, port)); } } diff --git a/service/http.proxy/src/test/scripts/org/kaazing/gateway/service/http/proxy/http.proxy.use.forwarded.headers.chained.proxies.rpt b/service/http.proxy/src/test/scripts/org/kaazing/gateway/service/http/proxy/http.proxy.use.forwarded.headers.chained.proxies.rpt index eaac066ee6..29071bf02b 100644 --- a/service/http.proxy/src/test/scripts/org/kaazing/gateway/service/http/proxy/http.proxy.use.forwarded.headers.chained.proxies.rpt +++ b/service/http.proxy/src/test/scripts/org/kaazing/gateway/service/http/proxy/http.proxy.use.forwarded.headers.chained.proxies.rpt @@ -47,8 +47,8 @@ read "X-Forwarded-Proto: http\r\n" read "X-Forwarded-Proto: http\r\n" read "X-Forwarded-Host: localhost:8112\r\n" read "X-Forwarded-Host: localhost:8113\r\n" -read "X-Forwarded-For: 127.0.0.1\r\n" -read "X-Forwarded-For: 127.0.0.1\r\n" +read "X-Forwarded-For: 127.0.0.1:" /[0-9]+/ "\r\n" +read "X-Forwarded-For: 127.0.0.1:" /[0-9]+/ "\r\n" /Via: 1.1 kaazing-.+/ "\r\n" /Via: 1.1 kaazing-.+/ "\r\n" /Via: 1.1 kaazing-.+/ "\r\n" @@ -56,8 +56,8 @@ read "X-Forwarded-For: 127.0.0.1\r\n" /Via: 1.1 kaazing-.+/ "\r\n" read "User-Agent: curl/7.37.1\r\n" read "Host: localhost:8081\r\n" -read "Forwarded: for=127.0.0.1;by=127.0.0.1;proto=http;host=localhost:8112\r\n" -read "Forwarded: for=127.0.0.1;by=127.0.0.1;proto=http;host=localhost:8113\r\n" +read "Forwarded: for=127.0.0.1:" /[0-9]+/ ";by=127.0.0.1;proto=http;host=localhost:8112\r\n" +read "Forwarded: for=127.0.0.1:" /[0-9]+/ ";by=127.0.0.1;proto=http;host=localhost:8113\r\n" read "Connection: close\r\n" read "\r\n" diff --git a/service/http.proxy/src/test/scripts/org/kaazing/gateway/service/http/proxy/http.proxy.use.forwarded.headers.rpt b/service/http.proxy/src/test/scripts/org/kaazing/gateway/service/http/proxy/http.proxy.use.forwarded.headers.rpt index d04c534434..a8434e5d4b 100644 --- a/service/http.proxy/src/test/scripts/org/kaazing/gateway/service/http/proxy/http.proxy.use.forwarded.headers.rpt +++ b/service/http.proxy/src/test/scripts/org/kaazing/gateway/service/http/proxy/http.proxy.use.forwarded.headers.rpt @@ -44,8 +44,8 @@ read method "GET" read header "Via" "1.1 kaazing" read header "User-Agent" "curl/7.37.1" read header "Host" "localhost:8080" -read header "Forwarded" /"for=127.0.0.1(:*)([0-9]*);by=127.0.0.1(:*)([0-9]*);proto=http;host=localhost:8110(.*)/ -read header "X-Forwarded-For" "127.0.0.1" +read header "Forwarded" /"for=127.0.0.1:".[0-9]+.";by=127.0.0.1;proto=http;host=localhost:8110"/ +read header "X-Forwarded-For" "127.0.0.1:" /[0-9]+/ read header "X-Forwarded-Server" "127.0.0.1" read header "X-Forwarded-Proto" "http" read header "X-Forwarded-Host" "localhost:8110" diff --git a/service/proxy/pom.xml b/service/proxy/pom.xml index a055e3d0d3..71e25d64f4 100644 --- a/service/proxy/pom.xml +++ b/service/proxy/pom.xml @@ -69,6 +69,10 @@ ${project.version} test + + org.kaazing + netx.data + diff --git a/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/ProxyServiceExtensionIT.java b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/ProxyServiceExtensionIT.java index 95e463c8c7..ec1cf2c9e2 100644 --- a/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/ProxyServiceExtensionIT.java +++ b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/ProxyServiceExtensionIT.java @@ -15,6 +15,8 @@ */ package org.kaazing.gateway.service.proxy; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -23,12 +25,25 @@ import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; import org.kaazing.k3po.junit.annotation.Specification; import org.kaazing.k3po.junit.rules.K3poRule; +import org.kaazing.netx.URLConnectionHelper; import org.kaazing.test.util.ITUtil; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import static java.util.stream.Collectors.joining; + public class ProxyServiceExtensionIT { - private K3poRule k3po = new K3poRule(); + private static ClassLoader classLoader; - public GatewayRule gateway = new GatewayRule() { + private final K3poRule k3po = new K3poRule(); + + private final GatewayRule gateway = new GatewayRule() { { GatewayConfiguration configuration = new GatewayConfigurationBuilder() .service() @@ -48,10 +63,47 @@ public class ProxyServiceExtensionIT { @Rule public TestRule chain = ITUtil.createRuleChain(gateway, k3po); + @BeforeClass + public static void before() throws Exception { + classLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(new TestClassLoader(TestExtension.class.getName())); + } + + @AfterClass + public static void after() { + Thread.currentThread().setContextClassLoader(classLoader); + } + @Specification("shouldInjectBytesBeforeForwardingMessages") @Test public void shouldInjectBytesBeforeForwardingMessages() throws Exception { k3po.finish(); } + /** + * A classloader whose getResources("META-INF/services/org.kaazing.gateway.service.proxy.ProxyServiceExtensionSpi") + * method will return a URL whose contents will be the list of class names supplied in the constructor. + * This avoids the need for test meta-info resources files to be available on the test class path. + */ + private static class TestClassLoader extends ClassLoader { + private final List urls; + + TestClassLoader(String... factorySpiClassNames) throws IOException { + URLConnectionHelper helper = URLConnectionHelper.newInstance(); + String contents = Arrays.stream(factorySpiClassNames).collect(joining("\n")); + URI uri = URI.create("data:," + contents); + URL url = helper.toURL(uri); + urls = Collections.singletonList(url); + } + + @Override + public Enumeration getResources(String name) throws IOException { + if (name.equals("META-INF/services/" + ProxyServiceExtensionSpi.class.getName())) { + return Collections.enumeration(urls); + } + return super.getResources(name); + } + + } + } diff --git a/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/ProxyServiceExtensionTest.java b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/ProxyServiceExtensionTest.java index 131c8320b0..170f7a7d4c 100644 --- a/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/ProxyServiceExtensionTest.java +++ b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/ProxyServiceExtensionTest.java @@ -16,25 +16,49 @@ package org.kaazing.gateway.service.proxy; import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.stream.Collectors.joining; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; +import java.net.URI; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; import org.kaazing.gateway.server.test.GatewayRule; import org.kaazing.gateway.server.test.config.GatewayConfiguration; import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; +import org.kaazing.netx.URLConnectionHelper; import org.kaazing.test.util.ITUtil; public class ProxyServiceExtensionTest { + private static ClassLoader classLoader; + + @BeforeClass + public static void before() throws Exception { + classLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(new TestClassLoader(TestExtension.class.getName())); + } + + @AfterClass + public static void after() { + Thread.currentThread().setContextClassLoader(classLoader); + } + private GatewayRule gateway = new GatewayRule() { { // @formatter:off @@ -83,6 +107,35 @@ public void run() { } catch (Exception ex) { fail("Unexpected exception in client connecting to server: " + ex); } + + Thread.currentThread().setContextClassLoader(new TestClassLoader()); + + } + + /** + * A classloader whose getResources("META-INF/services/org.kaazing.gateway.service.proxy.ProxyServiceExtensionSpi") + * method will return a URL whose contents will be the list of class names supplied in the constructor. + * This avoids the need for test meta-info resources files to be available on the test class path. + */ + private static class TestClassLoader extends ClassLoader { + private final List urls; + + TestClassLoader(String... factorySpiClassNames) throws IOException { + URLConnectionHelper helper = URLConnectionHelper.newInstance(); + String contents = Arrays.stream(factorySpiClassNames).collect(joining("\n")); + URI uri = URI.create("data:," + contents); + URL url = helper.toURL(uri); + urls = Collections.singletonList(url); + } + + @Override + public Enumeration getResources(String name) throws IOException { + if (name.equals("META-INF/services/" + ProxyServiceExtensionSpi.class.getName())) { + return Collections.enumeration(urls); + } + return super.getResources(name); + } + } } diff --git a/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/Tcp2TcpIT.java b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/Tcp2TcpIT.java new file mode 100644 index 0000000000..dbb57298ae --- /dev/null +++ b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/Tcp2TcpIT.java @@ -0,0 +1,75 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.service.proxy; + +import static org.junit.rules.RuleChain.outerRule; + +import java.util.concurrent.TimeUnit; + +import org.apache.log4j.PropertyConfigurator; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.gateway.server.test.GatewayRule; +import org.kaazing.gateway.server.test.config.GatewayConfiguration; +import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; +import org.kaazing.test.util.MethodExecutionTrace; + +public class Tcp2TcpIT { + + private final K3poRule k3po = new K3poRule().setScriptRoot("./"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, TimeUnit.SECONDS)); + + private final GatewayRule gaateway = new GatewayRule() { + { + GatewayConfiguration configuration = new GatewayConfigurationBuilder() + .service() + .type("proxy") + .accept("tcp://localhost:8080") + .connect("tcp://localhost:3101") + .done() + .done(); + + init(configuration); + } + }; + + private final TestRule trace = new MethodExecutionTrace(); + + @Rule + public final TestRule chain = outerRule(trace).around(k3po).around(gaateway).around(timeout); + + @BeforeClass + public static void init() throws Exception { + PropertyConfigurator.configure("src/test/resources/log4j.properties"); + } + + @Test + @Specification({ + "org/kaazing/specification/tcp/rfc793/echo.data/client", + "org/kaazing/gateway/service/proxy/echo.data/tcp.server" + }) + public void bidirectionalData() throws Exception { + k3po.finish(); + } + +} diff --git a/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/Tcp2UdpIT.java b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/Tcp2UdpIT.java new file mode 100644 index 0000000000..acfb0f67ac --- /dev/null +++ b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/Tcp2UdpIT.java @@ -0,0 +1,77 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.service.proxy; + +import static org.junit.rules.RuleChain.outerRule; + +import java.util.concurrent.TimeUnit; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.gateway.server.test.GatewayRule; +import org.kaazing.gateway.server.test.config.GatewayConfiguration; +import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; +import org.kaazing.test.util.MethodExecutionTrace; + +public class Tcp2UdpIT { + + private final K3poRule k3po = new K3poRule().setScriptRoot("./"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, TimeUnit.SECONDS)); + + private final GatewayRule gaateway = new GatewayRule() { + { + GatewayConfiguration configuration = new GatewayConfigurationBuilder() + .service() + .type("proxy") + .accept("tcp://localhost:8080") + .connect("udp://localhost:8080") + .done() + .done(); + + init(configuration); + } + }; + + private final TestRule trace = new MethodExecutionTrace(); + + @Rule + public final TestRule chain = outerRule(trace).around(k3po).around(gaateway).around(timeout); + + @Test + @Specification({ + "org/kaazing/specification/tcp/rfc793/echo.data/client", + "org/kaazing/specification/udp/rfc768/echo.data/server" + }) + public void bidirectionalData() throws Exception { + k3po.finish(); + } + + @Test + @Specification({ + "org/kaazing/specification/tcp/rfc793/concurrent.connections/client", + "org/kaazing/specification/udp/rfc768/concurrent.connections/server" + }) + public void concurrentConnections() throws Exception { + k3po.finish(); + } + +} diff --git a/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/Udp2TcpIT.java b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/Udp2TcpIT.java new file mode 100644 index 0000000000..1db51e386f --- /dev/null +++ b/service/proxy/src/test/java/org/kaazing/gateway/service/proxy/Udp2TcpIT.java @@ -0,0 +1,75 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.service.proxy; + +import static org.junit.rules.RuleChain.outerRule; + +import java.util.concurrent.TimeUnit; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.gateway.server.test.GatewayRule; +import org.kaazing.gateway.server.test.config.GatewayConfiguration; +import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; +import org.kaazing.test.util.MethodExecutionTrace; + +public class Udp2TcpIT { + + private final K3poRule k3po = new K3poRule().setScriptRoot("./"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, TimeUnit.SECONDS)); + + private final GatewayRule gateway = new GatewayRule() { + { + GatewayConfiguration configuration = new GatewayConfigurationBuilder() + .service() + .type("proxy") + .accept("udp://localhost:8080") + .connect("tcp://localhost:8080") + .done() + .done(); + + init(configuration); + } + }; + + private final TestRule trace = new MethodExecutionTrace(); + + @Rule + public final TestRule chain = outerRule(trace).around(k3po).around(gateway).around(timeout); + + @Test + @Specification({ + "org/kaazing/specification/udp/rfc768/echo.data/client", + "org/kaazing/specification/tcp/rfc793/echo.data/server" }) + public void bidirectionalData() throws Exception { + k3po.finish(); + } + + @Test + @Specification({ + "org/kaazing/specification/tcp/rfc793/concurrent.connections/client", + "org/kaazing/specification/tcp/rfc793/concurrent.connections/server" }) + public void concurrentConnections() throws Exception { + k3po.finish(); + } + +} diff --git a/service/proxy/src/test/resources/META-INF/services/org.kaazing.gateway.service.proxy.ProxyServiceExtensionSpi b/service/proxy/src/test/resources/META-INF/services/org.kaazing.gateway.service.proxy.ProxyServiceExtensionSpi deleted file mode 100644 index 1101915fbc..0000000000 --- a/service/proxy/src/test/resources/META-INF/services/org.kaazing.gateway.service.proxy.ProxyServiceExtensionSpi +++ /dev/null @@ -1 +0,0 @@ -org.kaazing.gateway.service.proxy.TestExtension diff --git a/service/proxy/src/test/scripts/org/kaazing/gateway/service/proxy/echo.data/tcp.server.rpt b/service/proxy/src/test/scripts/org/kaazing/gateway/service/proxy/echo.data/tcp.server.rpt new file mode 100644 index 0000000000..2e222c9b65 --- /dev/null +++ b/service/proxy/src/test/scripts/org/kaazing/gateway/service/proxy/echo.data/tcp.server.rpt @@ -0,0 +1,24 @@ +# +# Copyright 2007-2016, Kaazing Corporation. All rights reserved. +# +# Licensed 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. +# + +accept tcp://localhost:3101 + notify BOUND +accepted +connected +read "client data 1" +write "server data 1" +read "client data 2" +write "server data 2" diff --git a/service/spi/src/main/java/org/kaazing/gateway/service/messaging/collections/MemoryCollectionsFactory.java b/service/spi/src/main/java/org/kaazing/gateway/service/messaging/collections/MemoryCollectionsFactory.java index ad1ca7419d..02bb1213ed 100644 --- a/service/spi/src/main/java/org/kaazing/gateway/service/messaging/collections/MemoryCollectionsFactory.java +++ b/service/spi/src/main/java/org/kaazing/gateway/service/messaging/collections/MemoryCollectionsFactory.java @@ -15,6 +15,9 @@ */ package org.kaazing.gateway.service.messaging.collections; +import static java.lang.System.currentTimeMillis; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -277,6 +280,7 @@ public String toString() { private class IMapImpl implements IMap { private final ConcurrentHashMap map; + private final ConcurrentHashMap keyExpirations; private final EntryListenerSupport listenerSupport; private final ConcurrentMap locks; private final String name; @@ -286,6 +290,7 @@ public IMapImpl(String name) { this.map = new ConcurrentHashMap<>(); this.listenerSupport = new EntryListenerSupport<>(); this.locks = new ConcurrentHashMap<>(); + this.keyExpirations = new ConcurrentHashMap<>(); } @Override @@ -425,6 +430,7 @@ public Set> entrySet() { @Override public V get(Object key) { + removeExpiredEntries(); return map.get(key); } @@ -558,18 +564,23 @@ public Set localKeySet(Predicate predicate) { } @Override - public boolean lockMap(long time, TimeUnit timeunit) { + public boolean lockMap(long time, TimeUnit unit) { throw new UnsupportedOperationException("lockMap"); } @Override - public V put(K key, V value, long ttl, TimeUnit timeunit) { + public V put(K key, V value, long ttl, TimeUnit unit) { throw new UnsupportedOperationException("put"); } @Override - public V putIfAbsent(K key, V value, long ttl, TimeUnit timeunit) { - throw new UnsupportedOperationException("putIfAbsent"); + public V putIfAbsent(K key, V value, long ttl, TimeUnit unit) { + removeExpiredEntries(); + V result = this.map.putIfAbsent(key, value); + if(result == null){ + this.keyExpirations.put(key, currentTimeMillis() + unit.toMillis(ttl)); + } + return result; } @Override @@ -598,14 +609,28 @@ public Collection values(Predicate predicate) { @SuppressWarnings("unchecked") @Override public V remove(Object key) { + removeExpiredEntries(); V value = map.remove(key); boolean removed = (value != null); if (removed) { + this.keyExpirations.remove(key); EntryEvent event = new EntryEvent<>(name, null, EntryEvent.TYPE_REMOVED, (K)key, value); listenerSupport.entryRemoved(event); } return value; - } + } + + private void removeExpiredEntries() { + long currentMillis = currentTimeMillis(); + this.keyExpirations.entrySet().removeIf(e -> { + final Long expiration = e.getValue(); + if (currentMillis >= expiration.longValue()) { + map.remove(e.getKey()); + return true; + } + return false; + }); + } @Override public Future getAsync(K key) { diff --git a/service/spi/src/test/java/org/kaazing/gateway/service/messaging/collections/MemoryCollectionsFactoryTest.java b/service/spi/src/test/java/org/kaazing/gateway/service/messaging/collections/MemoryCollectionsFactoryTest.java index ebd751f8d4..f55d81ceaf 100644 --- a/service/spi/src/test/java/org/kaazing/gateway/service/messaging/collections/MemoryCollectionsFactoryTest.java +++ b/service/spi/src/test/java/org/kaazing/gateway/service/messaging/collections/MemoryCollectionsFactoryTest.java @@ -15,12 +15,20 @@ */ package org.kaazing.gateway.service.messaging.collections; +import static java.lang.Thread.sleep; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; -import junit.framework.Assert; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -234,4 +242,32 @@ public void shouldGetIMapEntrySetByPredicate() Assert.assertTrue("Expected entries, got null", entries != null); Assert.assertTrue(String.format("Expected entries %s, got %s", expected, entries), entries.equals(expected)); } + + @Test + public void expiringMap() throws Exception { + IMap map = factory.getMap(MAP_NAME); + + map.putIfAbsent("one", "1", 1, MILLISECONDS); + map.putIfAbsent("two", "2", 10000, MILLISECONDS); + map.putIfAbsent("three", "3", 2, MILLISECONDS); + + sleep(5); + + assertNull(map.putIfAbsent("one", "10", 1, MILLISECONDS)); + assertEquals("2", map.putIfAbsent("two", "20", 1000, MILLISECONDS)); + assertNull(map.putIfAbsent("three", "30", 10000, MILLISECONDS)); + + sleep(5); + + assertNull(map.get("one")); + assertEquals("2", map.get("two")); + assertEquals("30", map.get("three")); + + sleep(5); + + assertFalse(map.remove("one", "10")); + assertTrue(map.remove("three", "30")); + assertFalse(map.remove("three", "30")); + + } } diff --git a/service/turn.proxy/pom.xml b/service/turn.proxy/pom.xml deleted file mode 100644 index fe0208622c..0000000000 --- a/service/turn.proxy/pom.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - 4.0.0 - - - org.kaazing - gateway - develop-SNAPSHOT - ../../pom.xml - - gateway.service.turn.proxy - Turn Proxy Service - Proxy service to connect to backend services - - https://github.com/kaazing/gateway.service.proxy.git - - scm:git:${project.scm.url} - scm:git:${project.scm.url} - git@github.com:kaazing/gateway.service.proxy.git - - - - - org.slf4j - slf4j-log4j12 - - - org.kaazing - gateway.transport.http - ${project.version} - - - org.kaazing - gateway.service - ${project.version} - - - org.kaazing - gateway.service.proxy - ${project.version} - - - org.kaazing - gateway.util - ${project.version} - - - org.kaazing - gateway.test.ca - [1.0.0.0,1.1.0.0) - keystore - test - - - - - junit - junit - - - org.jmock - jmock - test - - - org.jmock - jmock-legacy - test - - - org.kaazing - test.util - ${project.version} - test - - - org.kaazing - gateway.server - ${project.version} - test - - - - - - - org.kaazing - k3po-maven-plugin - - - org.apache.maven.plugins - maven-failsafe-plugin - - - org.apache.maven.plugins - maven-dependency-plugin - 2.8 - - - unpack-truststore - generate-resources - - unpack-dependencies - - - gateway.test.ca - ${basedir}/target/truststore - - - - - - - - diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyAcceptHandler.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyAcceptHandler.java deleted file mode 100644 index a66ae9846b..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyAcceptHandler.java +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -import java.security.Key; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; - -import org.apache.mina.core.future.ConnectFuture; -import org.apache.mina.core.future.IoFutureListener; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.core.session.IoSessionInitializer; -import org.kaazing.gateway.security.SecurityContext; -import org.kaazing.gateway.service.ServiceContext; -import org.kaazing.gateway.service.ServiceProperties; -import org.kaazing.gateway.service.proxy.AbstractProxyAcceptHandler; -import org.kaazing.gateway.service.proxy.AbstractProxyHandler; -import org.kaazing.gateway.service.turn.proxy.stun.StunCodecFilter; -import org.kaazing.gateway.service.turn.proxy.stun.StunMaskAddressFilter; -import org.kaazing.gateway.service.turn.proxy.stun.StunProxyMessage; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.Username; -import org.kaazing.gateway.util.turn.TurnUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TurnProxyAcceptHandler extends AbstractProxyAcceptHandler { - - static final Logger LOGGER = LoggerFactory.getLogger(TurnProxyAcceptHandler.class); - public static final String TURN_SESSION_TRANSACTION_MAP = "turn-session-transactions-map"; - - // Configuration properties - public static final String PROPERTY_MAPPED_ADDRESS = "mapped.address"; - public static final String PROPERTY_AUTO_MAPPED_ADDRESS = "AUTO"; - public static final String PROPERTY_MASK_ADDRESS = "masking.key"; - public static final String PROPERTY_KEY_ALIAS = "key.alias"; - public static final String PROPERTY_KEY_ALGORITHM = "key.algorithm"; - - private String connectURI; - private Key sharedSecret; - private String keyAlgorithm; - private Long mask; - - public TurnProxyAcceptHandler() { - super(); - } - - /** - * Applies the configuration settings to the handler and the composed objects. - * - * @param serviceContext Configuration holder - * @param securityContext Security configuration holder - */ - public void init(ServiceContext serviceContext, SecurityContext securityContext) { - connectURI = serviceContext.getConnects().iterator().next(); - - boolean keyAliasRequired = false; - ServiceProperties properties = serviceContext.getProperties(); - String mappedAddress = properties.get(PROPERTY_MAPPED_ADDRESS); - if (mappedAddress != null) { - ((TurnProxyConnectHandler)getConnectHandler()).setFixedMappedAddress(mappedAddress); - keyAliasRequired = true; - } - - String maskProperty; - if ((maskProperty = properties.get(PROPERTY_MASK_ADDRESS)) != null) { - mask = Long.decode(maskProperty); - keyAliasRequired = true; - } - - if (properties.get(PROPERTY_KEY_ALIAS) != null) { - sharedSecret = TurnUtils.getSharedSecret(securityContext.getKeyStore(), properties.get(PROPERTY_KEY_ALIAS), - securityContext.getKeyStorePassword()); - if ((keyAlgorithm = properties.get(PROPERTY_KEY_ALGORITHM)) == null) { - keyAlgorithm = TurnUtils.HMAC_SHA_1; - } - } else if (keyAliasRequired) { - throw new TurnProxyException("Missing configuration property 'key.alias' required by 'mapped.address' or 'masking.key'."); - } - } - - @Override - protected AbstractProxyHandler createConnectHandler() { - return new TurnProxyConnectHandler(); - } - - @Override - public void sessionCreated(IoSession acceptSession) { - acceptSession.setAttribute(TURN_SESSION_TRANSACTION_MAP, new HashMap<>()); - acceptSession.getFilterChain().addLast("STUN_CODEC", new StunCodecFilter(sharedSecret, keyAlgorithm)); - if (mask != null) { - acceptSession.getFilterChain().addLast("STUN_MASK", new StunMaskAddressFilter(mask, StunMaskAddressFilter.Orientation.INCOMING)); - } - super.sessionCreated(acceptSession); - } - - @Override - public void sessionOpened(IoSession acceptSession) { - ConnectSessionInitializer sessionInitializer = new ConnectSessionInitializer(acceptSession); - ConnectFuture connectFuture = getServiceContext().connect(connectURI, getConnectHandler(), sessionInitializer); - connectFuture.addListener(new ConnectListener(acceptSession)); - super.sessionOpened(acceptSession); - } - - @Override - public void messageReceived(IoSession session, Object message) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("Received message [%s] from [%s]", message, session)); - } - if (message instanceof StunProxyMessage) { - // Store the incoming username for this transaction, will be reused if generating the message integrity - StunProxyMessage stunProxyMessage = (StunProxyMessage) message; - stunProxyMessage.getAttributes().stream().filter(attr -> attr instanceof Username).forEach( - attr -> { - String transactionId = Base64.getEncoder().encodeToString(stunProxyMessage.getTransactionId()); - String username = ((Username) attr).getUsername(); - LOGGER.trace(String.format("Storing username in transactionsMap %s -> %s", transactionId, username)); - @SuppressWarnings("unchecked") - Map currentTransactions = (Map) session.getAttribute(TURN_SESSION_TRANSACTION_MAP); - currentTransactions.put(transactionId, username); - } - ); - super.messageReceived(session, message); - } - } - - /* - * Initializer for connect session. It adds the processed accept session headers on the connect session - */ - class ConnectSessionInitializer implements IoSessionInitializer { - - private final IoSession acceptSession; - - public ConnectSessionInitializer(IoSession acceptSession) { - this.acceptSession = acceptSession; - } - - @Override - public void initializeSession(IoSession session, ConnectFuture future) { - session.setAttribute(TURN_SESSION_TRANSACTION_MAP, acceptSession.getAttribute(TURN_SESSION_TRANSACTION_MAP)); - session.getFilterChain().addLast("STUN_CODEC", new StunCodecFilter(sharedSecret, keyAlgorithm)); - if (mask != null) { - session.getFilterChain().addLast("STUN_MASK", new StunMaskAddressFilter(mask, StunMaskAddressFilter.Orientation.OUTGOING)); - } - } - } - - private class ConnectListener implements IoFutureListener { - - private final IoSession acceptSession; - - ConnectListener(IoSession acceptSession) { - this.acceptSession = acceptSession; - } - - @Override - public void operationComplete(ConnectFuture future) { - if (future.isConnected()) { - IoSession connectSession = future.getSession(); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Connected to " + connectURI + " [" + acceptSession + "->" + connectSession + "]"); - } - if (acceptSession == null || acceptSession.isClosing()) { - connectSession.close(true); - } else { - AttachedSessionManager attachedSessionManager = attachSessions(acceptSession, connectSession); - flushQueuedMessages(acceptSession, attachedSessionManager); - } - } else { - LOGGER.warn("Connection to " + connectURI + " failed [" + acceptSession + "->]"); - acceptSession.close(true); - } - } - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyConnectHandler.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyConnectHandler.java deleted file mode 100644 index f9c7bac4ca..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyConnectHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -import java.net.InetSocketAddress; - -import org.apache.mina.core.session.IoSession; -import org.kaazing.gateway.service.proxy.AbstractProxyHandler; -import org.kaazing.gateway.service.turn.proxy.stun.StunMessageClass; -import org.kaazing.gateway.service.turn.proxy.stun.StunMessageMethod; -import org.kaazing.gateway.service.turn.proxy.stun.StunProxyMessage; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.Attribute; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.MappedAddress; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.XorMappedAddress; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.kaazing.gateway.service.turn.proxy.TurnProxyAcceptHandler.PROPERTY_AUTO_MAPPED_ADDRESS; - -class TurnProxyConnectHandler extends AbstractProxyHandler { - - static final Logger LOGGER = LoggerFactory.getLogger(TurnProxyConnectHandler.class); - private String fixedMappedAddress; - private InetSocketAddress fixAddr; - - public void setFixedMappedAddress(String fixedMappedAddress) { - this.fixedMappedAddress = fixedMappedAddress; - if (!PROPERTY_AUTO_MAPPED_ADDRESS.equals(fixedMappedAddress) && fixedMappedAddress != null) { - int i = fixedMappedAddress.lastIndexOf(':'); - fixAddr = new InetSocketAddress( - fixedMappedAddress.substring(0, i), - Integer.parseInt(fixedMappedAddress.substring(i + 1, fixedMappedAddress.length())) - ); - } - } - - @Override - public void messageReceived(IoSession session, Object message) { - if (message instanceof StunProxyMessage) { - LOGGER.debug(String.format("Received message [%s] from [%s] ", message, session)); - StunProxyMessage stunMessage = (StunProxyMessage) message; - if (stunMessage.getMethod() == StunMessageMethod.ALLOCATE && - stunMessage.getMessageClass() == StunMessageClass.RESPONSE) { - InetSocketAddress acceptAddress = getMappedAddress(session); - if (acceptAddress != null) { - LOGGER.debug(String.format("Will override mapped-address or xor-mapped-address with: %s", acceptAddress.toString())); - overrideMappedAddress(stunMessage, acceptAddress); - } else { - LOGGER.debug("Will not override mapped-address or xor-mapped-address"); - } - } - } - super.messageReceived(session, message); - } - - - private void overrideMappedAddress(StunProxyMessage stunMessage, InetSocketAddress acceptAddress) { - for (int i = 0; i < stunMessage.getAttributes().size(); i++) { - Attribute attribute = stunMessage.getAttributes().get(i); - if (attribute instanceof MappedAddress) { - stunMessage.getAttributes().set(i, new MappedAddress(acceptAddress)); - stunMessage.setModified(true); - } else if (attribute instanceof XorMappedAddress) { - stunMessage.getAttributes().set(i, new XorMappedAddress(acceptAddress, stunMessage.getTransactionId())); - stunMessage.setModified(true); - } - } - } - - - private InetSocketAddress getMappedAddress(IoSession session) { - if (PROPERTY_AUTO_MAPPED_ADDRESS.equals(fixedMappedAddress)) { - AttachedSessionManager attachedSessionManager = getAttachedSessionManager(session); - IoSession acceptSession = attachedSessionManager.getAttachedSession(); - LOGGER.debug( - String.format("Extracting remote address and port from accept session: %s", - acceptSession.getRemoteAddress().toString()) - ); - if (acceptSession.getRemoteAddress() instanceof InetSocketAddress) { - return (InetSocketAddress) acceptSession.getRemoteAddress(); - } else { - LOGGER.debug("Remote address is not of type InetSocketAddress."); - } - } - return fixAddr; - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyException.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyException.java deleted file mode 100644 index fa3f1e67a9..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyException.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - - -public class TurnProxyException extends RuntimeException { - - public TurnProxyException(String message) { - super(message); - } - -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyService.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyService.java deleted file mode 100644 index 7e760e4afb..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyService.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -import org.kaazing.gateway.security.SecurityContext; -import org.kaazing.gateway.service.ServiceContext; -import org.kaazing.gateway.service.proxy.AbstractProxyService; -import org.kaazing.gateway.util.feature.EarlyAccessFeatures; - -import javax.annotation.Resource; -import java.util.Properties; - -public class TurnProxyService extends AbstractProxyService { - - public static final String SERVICE_TYPE = "turn.proxy"; - private final TurnProxyAcceptHandler turnProxyHandler = new TurnProxyAcceptHandler(); - private SecurityContext securityContext; - private Properties configuration; - - @Resource(name = "configuration") - public void setConfiguration(Properties configuration) { - this.configuration = configuration; - } - - @Resource(name = "securityContext") - public void setSecurityContext(SecurityContext securityContext) { - this.securityContext = securityContext; - } - - @Override - public String getType() { - return SERVICE_TYPE; - } - - @Override - protected TurnProxyAcceptHandler createHandler() { - return turnProxyHandler; - } - - @Override - public void init(ServiceContext serviceCtx) throws Exception { - EarlyAccessFeatures.TURN_PROXY.assertEnabled(configuration, serviceCtx.getLogger()); - turnProxyHandler.init(serviceCtx, securityContext); - super.init(serviceCtx); - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyServiceFactorySpi.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyServiceFactorySpi.java deleted file mode 100644 index de9707fbfc..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnProxyServiceFactorySpi.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -import java.util.Collection; -import java.util.Collections; - -import org.kaazing.gateway.service.Service; -import org.kaazing.gateway.service.ServiceFactorySpi; - -public class TurnProxyServiceFactorySpi extends ServiceFactorySpi { - - private static final Collection SERVICE_TYPES = Collections.singletonList(TurnProxyService.SERVICE_TYPE); - - @Override - public Collection getServiceTypes() { - return SERVICE_TYPES; - } - - @Override - public Service newService(String serviceType) { - assert TurnProxyService.SERVICE_TYPE.equals(serviceType); - return new TurnProxyService(); - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnSessionState.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnSessionState.java deleted file mode 100644 index d8adcc33b8..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/TurnSessionState.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -public enum TurnSessionState { - NOT_CONNECTED, ALLOCATED -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunAttributeFactory.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunAttributeFactory.java deleted file mode 100644 index 55b7285da9..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunAttributeFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun; - -import org.kaazing.gateway.service.turn.proxy.stun.attributes.Attribute; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.Fingerprint; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.MappedAddress; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.MessageIntegrity; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.ProxyNoopAttribute; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.Username; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.XorMappedAddress; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.XorPeerAddress; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.XorRelayAddress; - -public class StunAttributeFactory { - - public enum CredentialType { - SHORT_TERM, LONG_TERM - } - - private final CredentialType credentialType; - - public StunAttributeFactory(CredentialType credentialType) { - this.credentialType = credentialType; - } - - public Attribute get(int type, short length, byte[] value, byte[] transactionId) { - switch (AttributeType.valueOf(type)) { - case USERNAME: - return new Username(value); - case MAPPED_ADDRESS: - return new MappedAddress(value); - case XOR_MAPPED_ADDRESS: - return new XorMappedAddress(value, transactionId); - case XOR_PEER_ADDRESS: - return new XorPeerAddress(value, transactionId); - case XOR_RELAY_ADDRESS: - return new XorRelayAddress(value, transactionId); - case MESSAGE_INTEGRITY: - return new MessageIntegrity(value, credentialType); - case FINGERPRINT: - return new Fingerprint(value); - default: - // TODO: consider hard failing if white list of attributes is not allowed - return new ProxyNoopAttribute((short) type, length, value); - } - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunCodecFilter.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunCodecFilter.java deleted file mode 100644 index b50a314e6c..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunCodecFilter.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun; - -import static org.kaazing.gateway.service.turn.proxy.TurnProxyAcceptHandler.TURN_SESSION_TRANSACTION_MAP; -import static org.kaazing.gateway.service.turn.proxy.stun.StunAttributeFactory.CredentialType.SHORT_TERM; - -import java.security.Key; -import java.util.Map; - -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.codec.ProtocolCodecFactory; -import org.apache.mina.filter.codec.ProtocolDecoder; -import org.apache.mina.filter.codec.ProtocolEncoder; -import org.kaazing.mina.core.session.IoSessionEx; -import org.kaazing.mina.filter.codec.ProtocolCodecFilter; - -public class StunCodecFilter extends ProtocolCodecFilter { - - public StunCodecFilter(Key sharedSecret, String keyAlgorithm) { - super(new TurnCodecFactory(sharedSecret, keyAlgorithm)); - } - - private static class TurnCodecFactory implements ProtocolCodecFactory { - - private final Key sharedSecret; - private final String keyAlgorithm; - private final StunAttributeFactory stunAttributeFactory = new StunAttributeFactory(SHORT_TERM); - - public TurnCodecFactory(Key sharedSecret, String keyAlgorithm) { - this.sharedSecret = sharedSecret; - this.keyAlgorithm = keyAlgorithm; - } - - @SuppressWarnings("unchecked") - @Override - public ProtocolEncoder getEncoder(IoSession session) throws Exception { - IoSessionEx sessionEx = (IoSessionEx) session; - return new StunFrameEncoder(sessionEx.getBufferAllocator(), - (Map) session.getAttribute(TURN_SESSION_TRANSACTION_MAP), sharedSecret, keyAlgorithm); - } - - @Override - public ProtocolDecoder getDecoder(IoSession session) throws Exception { - IoSessionEx sessionEx = (IoSessionEx) session; - return new StunFrameDecoder(sessionEx.getBufferAllocator(), stunAttributeFactory); - } - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunFrameDecoder.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunFrameDecoder.java deleted file mode 100644 index 3bd18d96e4..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunFrameDecoder.java +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun; - -import static org.kaazing.gateway.service.turn.proxy.stun.StunProxyMessage.MAGIC_COOKIE; -import static org.kaazing.gateway.service.turn.proxy.stun.StunProxyMessage.attributePaddedLength; - -import java.nio.BufferUnderflowException; -import java.util.ArrayList; -import java.util.List; - -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.codec.ProtocolDecoderOutput; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.Attribute; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.ErrorCode; -import org.kaazing.mina.core.buffer.IoBufferAllocatorEx; -import org.kaazing.mina.core.buffer.IoBufferEx; -import org.kaazing.mina.filter.codec.CumulativeProtocolDecoderEx; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class StunFrameDecoder extends CumulativeProtocolDecoderEx { - private static final Logger LOGGER = LoggerFactory.getLogger("service.turn.proxy"); - private final StunAttributeFactory stunAttributeFactory; - - public StunFrameDecoder(IoBufferAllocatorEx allocator, StunAttributeFactory stunAttributeFactory) { - super(allocator); - this.stunAttributeFactory = stunAttributeFactory; - } - - @Override - protected boolean doDecode(IoSession session, IoBufferEx in, ProtocolDecoderOutput out) throws Exception { - - LOGGER.trace("Decoding STUN message: " + in); - if (in.remaining() < 20) { - return false; - } - in.mark(); - - // https://tools.ietf.org/html/rfc5389#section-6 - short leadingBitsAndMessageType = in.getShort(); - - validateIsStun(leadingBitsAndMessageType); - - StunMessageClass messageClass = StunMessageClass.valueOf(leadingBitsAndMessageType); - - StunMessageMethod method = StunMessageMethod.valueOf(leadingBitsAndMessageType); - - short messageLength = in.getShort(); - - int magicCookie = in.getInt(); - validateMagicCookie(magicCookie); - - byte[] transactionId = new byte[12]; - in.get(transactionId); - - List attributes = new ArrayList<>(); - if (in.remaining() < messageLength) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace(String.format("Message has %d bytes remaining, which is less than declared length of: %d", - in.remaining(), messageLength)); - } - in.reset(); - return false; - } else if (in.remaining() == 0) { - /* - https://tools.ietf.org/html/rfc5389#section-15 - After the STUN header are zero or more attributes. Each attribute - MUST be TLV encoded, with a 16-bit type, 16-bit length, and value. - Each STUN attribute MUST end on a 32-bit boundary. As mentioned - above, all fields in an attribute are transmitted most significant - bit first. - */ - LOGGER.debug("Message does not contain any attributes"); - } else { - try { - attributes = decodeAttributes(in, messageLength, transactionId); - } catch (BufferUnderflowException e) { - LOGGER.warn("Could not decode attributes", e); - List errors = new ArrayList<>(1); - ErrorCode errorCode = new ErrorCode(); - errorCode.setErrorCode(400); - errorCode.setErrMsg("Bad Request"); - errors.add(errorCode); - StunProxyMessage stunMessage = new StunProxyMessage(StunMessageClass.ERROR, StunMessageMethod.ALLOCATE, transactionId, errors); - LOGGER.warn("replying with error message: " + stunMessage); - session.write(stunMessage); - in.mark(); - return true; - } - } - StunProxyMessage stunMessage = new StunProxyMessage(messageClass, method, transactionId, attributes); - in.mark(); - out.write(stunMessage); - - return true; - } - - private List decodeAttributes(IoBufferEx in, short remaining, byte[] transactionId) { - List stunMessageAttributes = new ArrayList<>(); - // Any attribute type MAY appear more than once in a STUN message. - // Unless specified otherwise, the order of appearance is significant: - // only the first occurrence needs to be processed by a receiver, and - // any duplicates MAY be ignored by a receiver. - do { - short type = in.getShort(); - short length = in.getShort(); - remaining -= 4; - - // get variable - byte[] variable = new byte[length]; - in.get(variable); - Attribute attribute = stunAttributeFactory.get(type, length, variable, transactionId); - LOGGER.trace("Decoded STUN attribute: " + attribute); - stunMessageAttributes.add(attribute); - remaining -= length; - - // remove padding - for (int i = length; i < attributePaddedLength(length); i++) { - in.get(); - remaining -= 1; - } - } while (remaining > 0); - return stunMessageAttributes; - } - - private void validateIsStun(short leadingBitsAndMessageType) { - int leadingBytes = leadingBitsAndMessageType & 0xC00; - if (0 != leadingBytes) { - throw new IllegalArgumentException(String.format("Illegal leading bytes in STUN message: %02X from: %04X", leadingBytes, leadingBitsAndMessageType)); - } - } - - private void validateMagicCookie(int magicCookie) { - if (magicCookie != MAGIC_COOKIE) { - throw new IllegalArgumentException("Illegal magic cookie value: " + magicCookie); - } - - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunFrameEncoder.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunFrameEncoder.java deleted file mode 100644 index 11d8be6213..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunFrameEncoder.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun; - -import static org.kaazing.gateway.service.turn.proxy.stun.StunProxyMessage.attributePaddedLength; -import static org.kaazing.gateway.util.turn.TurnUtils.HMAC_SHA_1; - -import java.nio.ByteBuffer; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.util.Map; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.codec.ProtocolEncoderAdapter; -import org.apache.mina.filter.codec.ProtocolEncoderOutput; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.Attribute; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.Fingerprint; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.MessageIntegrity; -import org.kaazing.gateway.util.turn.TurnUtils; -import org.kaazing.mina.core.buffer.IoBufferAllocatorEx; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -/* - https://tools.ietf.org/html/rfc5389#section-15.4 - - Based on the rules above, the hash used to construct MESSAGE- - INTEGRITY includes the length field from the STUN message header. - Prior to performing the hash, the MESSAGE-INTEGRITY attribute MUST be - inserted into the message (with dummy content). The length MUST then - be set to point to the length of the message up to, and including, - the MESSAGE-INTEGRITY attribute itself, but excluding any attributes - after it. Once the computation is performed, the value of the - MESSAGE-INTEGRITY attribute can be filled in, and the value of the - length in the STUN header can be set to its correct value -- the - length of the entire message. Similarly, when validating the - MESSAGE-INTEGRITY, the length field should be adjusted to point to - the end of the MESSAGE-INTEGRITY attribute prior to calculating the - HMAC. Such adjustment is necessary when attributes, such as - FINGERPRINT, appear after MESSAGE-INTEGRITY. - */ - -public class StunFrameEncoder extends ProtocolEncoderAdapter { - - private final IoBufferAllocatorEx allocator; - private final Map currentTransactions; - private final Key sharedSecret; - private final String keyAlgorithm; - - private static final Logger LOGGER = LoggerFactory.getLogger("service.turn.proxy"); - - - public StunFrameEncoder(IoBufferAllocatorEx aloc, Map currentTrx, Key ss, String keyAlg) { - this.allocator = aloc; - this.currentTransactions = currentTrx; - this.sharedSecret = ss; - this.keyAlgorithm = keyAlg; - } - - @Override - public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { - if (!(message instanceof StunProxyMessage)) { - // easiest way to avoid race condition where decoder is removed on the filter chain prior to encoder - out.write(message); - } - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Encoding STUN message: " + message); - } - StunProxyMessage stunMessage = (StunProxyMessage) message; - String username = null; - if (stunMessage.getMessageClass().equals(StunMessageClass.RESPONSE) || - stunMessage.getMessageClass().equals(StunMessageClass.ERROR)) { - username = currentTransactions.remove(Base64.getEncoder().encodeToString(stunMessage.getTransactionId())); - LOGGER.trace(String.format("Removed username %s from transactions map", username)); - if (stunMessage.isModified() && (username == null || sharedSecret ==null)) { - LOGGER.warn("STUN message is modified but MESSAGE-INTEGRITY attribute can not be recalculated because username and/or shared secret is not available"); - } - } - ByteBuffer buf = allocator.allocate(StunProxyMessage.HEADER_BYTES + stunMessage.getMessageLength()); - short messageMethod = stunMessage.getMethod().getValue(); - short messageClass = stunMessage.getMessageClass().getValue(); - buf.putShort((short) (messageMethod | messageClass)); - buf.putShort(stunMessage.getMessageLength()); - buf.putInt(StunProxyMessage.MAGIC_COOKIE); - buf.put(stunMessage.getTransactionId()); - encodeAttributes(stunMessage, username, buf); - buf.flip(); - out.write(allocator.wrap(buf)); - } - - private void encodeAttributes(StunProxyMessage stunMessage, String username, ByteBuffer buf) throws NoSuchAlgorithmException, InvalidKeyException { - int lengthSoFar = StunProxyMessage.HEADER_BYTES; - for (Attribute attribute : stunMessage.getAttributes()) { - if (attribute instanceof MessageIntegrity && - stunMessage.isModified() && username != null && sharedSecret != null) { - LOGGER.debug("Message is modified will override attribute MESSAGE-INTEGRITY"); - // order counts when here we can safely recreate the message integrity - // overwrite message length and use the current buffer content, secret and password - buf.putShort(2, (short) lengthSoFar); - attribute = overrideMessageIntegrity(username, buf); - buf.putShort(2, stunMessage.getMessageLength()); - } else if (attribute instanceof Fingerprint && stunMessage.isModified()) { - LOGGER.debug("Message is modified will override atribute FINGERPRINT"); - // message length already at total value - attribute = new Fingerprint(); - ((Fingerprint) attribute).calculate(buf.array()); - } - - lengthSoFar += 4 + attributePaddedLength(attribute.getLength()); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Encoding STUN attribute: " + attribute); - } - buf.putShort(attribute.getType()); - short length = attribute.getLength(); - buf.putShort(length); - byte[] variable = attribute.getVariable(); - buf.put(variable); - for (int i = length; i < attributePaddedLength(length); i++) { - buf.put((byte) 0x00); - } - } - } - - private Attribute overrideMessageIntegrity(String username, ByteBuffer buf) throws NoSuchAlgorithmException, InvalidKeyException { - char[] password = TurnUtils.generatePassword(username, sharedSecret, keyAlgorithm); - Mac hMac = Mac.getInstance(HMAC_SHA_1); - SecretKey signingKey = new SecretKeySpec(new String(password).getBytes(), HMAC_SHA_1); - hMac.init(signingKey); - return new MessageIntegrity(hMac.doFinal(buf.array()), StunAttributeFactory.CredentialType.SHORT_TERM); - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunMaskAddressFilter.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunMaskAddressFilter.java deleted file mode 100644 index f0e29b7286..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunMaskAddressFilter.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun; - -import org.apache.mina.core.filterchain.IoFilterAdapter; -import org.apache.mina.core.session.IoSession; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.AbstractAddress; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.XorPeerAddress; -import org.kaazing.gateway.service.turn.proxy.stun.attributes.XorRelayAddress; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class StunMaskAddressFilter extends IoFilterAdapter { - - static final Logger LOGGER = LoggerFactory.getLogger(StunMaskAddressFilter.class); - - private final byte[] ipv4Mask; - private final byte[] ipv6Mask; - private final int portMask; - private final Orientation orientation; - private final Orientation.AddressVisitor maskVisitor = new MaskVisitor(); - - public enum Orientation { - INCOMING { - @Override - public void visitAddress(StunProxyMessage stunProxyMessage, AddressVisitor visitor) { - LOGGER.debug("INCOMING stun proxy message, unmasking XOR-PEER-ADDRESS"); - stunProxyMessage.getAttributes() - .stream() - .filter(attribute -> attribute instanceof XorPeerAddress) - .forEach(attribute -> { - visitor.visit((AbstractAddress) attribute); - stunProxyMessage.setModified(true); - }); - } - }, - OUTGOING { - @Override - public void visitAddress(StunProxyMessage stunProxyMessage, AddressVisitor visitor) { - LOGGER.debug("OUTGOING stun proxy message, masking XOR-RELAY-ADDRESS"); - stunProxyMessage.getAttributes() - .stream() - .filter(attribute -> attribute instanceof XorRelayAddress) - .forEach(attribute -> { - visitor.visit((AbstractAddress) attribute); - stunProxyMessage.setModified(true); - }); - } - }; - - public abstract void visitAddress(StunProxyMessage stunProxyMessage, AddressVisitor visitor); - - @FunctionalInterface - public interface AddressVisitor { - void visit(AbstractAddress address); - } - } - - public StunMaskAddressFilter(Long mask, Orientation orientation) { - ipv4Mask = new byte[4]; - long maskKey = mask; - for (int i = 3; i >= 0; i--) { - ipv4Mask[i] = (byte)(maskKey & 0xFF); - maskKey >>= 8; - } - ipv6Mask = new byte[16]; - for (byte i = 0; i < 4; i++) { - System.arraycopy(ipv4Mask, 0, ipv6Mask, i * ipv4Mask.length, ipv4Mask.length); - } - portMask = (int) (mask & 0xFFFF); - this.orientation = orientation; - if (LOGGER.isDebugEnabled()) { - logMasks(); - } - } - - private void logMasks() { - StringBuilder sb = new StringBuilder(); - for (byte b : ipv4Mask) { - sb.append(String.format("%02X ", b)); - } - String ipv4MaskStr = sb.toString(); - sb = new StringBuilder(); - for (byte b : ipv6Mask) { - sb.append(String.format("%02X ", b)); - } - String ipv6MaskStr = sb.toString(); - LOGGER.debug( - String.format("Initialized filter with ipv4Mask: %s, ipv6Mask: %s, portMask: %02X %02X", - ipv4MaskStr, - ipv6MaskStr, - portMask & 0xFF, - (portMask >> 8 ) & 0xFF - ) - ); - } - - @Override - public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { - if (message instanceof StunProxyMessage) { - orientation.visitAddress((StunProxyMessage) message, maskVisitor); - } - super.messageReceived(nextFilter, session, message); - } - - private class MaskVisitor implements Orientation.AddressVisitor { - - @Override - public void visit(AbstractAddress address) { - LOGGER.debug("Address before masking is: " + address); - address.setPort((short) (address.getPort() ^ portMask)); - byte[] mask = ipv4Mask; - if (address.getFamily().equals(AbstractAddress.Family.IPV6)) { - mask = ipv6Mask; - } - byte[] tmp = new byte[mask.length]; - byte[] add = address.getAddress(); - for (int i = 0; i < mask.length; i++) { - tmp[i] = (byte) (add[i] ^ mask[i]); - } - address.setAddress(tmp); - LOGGER.debug("Address after masking is: " + address); - } - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunMessageClass.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunMessageClass.java deleted file mode 100644 index 8d8ba9a72d..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunMessageClass.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun; - -public enum StunMessageClass { - //@formatter:off - REQUEST((short) 0x000), - INDICATION((short) 0x010), - RESPONSE((short) 0x100), - ERROR((short) 0x110); - //@formatter:on - - private static final short SIGNIFICANT_BITS = 0x110; - private final short value; - - StunMessageClass(short value) { - this.value = value; - } - - public static StunMessageClass valueOf(short leadingBitesAndMessageType) { - switch (leadingBitesAndMessageType & SIGNIFICANT_BITS) { - case 0x000: - return REQUEST; - case 0x010: - return INDICATION; - case 0x100: - return RESPONSE; - case 0x110: - return ERROR; - default: - throw new IllegalStateException( - String.format("No such STUN class from: 0x%02X", leadingBitesAndMessageType) - ); - } - } - - public short getValue() { - return value; - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunMessageMethod.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunMessageMethod.java deleted file mode 100644 index 90260e62dd..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunMessageMethod.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun; - -public enum StunMessageMethod { - //@formatter:off - RESERVED ((short) 0x000), - BINDING ((short) 0x001), - RESERVED2 ((short) 0x002), - ALLOCATE ((short) 0x003), - REFRESH ((short) 0x004), - SEND ((short) 0x006), - DATA ((short) 0x007), - CREATE_PERMISSION ((short) 0x008), - CHANNEL_BIND ((short) 0x009); - //@formatter:on - - private static final short SIGNIFICANT_BITS = 0xEEF; - private final short value; - - StunMessageMethod(short value) { - this.value = value; - } - - public static StunMessageMethod valueOf(short leadingBitesAndMessageType) { - int method = leadingBitesAndMessageType & SIGNIFICANT_BITS; - switch (method) { - case 0x000: - return RESERVED; - case 0x001: - return BINDING; - case 0x002: - return RESERVED2; - case 0x003: - return ALLOCATE; - case 0x004: - return REFRESH; - case 0x006: - return SEND; - case 0x007: - return DATA; - case 0x008: - return CREATE_PERMISSION; - case 0x009: - return CHANNEL_BIND; - default: - throw new IllegalStateException( - String.format("No such STUN method from: 0x%02X", method) - ); - } - } - - short getValue() { - return value; - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunProxyMessage.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunProxyMessage.java deleted file mode 100644 index b6f348d2a4..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/StunProxyMessage.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun; - -import java.util.List; - -import org.kaazing.gateway.service.turn.proxy.stun.attributes.Attribute; - -/** - * Stun Message as defined in https://tools.ietf.org/html/rfc5389#section-6. - * - */ -public class StunProxyMessage { - - private final StunMessageClass messageClass; - private final StunMessageMethod method; - private final byte[] transactionId; - private final List attributes; - public static final int MAGIC_COOKIE = 0x2112A442; - private static final int PADDED_TO = 4; - private boolean modified = false; - - public static final int HEADER_BYTES = 20; - - public StunProxyMessage(StunMessageClass messageClass, StunMessageMethod method, byte[] transactionId, - List attributes) { - this.messageClass = messageClass; - this.method = method; - this.transactionId = transactionId; - this.attributes = attributes; - } - - public StunMessageClass getMessageClass() { - return messageClass; - } - - public StunMessageMethod getMethod() { - return method; - } - - public short getMessageLength() { - short length = 0; - for (Attribute attribute : attributes) { - length += 4; - length += attributePaddedLength(attribute.getLength()); - } - return length; - } - - public byte[] getTransactionId() { - return transactionId; - } - - public List getAttributes() { - return attributes; - } - - @Override - public String toString() { - return String.format("%s %s with length: %d", messageClass.toString(), method.toString(), getMessageLength()); - } - - public static int attributePaddedLength(int length) { - return ((length + PADDED_TO - 1) / PADDED_TO) * PADDED_TO; - } - - /** - * Has the proxy modified the message. - * @return True if the message has been modified by the proxy service by calling the setter. - */ - public boolean isModified() { - return modified; - } - - /** - * Set whether the proxy has modified the message; - * @param modified Can also set value false to unmark the message as modified. - */ - public void setModified(boolean modified) { - this.modified = modified; - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/AbstractAddress.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/AbstractAddress.java deleted file mode 100644 index 05484af464..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/AbstractAddress.java +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AbstractAddress.Family.IPV4; -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AbstractAddress.Family.IPV6; - -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.Arrays; - -import org.kaazing.gateway.service.turn.proxy.stun.StunMaskAddressFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Abstract address based attribute class for other Address attributes to extend. - * Support XOR encoded fields as defined here: https://tools.ietf.org/html/rfc5389#section-15.2 - Excerpt: - X-Port is computed by taking the mapped port in host byte order, - XOR'ing it with the most significant 16 bits of the magic cookie, and - then the converting the result to network byte order. If the IP - address family is IPv4, X-Address is computed by taking the mapped IP - address in host byte order, XOR'ing it with the magic cookie, and - converting the result to network byte order. If the IP address - family is IPv6, X-Address is computed by taking the mapped IP address - in host byte order, XOR'ing it with the concatenation of the magic - cookie and the 96-bit transaction ID, and converting the result to - network byte order. - */ - -public abstract class AbstractAddress extends Attribute { - - static final Logger LOGGER = LoggerFactory.getLogger(StunMaskAddressFilter.class); - - private static final byte[] MAGIC_COOKIE = new byte[]{(byte) 0x21, (byte) 0x12, (byte) 0xA4, (byte) 0x42}; - - protected byte[] address; - protected int port; - private Family family; - private byte[] transactionId; - - public enum Family { - IPV4((byte) 0x01, 4), IPV6((byte) 0x02, 16); - - private final byte encoding; - private final int length; - - Family(byte encoding, int length) { - this.encoding = encoding; - this.length = length; - } - - byte getEncoding() { - return encoding; - } - - public int getLength() { - return length; - } - - public static Family fromValue(byte b) { - if (b == 0x01) { - return IPV4; - } else if (b == 0x02) { - return IPV6; - } - throw new InvalidAttributeException("No address family for: " + b); - } - } - - public AbstractAddress(InetSocketAddress address) { - InetAddress inetAddress = address.getAddress(); - if (inetAddress instanceof Inet4Address) { - this.family = IPV4; - } else if (inetAddress instanceof Inet6Address) { - this.family = IPV6; - } else { - throw new InvalidAttributeException("No address family for: " + inetAddress.getClass()); - } - this.setPort(address.getPort()); - this.address = inetAddress.getAddress(); - } - - public AbstractAddress(InetSocketAddress address, byte[] transactionId) { - this(address); - if (transactionId.length == IPV6.length - MAGIC_COOKIE.length) { - this.transactionId = transactionId; - } else { - throw new InvalidAttributeException("Invalid length for transactionId: " + transactionId.length); - } - } - - public AbstractAddress(byte[] variable) { - this.family = Family.fromValue(variable[1]); - this.setPort((variable[2] << 8) + (variable[3] & 0xff)); - this.address = Arrays.copyOfRange(variable, 4, 4 + this.family.length); - } - - public AbstractAddress(byte[] variable, byte[] transactionId) { - this(variable); - if (transactionId.length == IPV6.length - MAGIC_COOKIE.length) { - this.transactionId = transactionId; - } else { - throw new InvalidAttributeException("Invalid length for transactionId: " + transactionId.length); - } - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public byte[] getAddress() { - return this.address; - } - - public void setAddress(byte[] address) { - if (address.length == IPV4.length) { - family = IPV4; - } else if (address.length == IPV6.length) { - family = IPV6; - } else { - throw new InvalidAttributeException("Address is neither IPv4 or IPv6"); - } - this.address = address; - } - - @Override - public short getLength() { - return (short) (this.family.length + 4); - } - - public Family getFamily() { - if (address.length == IPV4.length) { - return IPV4; - } else if (address.length == IPV6.length) { - return IPV6; - } - throw new InvalidAttributeException("Address is not of IPv4 or IPv6 family"); - } - - protected short xorWithMagicCookie(short s) { - return (short) (s ^ 0x2112); - } - - protected byte[] xorWithMagicCookie(byte[] bytes) { - byte[] temp = bytes.clone(); - if (this.family.equals(Family.IPV4) && temp.length == IPV4.length) { - for (int i = 0; i < temp.length; i++) { - temp[i] = (byte) (temp[i] ^ MAGIC_COOKIE[i]); - } - } else if (this.family.equals(Family.IPV6) && temp.length == IPV6.length) { - byte[] xor = new byte[IPV6.length]; - System.arraycopy(MAGIC_COOKIE, 0, xor, 0, MAGIC_COOKIE.length); - System.arraycopy(transactionId, 0, xor, MAGIC_COOKIE.length, IPV6.length - MAGIC_COOKIE.length); - for (int i = 0; i < temp.length; i++) { - temp[i] = (byte) (temp[i] ^ xor[i]); - } - } else { - throw new InvalidAttributeException("Cannot xor given byte array, incorrect length: " + temp.length); - } - - return temp; - } - - @Override - public String toString() { - try { - StringBuilder sb = new StringBuilder(); - for (byte b : getVariable()) { - sb.append(String.format("%02X ", b)); - } - return String.format( - "%s - %s:%s - %s", - super.toString(), InetAddress.getByAddress(getAddress()), getPort(), sb.toString() - ); - } catch (UnknownHostException e) { - LOGGER.debug("Unable to transform address to string, using default implementation", e); - } - return super.toString(); - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/Attribute.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/Attribute.java deleted file mode 100644 index 5ab7c4245a..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/Attribute.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -/** - * Stun Attribute. - * - */ -public abstract class Attribute { - - public abstract short getType(); - - public abstract short getLength(); - - public abstract byte[] getVariable(); - - @Override - public String toString() { - int type = getType(); - return String.format("%s", AttributeType.valueOf(type)); - } - -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/AttributeType.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/AttributeType.java deleted file mode 100644 index 9ff5781fe2..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/AttributeType.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -/** - * Attribute types as defined in https://tools.ietf.org/html/rfc5389#section-18.2. - * - */ -public enum AttributeType { - // @formatter:off - UNKNOWN("UNKNOWN", (short) -0x001), - RESERVED0("Reserved", (short) 0x0000), - MAPPED_ADDRESS("MAPPED-ADDRESS", (short) 0x0001), - RESERVED1("was RESPONSE-ADDRESS", (short) 0x0002), - RESERVED2("was CHANGE-ADDRESS", (short) 0x0003), - RESERVED3("was SOURCE-ADDRESS", (short) 0x0004), - RESERVED5("was CHANGED-ADDRESS", (short) 0x0005), - USERNAME("USERNAME", (short) 0x0006), - RESERVED6("was PASSWORD", (short) 0x0007), - MESSAGE_INTEGRITY("MESSAGE-INTEGRITY", (short) 0x0008), - ERROR_CODE("ERROR-CODE", (short) 0x0009), - UNKNOWN_ATTRIBUTES("UNKNOWN-ATTRIBUTES", (short) 0x000A), - RESERVED7("was REFLECTED-FROM", (short) 0x000B), - XOR_PEER_ADDRESS("XOR-PEER-ADDRESS", (short) 0x0012), - REALM("REALM", (short) 0x0014), - NONCE("NONCE", (short) 0x0015), - XOR_RELAY_ADDRESS("XOR-RELAYED-ADDRESS", (short) 0x0016), - EVEN_PORT("EVEN-PORT", (short) 0x0018), - XOR_MAPPED_ADDRESS("XOR-MAPPED-ADDRESS", (short) 0x0020), - RESERVATION_TOKEN("RESERVATION-TOKEN", (short) 0x0022), - SOFTWARE("SOFTWARE", (short) 0x8022), - ALTERNATE_SERVER("ALTERNATE-SERVER", (short) 0x8023), - FINGERPRINT("FINGERPRINT", (short) 0x8028); - // @formatter:on - - private final String name; - private short hexCode; - - AttributeType(String name, short type) { - this.name = name; - this.hexCode = type; - } - - public String getName() { - return name; - } - - public short getType() { - return hexCode; - } - - public static AttributeType valueOf(int type) { - switch (type) { - case 0x0000: - return RESERVED0; - case 0x0001: - return MAPPED_ADDRESS; - case 0x0002: - return RESERVED1; - case 0x0003: - return RESERVED2; - case 0x0004: - return RESERVED3; - case 0x0005: - return RESERVED5; - case 0x0006: - return USERNAME; - case 0x0007: - return RESERVED6; - case 0x0008: - return MESSAGE_INTEGRITY; - case 0x0009: - return ERROR_CODE; - case 0x0012: - return XOR_PEER_ADDRESS; - case 0x000A: - return UNKNOWN_ATTRIBUTES; - case 0x000B: - return RESERVED7; - case 0x0014: - return REALM; - case 0x0015: - return NONCE; - case 0x0016: - return XOR_RELAY_ADDRESS; - case 0x0018: - return EVEN_PORT; - case 0x0020: - return XOR_MAPPED_ADDRESS; - case 0x0022: - return RESERVATION_TOKEN; - case (short)0x8022: - return SOFTWARE; - case (short)0x8023: - return ALTERNATE_SERVER; - case (short)0x8028: - return FINGERPRINT; - default: - AttributeType result = AttributeType.UNKNOWN; - result.setType((short) type); - return AttributeType.UNKNOWN; - } - } - - private void setType(short type) { - this.hexCode = type; - } -} \ No newline at end of file diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/ErrorCode.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/ErrorCode.java deleted file mode 100644 index 8b6d0280dd..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/ErrorCode.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.ERROR_CODE; - -/** - * STUN ERROR-CODE attribute as described in https://tools.ietf.org/html/rfc5389#section-15.6. - * - */ - -public class ErrorCode extends Attribute { - - private int errorCode; - private String errMsg; - - public void setErrMsg(String errMsg) { - if (errMsg.length() > 128) { - throw new InvalidAttributeException("Error message MUST be at most 128 characters"); - } - this.errMsg = errMsg; - } - - public void setErrorCode(int errorCode) { - if (errorCode < 300 || errorCode > 699) { - throw new InvalidAttributeException("Error code MUST be in the range of 300 to 699"); - } - this.errorCode = errorCode; - } - - @Override - public short getType() { - return ERROR_CODE.getType(); - } - - @Override - public short getLength() { - return (short) (4 + errMsg.getBytes(Charset.forName("UTF-8")).length); - } - - @Override - public byte[] getVariable() { - ByteBuffer byteBuffer = ByteBuffer.allocate(getLength()); - byteBuffer.put((byte) 0x00); - byteBuffer.putShort((short) (errorCode / 100)); - byteBuffer.put((byte) (errorCode % 100)); - byteBuffer.put(errMsg.getBytes(Charset.forName("UTF-8"))); - return byteBuffer.array(); - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/EvenPort.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/EvenPort.java deleted file mode 100644 index b5cadda441..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/EvenPort.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.EVEN_PORT; - -/** - * TURN Even Port attribute as described in https://tools.ietf.org/html/rfc5766#section-14.6. - * - */ -public class EvenPort extends Attribute { - - private boolean reserveNextHigherPort; - - public EvenPort(byte[] value) { - reserveNextHigherPort = value[0] == ((byte) 0x80); - } - - @Override - public short getType() { - return EVEN_PORT.getType(); - } - - @Override - public short getLength() { - return 4; - } - - @Override - public byte[] getVariable() { - return reserveNextHigherPort ? new byte[]{(byte) 0x80, 0x00, 0x00, 0x00} : new byte[]{0x00, 0x00, 0x00, 0x00}; - } - - public void setReserveNextHigherPort(boolean flag) { - reserveNextHigherPort = flag; - } - - public boolean getReserveNextHigherPort() { - return reserveNextHigherPort; - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/Fingerprint.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/Fingerprint.java deleted file mode 100644 index f15737ce93..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/Fingerprint.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import java.util.zip.CRC32; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.FINGERPRINT; - -public class Fingerprint extends Attribute { - - protected byte[] value = new byte[4]; - - private static final long DEFAULT_XOR_VALUE = 0x5354554e; - - public Fingerprint() { - // nothing to do, the calculation is done in a separate method - } - - public Fingerprint(byte[] key) { - this.value = key; - } - - public void calculate(byte[] input) { - CRC32 crc32 = new CRC32(); - crc32.update(input); - long xorKey = crc32.getValue() ^ DEFAULT_XOR_VALUE; - for (int i = 3; i >= 0; i--) { - value[i] = (byte)(xorKey & 0xFF); - xorKey >>= 8; - } - } - - @Override - public short getType() { - return FINGERPRINT.getType(); - } - - @Override - public short getLength() { - return (short) value.length; - } - - @Override - public byte[] getVariable() { - return value; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - for (byte b : getVariable()) { - sb.append(String.format("%02X ", b)); - } - return String.format("%s - %s", super.toString(), sb.toString()); - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/InvalidAttributeException.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/InvalidAttributeException.java deleted file mode 100644 index 27ec515c32..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/InvalidAttributeException.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -public class InvalidAttributeException extends RuntimeException { - - /** - * Generated UID. - */ - private static final long serialVersionUID = -3139438402432719633L; - - public InvalidAttributeException(String msg) { - super(msg); - } - - -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/MappedAddress.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/MappedAddress.java deleted file mode 100644 index 8e5efdc562..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/MappedAddress.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.MAPPED_ADDRESS; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; - -/** - * Stun Mapped Address attribute as defined in https://tools.ietf.org/html/rfc5389#section-15.1 - */ -public class MappedAddress extends AbstractAddress { - - public MappedAddress(InetSocketAddress address) { - super(address); - } - - public MappedAddress(byte[] variable) { - super(variable); - } - - @Override - public short getType() { - return MAPPED_ADDRESS.getType(); - } - - @Override - public byte[] getVariable() { - ByteBuffer byteBuffer = ByteBuffer.allocate((getFamily() == Family.IPV4) ? 4 + 32/8 : 4 + 128/8); - byteBuffer.put((byte) 0x00); - byteBuffer.put(getFamily().getEncoding()); - byteBuffer.putShort((short) getPort()); - byteBuffer.put(getAddress()); - return byteBuffer.array(); - } - -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/MessageIntegrity.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/MessageIntegrity.java deleted file mode 100644 index 00c9f4bb6b..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/MessageIntegrity.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import org.kaazing.gateway.service.turn.proxy.stun.StunAttributeFactory; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.MESSAGE_INTEGRITY; - -public class MessageIntegrity extends Attribute { - - protected final byte[] value; - - @SuppressWarnings("For now only supporting short term credentials") - private final StunAttributeFactory.CredentialType credentialType; - - public MessageIntegrity(byte[] value, StunAttributeFactory.CredentialType credentialType) { - this.value = value; - this.credentialType = credentialType; - } - - @Override - public short getType() { - return MESSAGE_INTEGRITY.getType(); - } - - @Override - public short getLength() { - return 20; - } - - @Override - public byte[] getVariable() { - return value; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - for (byte b : getVariable()) { - sb.append(String.format("%02X ", b)); - } - return String.format("%s - %s", super.toString(), sb.toString()); - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/ProxyNoopAttribute.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/ProxyNoopAttribute.java deleted file mode 100644 index 4134ab328f..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/ProxyNoopAttribute.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -/** - * When we pass Attribute through proxy without modifying or needing to understand it - * - */ -public class ProxyNoopAttribute extends Attribute { - - private final short type; - private final short length; - private final byte[] value; - - public ProxyNoopAttribute(short type, short length, byte[] value) { - this.type = type; - this.length = length; - this.value = value; - } - - @Override - public short getType() { - return type; - } - - @Override - public short getLength() { - return length; - } - - @Override - public byte[] getVariable() { - return value; - } - -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/ReservationToken.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/ReservationToken.java deleted file mode 100644 index 00c02d9691..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/ReservationToken.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.RESERVATION_TOKEN; - -public class ReservationToken extends Attribute { - - final byte[] token; - - public ReservationToken(byte[] value) { - this.token = value; - } - - @Override - public short getType() { - return RESERVATION_TOKEN.getType(); - } - - @Override - public short getLength() { - return 8; - } - - @Override - public byte[] getVariable() { - return token; - } - - public byte[] getToken() { - return token; - } - -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/Username.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/Username.java deleted file mode 100644 index 108ea189aa..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/Username.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import java.nio.charset.Charset; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.USERNAME; - -/** - * STUN USERNAME attribute as described in https://tools.ietf.org/html/rfc5389#section-15.3. - * - */ - -public class Username extends Attribute { - - private String username; - private static final Charset UTF8 = Charset.forName("UTF-8"); - - public void setUsername(String username) { - if (username.length() > 512) { - throw new InvalidAttributeException("Username MUST be at most 512 characters"); - } - this.username = username; - } - - public String getUsername() { - return username; - } - - public Username(byte[] value) { - username = new String(value, UTF8); - } - - @Override - public short getType() { - return USERNAME.getType(); - } - - @Override - public short getLength() { - return (short) (username.getBytes(UTF8).length); - } - - @Override - public byte[] getVariable() { - return username.getBytes(UTF8); - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/XorMappedAddress.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/XorMappedAddress.java deleted file mode 100644 index 5343773409..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/XorMappedAddress.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.XOR_MAPPED_ADDRESS; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; - -public class XorMappedAddress extends AbstractAddress { - - public XorMappedAddress(InetSocketAddress address, byte[] transactionId) { - super(address, transactionId); - } - - public XorMappedAddress(byte[] variable, byte[] transactionId) { - super(variable, transactionId); - setPort(xorWithMagicCookie((short) getPort())); - setAddress(xorWithMagicCookie(getAddress())); - } - - @Override - public short getType() { - return XOR_MAPPED_ADDRESS.getType(); - } - - @Override - public byte[] getVariable() { - ByteBuffer byteBuffer = ByteBuffer.allocate((getFamily() == Family.IPV4) ? 4 + 32/8 : 4 + 128/8); - byteBuffer.put((byte) 0x00); - byteBuffer.put(getFamily().getEncoding()); - byteBuffer.putShort(xorWithMagicCookie((short) getPort())); - byteBuffer.put(xorWithMagicCookie(getAddress())); - return byteBuffer.array(); - } -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/XorPeerAddress.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/XorPeerAddress.java deleted file mode 100644 index 65fd58e24d..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/XorPeerAddress.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.XOR_PEER_ADDRESS; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; - -public class XorPeerAddress extends AbstractAddress { - - public XorPeerAddress(InetSocketAddress address, byte[] transactionId) { - super(address, transactionId); - } - - public XorPeerAddress(byte[] variable, byte[] transactionId) { - super(variable, transactionId); - setPort(xorWithMagicCookie((short) getPort())); - setAddress(xorWithMagicCookie(getAddress())); - } - - @Override - public short getType() { - return XOR_PEER_ADDRESS.getType(); - } - - @Override - public byte[] getVariable() { - ByteBuffer byteBuffer = ByteBuffer.allocate((getFamily() == Family.IPV4) ? 4 + 32 / 8 : 4 + 128 / 8); - byteBuffer.put((byte) 0x00); - byteBuffer.put(getFamily().getEncoding()); - byteBuffer.putShort(xorWithMagicCookie((short) getPort())); - byteBuffer.put(xorWithMagicCookie(getAddress())); - - return byteBuffer.array(); - } - -} diff --git a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/XorRelayAddress.java b/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/XorRelayAddress.java deleted file mode 100644 index 7975b00c58..0000000000 --- a/service/turn.proxy/src/main/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/XorRelayAddress.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.XOR_RELAY_ADDRESS; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; - -public class XorRelayAddress extends AbstractAddress { - - public XorRelayAddress(InetSocketAddress address, byte[] transactionId) { - super(address, transactionId); - } - - public XorRelayAddress(byte[] variable, byte[] transactionId) { - super(variable, transactionId); - setPort(xorWithMagicCookie((short) getPort())); - setAddress(xorWithMagicCookie(getAddress())); - } - - @Override - public short getType() { - return XOR_RELAY_ADDRESS.getType(); - } - - @Override - public byte[] getVariable() { - ByteBuffer byteBuffer = ByteBuffer.allocate((getFamily() == Family.IPV4) ? 4 + 32 / 8 : 4 + 128 / 8); - byteBuffer.put((byte) 0x00); - byteBuffer.put(getFamily().getEncoding()); - byteBuffer.putShort(xorWithMagicCookie((short) getPort())); - byteBuffer.put(xorWithMagicCookie(getAddress())); - - return byteBuffer.array(); - } -} diff --git a/service/turn.proxy/src/main/resources/META-INF/services/org.kaazing.gateway.service.ServiceFactorySpi b/service/turn.proxy/src/main/resources/META-INF/services/org.kaazing.gateway.service.ServiceFactorySpi deleted file mode 100644 index 6b070a32d6..0000000000 --- a/service/turn.proxy/src/main/resources/META-INF/services/org.kaazing.gateway.service.ServiceFactorySpi +++ /dev/null @@ -1 +0,0 @@ -org.kaazing.gateway.service.turn.proxy.TurnProxyServiceFactorySpi diff --git a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/AllocationsIT.java b/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/AllocationsIT.java deleted file mode 100644 index ef1d893690..0000000000 --- a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/AllocationsIT.java +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -import static java.nio.charset.Charset.forName; -import static org.kaazing.test.util.ITUtil.createRuleChain; - -import java.io.FileInputStream; -import java.security.KeyStore; - -import javax.crypto.spec.SecretKeySpec; - -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.kaazing.gateway.server.test.GatewayRule; -import org.kaazing.gateway.server.test.config.GatewayConfiguration; -import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; -import org.kaazing.gateway.util.feature.EarlyAccessFeatures; -import org.kaazing.k3po.junit.annotation.Specification; -import org.kaazing.k3po.junit.rules.K3poRule; - -/** - * Test to validate behavior as specified in RFC 5766: TURN through TCP. - */ -public class AllocationsIT { - - private final K3poRule k3po = new K3poRule() - .setScriptRoot("org/kaazing/specification/turn/allocations") - .scriptProperty("acceptURI 'tcp://localhost:3479'"); - - private final GatewayRule gateway = new GatewayRule() { - { - KeyStore keyStore = null; - char[] password = "ab987c".toCharArray(); - try { - FileInputStream fileInStr = new FileInputStream(System.getProperty("user.dir") - + "/target/truststore/keystore.db"); - keyStore = KeyStore.getInstance("JCEKS"); - keyStore.load(fileInStr, "ab987c".toCharArray()); - keyStore.setKeyEntry( - "turn.shared.secret", - new SecretKeySpec("turnAuthenticationSharedSecret".getBytes(forName("UTF-8")), "PBEWithMD5AndDES"), - "ab987c".toCharArray(), - null - ); - } - catch (Exception e) { - e.printStackTrace(); - } - // @formatter:off - GatewayConfiguration configuration = - new GatewayConfigurationBuilder() - .property(EarlyAccessFeatures.TURN_PROXY.getPropertyName(), "true") - .service() - .accept("tcp://localhost:3478") - .connect("tcp://localhost:3479") - .type("turn.proxy") - .property("mapped.address", "192.0.2.15:8080") - .property("key.alias", "turn.shared.secret") - .property("key.algorithm", "HmacMD5") - // TODO relay adress override - //.property("relay.address.mask", propertyValue) - .done() - .security() - .keyStore(keyStore) - .keyStorePassword(password) - .done() - .done(); - // @formatter:on - init(configuration); - } - }; - - @Rule - public TestRule chain = createRuleChain(gateway, k3po); - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "simple.allocate.method/request", - "simple.allocate.method/response" }) - public void shouldSucceedWithGenericSTUNHeader() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "two.allocate.methods.with.no.credentials/request", - "two.allocate.methods.with.no.credentials/response" }) - public void shouldRespondWithTwo401sWhenGivenAllocateMethodsWithNoCred() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "allocate.method.with.requested.transport.attribute/request", - "allocate.method.with.requested.transport.attribute/response" }) - public void shouldSucceedWithOnlyTransportAttribute() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "correct.allocation.method/request", - "correct.allocation.method/response" }) - public void shouldSucceedWithCorrectAllocation() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "incorrect.attribute.length.with.error.message.length/request", - "incorrect.attribute.length.with.error.message.length/no.response"}) - public void shouldGive400WithIncorrectLength() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "incorrect.length.given/request", - "incorrect.length.given/response" }) - @Ignore("No script in specification") - public void shouldGive401IfDirectlyGivesCredentials() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "multiple.connections.with.same.credentials.responds.437/request", - "multiple.connections.with.same.credentials.responds.437/response" }) - public void shouldRespond437ToMultipleConnectionsWithSameCredentials() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "wrong.credentials.responds.441/request", - "wrong.credentials.responds.441/response" }) - public void shouldRespond441ToWrongCredentials() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "unknown.attribute.responds.420/request", - "unknown.attribute.responds.420/response" }) - public void shouldRespond420ToExtraBytes() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766 section 6: Allocations. - */ - @Test - @Specification({ - "no.requested.transport.attribute.responds.400/request", - "no.requested.transport.attribute.responds.400/response" }) - public void shouldRespond400ToAllocateWithNoRequestedTransportAttribute() throws Exception { - k3po.finish(); - } - -} - diff --git a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/MappedAutoIT.java b/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/MappedAutoIT.java deleted file mode 100644 index c0766b6bb8..0000000000 --- a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/MappedAutoIT.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -import static java.nio.charset.Charset.forName; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.rules.RuleChain.outerRule; - -import java.io.FileInputStream; -import java.security.KeyStore; - -import javax.crypto.spec.SecretKeySpec; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.DisableOnDebug; -import org.junit.rules.TestRule; -import org.junit.rules.Timeout; -import org.kaazing.gateway.server.test.GatewayRule; -import org.kaazing.gateway.server.test.config.GatewayConfiguration; -import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; -import org.kaazing.gateway.util.feature.EarlyAccessFeatures; -import org.kaazing.k3po.junit.annotation.Specification; -import org.kaazing.k3po.junit.rules.K3poRule; - -public class MappedAutoIT { - - private final K3poRule k3po = new K3poRule() - .setScriptRoot("org/kaazing/gateway/service/turn/proxy") - .scriptProperty("acceptURI 'tcp://localhost:3479'"); - - private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); - - private final GatewayRule gateway = new GatewayRule() { - { - KeyStore keyStore = null; - char[] password = "ab987c".toCharArray(); - try { - FileInputStream fileInStr = new FileInputStream(System.getProperty("user.dir") - + "/target/truststore/keystore.db"); - keyStore = KeyStore.getInstance("JCEKS"); - keyStore.load(fileInStr, "ab987c".toCharArray()); - keyStore.setKeyEntry( - "turn.shared.secret", - new SecretKeySpec("turnAuthenticationSharedSecret".getBytes(forName("UTF-8")), "PBEWithMD5AndDES"), - password, - null - ); - } - catch (Exception e) { - e.printStackTrace(); - } - // @formatter:off - GatewayConfiguration configuration = - new GatewayConfigurationBuilder() - .property(EarlyAccessFeatures.TURN_PROXY.getPropertyName(), "true") - .service() - .accept("tcp://localhost:3478") - .connect("tcp://localhost:3479") - .type("turn.proxy") - .property("mapped.address", "AUTO") - .property("key.alias", "turn.shared.secret") - .done() - .security() - .keyStore(keyStore) - .keyStorePassword(password) - .done() - .done(); - // @formatter:on - init(configuration); - } - }; - - @Rule - public final TestRule chain = outerRule(gateway).around(k3po).around(timeout); - - @Test - @Specification({ - "auto.mapped.address.test/request", - "auto.mapped.address.test/response" - }) - public void shouldPassWithDefaultTurnProtocolTest() throws Exception { - k3po.finish(); - } - - // TODO create also a test for IPv6 - // TODO create test for AUTO mask - -} diff --git a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/MaskingIT.java b/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/MaskingIT.java deleted file mode 100644 index 76b4deb2e0..0000000000 --- a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/MaskingIT.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.DisableOnDebug; -import org.junit.rules.TestRule; -import org.junit.rules.Timeout; -import org.kaazing.gateway.server.test.GatewayRule; -import org.kaazing.gateway.server.test.config.GatewayConfiguration; -import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; -import org.kaazing.gateway.util.feature.EarlyAccessFeatures; -import org.kaazing.k3po.junit.annotation.Specification; -import org.kaazing.k3po.junit.rules.K3poRule; - -import javax.crypto.spec.SecretKeySpec; -import java.io.FileInputStream; -import java.security.KeyStore; - -import static java.nio.charset.Charset.forName; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.rules.RuleChain.outerRule; - -public class MaskingIT { - - private final K3poRule k3po = new K3poRule() - .setScriptRoot("org/kaazing/gateway/service/turn/proxy") - .scriptProperty("acceptURI 'tcp://localhost:3479'"); - - private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); - - private final GatewayRule gateway = new GatewayRule() { - { - KeyStore keyStore = null; - char[] password = "ab987c".toCharArray(); - try { - FileInputStream fileInStr = new FileInputStream(System.getProperty("user.dir") - + "/target/truststore/keystore.db"); - keyStore = KeyStore.getInstance("JCEKS"); - keyStore.load(fileInStr, "ab987c".toCharArray()); - keyStore.setKeyEntry( - "turn.shared.secret", - new SecretKeySpec("turnAuthenticationSharedSecret".getBytes(forName("UTF-8")), "PBEWithMD5AndDES"), - password, - null - ); - } - catch (Exception e) { - e.printStackTrace(); - } - // @formatter:off - GatewayConfiguration configuration = - new GatewayConfigurationBuilder() - .property(EarlyAccessFeatures.TURN_PROXY.getPropertyName(), "true") - .service() - .accept("tcp://localhost:3478") - .connect("tcp://localhost:3479") - .type("turn.proxy") - .property("mapped.address", "192.0.2.15:8080") - .property("masking.key", "0x1010101") - .property("key.alias", "turn.shared.secret") - .property("key.algorithm", "HmacMD5") - .done() - .security() - .keyStore(keyStore) - .keyStorePassword(password) - .done() - .done(); - // @formatter:on - init(configuration); - } - }; - - @Rule - public final TestRule chain = outerRule(gateway).around(k3po).around(timeout); - - @Test - @Specification({ - "mask.relay.peer.address/request", - "mask.relay.peer.address/response" - }) - public void shouldPassWithDefaultTurnProtocolTest() throws Exception { - k3po.finish(); - } - - // TODO create also a test for IPv6 - -} diff --git a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/PeerConnectionIT.java b/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/PeerConnectionIT.java deleted file mode 100644 index cfbd5ac35f..0000000000 --- a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/PeerConnectionIT.java +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -import static java.nio.charset.Charset.forName; -import static org.kaazing.test.util.ITUtil.createRuleChain; - -import java.io.FileInputStream; -import java.security.KeyStore; - -import javax.crypto.spec.SecretKeySpec; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.kaazing.gateway.server.test.GatewayRule; -import org.kaazing.gateway.server.test.config.GatewayConfiguration; -import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; -import org.kaazing.gateway.util.feature.EarlyAccessFeatures; -import org.kaazing.k3po.junit.annotation.Specification; -import org.kaazing.k3po.junit.rules.K3poRule; - -public class PeerConnectionIT { - - private final K3poRule k3po = new K3poRule() - .setScriptRoot("org/kaazing/specification/turn/peer.connection") - .scriptProperty("acceptURI 'tcp://localhost:3479'"); - - private final GatewayRule gateway = new GatewayRule() { - { - KeyStore keyStore = null; - char[] password = "ab987c".toCharArray(); - try { - FileInputStream fileInStr = new FileInputStream(System.getProperty("user.dir") - + "/target/truststore/keystore.db"); - keyStore = KeyStore.getInstance("JCEKS"); - keyStore.load(fileInStr, "ab987c".toCharArray()); - keyStore.setKeyEntry( - "turn.shared.secret", - new SecretKeySpec("turnAuthenticationSharedSecret".getBytes(forName("UTF-8")), "PBEWithMD5AndDES"), - "ab987c".toCharArray(), - null - ); - } - catch (Exception e) { - e.printStackTrace(); - } - // @formatter:off - GatewayConfiguration configuration = - new GatewayConfigurationBuilder() - .property(EarlyAccessFeatures.TURN_PROXY.getPropertyName(), "true") - .service() - .accept("tcp://localhost:3478") - .connect("tcp://localhost:3479") - .type("turn.proxy") - .property("mapped.address", "192.0.2.15:8080") - .property("key.alias", "turn.shared.secret") - .property("key.algorithm", "HmacMD5") - // TODO relay adress override - //.property("relay.address.mask", propertyValue) - .done() - .security() - .keyStore(keyStore) - .keyStorePassword(password) - .done() - .done(); - // @formatter:on - init(configuration); - } - }; - - @Rule - public TestRule chain = createRuleChain(gateway, k3po); - - /** - * See RFC 5766: Turn Protocol. - */ - @Test - @Specification({ - "correct.turn.protocol/request", - "correct.turn.protocol/response"}) - public void shouldSucceedWithCorrectTURNProcess() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766: Turn Protocol. - */ - @Test - @Specification({ - "no.peer.address.with.permissions.responds.400/request", - "no.peer.address.with.permissions.responds.400/response" }) - public void shouldRespond400ToNoPeerAddressInPermissionRequest() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766: Turn Protocol. - */ - @Test - @Specification({ - "no.channel.number.with.binding.request.responds.400/request", - "no.channel.number.with.binding.request.responds.400/response" }) - public void shouldRespond400ToNoChannelNumberInBindingRequest() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766: Turn Protocol. - */ - @Test - @Specification({ - "no.peer.address.with.binding.request.responds.400/request", - "no.peer.address.with.binding.request.responds.400/response" }) - public void shouldRespond400ToNoPeerAddressInBindingRequest() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766: Turn Protocol. - */ - @Test - @Specification({ - "invalid.channel.number.responds.400/request", - "invalid.channel.number.responds.400/response" }) - public void shouldRespond400ToInvalidChannelNumber() throws Exception { - k3po.finish(); - } - - /** - * See RFC 5766: Turn Protocol. - */ - @Test - @Specification({ - "correct.turn.protocol.with.sent.data/request", - "correct.turn.protocol.with.sent.data/response" }) - public void shouldSuccessfullySendData() throws Exception { - k3po.finish(); - } - - -} diff --git a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/TurnProxyIT.java b/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/TurnProxyIT.java deleted file mode 100644 index fcf1e3be30..0000000000 --- a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/TurnProxyIT.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy; - -import static java.nio.charset.Charset.forName; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.rules.RuleChain.outerRule; - -import java.io.FileInputStream; -import java.security.KeyStore; - -import javax.crypto.spec.SecretKeySpec; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.DisableOnDebug; -import org.junit.rules.TestRule; -import org.junit.rules.Timeout; -import org.kaazing.gateway.server.test.GatewayRule; -import org.kaazing.gateway.server.test.config.GatewayConfiguration; -import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; -import org.kaazing.gateway.util.feature.EarlyAccessFeatures; -import org.kaazing.k3po.junit.annotation.Specification; -import org.kaazing.k3po.junit.rules.K3poRule; - -public class TurnProxyIT { - - private final K3poRule k3po = new K3poRule() - .setScriptRoot("org/kaazing/gateway/service/turn/proxy") - .scriptProperty("acceptURI 'tcp://localhost:3479'"); - - private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); - - private final GatewayRule gateway = new GatewayRule() { - { - KeyStore keyStore = null; - char[] password = "ab987c".toCharArray(); - try { - FileInputStream fileInStr = new FileInputStream(System.getProperty("user.dir") - + "/target/truststore/keystore.db"); - keyStore = KeyStore.getInstance("JCEKS"); - keyStore.load(fileInStr, "ab987c".toCharArray()); - keyStore.setKeyEntry( - "turn.shared.secret", - new SecretKeySpec("turnAuthenticationSharedSecret".getBytes(forName("UTF-8")), "PBEWithMD5AndDES"), - password, - null - ); - } - catch (Exception e) { - e.printStackTrace(); - } - // @formatter:off - GatewayConfiguration configuration = - new GatewayConfigurationBuilder() - .property(EarlyAccessFeatures.TURN_PROXY.getPropertyName(), "true") - .service() - .accept("tcp://localhost:3478") - .connect("tcp://localhost:3479") - .type("turn.proxy") - .property("mapped.address", "192.0.2.15:8080") - .property("key.alias", "turn.shared.secret") - .property("key.algorithm", "HmacMD5") - .done() - .security() - .keyStore(keyStore) - .keyStorePassword(password) - .done() - .done(); - // @formatter:on - init(configuration); - } - }; - - @Rule - public final TestRule chain = outerRule(gateway).around(k3po).around(timeout); - - @Test - @Specification({ - "default.turn.protocol.test/request", - "default.turn.protocol.test/response" - }) - public void shouldPassWithDefaultTurnProtocolTest() throws Exception { - k3po.finish(); - } - - // TODO create also a test for IPv6 - // TODO create test for AUTO mask - -} diff --git a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/AttributeTest.java b/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/AttributeTest.java deleted file mode 100644 index 1e94a8522b..0000000000 --- a/service/turn.proxy/src/test/java/org/kaazing/gateway/service/turn/proxy/stun/attributes/AttributeTest.java +++ /dev/null @@ -1,396 +0,0 @@ -/** - * Copyright 2007-2016, Kaazing Corporation. All rights reserved. - * - * Licensed 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.kaazing.gateway.service.turn.proxy.stun.attributes; - -import static org.kaazing.gateway.service.turn.proxy.stun.StunAttributeFactory.CredentialType.SHORT_TERM; -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.EVEN_PORT; -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.FINGERPRINT; -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.MAPPED_ADDRESS; -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.MESSAGE_INTEGRITY; -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.RESERVATION_TOKEN; -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.XOR_MAPPED_ADDRESS; -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.XOR_PEER_ADDRESS; -import static org.kaazing.gateway.service.turn.proxy.stun.attributes.AttributeType.XOR_RELAY_ADDRESS; - -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.util.Arrays; - -import org.junit.Assert; -import org.junit.Assume; -import org.junit.BeforeClass; -import org.junit.Test; -import org.kaazing.gateway.service.turn.proxy.stun.StunAttributeFactory; - -public class AttributeTest { - - private final StunAttributeFactory factory = new StunAttributeFactory(SHORT_TERM); - private static byte[] IPV4_ADDR_1, IPV4_ADDR_2, IPV6_ADDR_1, IPV6_ADDR_2; - - @BeforeClass - public static void init() throws UnknownHostException { - IPV4_ADDR_1 = Inet4Address.getByName("127.0.0.1").getAddress(); - IPV4_ADDR_2 = Inet4Address.getByName("255.255.255.255").getAddress(); - IPV6_ADDR_1 = Inet6Address.getByName("2001:0db8:85a3:0000:0000:8a2e:0370:7334").getAddress(); - IPV6_ADDR_2 = Inet6Address.getByName("5555:5555:5555:5555:5555:5555:5555:5555").getAddress(); - } - - @Test - public void mappedAddressIpv4() { - // Construct Attribute - short type = MAPPED_ADDRESS.getType(); - short length = 4 + (32 / 8); - ByteBuffer buf = constructBytesAddress((byte) 0x01, (short) 8080, IPV4_ADDR_1); - - // Assert constructed - Attribute attr = factory.get(type, length, buf.array(), null); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - - // Test Mapped Attribute setters - MappedAddress mappedAddressAttribute = (MappedAddress) attr; - mappedAddressAttribute.setAddress(IPV4_ADDR_2); - mappedAddressAttribute.setPort(8000); - - ByteBuffer newValue = constructBytesAddress((byte) 0x01, (short) 8000, IPV4_ADDR_2); - - // Assert Mapped Attribute Characteristics ok - Assert.assertEquals(mappedAddressAttribute.getType(), type); - Assert.assertTrue(Arrays.equals(mappedAddressAttribute.getVariable(), newValue.array())); - - // Assert Attribute ok - Assert.assertEquals(mappedAddressAttribute.getLength(), length); - Assert.assertEquals(mappedAddressAttribute.getAddress(), IPV4_ADDR_2); - } - - @Test - public void mappedAddressIpv6() throws UnknownHostException { - short type = MAPPED_ADDRESS.getType(); - short length = 4 + (128 / 8); - ByteBuffer buf = constructBytesAddress((byte) 0x02, (short) 8080, IPV6_ADDR_1); - - Attribute attr = factory.get(type, length, buf.array(), null); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - - MappedAddress mappedAddressAttribute = (MappedAddress) attr; - mappedAddressAttribute.setAddress(IPV6_ADDR_2); - mappedAddressAttribute.setPort(8000); - - ByteBuffer newValue = constructBytesAddress((byte) 0x02, (short) 8000, IPV6_ADDR_2); - - Assert.assertEquals(mappedAddressAttribute.getType(), type); - Assert.assertTrue(Arrays.equals(mappedAddressAttribute.getVariable(), newValue.array())); - - Assert.assertEquals(mappedAddressAttribute.getLength(), length); - Assert.assertEquals(mappedAddressAttribute.getAddress(), IPV6_ADDR_2); - } - - @Test - public void noopAttribute() { - // Random type 0x0030 - short type = 0x0030; - short length = 8; - ByteBuffer buf = ByteBuffer.allocate(length); - - Attribute attr = factory.get(type, length, buf.array(), null); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - } - - @Test - public void username() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void messageIntegrity() { - short type = MESSAGE_INTEGRITY.getType(); - short length = 20; - byte[] key = new byte[]{(byte) 0x84, (byte) 0x93, (byte) 0xfb, (byte) 0xc5, (byte) 0x3b, (byte) 0xa5, (byte) 0x82, - (byte) 0xfb, (byte) 0x4c, (byte) 0x04, (byte) 0x4c, (byte) 0x45, (byte) 0x6b, (byte) 0xdc, (byte) 0x40, - (byte) 0xeb, (byte) 0x45, (byte) 0xf2, (byte) 0x97}; - Attribute attr = factory.get(type, length, key, null); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), key)); - - Assert.assertTrue(attr instanceof MessageIntegrity); - } - - @Test - public void errorCode() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void unknownErrorCode() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void relam() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void nonce() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void xorMappedAddressIpv4() { - short type = XOR_MAPPED_ADDRESS.getType(); - short length = 4 + (32 / 8); - ByteBuffer buf = constructBytesAddress((byte) 0x01, (short) 8080, IPV4_ADDR_1); - - Attribute attr = factory.get(type, length, buf.array(), new byte[]{1,2,3,4,5,6,7,8,9,10,11,12}); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - - XorMappedAddress xorMappedAddressAttribute = (XorMappedAddress) attr; - Assert.assertEquals(xorMappedAddressAttribute.getPort(), 0x3e82); - Assert.assertTrue(Arrays.equals(xorMappedAddressAttribute.getAddress(), new byte[]{0x5e, 0x12, (byte) 0xa4, 0x43})); - - xorMappedAddressAttribute.setPort(xorMappedAddressAttribute.xorWithMagicCookie((short) 8000)); - xorMappedAddressAttribute.setAddress(xorMappedAddressAttribute.xorWithMagicCookie(IPV4_ADDR_2)); - - ByteBuffer newValue = constructBytesAddress((byte) 0x01, (short) 8000, IPV4_ADDR_2); - - Assert.assertEquals(xorMappedAddressAttribute.getLength(), length); - Assert.assertEquals(xorMappedAddressAttribute.getType(), type); - Assert.assertTrue(Arrays.equals(xorMappedAddressAttribute.getVariable(), newValue.array())); - } - - @Test - public void xorMappedAddressIpv6() throws UnknownHostException { - short type = XOR_MAPPED_ADDRESS.getType(); - short length = 4 + (128 / 8); - ByteBuffer buf = constructBytesAddress((byte) 0x02, (short) 8080, IPV6_ADDR_1); - - Attribute attr = factory.get(type, length, buf.array(), new byte[]{1,2,3,4,5,6,7,8,9,10,11,12}); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - - XorMappedAddress xorMappedAddressAttribute = (XorMappedAddress) attr; - xorMappedAddressAttribute.setAddress(xorMappedAddressAttribute.xorWithMagicCookie(IPV6_ADDR_2)); - xorMappedAddressAttribute.setPort(xorMappedAddressAttribute.xorWithMagicCookie((short) 8000)); - - ByteBuffer newValue = constructBytesAddress((byte) 0x02, (short) 8000, IPV6_ADDR_2); - - Assert.assertEquals(xorMappedAddressAttribute.getLength(), length); - Assert.assertEquals(xorMappedAddressAttribute.getType(), type); - Assert.assertTrue(Arrays.equals(xorMappedAddressAttribute.getVariable(), newValue.array())); - } - - @Test - public void software() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void alternativeServer() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void fingerprint() { - short type = FINGERPRINT.getType(); - short length = 16; - ByteBuffer buf = ByteBuffer.allocate(length); - - Attribute attr = factory.get(type, length, buf.array(), null); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - - Assert.assertTrue(attr instanceof Fingerprint); - } - - @Test - public void channelNumber() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void lifetime() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void xorPeerAddressIPv4() { - short type = XOR_PEER_ADDRESS.getType(); - short length = 4 + (32 / 8); - ByteBuffer buf = constructBytesAddress((byte) 0x01, (short) 8080, IPV4_ADDR_1); - - Attribute attr = factory.get(type, length, buf.array(), new byte[]{1,2,3,4,5,6,7,8,9,10,11,12}); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - - XorPeerAddress xorPeerAddressAttribute = (XorPeerAddress) attr; - Assert.assertEquals(xorPeerAddressAttribute.getPort(), 0x3e82); - Assert.assertTrue(Arrays.equals(xorPeerAddressAttribute.getAddress(), new byte[]{0x5e, 0x12, (byte) 0xa4, 0x43})); - - xorPeerAddressAttribute.setPort(xorPeerAddressAttribute.xorWithMagicCookie((short) 8000)); - xorPeerAddressAttribute.setAddress(xorPeerAddressAttribute.xorWithMagicCookie(IPV4_ADDR_2)); - - ByteBuffer newValue = constructBytesAddress((byte) 0x01, (short) 8000, IPV4_ADDR_2); - - Assert.assertEquals(xorPeerAddressAttribute.getLength(), length); - Assert.assertEquals(xorPeerAddressAttribute.getType(), type); - Assert.assertTrue(Arrays.equals(xorPeerAddressAttribute.getVariable(), newValue.array())); - } - - @Test - public void xorPeerAddressIPv6() throws UnknownHostException { - short type = XOR_PEER_ADDRESS.getType(); - short length = 4 + (128 / 8); - ByteBuffer buf = constructBytesAddress((byte) 0x02, (short) 8080, IPV6_ADDR_1); - - Attribute attr = factory.get(type, length, buf.array(), new byte[]{1,2,3,4,5,6,7,8,9,10,11,12}); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - - XorPeerAddress xorPeerAddressAttribute = (XorPeerAddress) attr; - xorPeerAddressAttribute.setAddress(xorPeerAddressAttribute.xorWithMagicCookie(IPV6_ADDR_2)); - xorPeerAddressAttribute.setPort(xorPeerAddressAttribute.xorWithMagicCookie((short) 8000)); - - ByteBuffer newValue = constructBytesAddress((byte) 0x02, (short) 8000, IPV6_ADDR_2); - Assert.assertEquals(xorPeerAddressAttribute.getLength(), length); - Assert.assertEquals(xorPeerAddressAttribute.getType(), type); - Assert.assertTrue(Arrays.equals(xorPeerAddressAttribute.getVariable(), newValue.array())); - } - - @Test - public void data() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void xorRelayAddressIPv4() { - short type = XOR_RELAY_ADDRESS.getType(); - short length = 4 + (32 / 8); - ByteBuffer buf = constructBytesAddress((byte) 0x01, (short) 8080, IPV4_ADDR_1); - - Attribute attr = factory.get(type, length, buf.array(), new byte[]{0,0,0,0,0,0,0,0,0,0,0,0}); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - - XorRelayAddress xorMappedAddressAttribute = (XorRelayAddress) attr; - Assert.assertEquals(xorMappedAddressAttribute.getPort(), 0x3e82); - Assert.assertTrue(Arrays.equals(xorMappedAddressAttribute.getAddress(), new byte[]{0x5e, 0x12, (byte) 0xa4, 0x43})); - - xorMappedAddressAttribute.setPort(xorMappedAddressAttribute.xorWithMagicCookie((short) 8000)); - xorMappedAddressAttribute.setAddress(xorMappedAddressAttribute.xorWithMagicCookie(IPV4_ADDR_2)); - - ByteBuffer newValue = constructBytesAddress((byte) 0x01, (short) 8000, IPV4_ADDR_2); - - Assert.assertEquals(xorMappedAddressAttribute.getLength(), length); - Assert.assertEquals(xorMappedAddressAttribute.getType(), type); - Assert.assertTrue(Arrays.equals(xorMappedAddressAttribute.getVariable(), newValue.array())); - } - - @Test - public void xorRelayAddressIPv6() throws UnknownHostException { - short type = XOR_RELAY_ADDRESS.getType(); - short length = 4 + (128 / 8); - ByteBuffer buf = constructBytesAddress((byte) 0x02, (short) 8080, IPV6_ADDR_1); - - Attribute attr = factory.get(type, length, buf.array(), new byte[]{1,2,3,4,5,6,7,8,9,10,11,12}); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), buf.array())); - - XorRelayAddress xorRelayAddressAttribute = (XorRelayAddress) attr; - xorRelayAddressAttribute.setAddress(xorRelayAddressAttribute.xorWithMagicCookie(IPV6_ADDR_2)); - xorRelayAddressAttribute.setPort(xorRelayAddressAttribute.xorWithMagicCookie((short) 8000)); - - ByteBuffer newValue = constructBytesAddress((byte) 0x02, (short) 8000, IPV6_ADDR_2); - - Assert.assertEquals(xorRelayAddressAttribute.getLength(), length); - Assert.assertEquals(xorRelayAddressAttribute.getType(), type); - Assert.assertTrue(Arrays.equals(xorRelayAddressAttribute.getVariable(), newValue.array())); - } - - @Test - public void evenPort() { - short type = EVEN_PORT.getType(); - short length = 4; - byte[] data = new byte[4]; - data[0] = (byte) 0x00; - - Attribute attr = factory.get(type, length, data, null); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), data)); - - Assert.assertTrue(attr instanceof ProxyNoopAttribute); -// -// EvenPort ep = (EvenPort) attr; -// Assert.assertFalse(ep.getReserveNextHigherPort()); -// ep.setReserveNextHigherPort(true); -// data[0] = (byte) 0x80; -// Assert.assertEquals(ep.getLength(), length); -// Assert.assertEquals(ep.getType(), type); -// Assert.assertTrue(Arrays.equals(ep.getVariable(), data)); - } - - @Test - public void requestedTransport() { - // TODO: We should implement this and enforce UDP for now. - // https://tools.ietf.org/html/rfc5766#section-14.7 - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void dontFragment() { - Assume.assumeTrue("Not implemented", true); - } - - @Test - public void reservationToken() { - short type = RESERVATION_TOKEN.getType(); - short length = 8; - byte[] token = new byte[]{0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}; - Attribute attr = factory.get(type, length, token, null); - Assert.assertEquals(attr.getLength(), length); - Assert.assertEquals(attr.getType(), type); - Assert.assertTrue(Arrays.equals(attr.getVariable(), token)); - - Assert.assertTrue(attr instanceof ProxyNoopAttribute); - } - - private ByteBuffer constructBytesAddress(byte family, short port, byte[] address) { - ByteBuffer buf = ByteBuffer.allocate(2 + 2 + address.length); - buf.put(0, (byte) 0x00); - buf.put(1, family); - buf.putShort(2, port); - for (int i = 0; i < address.length; i++) { - buf.put(i + 4, address[i]); - } - return buf; - } -} diff --git a/service/turn.proxy/src/test/resources/META-INF/services/org.kaazing.gateway.service.proxy.ProxyServiceExtensionSpi b/service/turn.proxy/src/test/resources/META-INF/services/org.kaazing.gateway.service.proxy.ProxyServiceExtensionSpi deleted file mode 100644 index 1101915fbc..0000000000 --- a/service/turn.proxy/src/test/resources/META-INF/services/org.kaazing.gateway.service.proxy.ProxyServiceExtensionSpi +++ /dev/null @@ -1 +0,0 @@ -org.kaazing.gateway.service.proxy.TestExtension diff --git a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/auto.mapped.address.test/request.rpt b/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/auto.mapped.address.test/request.rpt deleted file mode 100644 index 516818eaaf..0000000000 --- a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/auto.mapped.address.test/request.rpt +++ /dev/null @@ -1,172 +0,0 @@ -# -# Copyright 2007-2016, Kaazing Corporation. All rights reserved. -# -# Licensed 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. -# - -property connectURI "tcp://localhost:3478" -property peerAddressClient "192.0.2.1" -property messageIntegrityClient "joe:example.com:welcome" - -connect ${connectURI} -connected - -##Allocation request -#STUN Message type | Message Length (***CONFIRM***) -write [0x00] [0x03] [0x00] [0x14] -#Magic Cookie (fixed required value 0x2112A442) -write [0x21] [0x12] [0xa4] [0x42] -#Transaction ID -write ${turn:generateTransactionId()} -#Attribute Lifetime -write [0x00] [0x0d] [0x00] [0x04] -write [0x00] [0x00] [0x0e] [0x10] #3600 seconds -#Attribute Requested Transport (Type|Length|Value) -write [0x00] [0x19] [0x00] [0x01] -write [0x11] [0x00] [0x00] [0x00] #UDP is 17 -#Attribute Don't Fragment (Type|Length|Value) -write [0x00] [0x1a] [0x00] [0x00] - -#401 Allocation error response -read [0x01] [0x13] [0x00] [0x48] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attributes -read [0x00] [0x09] [0x00] [0x10] -read [0x00] [0x00] [0x04] [0x01] -read "Unauthorized" -#Attributes -read [0x00] [0x14] [0x00] [0x0b] -read ([0..11]:realm) [0x00] -#Attributes -read [0x00] [0x15] [0x00] [0x20] -read ([0..32]:nonce) - -##Allocation request (w/ cred) -#STUN Message type | Message Length (***CONFIRM***) -write [0x00] [0x03] [0x00] [0x68] -#Magic Cookie -write [0x21] [0x12] [0xa4] [0x42] -#Transaction ID -write ${turn:generateTransactionId()} -#Attribute Lifetime -write [0x00] [0x0d] [0x00] [0x04] -write [0x00] [0x00] [0x0e] [0x10] #3600 seconds -#Attribute Requested Transport -write [0x00] [0x19] [0x00] [0x01] -write [0x11] [0x00] [0x00] [0x00] -#Attribute Don't Fragment -write [0x00] [0x1a] [0x00] [0x00] -#Attribute Username -write [0x00] [0x06] [0x00] [0x03] -write "joe" [0x00] -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] [0x14] -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#200 response w/ mapped address -#STUN Header -read [0x01] [0x03] [0x00] [0x40] -read [0x21] [0x12] [0xa4] [0x42] -read ${transactionID} -#Attribute Lifetime -read [0x00] [0x0d] [0x00] [0x04] -read [0x00] [0x00] [0x0c] [0x08] -#Attribute XOR-Relayed-Address -read [0x00] [0x16] [0x00] [0x08] -read [0x00] [0x01] -read [0..2] -read ([0..4]:relay_address) -#Attribute XOR-Mapped-Address -read [0x00] [0x20] [0x00] [0x08] -read [0x00] [0x01] -read [0..2] -read [0..4] -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] (byte:length) -read ([0..${length}]:messageDigest) -#Attribute Fingerprint -read [0x80] [0x28] [0x00] [0x04] -read ([0..4]:fingerprint) - -#creates permissions for peer -#STUN Header -write [0x00] [0x08] [0x00] [0x5c] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute XOR-Peer-Address -write [0x00] [0x12] [0x00] [0x08] - -# unmodified peer address -write [0x00] [0x01] ${turn:portXOR(8001)} #port 8001 -write ${turn:ipXOR("192.0.2.16")} - -#Attribute Username -write [0x00] [0x06] [0x00] [0x03] -write "joe" [0x00] -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#success -read [0x01] [0x08] [0x00] [0x18] -read [0x21] [0x12] [0xa4] [0x42] -read ${transactionID} -#attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ${messageDigest} - -#binds to peer -#STUN Header -write [0x00] [0x09] [0x00] [0x68] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute Channel-Number -write [0x00] [0x0c] [0x00] [0x02] -write [0x40] [0x00] [0x00] [0x00] -#Attribute XOR-Peer-Address -write [0x00] [0x12] [0x00] [0x08] -write [0xff] [0x01] ${turn:portXOR(8001)} #port 8001 -write ${turn:ipXOR("192.0.2.1")} -#Attribute Username -write [0x00] [0x06] [0x00] [0x03] -write "joe" [0x00] -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] [0x14] -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#success -read [0x01] [0x09] [0x00] [0x18] -read [0x21] [0x12] [0xa4] [0x42] -read ${transactionID} -#attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ${messageDigest} diff --git a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/auto.mapped.address.test/response.rpt b/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/auto.mapped.address.test/response.rpt deleted file mode 100644 index dc900f652f..0000000000 --- a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/auto.mapped.address.test/response.rpt +++ /dev/null @@ -1,161 +0,0 @@ -# -# Copyright 2007-2016, Kaazing Corporation. All rights reserved. -# -# Licensed 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. -# - -property realm "example.com" -property nonce "adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" -property acceptURI "tcp://localhost:3478" - -accept ${acceptURI} -accepted -connected - -##Allocation request -read [0x00] [0x03] [0x00] [0x14] #Type|MessageLength -read [0x21] [0x12] [0xa4] [0x42] #Magic cookie -read ([0..12]:transactionID) -#Attributes -read [0x00] [0x0d] [0x00] [0x04] -read [0x00] [0x00] [0x0e] [0x10] -#Attributes -read [0x00] [0x19] [0x00] [0x01] -read [0x11] [0x00] [0x00] [0x00] -#Attributes -read [0x00] [0x1a] [0x00] [0x00] - -##401 Allocation error response -#STUN Header -write [0x01] [0x13] [0x00] [0x48] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute ERROR-CODE -write [0x00] [0x09] [0x00] [0x10] -write [0x00] [0x00] [0x04] [0x01] -write "Unauthorized" -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} - -#allocation request (w/ cred) -read [0x00] [0x03] [0x00] [0x68] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attribute Lifetime -read [0x00] [0x0d] [0x00] [0x04] -read [0x00] [0x00] [0x0e] [0x10] #3600 seconds -#Attribute Requested Transport -read [0x00] [0x19] [0x00] [0x01] -read [0x11] [0x00] [0x00] [0x00] -#Attribute Don't Fragment -read [0x00] [0x1a] [0x00] [0x00] -#Attribute Username -read [0x00] [0x06] [0x00] [0x03] -read "joe" [0x00] -#Attribute Realm -read [0x00] [0x14] [0x00] [0x0b] -read ${realm} [0x00] -#Attribute Nonce -read [0x00] [0x15] [0x00] [0x20] -read ${nonce} -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] (byte:length) -read ([0..${length}]:messageDigest) - -#200 response w/ mapped address -#STUN Header -write [0x01] [0x03] [0x00] [0x40] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute Lifetime -write [0x00] [0x0d] [0x00] [0x04] -write [0x00] [0x00] [0x0c] [0x08] #3080 seconds -#Attribute XOR-Relayed-Address -write [0x00] [0x16] [0x00] [0x08] -write [0x00] [0x01] ${turn:portXOR(8080)} #port 8080 -write ${turn:ipXOR("192.0.2.16")} -#Attribute XOR-Mapped-Address -write [0x00] [0x20] [0x00] [0x08] -write [0xff] [0x01] ${turn:portXOR(8080)} #port 8080 -write ${turn:ipXOR("192.0.2.16")} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} -#Attribute Fingerprint - dummy content -write [0x80] [0x28] [0x00] [0x04] -write [0x00] [0x00] [0x00] [0x00] - -#creates permissions for peer -read [0x00] [0x08] [0x00] [0x60] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attribute XOR-Peer-Address -read [0x00] [0x12] [0x00] [0x08] -read [0x00] [0x01] ${turn:portXOR(8001)} #port 8001 -read ([0..4]:peer_address) -#Attribute Username -read [0x00] [0x06] [0x00] [0x03] -read "joe" [0x00] -#Attribute Realm -read [0x00] [0x14] [0x00] [0x0b] -read ${realm} [0x00] -#Attribute Nonce -read [0x00] [0x15] [0x00] [0x20] -read ${nonce} -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ([0..${length}]:messageDigest) - -#success -write [0x01] [0x08] [0x00] [0x14] -write [0x21] [0x12] [0xa4] [0x42] -write ${transactionID} -#attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#binds to peer -read [0x00] [0x09] [0x00] [0x68] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attribute Channel-Number -read [0x00] [0x0c] [0x00] [0x02] -read [0x40] [0x00] [0x00] [0x00] -#Attribute XOR-Peer-Address -read [0x00] [0x12] [0x00] [0x08] -read [0x00] [0x01] ${turn:portXOR(8001)} #port 8001 -read ([0..4]:peer_address) -#Attribute Username -read [0x00] [0x06] [0x00] [0x03] -read "joe" [0x00] -#Attribute Realm -read [0x00] [0x14] [0x00] [0x0b] -read ${realm} [0x00] -#Attribute Nonce -read [0x00] [0x15] [0x00] [0x20] -read ${nonce} -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ([0..${length}]:messageDigest) - -#success -write [0x01] [0x09] [0x00] [0x18] -write [0x21] [0x12] [0xa4] [0x42] -write ${transactionID} -#attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} diff --git a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/default.turn.protocol.test/request.rpt b/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/default.turn.protocol.test/request.rpt deleted file mode 100644 index 7ea4c41e03..0000000000 --- a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/default.turn.protocol.test/request.rpt +++ /dev/null @@ -1,170 +0,0 @@ -# -# Copyright 2007-2016, Kaazing Corporation. All rights reserved. -# -# Licensed 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. -# - -property connectURI "tcp://localhost:3478" -property peerAddressClient "192.0.2.1" -property messageIntegrityClient "joe:example.com:welcome" - -connect ${connectURI} -connected - -##Allocation request -#STUN Message type | Message Length (***CONFIRM***) -write [0x00] [0x03] [0x00] [0x14] -#Magic Cookie (fixed required value 0x2112A442) -write [0x21] [0x12] [0xa4] [0x42] -#Transaction ID -write ${turn:generateTransactionId()} -#Attribute Lifetime -write [0x00] [0x0d] [0x00] [0x04] -write [0x00] [0x00] [0x0e] [0x10] #3600 seconds -#Attribute Requested Transport (Type|Length|Value) -write [0x00] [0x19] [0x00] [0x01] -write [0x11] [0x00] [0x00] [0x00] #UDP is 17 -#Attribute Don't Fragment (Type|Length|Value) -write [0x00] [0x1a] [0x00] [0x00] - -#401 Allocation error response -read [0x01] [0x13] [0x00] [0x48] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attributes -read [0x00] [0x09] [0x00] [0x10] -read [0x00] [0x00] [0x04] [0x01] -read "Unauthorized" -#Attributes -read [0x00] [0x14] [0x00] [0x0b] -read ([0..11]:realm) [0x00] -#Attributes -read [0x00] [0x15] [0x00] [0x20] -read ([0..32]:nonce) - -##Allocation request (w/ cred) -#STUN Message type | Message Length (***CONFIRM***) -write [0x00] [0x03] [0x00] [0x68] -#Magic Cookie -write [0x21] [0x12] [0xa4] [0x42] -#Transaction ID -write ${turn:generateTransactionId()} -#Attribute Lifetime -write [0x00] [0x0d] [0x00] [0x04] -write [0x00] [0x00] [0x0e] [0x10] #3600 seconds -#Attribute Requested Transport -write [0x00] [0x19] [0x00] [0x01] -write [0x11] [0x00] [0x00] [0x00] -#Attribute Don't Fragment -write [0x00] [0x1a] [0x00] [0x00] -#Attribute Username -write [0x00] [0x06] [0x00] [0x03] -write "joe" [0x00] -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] [0x14] -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#200 response w/ mapped address -#STUN Header -read [0x01] [0x03] [0x00] [0x40] -read [0x21] [0x12] [0xa4] [0x42] -read ${transactionID} -#Attribute Lifetime -read [0x00] [0x0d] [0x00] [0x04] -read [0x00] [0x00] [0x0c] [0x08] -#Attribute XOR-Relayed-Address -read [0x00] [0x16] [0x00] [0x08] -read [0x00] [0x01] ${turn:portXOR(8080)} #port 8080 -read ([0..4]:relay_address) -#Attribute XOR-Mapped-Address -read [0x00] [0x20] [0x00] [0x08] -read [0x00] [0x01] ${turn:portXOR(8080)} #port 8080 -read [0xE1] [0x12] [0xA6] [0x4D] -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] (byte:length) -read ([0..${length}]:messageDigest) -#Attribute Fingerprint -read [0x80] [0x28] [0x00] [0x04] -read ([0..4]:fingerprint) - -#creates permissions for peer -#STUN Header -write [0x00] [0x08] [0x00] [0x5c] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute XOR-Peer-Address -write [0x00] [0x12] [0x00] [0x08] - -# unmodified peer address -write [0x00] [0x01] ${turn:portXOR(8001)} #port 8001 -write ${turn:ipXOR("192.0.2.16")} - -#Attribute Username -write [0x00] [0x06] [0x00] [0x03] -write "joe" [0x00] -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#success -read [0x01] [0x08] [0x00] [0x18] -read [0x21] [0x12] [0xa4] [0x42] -read ${transactionID} -#attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ${messageDigest} - -#binds to peer -#STUN Header -write [0x00] [0x09] [0x00] [0x68] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute Channel-Number -write [0x00] [0x0c] [0x00] [0x02] -write [0x40] [0x00] [0x00] [0x00] -#Attribute XOR-Peer-Address -write [0x00] [0x12] [0x00] [0x08] -write [0xff] [0x01] ${turn:portXOR(8001)} #port 8001 -write ${turn:ipXOR("192.0.2.1")} -#Attribute Username -write [0x00] [0x06] [0x00] [0x03] -write "joe" [0x00] -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] [0x14] -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#success -read [0x01] [0x09] [0x00] [0x18] -read [0x21] [0x12] [0xa4] [0x42] -read ${transactionID} -#attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ${messageDigest} diff --git a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/default.turn.protocol.test/response.rpt b/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/default.turn.protocol.test/response.rpt deleted file mode 100644 index dc900f652f..0000000000 --- a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/default.turn.protocol.test/response.rpt +++ /dev/null @@ -1,161 +0,0 @@ -# -# Copyright 2007-2016, Kaazing Corporation. All rights reserved. -# -# Licensed 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. -# - -property realm "example.com" -property nonce "adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" -property acceptURI "tcp://localhost:3478" - -accept ${acceptURI} -accepted -connected - -##Allocation request -read [0x00] [0x03] [0x00] [0x14] #Type|MessageLength -read [0x21] [0x12] [0xa4] [0x42] #Magic cookie -read ([0..12]:transactionID) -#Attributes -read [0x00] [0x0d] [0x00] [0x04] -read [0x00] [0x00] [0x0e] [0x10] -#Attributes -read [0x00] [0x19] [0x00] [0x01] -read [0x11] [0x00] [0x00] [0x00] -#Attributes -read [0x00] [0x1a] [0x00] [0x00] - -##401 Allocation error response -#STUN Header -write [0x01] [0x13] [0x00] [0x48] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute ERROR-CODE -write [0x00] [0x09] [0x00] [0x10] -write [0x00] [0x00] [0x04] [0x01] -write "Unauthorized" -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} - -#allocation request (w/ cred) -read [0x00] [0x03] [0x00] [0x68] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attribute Lifetime -read [0x00] [0x0d] [0x00] [0x04] -read [0x00] [0x00] [0x0e] [0x10] #3600 seconds -#Attribute Requested Transport -read [0x00] [0x19] [0x00] [0x01] -read [0x11] [0x00] [0x00] [0x00] -#Attribute Don't Fragment -read [0x00] [0x1a] [0x00] [0x00] -#Attribute Username -read [0x00] [0x06] [0x00] [0x03] -read "joe" [0x00] -#Attribute Realm -read [0x00] [0x14] [0x00] [0x0b] -read ${realm} [0x00] -#Attribute Nonce -read [0x00] [0x15] [0x00] [0x20] -read ${nonce} -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] (byte:length) -read ([0..${length}]:messageDigest) - -#200 response w/ mapped address -#STUN Header -write [0x01] [0x03] [0x00] [0x40] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute Lifetime -write [0x00] [0x0d] [0x00] [0x04] -write [0x00] [0x00] [0x0c] [0x08] #3080 seconds -#Attribute XOR-Relayed-Address -write [0x00] [0x16] [0x00] [0x08] -write [0x00] [0x01] ${turn:portXOR(8080)} #port 8080 -write ${turn:ipXOR("192.0.2.16")} -#Attribute XOR-Mapped-Address -write [0x00] [0x20] [0x00] [0x08] -write [0xff] [0x01] ${turn:portXOR(8080)} #port 8080 -write ${turn:ipXOR("192.0.2.16")} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} -#Attribute Fingerprint - dummy content -write [0x80] [0x28] [0x00] [0x04] -write [0x00] [0x00] [0x00] [0x00] - -#creates permissions for peer -read [0x00] [0x08] [0x00] [0x60] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attribute XOR-Peer-Address -read [0x00] [0x12] [0x00] [0x08] -read [0x00] [0x01] ${turn:portXOR(8001)} #port 8001 -read ([0..4]:peer_address) -#Attribute Username -read [0x00] [0x06] [0x00] [0x03] -read "joe" [0x00] -#Attribute Realm -read [0x00] [0x14] [0x00] [0x0b] -read ${realm} [0x00] -#Attribute Nonce -read [0x00] [0x15] [0x00] [0x20] -read ${nonce} -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ([0..${length}]:messageDigest) - -#success -write [0x01] [0x08] [0x00] [0x14] -write [0x21] [0x12] [0xa4] [0x42] -write ${transactionID} -#attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#binds to peer -read [0x00] [0x09] [0x00] [0x68] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attribute Channel-Number -read [0x00] [0x0c] [0x00] [0x02] -read [0x40] [0x00] [0x00] [0x00] -#Attribute XOR-Peer-Address -read [0x00] [0x12] [0x00] [0x08] -read [0x00] [0x01] ${turn:portXOR(8001)} #port 8001 -read ([0..4]:peer_address) -#Attribute Username -read [0x00] [0x06] [0x00] [0x03] -read "joe" [0x00] -#Attribute Realm -read [0x00] [0x14] [0x00] [0x0b] -read ${realm} [0x00] -#Attribute Nonce -read [0x00] [0x15] [0x00] [0x20] -read ${nonce} -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ([0..${length}]:messageDigest) - -#success -write [0x01] [0x09] [0x00] [0x18] -write [0x21] [0x12] [0xa4] [0x42] -write ${transactionID} -#attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} diff --git a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/mask.relay.peer.address/request.rpt b/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/mask.relay.peer.address/request.rpt deleted file mode 100644 index 02c835b8c5..0000000000 --- a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/mask.relay.peer.address/request.rpt +++ /dev/null @@ -1,135 +0,0 @@ -# -# Copyright 2007-2016, Kaazing Corporation. All rights reserved. -# -# Licensed 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. -# - -property connectURI "tcp://localhost:3478" -connect ${connectURI} -connected - -##Allocation request (w/ cred) -#STUN Message type | Message Length (***CONFIRM***) -write [0x00] [0x03] [0x00] [0x68] -#Magic Cookie -write [0x21] [0x12] [0xa4] [0x42] -#Transaction ID -write ${turn:generateTransactionId()} -#Attribute Lifetime -write [0x00] [0x0d] [0x00] [0x04] -write [0x00] [0x00] [0x0e] [0x10] #3600 seconds -#Attribute Requested Transport -write [0x00] [0x19] [0x00] [0x01] -write [0x11] [0x00] [0x00] [0x00] -#Attribute Don't Fragment -write [0x00] [0x1a] [0x00] [0x00] -#Attribute Username -write [0x00] [0x06] [0x00] [0x03] -write "joe" [0x00] -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] [0x14] -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#200 response w/ mapped address -#STUN Header -read [0x01] [0x03] [0x00] [0x40] -read [0x21] [0x12] [0xa4] [0x42] -read ${transactionID} -#Attribute Lifetime -read [0x00] [0x0d] [0x00] [0x04] -read [0x00] [0x00] [0x0c] [0x08] -#Attribute XOR-Relayed-Address -read [0x00] [0x16] [0x00] [0x08] -read [0x00] [0x01] [0x3F] [0x83] -read [0x5F] [0x13] [0xA5] [0x42] -#Attribute XOR-Mapped-Address -read [0x00] [0x20] [0x00] [0x08] -read [0x00] [0x01] ${turn:portXOR(8080)} #port 8080 -read [0xE1] [0x12] [0xA6] [0x4D] -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] (byte:length) -read ([0..${length}]:messageDigest) -#Attribute Fingerprint -read [0x80] [0x28] [0x00] [0x04] -read ([0..4]:fingerprint) - -#creates permissions for peer -#STUN Header -write [0x00] [0x08] [0x00] [0x5c] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute XOR-Peer-Address -write [0x00] [0x12] [0x00] [0x08] -# Modified peer address -write [0x00] [0x01] ${turn:portXOR(7744)} # Masked port 8001 -write ${turn:ipXOR("127.0.0.1")} -#Attribute Username -write [0x00] [0x06] [0x00] [0x03] -write "joe" [0x00] -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#success -read [0x01] [0x08] [0x00] [0x18] -read [0x21] [0x12] [0xa4] [0x42] -read ${transactionID} -#attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ([0..${length}]:messageDigest) - -#binds to peer -#STUN Header -write [0x00] [0x09] [0x00] [0x68] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute Channel-Number -write [0x00] [0x0c] [0x00] [0x02] -write [0x40] [0x00] [0x00] [0x00] -#Attribute XOR-Peer-Address -write [0x00] [0x12] [0x00] [0x08] -# Modified peer address -write [0x00] [0x01] ${turn:portXOR(7744)} # Masked port 8001 -write ${turn:ipXOR("127.0.0.1")} -#Attribute Username -write [0x00] [0x06] [0x00] [0x03] -write "joe" [0x00] -#Attribute Realm -write [0x00] [0x14] [0x00] [0x0b] -write ${realm} [0x00] -#Attribute Nonce -write [0x00] [0x15] [0x00] [0x20] -write ${nonce} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] [0x14] -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#success -read [0x01] [0x09] [0x00] [0x18] -read [0x21] [0x12] [0xa4] [0x42] -read ${transactionID} -#attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ${messageDigest} diff --git a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/mask.relay.peer.address/response.rpt b/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/mask.relay.peer.address/response.rpt deleted file mode 100644 index 2c1ef57d71..0000000000 --- a/service/turn.proxy/src/test/scripts/org/kaazing/gateway/service/turn/proxy/mask.relay.peer.address/response.rpt +++ /dev/null @@ -1,132 +0,0 @@ -# -# Copyright 2007-2016, Kaazing Corporation. All rights reserved. -# -# Licensed 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. -# - -property realm "example.com" -property nonce "adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" -property acceptURI "tcp://localhost:3478" - -accept ${acceptURI} -accepted -connected - -#allocation request (w/ cred) -read [0x00] [0x03] [0x00] [0x68] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attribute Lifetime -read [0x00] [0x0d] [0x00] [0x04] -read [0x00] [0x00] [0x0e] [0x10] #3600 seconds -#Attribute Requested Transport -read [0x00] [0x19] [0x00] [0x01] -read [0x11] [0x00] [0x00] [0x00] -#Attribute Don't Fragment -read [0x00] [0x1a] [0x00] [0x00] -#Attribute Username -read [0x00] [0x06] [0x00] [0x03] -read "joe" [0x00] -#Attribute Realm -read [0x00] [0x14] [0x00] [0x0b] -read ${realm} [0x00] -#Attribute Nonce -read [0x00] [0x15] [0x00] [0x20] -read ${nonce} -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] (byte:length) -read ([0..${length}]:messageDigest) - -#200 response w/ mapped address -#STUN Header -write [0x01] [0x03] [0x00] [0x40] #Type|Length -write [0x21] [0x12] [0xa4] [0x42] #Magic Cookie -write ${transactionID} -#Attribute Lifetime -write [0x00] [0x0d] [0x00] [0x04] -write [0x00] [0x00] [0x0c] [0x08] #3080 seconds -#Attribute XOR-Relayed-Address -write [0x00] [0x16] [0x00] [0x08] -write [0x00] [0x01] ${turn:portXOR(8080)} #port 8080 -write ${turn:ipXOR("127.0.0.1")} -#Attribute XOR-Mapped-Address -write [0x00] [0x20] [0x00] [0x08] -write [0xff] [0x01] ${turn:portXOR(1)} #port 8080 -write ${turn:ipXOR("127.0.0.1")} -#Attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} -#Attribute Fingerprint - dummy content -write [0x80] [0x28] [0x00] [0x04] -write [0x00] [0x00] [0x00] [0x00] - -#creates permissions for peer -read [0x00] [0x08] [0x00] [0x60] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attribute XOR-Peer-Address -read [0x00] [0x12] [0x00] [0x08] -read [0x00] [0x01] ${turn:portXOR(8001)} #port 8001 -read ([0..4]:peer_address) -#Attribute Username -read [0x00] [0x06] [0x00] [0x03] -read "joe" [0x00] -#Attribute Realm -read [0x00] [0x14] [0x00] [0x0b] -read ${realm} [0x00] -#Attribute Nonce -read [0x00] [0x15] [0x00] [0x20] -read ${nonce} -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ([0..${length}]:messageDigest) - -#success -write [0x01] [0x08] [0x00] [0x14] -write [0x21] [0x12] [0xa4] [0x42] -write ${transactionID} -#attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} - -#binds to peer -read [0x00] [0x09] [0x00] [0x68] -read [0x21] [0x12] [0xa4] [0x42] -read ([0..12]:transactionID) -#Attribute Channel-Number -read [0x00] [0x0c] [0x00] [0x02] -read [0x40] [0x00] [0x00] [0x00] -#Attribute XOR-Peer-Address -read [0x00] [0x12] [0x00] [0x08] -read [0x00] [0x01] ${turn:portXOR(8001)} #port 8001 -read ([0..4]:peer_address) -#Attribute Username -read [0x00] [0x06] [0x00] [0x03] -read "joe" [0x00] -#Attribute Realm -read [0x00] [0x14] [0x00] [0x0b] -read ${realm} [0x00] -#Attribute Nonce -read [0x00] [0x15] [0x00] [0x20] -read ${nonce} -#Attribute Message-Integrity -read [0x00] [0x08] [0x00] ${length} -read ([0..${length}]:messageDigest) - -#success -write [0x01] [0x09] [0x00] [0x18] -write [0x21] [0x12] [0xa4] [0x42] -write ${transactionID} -#attribute Message-Integrity -write [0x00] [0x08] [0x00] ${length} -write [0x01] [0x02] [0x03] [0x04] ${turn:messageDigestMD5Encoding("joe:example.com:welcome")} diff --git a/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestJSONResponse.java b/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestJSONResponse.java index 133ca9c68a..e5a0610898 100644 --- a/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestJSONResponse.java +++ b/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestJSONResponse.java @@ -22,12 +22,18 @@ public final class TurnRestJSONResponse { private TurnRestJSONResponse() { } - public static String createResponse(String username, char[] password, String ttl, String urls) { + public static String createResponse(String username, char[] password, String ttl, String turnUrls, String stunUrls) { String response = ""; if (username != null && password != null) { - response = MessageFormat.format("\"username\":\"{0}\",\"password\":\"{1}\",", username, new String(password)); + response = MessageFormat.format("\"username\":\"{0}\",\"credential\":\"{1}\",", username, new String(password)); } - response = MessageFormat.format("'{'{0}\"ttl\":{1},\"urls\":[{2}]'}'", response, ttl, urls); + if (stunUrls.length() != 0) { + stunUrls = MessageFormat.format("'{'\"urls\":[{0}]'}',", stunUrls); + } + if (turnUrls.length() != 0) { + turnUrls = MessageFormat.format(",\"urls\":[{0}]", turnUrls); + } + response = MessageFormat.format("[{0}'{'{1}\"ttl\":{2}{3}'}']", stunUrls, response, ttl, turnUrls); return response; } } diff --git a/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestService.java b/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestService.java index e5d73f0831..7dbe357133 100644 --- a/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestService.java +++ b/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestService.java @@ -67,12 +67,13 @@ public void init(ServiceContext serviceContext) throws Exception { EarlyAccessFeatures.TURN_REST_SERVICE.assertEnabled(getConfiguration(), serviceContext.getLogger()); ServiceProperties properties = serviceContext.getProperties(); - String urls = getTurnURLs(properties); + String turnUrls = getUrls(properties, "turn"); + String stunUrls = getUrls(properties, "stun"); TurnRestCredentialsGenerator credentialGeneratorInstance = setUpCredentialsGenerator(properties); String ttl = properties.get("credentials.ttl") != null ? properties.get("credentials.ttl") : DEFAULT_CREDENTIALS_TTL; handler = new TurnRestServiceHandler(Long.toString(Utils.parseTimeInterval(ttl, TimeUnit.SECONDS, 0)), - credentialGeneratorInstance, urls); + credentialGeneratorInstance, turnUrls, stunUrls); } private TurnRestCredentialsGenerator setUpCredentialsGenerator(ServiceProperties properties) @@ -90,13 +91,17 @@ private TurnRestCredentialsGenerator setUpCredentialsGenerator(ServiceProperties return credentialGeneratorInstance; } - private String getTurnURLs(ServiceProperties properties) { - StringBuilder u = new StringBuilder(); + private String getUrls(ServiceProperties properties, String protocolName) { + StringBuilder urls = new StringBuilder(); for (String url : properties.get("url").split(LIST_SEPARATOR)) { - u.append("\"").append(url).append("\","); + if (url.toLowerCase().startsWith(protocolName)) { + urls.append("\"").append(url).append("\","); + } + } + if (urls.length() != 0) { + urls.setLength(urls.length() - 1); } - u.setLength(u.length() - 1); - return u.toString(); + return urls.toString(); } private Key resolveSharedSecret(ServiceProperties properties) { diff --git a/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestServiceHandler.java b/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestServiceHandler.java index dd445403e5..193f5fb09c 100644 --- a/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestServiceHandler.java +++ b/service/turn.rest/src/main/java/org/kaazing/gateway/service/turn/rest/internal/TurnRestServiceHandler.java @@ -34,17 +34,16 @@ class TurnRestServiceHandler extends IoHandlerAdapter { private TurnRestCredentialsGenerator credentialGenerator; - private String urls; + private String turnUrls; + private String stunUrls; private String ttl; - TurnRestServiceHandler(String ttl, TurnRestCredentialsGenerator credentialGenerator, - String urls) { - + TurnRestServiceHandler(String ttl, TurnRestCredentialsGenerator credentialGenerator, String turnUrls, String stunUrls) { this.ttl = ttl; this.credentialGenerator = credentialGenerator; - this.urls = urls; - + this.turnUrls = turnUrls; + this.stunUrls = stunUrls; } @Override @@ -78,7 +77,7 @@ protected void doSessionOpened(HttpAcceptSession session) throws Exception { password = credentials.getPassword(); } - String response = TurnRestJSONResponse.createResponse(username, password, ttl, this.urls); + String response = TurnRestJSONResponse.createResponse(username, password, ttl, turnUrls, stunUrls); if (password != null) { Arrays.fill(password, '0'); diff --git a/service/turn.rest/src/test/java/org/kaazing/gateway/service/turn/rest/TestConfigurationIT.java b/service/turn.rest/src/test/java/org/kaazing/gateway/service/turn/rest/TestConfigurationIT.java new file mode 100644 index 0000000000..4ccef49a67 --- /dev/null +++ b/service/turn.rest/src/test/java/org/kaazing/gateway/service/turn/rest/TestConfigurationIT.java @@ -0,0 +1,154 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.service.turn.rest; + +import org.junit.Assert; +import org.junit.Test; + +import org.kaazing.gateway.server.test.Gateway; +import org.kaazing.gateway.server.test.config.GatewayConfiguration; +import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; +import org.kaazing.gateway.util.feature.EarlyAccessFeatures; + +import java.security.KeyStore; + +public class TestConfigurationIT { + private static final String ACCEPT_URL = "http://localhost:8000/"; + + @Test + public void multipleCredentialsGenerators() throws Exception { + Gateway gateway = new Gateway(); + + // @formatter:off + GatewayConfiguration configuration = + new GatewayConfigurationBuilder() + .property(EarlyAccessFeatures.TURN_REST_SERVICE.getPropertyName(), "true") + .service() + .accept(ACCEPT_URL) + .type("turn.rest") + + .property("key.alias", "turn.shared.secret") + .property("key.algorithm", "HmacSHA1") + .property("credentials.generator", "class:" + DefaultCredentialsGenerator.class.getName()) + .property("credentials.generator", "class:" + DefaultCredentialsGenerator.class.getName()) + .property("credentials.ttl", "43200") + .property("username.separator", ":") + .property("url", "turn:192.168.99.100:3478?transport=tcp") + .done() + + .done(); + // @formatter:on + + try { + gateway.start(configuration); + } + catch(IllegalArgumentException e){ + Assert.assertEquals(e.getMessage(),"Unknown credential generator class: org.kaazing.gateway.service.turn.rest.DefaultCredentialsGenerator,class:org.kaazing.gateway.service.turn.rest.DefaultCredentialsGenerator"); + } + finally { + gateway.stop(); + } + } + + @Test + public void earlyAccessFeatureIsDisabled() throws Exception { + Gateway gateway = new Gateway(); + + // @formatter:off + GatewayConfiguration configuration = + new GatewayConfigurationBuilder() + .property(EarlyAccessFeatures.TURN_REST_SERVICE.getPropertyName(), "false") + .service() + .accept(ACCEPT_URL) + .type("turn.rest") + + .property("key.alias", "turn.shared.secret") + .property("key.algorithm", "HmacSHA1") + .property("credentials.generator", "class:" + DefaultCredentialsGenerator.class.getName()) + .property("credentials.ttl", "43200") + .property("username.separator", ":") + .property("url", "turn:192.168.99.100:3478?transport=tcp") + .done() + + .done(); + // @formatter:on + + try { + gateway.start(configuration); + } + catch(UnsupportedOperationException e){ + Assert.assertEquals(e.getMessage(),"Feature \"turn.rest\" (TURN REST Service) not enabled"); + } + finally { + gateway.stop(); + } + } + + @Test + public void keyStoreIsNull() throws Exception { + Gateway gateway = new Gateway(); + + KeyStore keyStore = null; + char[] password = "ab987c".toCharArray(); + + // @formatter:off + GatewayConfiguration configuration = + new GatewayConfigurationBuilder() + .property(EarlyAccessFeatures.TURN_REST_SERVICE.getPropertyName(), "true") + .service() + .accept(ACCEPT_URL) + .type("turn.rest") + .realmName("turn") + .authorization() + .requireRole("username") + .done() + + .property("key.alias", "turn.shared.secret") + .property("key.algorithm", "HmacSHA1") + .property("credentials.generator", "class:" + DefaultCredentialsGenerator.class.getName()) + .property("credentials.ttl", "43200") + .property("username.separator", ":") + .property("url", "turn:192.168.99.100:3478?transport=tcp") + .done() + .security() + .keyStore(keyStore) + .keyStorePassword(password) + .realm() + .name("turn") + .description("TURN REST Login Module Test") + .httpChallengeScheme("Basic") + .loginModule() + .type("class:" + TestLoginModule.class.getName()) + .success("requisite") + .option("roles", "username") + .done() + .done() + .done() + .done(); + // @formatter:on + + try { + gateway.start(configuration); + } + catch(NullPointerException e){ + Assert.assertEquals(e.getMessage(),null); + } + finally { + gateway.stop(); + } + } + +} diff --git a/service/turn.rest/src/test/java/org/kaazing/gateway/service/turn/rest/TurnRestAuthenticationIT.java b/service/turn.rest/src/test/java/org/kaazing/gateway/service/turn/rest/TurnRestAuthenticationIT.java new file mode 100644 index 0000000000..42a13c52ff --- /dev/null +++ b/service/turn.rest/src/test/java/org/kaazing/gateway/service/turn/rest/TurnRestAuthenticationIT.java @@ -0,0 +1,137 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.service.turn.rest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.kaazing.gateway.server.test.GatewayRule; +import org.kaazing.gateway.server.test.config.GatewayConfiguration; +import org.kaazing.gateway.server.test.config.builder.GatewayConfigurationBuilder; +import org.kaazing.gateway.util.feature.EarlyAccessFeatures; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import javax.crypto.spec.SecretKeySpec; +import java.io.FileInputStream; +import java.security.KeyStore; + +import static java.nio.charset.Charset.forName; +import static org.kaazing.test.util.ITUtil.createRuleChain; + +public class TurnRestAuthenticationIT { + private static final String ACCEPT_URL = "http://localhost:8001/"; + + private final K3poRule robot = new K3poRule(); + + private final GatewayRule gateway = new GatewayRule() { + { + KeyStore keyStore = null; + char[] password = "ab987c".toCharArray(); + try { + FileInputStream fileInStr = + new FileInputStream(System.getProperty("user.dir") + "/target/truststore/keystore.db"); + keyStore = KeyStore.getInstance("JCEKS"); + keyStore.load(fileInStr, "ab987c".toCharArray()); + // as per https://github.com/kaazing/gateway/pull/674#discussion_r75177451, we encrypt using the same + // password as the keystore + keyStore.setKeyEntry("turn.shared.secret", + new SecretKeySpec("turnAuthenticationSharedSecret".getBytes(forName("UTF-8")), "PBEWithMD5AndDES"), + password, null); + } catch (Exception e) { + e.printStackTrace(); + } + // @formatter:off + GatewayConfiguration configuration = + new GatewayConfigurationBuilder() + .property(EarlyAccessFeatures.TURN_REST_SERVICE.getPropertyName(), "true") + .service() + .accept(ACCEPT_URL) + .type("turn.rest") + .realmName("turn") + .authorization() + .requireRole("username") + .done() + + .property("key.alias", "turn.shared.secret") + .property("key.algorithm", "HmacSHA1") + .property("credentials.generator", "class:" + DefaultCredentialsGenerator.class.getName()) + .property("credentials.ttl", "43200") + .property("username.separator", ":") + .property("url", "turn:192.168.99.100:3478?transport=tcp") + .done() + .security() + .keyStore(keyStore) + .keyStorePassword(password) + .realm() + .name("turn") + .description("TURN REST Login Module Test") + .httpChallengeScheme("Basic") + .loginModule() + .type("class:" + TestLoginModule.class.getName()) + .success("requisite") + .option("roles", "username") + .done() + .done() + .done() + + + .service() + .accept("http://localhost:8002/") + .type("turn.rest") + .property("key.alias", "turn.shared.secret") + .property("key.algorithm", "HmacSHA1") + .property("credentials.generator", "class:" + DefaultCredentialsGenerator.class.getName()) + .property("credentials.ttl", "43200") + .property("username.separator", ":") + .property("url", "turn:192.168.99.100:3478?transport=tcp") + .done() + + .done(); + // @formatter:on + init(configuration); + } + }; + + + @Rule + public TestRule chain = createRuleChain(gateway, robot); + + @Specification("no.auth.credentials") + @Test + public void noCredentials() throws Exception { + robot.finish(); + } + + @Specification("invalid.auth.credentials") + @Test + public void invalidCredentials() throws Exception { + robot.finish(); + } + + @Specification("valid.auth.credentials") + @Test + public void validCredentials() throws Exception { + robot.finish(); + } + + @Specification("no.security.realm") + @Test + public void serviceWithoutSecurityRealm() throws Exception { + robot.finish(); + } + +} diff --git a/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/invalid.auth.credentials.rpt b/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/invalid.auth.credentials.rpt new file mode 100644 index 0000000000..5e71abf0eb --- /dev/null +++ b/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/invalid.auth.credentials.rpt @@ -0,0 +1,39 @@ +# +# Copyright 2007-2016, Kaazing Corporation. All rights reserved. +# +# Licensed 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. +# + +property invalidCredentials ${http:loginBase64Encoder("baduser:badpass")} +property invalidAuthHeader ${http:append("Basic ", invalidCredentials)} + +connect http://localhost:8001/ +connected + + +write method "GET" +write version "HTTP/1.1" +write header host +write parameter "service" "turn" +write header "Authorization" ${invalidAuthHeader} + + +read method "GET" +read version "HTTP/1.1" +read header "Content-Type" "text/html" +read header "Content-Length" /.*/ + +read status "401" "Unauthorized" + +close +closed diff --git a/service/turn.proxy/src/test/resources/log4j.properties b/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/no.auth.credentials.rpt similarity index 64% rename from service/turn.proxy/src/test/resources/log4j.properties rename to service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/no.auth.credentials.rpt index 4bc7f893f2..e3f781ece2 100644 --- a/service/turn.proxy/src/test/resources/log4j.properties +++ b/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/no.auth.credentials.rpt @@ -14,12 +14,18 @@ # limitations under the License. # -# Set root logger level to TRACE and its only appender to A1. -log4j.rootLogger=TRACE, A1 +connect http://localhost:8001/ +connected -# A1 is set to be a ConsoleAppender. -log4j.appender.A1=org.apache.log4j.ConsoleAppender +write method "GET" +write version "HTTP/1.1" +write header host +write parameter "service" "turn" -# A1 uses PatternLayout. -log4j.appender.A1.layout=org.apache.log4j.PatternLayout -log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c{1} %x - %m%n +read method "GET" +read version "HTTP/1.1" +read header "Content-Length" /.*/ +read status "401" "Unauthorized" + +close +closed diff --git a/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/no.security.realm.rpt b/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/no.security.realm.rpt new file mode 100644 index 0000000000..761231e56f --- /dev/null +++ b/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/no.security.realm.rpt @@ -0,0 +1,26 @@ +# +# Copyright 2007-2016, Kaazing Corporation. All rights reserved. +# +# Licensed 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. +# + +connect http://localhost:8002/ +connected + +write method "GET" +write version "HTTP/1.1" +write header host +write parameter "service" "turn" + +close +closed diff --git a/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/valid.auth.credentials.rpt b/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/valid.auth.credentials.rpt new file mode 100644 index 0000000000..d6932e73b1 --- /dev/null +++ b/service/turn.rest/src/test/scripts/org/kaazing/gateway/service/turn/rest/valid.auth.credentials.rpt @@ -0,0 +1,37 @@ +# +# Copyright 2007-2016, Kaazing Corporation. All rights reserved. +# +# Licensed 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. +# + +property validCredentials ${http:loginBase64Encoder("username:welcome")} +property validAuthHeader ${http:append("Basic ", validCredentials)} + +connect http://localhost:8001/ +connected + +write method "GET" +write version "HTTP/1.1" +write header host +write parameter "service" "turn" +write header "Authorization" ${validAuthHeader} + +read method "GET" +read version "HTTP/1.1" +read header "Content-Type" "application/json" +read header "Content-Length" /.*/ + +read status "200" "OK" + +close +closed diff --git a/service/update.check/src/main/java/org/kaazing/gateway/service/update/check/UpdateCheckService.java b/service/update.check/src/main/java/org/kaazing/gateway/service/update/check/UpdateCheckService.java index bb289196a8..5abfca8376 100644 --- a/service/update.check/src/main/java/org/kaazing/gateway/service/update/check/UpdateCheckService.java +++ b/service/update.check/src/main/java/org/kaazing/gateway/service/update/check/UpdateCheckService.java @@ -113,7 +113,7 @@ public void destroy() throws Exception { } /** - * Forces a check for an update and registers the listener if it is not already registerd + * Forces a check for an update and registers the listener if it is not already registered * @param updateCheckListener */ public void checkForUpdate(UpdateCheckListener updateCheckListener) { diff --git a/transport/http/src/main/java/org/kaazing/gateway/transport/http/DefaultHttpSession.java b/transport/http/src/main/java/org/kaazing/gateway/transport/http/DefaultHttpSession.java index 741804cf99..2566310804 100644 --- a/transport/http/src/main/java/org/kaazing/gateway/transport/http/DefaultHttpSession.java +++ b/transport/http/src/main/java/org/kaazing/gateway/transport/http/DefaultHttpSession.java @@ -16,7 +16,6 @@ package org.kaazing.gateway.transport.http; import static java.lang.String.format; -import static org.kaazing.gateway.transport.BridgeSession.LOCAL_ADDRESS; import static org.kaazing.gateway.transport.http.bridge.filter.HttpProtocolCompatibilityFilter.HttpConditionalWrappedResponseFilter.conditionallyWrappedResponsesRequired; import static org.kaazing.gateway.util.InternalSystemProperty.HTTPXE_SPECIFICATION; diff --git a/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpConnector.java b/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpConnector.java index 58f90864ab..8db0f9cc2f 100644 --- a/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpConnector.java +++ b/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpConnector.java @@ -16,6 +16,7 @@ package org.kaazing.gateway.transport.http; import static java.lang.String.format; +import static java.net.Authenticator.RequestorType.SERVER; import static java.util.Collections.unmodifiableMap; import static java.util.EnumSet.allOf; import static java.util.EnumSet.complementOf; @@ -23,16 +24,27 @@ import static org.kaazing.gateway.resource.address.ResourceAddress.NEXT_PROTOCOL; import static org.kaazing.gateway.resource.address.ResourceAddress.QUALIFIER; import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.MAXIMUM_REDIRECTS; +import static org.kaazing.gateway.resource.address.http.HttpResourceAddress.MAX_AUTHENTICATION_ATTEMPTS; import static org.kaazing.gateway.transport.BridgeSession.LOCAL_ADDRESS; import static org.kaazing.gateway.transport.http.HttpConnectFilter.CONTENT_LENGTH_ADJUSTMENT; import static org.kaazing.gateway.transport.http.HttpConnectFilter.PROTOCOL_HTTPXE; +import static org.kaazing.gateway.transport.http.HttpHeaders.HEADER_AUTHORIZATION; +import static org.kaazing.gateway.transport.http.HttpHeaders.HEADER_PROXY_AUTHORIZATION; import static org.kaazing.gateway.transport.http.HttpUtils.hasCloseHeader; import static org.kaazing.gateway.transport.http.bridge.filter.HttpNextProtocolHeaderFilter.PROTOCOL_HTTPXE_1_1; import static org.kaazing.gateway.transport.http.bridge.filter.HttpProtocolFilter.PROTOCOL_HTTP_1_1; +import static org.kaazing.gateway.transport.http.security.auth.WWWAuthenticateHeaderUtils.getChallenges; +import static org.kaazing.gateway.util.feature.EarlyAccessFeatures.HTTP_AUTHENTICATOR; import java.io.IOException; +import java.net.Authenticator; +import java.net.Authenticator.RequestorType; +import java.net.InetAddress; +import java.net.PasswordAuthentication; import java.net.SocketAddress; +import java.net.URI; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -69,6 +81,7 @@ import org.kaazing.gateway.transport.http.bridge.HttpResponseMessage; import org.kaazing.gateway.transport.http.bridge.filter.HttpBuffer; import org.kaazing.gateway.transport.http.bridge.filter.HttpBufferAllocator; +import org.kaazing.gateway.transport.http.security.auth.WWWAuthChallenge; import org.kaazing.mina.core.buffer.IoBufferAllocatorEx; import org.kaazing.mina.core.buffer.IoBufferEx; import org.kaazing.mina.core.service.IoProcessorEx; @@ -79,13 +92,13 @@ public class HttpConnector extends AbstractBridgeConnector { private static final TypedAttributeKey HTTP_SESSION_FACTORY_KEY = new TypedAttributeKey<>(HttpConnector.class, "httpSessionFactory"); public static final TypedAttributeKey HTTP_SESSION_KEY = new TypedAttributeKey<>(HttpConnector.class, "httpSession"); private static final TypedAttributeKey HTTP_CONNECT_FUTURE_KEY = new TypedAttributeKey<>(HttpConnector.class, "httpConnectFuture"); - + private Properties configuration; + private final Map> connectFiltersByProtocol; private final Set allConnectFilters; private BridgeServiceFactory bridgeServiceFactory; ResourceAddressFactory addressFactory; private final PersistentConnectionPool persistentConnectionsStore; - Properties configuration; public HttpConnector() { super(new DefaultIoSessionConfigEx()); @@ -99,6 +112,11 @@ public HttpConnector() { this.persistentConnectionsStore = new PersistentConnectionPool(logger); } + @Resource(name = "configuration") + public void setConfiguration(Properties configuration) { + this.configuration = configuration; + } + @Resource(name = "bridgeServiceFactory") public void setBridgeServiceFactory(BridgeServiceFactory bridgeServiceFactory) { this.bridgeServiceFactory = bridgeServiceFactory; @@ -109,11 +127,6 @@ public void setResourceAddressFactory(ResourceAddressFactory resourceAddressFact this.addressFactory = resourceAddressFactory; } - @Resource(name = "configuration") - public void setConfiguration(Properties configuration) { - this.configuration = configuration; - } - @Override protected IoProcessorEx initProcessor() { return new HttpConnectProcessor(persistentConnectionsStore, logger); @@ -130,10 +143,10 @@ protected boolean canConnect(String transportName) { } @Override - protected ConnectFuture connectInternal(final ResourceAddress address, - final IoHandler handler, final IoSessionInitializer initializer) { + protected ConnectFuture connectInternal(final ResourceAddress address, final IoHandler handler, + final IoSessionInitializer initializer) { - final ConnectFuture connectFuture = new DefaultConnectFuture(); + final ConnectFuture connectFuture = new DefaultConnectFuture(); final ResourceAddress transportAddress = address.getTransport(); // initializer for bridge session to specify bridge handler, @@ -232,7 +245,7 @@ public void removeBridgeFilters(IoFilterChain filterChain) { switch (filter) { case CODEC: // Note: we MUST NOT remove the codec filter until - // after the first IoBuffer is received post-upgrade + // after the first IoBuffer is received post-upgrade break; default: removeFilter(filterChain, filter.filterName()); @@ -244,8 +257,8 @@ public void removeBridgeFilters(IoFilterChain filterChain) { @Override protected void finishSessionInitialization0(IoSession session, IoFuture future) { - DefaultHttpSession httpSession = (DefaultHttpSession)session; - HttpConnectProcessor processor = (HttpConnectProcessor)httpSession.getProcessor(); + DefaultHttpSession httpSession = (DefaultHttpSession) session; + HttpConnectProcessor processor = (HttpConnectProcessor) httpSession.getProcessor(); processor.finishConnect(httpSession); } @@ -258,9 +271,6 @@ private IoSessionInitializer createPare }; } - - // initializer for bridge session to specify bridge handler, - // and call user-defined bridge session initializer if present private IoSessionInitializer createHttpSessionInitializer(final IoHandler handler, final IoSessionInitializer initializer) { return (session, future) -> { DefaultHttpSession httpSession = (DefaultHttpSession) session; @@ -304,7 +314,6 @@ protected void doSessionClosed(IoSessionEx session) throws Exception { if (connectionClose && !httpSession.isClosing()) { httpSession.getProcessor().remove(httpSession); } - if (!session.isClosing()) { IoFilterChain filterChain = session.getFilterChain(); removeBridgeFilters(filterChain); @@ -313,15 +322,13 @@ protected void doSessionClosed(IoSessionEx session) throws Exception { } @Override - protected void doExceptionCaught(IoSessionEx session, Throwable cause) - throws Exception { + protected void doExceptionCaught(IoSessionEx session, Throwable cause) throws Exception { if (logger.isDebugEnabled()) { String message = format("Error on HTTP connection attempt: %s", cause); if (logger.isTraceEnabled()) { // note: still debug level, but with extra detail about the exception logger.debug(message, cause); - } - else { + } else { logger.debug(message); } } @@ -335,8 +342,7 @@ protected void doExceptionCaught(IoSessionEx session, Throwable cause) } @Override - protected void doSessionIdle(IoSessionEx session, IdleStatus status) - throws Exception { + protected void doSessionIdle(IoSessionEx session, IdleStatus status) throws Exception { // TODO Auto-generated method stub super.doSessionIdle(session, status); } @@ -351,8 +357,9 @@ protected void doMessageReceived(final IoSessionEx session, Object message) thro switch (httpMessage.getKind()) { case RESPONSE: - HttpResponseMessage httpResponse = (HttpResponseMessage)httpMessage; + HttpResponseMessage httpResponse = (HttpResponseMessage) httpMessage; HttpStatus httpStatus = httpResponse.getStatus(); + httpSession.setStatus(httpStatus); httpSession.setReason(httpResponse.getReason()); httpSession.setVersion(httpResponse.getVersion()); @@ -392,6 +399,19 @@ protected void doMessageReceived(final IoSessionEx session, Object message) thro case REDIRECT_NOT_MODIFIED: httpSession.close(false); break; + case CLIENT_UNAUTHORIZED: + String authenticate = getAuthentication(httpSession, (HttpResponseMessage) httpMessage, SERVER); + if (authenticate != null) { + authenticate(httpSession, session, authenticate, SERVER); + }else{ + HttpContentMessage httpContent = httpResponse.getContent(); + if (httpContent == null) { + IoBufferAllocatorEx allocator = httpSession.getBufferAllocator(); + httpContent = new HttpContentMessage(allocator.wrap(allocator.allocate(0)), true); + } + fireContentReceived(httpSession, httpContent); + } + break; default: HttpContentMessage httpContent = httpResponse.getContent(); if (httpContent == null) { @@ -426,15 +446,98 @@ private boolean shouldFollowRedirects(DefaultHttpSession httpSession) { return redirctBehavior != null && redirctBehavior > 0; } + + /** + * Gets the password Authentication header value + * @param httpSession + * @param httpMessage + * @param requestorType + * @return + */ + String getAuthentication(DefaultHttpSession httpSession, + HttpResponseMessage httpMessage, RequestorType requestorType) { + Integer maxAthenticates = new Integer((httpSession.getRemoteAddress().getOption(MAX_AUTHENTICATION_ATTEMPTS))); + String result = null; + if (maxAthenticates > 0 && HTTP_AUTHENTICATOR.isEnabled(configuration)) { + try { + ResourceAddress remoteAddress = httpSession.getRemoteAddress(); + final URI remoteURI = remoteAddress.getResource(); + List challenges = + getChallenges(httpMessage.getHeader(HttpHeaders.HEADER_WWW_AUTHENTICATE)); + for (WWWAuthChallenge challenge : challenges) { + // @formatter:off + String scheme = challenge.getScheme(); + PasswordAuthentication credentials = Authenticator.requestPasswordAuthentication( + remoteURI.getHost(), + InetAddress.getByName(remoteURI.getHost()), + remoteURI.getPort(), + "HTTP", + challenge.getChallenge().replaceFirst(scheme + " ", ""), + scheme, + remoteURI.toURL(), + requestorType); + // @formatter:on + result = WWWAuthChallenge.encodeAuthorizationHeader(scheme, credentials); + if (result != null) { + break; + } + } + } catch (Exception e) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to get a valid response from Authenticator due to exception ", e); + } + } + } + return result; + } + + /** + * Attempts authentication to a http address. + * @param httpSession + * @param session + * @param challengeToCredentials + * @param proxy + * @return + */ + private DefaultConnectFuture authenticate(DefaultHttpSession httpSession, IoSessionEx session, String authorizationValue, + RequestorType requestorType) { + String location = httpSession.getRemoteAddress().getExternalURI(); + HashMap, Object> overrides = new HashMap<>(); + Integer maxAthenticates = new Integer((httpSession.getRemoteAddress().getOption(MAX_AUTHENTICATION_ATTEMPTS)) - 1); + overrides.put(MAX_AUTHENTICATION_ATTEMPTS, maxAthenticates); + ResourceAddress newConnectAddress = addressFactory.newResourceAddress(location.replaceFirst("ws", "http"), + new WrappedResourceOptionsForConnectionRetry(httpSession, overrides)); + if (RequestorType.SERVER.equals(requestorType)) { + httpSession.setWriteHeader(HEADER_AUTHORIZATION, authorizationValue); + } else { + httpSession.setWriteHeader(HEADER_PROXY_AUTHORIZATION, authorizationValue); + } + return retryConnect(httpSession, session, newConnectAddress); + } + + /** + * Follows a redirect. + * @param httpSession + * @param session + * @return + */ private DefaultConnectFuture followRedirect(DefaultHttpSession httpSession, IoSessionEx session) { + HashMap, Object> overrides = new HashMap<>(); String location = httpSession.getReadHeader("location"); + Integer maxRedirects = new Integer((httpSession.getRemoteAddress().getOption(MAXIMUM_REDIRECTS)) - 1); + overrides.put(MAXIMUM_REDIRECTS, maxRedirects); ResourceAddress newConnectAddress = - addressFactory.newResourceAddress(location.replaceFirst("ws","http"), new HttpRedirectResourceOptions(httpSession)); + addressFactory.newResourceAddress(location.replaceFirst("ws","http"), new WrappedResourceOptionsForConnectionRetry(httpSession, overrides)); + return retryConnect(httpSession, session, newConnectAddress); + } + + private DefaultConnectFuture retryConnect(DefaultHttpSession httpSession, IoSessionEx session, + ResourceAddress newConnectAddress) { DefaultConnectFuture connectFuture = new DefaultConnectFuture(); HTTP_SESSION_KEY.remove(session); connectFuture.addListener(future -> session.close(false)); httpSession.setRemoteAddress(newConnectAddress); - final HttpConnectSessionFactory httpSessionFactory = new HttpRedirectSessionFactory(httpSession); + final HttpConnectSessionFactory httpSessionFactory = new HttpRetryConnectSessionFactory(httpSession); connectInternal0(connectFuture, newConnectAddress, httpSessionFactory); return connectFuture; } @@ -453,11 +556,13 @@ private void fireContentReceived(HttpSession session, HttpContentMessage content } }; - private final class HttpRedirectResourceOptions implements ResourceOptions { + private final class WrappedResourceOptionsForConnectionRetry implements ResourceOptions { private final DefaultHttpSession httpSession; + private final Map, Object> optionOverrides; - public HttpRedirectResourceOptions(DefaultHttpSession httpSession) { + public WrappedResourceOptionsForConnectionRetry(DefaultHttpSession httpSession, HashMap, Object> overrides) { this.httpSession = httpSession; + this.optionOverrides = overrides; } @Override @@ -481,24 +586,22 @@ public T getOption(ResourceOption key) { || ResourceAddress.TRANSPORTED_URI.equals(key)) { return null; } - if (HttpResourceAddress.MAXIMUM_REDIRECTS.equals(key)) { - return (T) new Integer(((Integer) httpSession.getRemoteAddress().getOption(key)) - 1); - } - return httpSession.getRemoteAddress().getOption(key); + Object override = optionOverrides.get(key); + return override != null ? (T) override : httpSession.getRemoteAddress().getOption(key); } } class DefaultHttpConnectSessionFactory implements HttpConnectSessionFactory { - private final HttpConnector httpConnectSessionFactory; + private final HttpConnector httpConnector; private final ResourceAddress connectAddress; private final IoSessionInitializer httpSessionInitializer; private final IoFuture connectFuture; public DefaultHttpConnectSessionFactory(HttpConnector httpConnector, ResourceAddress connectAddress, IoSessionInitializer httpSessionInitializer, ConnectFuture connectFuture) { - httpConnectSessionFactory = httpConnector; + this.httpConnector = httpConnector; this.connectAddress = connectAddress; this.httpSessionInitializer = httpSessionInitializer; this.connectFuture = connectFuture; @@ -508,18 +611,18 @@ public DefaultHttpConnectSessionFactory(HttpConnector httpConnector, ResourceAdd public DefaultHttpSession get(IoSession parent) throws Exception { ResourceAddress transportAddress = LOCAL_ADDRESS.get(parent); final ResourceAddress localAddress = - httpConnectSessionFactory.addressFactory.newResourceAddress(connectAddress, transportAddress); + httpConnector.addressFactory.newResourceAddress(connectAddress, transportAddress); Callable httpSessionFactory = () -> { IoSessionEx parentEx = (IoSessionEx) parent; IoBufferAllocatorEx parentAllocator = parentEx.getBufferAllocator(); - DefaultHttpSession httpSession = new DefaultHttpSession(httpConnectSessionFactory, - httpConnectSessionFactory.getProcessor(), localAddress, connectAddress, parentEx, - new HttpBufferAllocator(parentAllocator), httpConnectSessionFactory.configuration); - parent.setAttribute(HttpConnector.HTTP_SESSION_KEY, httpSession); + DefaultHttpSession httpSession = new DefaultHttpSession(httpConnector, + httpConnector.getProcessor(), localAddress, connectAddress, parentEx, + new HttpBufferAllocator(parentAllocator), httpConnector.configuration); + parent.setAttribute(HTTP_SESSION_KEY, httpSession); return httpSession; }; - return httpConnectSessionFactory.newSession(httpSessionInitializer, connectFuture, httpSessionFactory); + return httpConnector.newSession(httpSessionInitializer, connectFuture, httpSessionFactory); } } } diff --git a/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpHeaders.java b/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpHeaders.java index 2e23759e16..c2e420c826 100644 --- a/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpHeaders.java +++ b/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpHeaders.java @@ -18,6 +18,7 @@ public interface HttpHeaders { String HEADER_AUTHORIZATION = "Authorization"; + String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; String HEADER_CONTENT_LENGTH = "Content-Length"; String HEADER_CONTENT_TYPE = "Content-Type"; String HEADER_DATE = "Date"; @@ -45,5 +46,6 @@ public interface HttpHeaders { String HEADER_X_SEQUENCE_NO = "X-Sequence-No"; String HEADER_SET_COOKIE = "Set-Cookie"; String HEADER_LOCATION = "Location"; + String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; } diff --git a/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpRedirectSessionFactory.java b/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpRetryConnectSessionFactory.java similarity index 89% rename from transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpRedirectSessionFactory.java rename to transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpRetryConnectSessionFactory.java index f1d3a6d0d5..3bd93a0036 100644 --- a/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpRedirectSessionFactory.java +++ b/transport/http/src/main/java/org/kaazing/gateway/transport/http/HttpRetryConnectSessionFactory.java @@ -23,11 +23,11 @@ * Session factory used when HttpConnector is following a redirect * */ -class HttpRedirectSessionFactory implements HttpConnectSessionFactory { +class HttpRetryConnectSessionFactory implements HttpConnectSessionFactory { private final DefaultHttpSession httpSession; - public HttpRedirectSessionFactory(DefaultHttpSession httpSession) { + public HttpRetryConnectSessionFactory(DefaultHttpSession httpSession) { this.httpSession = httpSession; } diff --git a/transport/http/src/main/java/org/kaazing/gateway/transport/http/security/auth/WWWAuthChallenge.java b/transport/http/src/main/java/org/kaazing/gateway/transport/http/security/auth/WWWAuthChallenge.java new file mode 100644 index 0000000000..6efcde0a8c --- /dev/null +++ b/transport/http/src/main/java/org/kaazing/gateway/transport/http/security/auth/WWWAuthChallenge.java @@ -0,0 +1,76 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.transport.http.security.auth; + +import static java.lang.String.format; + +import java.net.PasswordAuthentication; +import java.util.Arrays; +import java.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class WWWAuthChallenge { + + static final String TYPE_GROUP_NAME = "scheme"; + static final String REALM_GROUP_NAME = "realm"; + static final Pattern SCHEME_PATTERN = + Pattern.compile(format("(?<%s>[a-zA-Z_]+) realm=\"(?<%s>[^\"]+)\".*", TYPE_GROUP_NAME, REALM_GROUP_NAME)); + protected static final String[] SUPPORTED_SCHEMES = new String[]{"basic", "digest"}; + + private final String challenge; + private final String realm; + private final String type; + + public WWWAuthChallenge(String challenge) { + this.challenge = challenge; + Matcher matcher = SCHEME_PATTERN.matcher(challenge); + matcher.matches(); + this.realm = matcher.group(REALM_GROUP_NAME); + this.type = matcher.group(TYPE_GROUP_NAME); + } + + public String getChallenge() { + return challenge; + } + + public String getRealm() { + return realm; + } + + public String getScheme() { + return type; + } + + public static String[] getSupportedSchemes() { + return SUPPORTED_SCHEMES; + } + + public static String encodeAuthorizationHeader(String scheme, PasswordAuthentication value) { + if(value == null){ + return null; + }else if("basic".equalsIgnoreCase(scheme)){ + // https://tools.ietf.org/html/rfc7617 + char[] pwd = value.getPassword(); + String encodedBytes = Base64.getEncoder().encodeToString((value.getUserName() + ":" + new String(pwd)).getBytes()); + Arrays.fill(pwd, ' '); + return "Basic " + encodedBytes; + } + return null; + } + +} diff --git a/transport/http/src/main/java/org/kaazing/gateway/transport/http/security/auth/WWWAuthenticateHeaderUtils.java b/transport/http/src/main/java/org/kaazing/gateway/transport/http/security/auth/WWWAuthenticateHeaderUtils.java new file mode 100644 index 0000000000..29babf9af6 --- /dev/null +++ b/transport/http/src/main/java/org/kaazing/gateway/transport/http/security/auth/WWWAuthenticateHeaderUtils.java @@ -0,0 +1,40 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.transport.http.security.auth; + +import java.util.ArrayList; +import java.util.List; + +public final class WWWAuthenticateHeaderUtils { + + private WWWAuthenticateHeaderUtils() { + // Utility class + } + + static String[] splitWWWAuthenticateHeaderBySchemes(String headerValue) { + return headerValue.split(",\\s(?=[^\\s]+\\srealm=)"); + } + + public static List getChallenges(String headerValue) { + List challenges = new ArrayList<>(); + for (String challenge : splitWWWAuthenticateHeaderBySchemes(headerValue)) { + challenges.add(new WWWAuthChallenge(challenge)); + } + return challenges; + } + +} diff --git a/transport/http/src/test/java/org/kaazing/gateway/transport/http/HttpConnectorRule.java b/transport/http/src/test/java/org/kaazing/gateway/transport/http/HttpConnectorRule.java index 67ebe0655a..d76b458c0c 100644 --- a/transport/http/src/test/java/org/kaazing/gateway/transport/http/HttpConnectorRule.java +++ b/transport/http/src/test/java/org/kaazing/gateway/transport/http/HttpConnectorRule.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Properties; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoHandler; @@ -45,21 +46,23 @@ public class HttpConnectorRule implements TestRule { private ResourceAddressFactory addressFactory; private HttpConnector httpConnector; private Map connectOptions = new HashMap<>(); + private final Properties props = new Properties(); @Override public Statement apply(Statement base, Description description) { return new ConnectorStatement(base); } - public ConnectFuture connect(String connect, IoHandler connectHandler, IoSessionInitializer initializer) { + public ConnectFuture connect(String connect, IoHandler connectHandler, IoSessionInitializer initializer, Map connectOptions) { ResourceAddress connectAddress = addressFactory.newResourceAddress(connect, getConnectOptions()); return httpConnector.connect(connectAddress, connectHandler, initializer); } - public Map getConnectOptions() { - return connectOptions; + public ConnectFuture connect(String connect, IoHandler connectHandler, IoSessionInitializer initializer) { + Map connectOptions = new HashMap<>(); + return this.connect(connect, connectHandler, initializer, connectOptions); } private final class ConnectorStatement extends Statement { @@ -98,6 +101,8 @@ public void evaluate() throws Throwable { httpConnector.setBridgeServiceFactory(serviceFactory); httpConnector.setResourceAddressFactory(addressFactory); + httpConnector.setConfiguration(props); + base.evaluate(); } finally { tcpConnector.dispose(); @@ -108,4 +113,13 @@ public void evaluate() throws Throwable { } } + + public Map getConnectOptions() { + return connectOptions; + } + + public HttpConnectorRule addProperty(String key, String value) { + props.put(key, value); + return this; + } } diff --git a/transport/http/src/test/java/org/kaazing/gateway/transport/http/jmsBalancerIT.java b/transport/http/src/test/java/org/kaazing/gateway/transport/http/jmsBalancerIT.java new file mode 100644 index 0000000000..14acba3f0f --- /dev/null +++ b/transport/http/src/test/java/org/kaazing/gateway/transport/http/jmsBalancerIT.java @@ -0,0 +1,74 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.transport.http; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertTrue; +import static org.kaazing.test.util.ITUtil.createRuleChain; + +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.service.IoHandler; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.session.IoSessionInitializer; +import org.jmock.Mockery; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.kaazing.gateway.transport.http.HttpConnectSession; +import org.kaazing.gateway.transport.http.HttpMethod; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; +@Ignore +public class jmsBalancerIT { + + private final HttpConnectorRule connector = new HttpConnectorRule(); + private final K3poRule k3po = new K3poRule(); + @Rule + public TestRule chain = createRuleChain(connector, k3po); + private Mockery context; + + @Before + public void initialize() { + context = new Mockery(); + } + + @Test + @Specification("should.receive.redirect.response") + public void responseMustBeARedirect() throws Exception { + final IoHandler handler = context.mock(IoHandler.class); + ConnectFuture connectFuture = connector.connect("http://localhost:8080/jms", handler, + new ConnectSessionInitializer()); + connectFuture.awaitUninterruptibly(); + assertTrue(connectFuture.isConnected()); + + k3po.finish(); + } + + private static class ConnectSessionInitializer implements IoSessionInitializer { + @Override + public void initializeSession(IoSession session, ConnectFuture future) { + HttpConnectSession connectSession = (HttpConnectSession) session; + connectSession.setMethod(HttpMethod.GET); + connectSession.addWriteHeader("Upgrade", "websocket"); + connectSession.addWriteHeader("Sec-WebSocket-Version" , "13"); + connectSession.addWriteHeader("Sec-WebSocket-Key" , "dGhlIHNhbXBsZSBub25jZQ=="); + connectSession.addWriteHeader("Connection", "Upgrade"); + } + } + +} diff --git a/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/WWWAuthenticateHeaderUtilsTest.java b/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/WWWAuthenticateHeaderUtilsTest.java new file mode 100644 index 0000000000..ce90372894 --- /dev/null +++ b/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/WWWAuthenticateHeaderUtilsTest.java @@ -0,0 +1,96 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.transport.http.security.auth; + +import static org.kaazing.gateway.transport.http.security.auth.WWWAuthenticateHeaderUtils.getChallenges; +import static org.kaazing.gateway.transport.http.security.auth.WWWAuthenticateHeaderUtils.splitWWWAuthenticateHeaderBySchemes; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +public class WWWAuthenticateHeaderUtilsTest { + + private static final String SINGLE_TYPE_WWW_AUTH_HEADER = "Basic realm=\"factor1\""; + private static final String SINGLE_TYPE_WWW_AUTH_HEADER_WITH_PARAMS = + "Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\""; + private static final String MULTIPLE_TYPE_WWW_AUTH_HEADER_WITH_PARAMS = + "Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\""; + + @Test + public void getChallengeFromSingleWWWAuthHeader() { + // single value + String[] result = splitWWWAuthenticateHeaderBySchemes(SINGLE_TYPE_WWW_AUTH_HEADER); + Assert.assertEquals(1, result.length); + Assert.assertEquals(SINGLE_TYPE_WWW_AUTH_HEADER, result[0]); + + WWWAuthChallenge challenge = new WWWAuthChallenge(result[0]); + Assert.assertEquals("Basic", challenge.getScheme()); + Assert.assertEquals("factor1", challenge.getRealm()); + Assert.assertEquals(SINGLE_TYPE_WWW_AUTH_HEADER, challenge.getChallenge()); + } + + @Test + public void getChallengeFromSingleWWWAuthHeaderWithParams() { + String[] result = splitWWWAuthenticateHeaderBySchemes(SINGLE_TYPE_WWW_AUTH_HEADER_WITH_PARAMS); + Assert.assertEquals(1, result.length); + Assert.assertEquals(SINGLE_TYPE_WWW_AUTH_HEADER_WITH_PARAMS, result[0]); + + WWWAuthChallenge challenge = new WWWAuthChallenge(result[0]); + Assert.assertEquals("Newauth", challenge.getScheme()); + Assert.assertEquals("apps", challenge.getRealm()); + Assert.assertEquals(SINGLE_TYPE_WWW_AUTH_HEADER_WITH_PARAMS, challenge.getChallenge()); + } + + @Test + public void getChallengeFromMultipleWWWAuthHeader() { + String[] result = splitWWWAuthenticateHeaderBySchemes(MULTIPLE_TYPE_WWW_AUTH_HEADER_WITH_PARAMS); + Assert.assertEquals(2, result.length); + final String expectedChallenge1 = "Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\""; + final String expectedChallenge2 = "Basic realm=\"simple\""; + Assert.assertEquals(expectedChallenge1, result[0]); + Assert.assertEquals(expectedChallenge2, result[1]); + + WWWAuthChallenge challenge = new WWWAuthChallenge(result[0]); + Assert.assertEquals("Newauth", challenge.getScheme()); + Assert.assertEquals("apps", challenge.getRealm()); + Assert.assertEquals(expectedChallenge1, challenge.getChallenge()); + challenge = new WWWAuthChallenge(result[1]); + Assert.assertEquals("Basic", challenge.getScheme()); + Assert.assertEquals("simple", challenge.getRealm()); + Assert.assertEquals(expectedChallenge2, challenge.getChallenge()); + } + + @Test + public void getChallengesFromMultipleWWWAuthHeader() { + final String expectedChallenge1 = "Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\""; + final String expectedChallenge2 = "Basic realm=\"simple\""; + + List result = getChallenges(MULTIPLE_TYPE_WWW_AUTH_HEADER_WITH_PARAMS); + + WWWAuthChallenge challenge = result.get(0); + Assert.assertEquals("Newauth", challenge.getScheme()); + Assert.assertEquals("apps", challenge.getRealm()); + Assert.assertEquals(expectedChallenge1, challenge.getChallenge()); + challenge = result.get(1); + Assert.assertEquals("Basic", challenge.getScheme()); + Assert.assertEquals("simple", challenge.getRealm()); + Assert.assertEquals(expectedChallenge2, challenge.getChallenge()); + } + +} diff --git a/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/connector/AuthenticatorIT.java b/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/connector/AuthenticatorIT.java new file mode 100644 index 0000000000..7fa2fbfecd --- /dev/null +++ b/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/connector/AuthenticatorIT.java @@ -0,0 +1,235 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.transport.http.security.auth.connector; + +import static java.net.Authenticator.setDefault; +import static org.kaazing.gateway.transport.http.HttpMethod.GET; +import static org.kaazing.gateway.transport.http.HttpStatus.CLIENT_UNAUTHORIZED; +import static org.kaazing.gateway.transport.http.HttpStatus.SUCCESS_OK; +import static org.kaazing.gateway.util.feature.EarlyAccessFeatures.HTTP_AUTHENTICATOR; +import static org.kaazing.test.util.ITUtil.createRuleChain; + +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.util.HashMap; +import java.util.Map; + +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.service.IoHandler; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.session.IoSessionInitializer; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.jmock.States; +import org.jmock.lib.concurrent.Synchroniser; +import org.jmock.lib.legacy.ClassImposteriser; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.kaazing.gateway.transport.http.HttpConnectSession; +import org.kaazing.gateway.transport.http.HttpConnectorRule; +import org.kaazing.gateway.transport.http.HttpSession; +import org.kaazing.gateway.transport.http.HttpStatus; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; +import org.kaazing.mina.core.buffer.IoBufferEx; + +public class AuthenticatorIT { + + private final HttpConnectorRule connector = new HttpConnectorRule().addProperty(HTTP_AUTHENTICATOR.getPropertyName(), "true"); + private final K3poRule k3po = new K3poRule(); + private States testState; + + @Rule + public TestRule chain = createRuleChain(connector, k3po); + + @Rule + public ResetAuthenticatorRule resetAuthenticatorRule = new ResetAuthenticatorRule(); + + AuthenticatorMock authenticator; + private Mockery context; + + Synchroniser syncronizer; + + public abstract class AuthenticatorMock extends Authenticator { + public abstract PasswordAuthentication getPasswordAuthentication(); + } + + @Before + public void initialize() { + context = new Mockery() { + { + setImposteriser(ClassImposteriser.INSTANCE); + } + }; + testState = context.states("testState").startsAs("initial-state"); + syncronizer = new Synchroniser(); + context.setThreadingPolicy(syncronizer); + authenticator = context.mock(AuthenticatorMock.class); + // inner class this + setDefault(authenticator); + } + + @After + public void after() { + context.assertIsSatisfied(); + } + + private SessionWithStatus withHttpSessionOfStatus(HttpStatus status) { + return new SessionWithStatus(status); + } + + private class SessionWithStatus extends BaseMatcher { + + private HttpStatus status; + + public SessionWithStatus(HttpStatus status) { + this.status = status; + } + + @Override + public boolean matches(Object item) { + return item instanceof HttpSession && status.equals(((HttpSession) item).getStatus()); + } + + @Override + public void describeTo(Description description) { + description.appendText("Matches HttpSession with status " + status); + } + } + + @Specification("basic.challenge") + @Test + public void basicChallenge() throws Exception { + connector.getConnectOptions().put("http.max.authentication.attempts", "0"); + final IoHandler handler = context.mock(IoHandler.class); + context.checking(new Expectations() { + { + oneOf(handler).sessionCreated(with(any(IoSession.class))); + oneOf(handler).sessionOpened(with(any(IoSession.class))); + oneOf(handler).messageReceived(with(withHttpSessionOfStatus(CLIENT_UNAUTHORIZED)), with(any(IoBufferEx.class))); + oneOf(handler).sessionClosed(with(any(IoSession.class))); + then(testState.is("finished")); + } + }); + Map connectOptions = new HashMap<>(); + connector.connect("http://localhost:8080/resource", handler, new IoSessionInitializer() { + @Override + public void initializeSession(IoSession session, ConnectFuture future) { + HttpConnectSession connectSession = (HttpConnectSession) session; + connectSession.setMethod(GET); + } + }, connectOptions); + + k3po.finish(); + syncronizer.waitUntil(testState.is("finished")); + } + + @Specification("basic.challenge.and.accept") + @Test + public void basicChallengeAndAccept() throws Exception { + connector.getConnectOptions().put("http.max.authentication.attempts", "1"); + final IoHandler handler = context.mock(IoHandler.class); + context.checking(new Expectations() { + { + oneOf(handler).sessionCreated(with(any(IoSession.class))); + oneOf(handler).sessionOpened(with(any(IoSession.class))); + oneOf(authenticator).getPasswordAuthentication(); + will(returnValue(new PasswordAuthentication("joe", new char[]{'w', 'e', 'l', 'c', 'o', 'm', 'e'}))); + oneOf(handler).messageReceived(with(withHttpSessionOfStatus(SUCCESS_OK)), with(any(IoBufferEx.class))); + oneOf(handler).sessionClosed(with(any(IoSession.class))); + then(testState.is("finished")); + } + }); + Map connectOptions = new HashMap<>(); + + connector.connect("http://localhost:8080/resource", handler, new IoSessionInitializer() { + @Override + public void initializeSession(IoSession session, ConnectFuture future) { + HttpConnectSession connectSession = (HttpConnectSession) session; + connectSession.setMethod(GET); + } + }, connectOptions); + + k3po.finish(); + syncronizer.waitUntil(testState.is("finished")); + } + + @Specification("basic.challenge.twice") + @Test + public void wontChallengeMoreThenNumberOfAttempts() throws Exception { + connector.getConnectOptions().put("http.max.authentication.attempts", "1"); + final IoHandler handler = context.mock(IoHandler.class); + context.checking(new Expectations() { + { + oneOf(handler).sessionCreated(with(any(IoSession.class))); + oneOf(handler).sessionOpened(with(any(IoSession.class))); + oneOf(authenticator).getPasswordAuthentication(); + will(returnValue(new PasswordAuthentication("joe", new char[]{'w', 'e', 'l', 'c', 'o', 'm', 'e'}))); + oneOf(handler).messageReceived(with(withHttpSessionOfStatus(CLIENT_UNAUTHORIZED)), with(any(IoBufferEx.class))); + oneOf(handler).sessionClosed(with(any(IoSession.class))); + then(testState.is("finished")); + } + }); + Map connectOptions = new HashMap<>(); + + connector.connect("http://localhost:8080/resource", handler, new IoSessionInitializer() { + @Override + public void initializeSession(IoSession session, ConnectFuture future) { + HttpConnectSession connectSession = (HttpConnectSession) session; + connectSession.setMethod(GET); + } + }, connectOptions); + + k3po.finish(); + syncronizer.waitUntil(testState.is("finished")); + } + + @Specification("basic.challenge.twice.and.accept") + @Test + public void willChallengeMultipleAttempts() throws Exception { + connector.getConnectOptions().put("http.max.authentication.attempts", "2"); + final IoHandler handler = context.mock(IoHandler.class); + context.checking(new Expectations() { + { + oneOf(handler).sessionCreated(with(any(IoSession.class))); + oneOf(handler).sessionOpened(with(any(IoSession.class))); + exactly(2).of(authenticator).getPasswordAuthentication(); + will(returnValue(new PasswordAuthentication("joe", new char[]{'w', 'e', 'l', 'c', 'o', 'm', 'e'}))); + oneOf(handler).messageReceived(with(withHttpSessionOfStatus(SUCCESS_OK)), with(any(IoBufferEx.class))); + oneOf(handler).sessionClosed(with(any(IoSession.class))); + then(testState.is("finished")); + } + }); + Map connectOptions = new HashMap<>(); + + connector.connect("http://localhost:8080/resource", handler, new IoSessionInitializer() { + @Override + public void initializeSession(IoSession session, ConnectFuture future) { + HttpConnectSession connectSession = (HttpConnectSession) session; + connectSession.setMethod(GET); + } + }, connectOptions); + + k3po.finish(); + syncronizer.waitUntil(testState.is("finished")); + } + +} diff --git a/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/connector/ResetAuthenticatorRule.java b/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/connector/ResetAuthenticatorRule.java new file mode 100644 index 0000000000..f1da9e2527 --- /dev/null +++ b/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/connector/ResetAuthenticatorRule.java @@ -0,0 +1,43 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.transport.http.security.auth.connector; + +import static java.net.Authenticator.setDefault; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public class ResetAuthenticatorRule implements TestRule { + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + + @Override + public void evaluate() throws Throwable { + try { + base.evaluate(); + } + finally { + setDefault(null); + } + } + + }; + } + +} \ No newline at end of file diff --git a/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/connector/TestAuthenticator.java b/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/connector/TestAuthenticator.java new file mode 100644 index 0000000000..30bdf4f4a4 --- /dev/null +++ b/transport/http/src/test/java/org/kaazing/gateway/transport/http/security/auth/connector/TestAuthenticator.java @@ -0,0 +1,54 @@ +/** + * Copyright 2007-2016, Kaazing Corporation. All rights reserved. + * + * Licensed 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.kaazing.gateway.transport.http.security.auth.connector; + +import java.net.Authenticator; +import java.net.InetAddress; +import java.net.PasswordAuthentication; +import java.net.URL; + +public class TestAuthenticator extends Authenticator{ + + static String host; + static int port; + static String prompt; + static String protocol; + static String scheme; + static InetAddress site; + static URL url; + static RequestorType type; + + @Override + protected PasswordAuthentication getPasswordAuthentication() { + host = this.getRequestingHost(); + port = this.getRequestingPort(); + prompt = this.getRequestingPrompt(); + protocol = this.getRequestingProtocol(); + scheme = this.getRequestingScheme(); + site = this.getRequestingSite(); + url = this.getRequestingURL(); + type = this.getRequestorType(); + return new PasswordAuthentication("joe", new char[] {'w', 'e', 'l', 'c', 'o', 'm', 'e'}); + } + + @Override + public String toString() { + return "host: " + host + ", " + "port: " + port + ", " + "prompt: " + prompt + ", " + "protocol: " + protocol + ", " + + "scheme: " + scheme + ", " + "site: " + site + ", " + "url: " + url + ", " + "type: " + type; + } +} + diff --git a/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/application.challenge.and.accept.rpt b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/application.challenge.and.accept.rpt new file mode 100644 index 0000000000..9add236354 --- /dev/null +++ b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/application.challenge.and.accept.rpt @@ -0,0 +1,39 @@ +# +# Copyright 2007-2016, Kaazing Corporation. All rights reserved. +# +# Licensed 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. +# + +property partialCredentials ${http:loginBase64Encoder("joe")} +property validCredentials ${http:loginBase64Encoder("joe:welcome")} +property authHeader ${http:append("Basic ", validCredentials)} +accept http://localhost:8080/resource +accepted +connected + +read method "GET" + +write status "401" "Unauthorized" +write header "WWW-Authenticate" "Application realm=\"Kaazing Gateway Demo\"" +write header content-length +write flush + +accepted +connected + +read method "GET" +read header "Authorization" ${authHeader} + +write status "200" "OK" +write header content-length +write flush \ No newline at end of file diff --git a/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.and.accept.rpt b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.and.accept.rpt new file mode 100644 index 0000000000..f5ce0bbeb1 --- /dev/null +++ b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.and.accept.rpt @@ -0,0 +1,41 @@ +# +# Copyright 2007-2016, Kaazing Corporation. All rights reserved. +# +# Licensed 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. +# + +property partialCredentials ${http:loginBase64Encoder("joe")} +property validCredentials ${http:loginBase64Encoder("joe:welcome")} +property authHeader ${http:append("Basic ", validCredentials)} +accept http://localhost:8080/resource +accepted +connected + +read method "GET" + +write status "401" "Unauthorized" +write header "WWW-Authenticate" "Basic realm=\"Kaazing Gateway Demo\"" +write header content-length +write "not authorized" +write close + +accepted +connected + +read method "GET" +read header "Authorization" ${authHeader} + +write status "200" "OK" +write header content-length +write "authorized" +write close diff --git a/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.rpt b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.rpt new file mode 100644 index 0000000000..132dafb2b7 --- /dev/null +++ b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.rpt @@ -0,0 +1,30 @@ +# +# Copyright 2007-2016, Kaazing Corporation. All rights reserved. +# +# Licensed 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. +# + +property partialCredentials ${http:loginBase64Encoder("joe")} +property validCredentials ${http:loginBase64Encoder("joe:welcome")} +property authHeader ${http:append("Basic ", validCredentials)} +accept http://localhost:8080/resource +accepted +connected + +read method "GET" + +write status "401" "Unauthorized" +write header "WWW-Authenticate" "Basic realm=\"Kaazing Gateway Demo\"" +write header content-length +write "Not authorized" +write flush diff --git a/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.twice.and.accept.rpt b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.twice.and.accept.rpt new file mode 100644 index 0000000000..898aaebe51 --- /dev/null +++ b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.twice.and.accept.rpt @@ -0,0 +1,53 @@ +# +# Copyright 2007-2016, Kaazing Corporation. All rights reserved. +# +# Licensed 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. +# + +property partialCredentials ${http:loginBase64Encoder("joe")} +property validCredentials ${http:loginBase64Encoder("joe:welcome")} +property authHeader ${http:append("Basic ", validCredentials)} +accept http://localhost:8080/resource +accepted +connected + +read method "GET" + +write status "401" "Unauthorized" +write header "WWW-Authenticate" "Basic realm=\"Kaazing Gateway Demo\"" +write header content-length +write "not authorized" +write close + +accepted +connected + +read method "GET" +read header "Authorization" ${authHeader} + +write status "401" "Unauthorized" +write header "WWW-Authenticate" "Basic realm=\"Kaazing Gateway Demo\"" +write header content-length +write "not authorized" +write close + +read method "GET" +read header "Authorization" ${authHeader} + +accepted +connected + +write status "200" "OK" +write header content-length +write "authorized" +write close diff --git a/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.twice.rpt b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.twice.rpt new file mode 100644 index 0000000000..eb8ce5f232 --- /dev/null +++ b/transport/http/src/test/scripts/org/kaazing/gateway/transport/http/security/auth/connector/basic.challenge.twice.rpt @@ -0,0 +1,42 @@ +# +# Copyright 2007-2016, Kaazing Corporation. All rights reserved. +# +# Licensed 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. +# + +property partialCredentials ${http:loginBase64Encoder("joe")} +property validCredentials ${http:loginBase64Encoder("joe:welcome")} +property authHeader ${http:append("Basic ", validCredentials)} +accept http://localhost:8080/resource +accepted +connected + +read method "GET" + +write status "401" "Unauthorized" +write header "WWW-Authenticate" "Basic realm=\"Kaazing Gateway Demo\"" +write header content-length +write "not authorized" +write close + +accepted +connected + +read method "GET" +read header "Authorization" ${authHeader} + +write status "401" "Unauthorized" +write header "WWW-Authenticate" "Basic realm=\"Kaazing Gateway Demo\"" +write header content-length +write "not authorized" +write close \ No newline at end of file diff --git a/transport/nio/src/main/java/org/kaazing/gateway/transport/nio/internal/NioDatagramAcceptor.java b/transport/nio/src/main/java/org/kaazing/gateway/transport/nio/internal/NioDatagramAcceptor.java index 46cc14bcb2..aa8c359bd4 100644 --- a/transport/nio/src/main/java/org/kaazing/gateway/transport/nio/internal/NioDatagramAcceptor.java +++ b/transport/nio/src/main/java/org/kaazing/gateway/transport/nio/internal/NioDatagramAcceptor.java @@ -18,31 +18,39 @@ import org.apache.mina.core.future.IoFuture; import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.session.IdleStatus; -import org.apache.mina.core.session.IoSession; import org.apache.mina.core.session.IoSessionInitializer; +import org.jboss.netty.channel.socket.nio.NioServerDatagramChannelFactory; +import org.jboss.netty.channel.socket.nio.NioWorker; +import org.jboss.netty.channel.socket.nio.WorkerPool; import org.kaazing.gateway.resource.address.ResourceAddress; import org.kaazing.gateway.resource.address.uri.URIUtils; -import org.kaazing.gateway.transport.BridgeAcceptHandler; import org.kaazing.gateway.transport.BridgeSessionInitializer; -import org.kaazing.gateway.transport.LoggingFilter; import org.kaazing.gateway.transport.NioBindException; import org.kaazing.gateway.transport.bio.MulticastAcceptor; -import org.kaazing.gateway.transport.nio.NioSystemProperty; import org.kaazing.mina.core.service.IoAcceptorEx; import org.kaazing.mina.netty.socket.DatagramChannelIoSessionConfig; import org.kaazing.mina.netty.socket.DefaultDatagramChannelIoSessionConfig; import org.kaazing.mina.netty.socket.nio.NioDatagramChannelIoAcceptor; import org.slf4j.LoggerFactory; +import javax.annotation.Resource; import java.net.InetAddress; import java.util.Properties; +import static java.util.concurrent.Executors.newCachedThreadPool; import static org.kaazing.gateway.transport.nio.NioSystemProperty.UDP_IDLE_TIMEOUT; public class NioDatagramAcceptor extends AbstractNioAcceptor { private static final String LOGGER_NAME = String.format("transport.%s.accept", NioProtocol.UDP.name().toLowerCase()); + private NioSocketAcceptor tcpAcceptor; + + @Resource(name = "tcp.acceptor") + public void setTcpAcceptor(NioSocketAcceptor tcpAcceptor) { + this.tcpAcceptor = tcpAcceptor; + } + public NioDatagramAcceptor(Properties configuration) { super(configuration, LoggerFactory.getLogger(LOGGER_NAME)); } @@ -54,7 +62,9 @@ protected String getTransportName() { @Override protected IoAcceptorEx initAcceptor(final IoSessionInitializer initializer) { DatagramChannelIoSessionConfig config = new DefaultDatagramChannelIoSessionConfig(); - NioDatagramChannelIoAcceptor acceptor = new NioDatagramChannelIoAcceptor(config); + WorkerPool workerPool = tcpAcceptor.initWorkerPool(logger, "UDP acceptor: {}", configuration); + NioServerDatagramChannelFactory channelFactory = new NioServerDatagramChannelFactory(newCachedThreadPool(), 1, workerPool); + NioDatagramChannelIoAcceptor acceptor = new NioDatagramChannelIoAcceptor(config, channelFactory); acceptor.setIoSessionInitializer(initializer); String readBufferSize = configuration.getProperty("org.kaazing.gateway.transport.udp.READ_BUFFER_SIZE"); diff --git a/transport/nio/src/main/java/org/kaazing/gateway/transport/nio/internal/NioDatagramConnector.java b/transport/nio/src/main/java/org/kaazing/gateway/transport/nio/internal/NioDatagramConnector.java index d4f3a845c9..4d6d45354c 100644 --- a/transport/nio/src/main/java/org/kaazing/gateway/transport/nio/internal/NioDatagramConnector.java +++ b/transport/nio/src/main/java/org/kaazing/gateway/transport/nio/internal/NioDatagramConnector.java @@ -22,14 +22,14 @@ import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoHandler; -import org.apache.mina.core.session.IoSession; import org.apache.mina.core.session.IoSessionInitializer; +import org.jboss.netty.channel.socket.nio.NioClientDatagramChannelFactory; +import org.jboss.netty.channel.socket.nio.NioWorker; +import org.jboss.netty.channel.socket.nio.WorkerPool; import org.kaazing.gateway.resource.address.ResourceAddress; import org.kaazing.gateway.resource.address.ResourceAddressFactory; import org.kaazing.gateway.resource.address.uri.URIUtils; -import org.kaazing.gateway.transport.BridgeAcceptHandler; import org.kaazing.gateway.transport.BridgeServiceFactory; -import org.kaazing.gateway.transport.LoggingFilter; import org.kaazing.gateway.transport.bio.MulticastConnector; import org.kaazing.mina.core.service.IoConnectorEx; import org.kaazing.mina.netty.socket.DatagramChannelIoSessionConfig; @@ -44,6 +44,12 @@ public class NioDatagramConnector extends AbstractNioConnector { private BridgeServiceFactory bridgeServiceFactory; private ResourceAddressFactory resourceAddressFactory; + private NioSocketAcceptor tcpAcceptor; + + @Resource(name = "tcp.acceptor") + public void setTcpAcceptor(NioSocketAcceptor tcpAcceptor) { + this.tcpAcceptor = tcpAcceptor; + } @Resource(name = "bridgeServiceFactory") public void setBridgeServiceFactory(BridgeServiceFactory bridgeServiceFactory) { @@ -72,7 +78,9 @@ public NioDatagramConnector(Properties configuration) { @Override protected IoConnectorEx initConnector() { DatagramChannelIoSessionConfig config = new DefaultDatagramChannelIoSessionConfig(); - NioDatagramChannelIoConnector connector = new NioDatagramChannelIoConnector(config); + WorkerPool workerPool = tcpAcceptor.initWorkerPool(logger, "UDP connector: {}", getConfiguration()); + NioClientDatagramChannelFactory channelFactory = new NioClientDatagramChannelFactory(workerPool); + NioDatagramChannelIoConnector connector = new NioDatagramChannelIoConnector(config, channelFactory); String readBufferSize = configuration.getProperty("org.kaazing.gateway.transport.udp.READ_BUFFER_SIZE"); String minimumReadBufferSize = configuration.getProperty("org.kaazing.gateway.transport.udp.MINIMUM_READ_BUFFER_SIZE"); diff --git a/transport/nio/src/test/java/org/kaazing/gateway/transport/nio/internal/NioDatagramAcceptorTest.java b/transport/nio/src/test/java/org/kaazing/gateway/transport/nio/internal/NioDatagramAcceptorTest.java index cf09d20c16..c903a3ee4a 100644 --- a/transport/nio/src/test/java/org/kaazing/gateway/transport/nio/internal/NioDatagramAcceptorTest.java +++ b/transport/nio/src/test/java/org/kaazing/gateway/transport/nio/internal/NioDatagramAcceptorTest.java @@ -62,8 +62,10 @@ public void echo() throws Exception { ResourceAddress bindAddress = addressFactory.newResourceAddress("udp://localhost:8080", new HashMap<>()); Properties configuration = new Properties(); + NioSocketAcceptor tcpAcceptor = new NioSocketAcceptor(configuration); NioDatagramAcceptor acceptor = new NioDatagramAcceptor(configuration); acceptor.setResourceAddressFactory(newResourceAddressFactory()); + acceptor.setTcpAcceptor(tcpAcceptor); acceptor.bind(bindAddress, handler, null); String str = "Hello World"; diff --git a/transport/nio/src/test/java/org/kaazing/gateway/transport/udp/specification/UdpAcceptorRule.java b/transport/nio/src/test/java/org/kaazing/gateway/transport/udp/specification/UdpAcceptorRule.java index 3c03f4fcff..78764db006 100644 --- a/transport/nio/src/test/java/org/kaazing/gateway/transport/udp/specification/UdpAcceptorRule.java +++ b/transport/nio/src/test/java/org/kaazing/gateway/transport/udp/specification/UdpAcceptorRule.java @@ -98,6 +98,7 @@ public void evaluate() throws Throwable { in.close(); PropertyConfigurator.configure(log4j); } + NioSocketAcceptor tcpAcceptor = null; try { // Connector setup schedulerProvider = new SchedulerProvider(); @@ -106,13 +107,20 @@ public void evaluate() throws Throwable { TransportFactory transportFactory = TransportFactory.newTransportFactory((Map) configuration); BridgeServiceFactory serviceFactory = new BridgeServiceFactory(transportFactory); + tcpAcceptor = (NioSocketAcceptor)transportFactory.getTransport("tcp").getAcceptor(); + tcpAcceptor.setResourceAddressFactory(addressFactory); + tcpAcceptor.setBridgeServiceFactory(serviceFactory); + tcpAcceptor.setSchedulerProvider(schedulerProvider); + acceptor = (NioDatagramAcceptor) transportFactory.getTransport("udp").getAcceptor(); + acceptor.setTcpAcceptor(tcpAcceptor); acceptor.setResourceAddressFactory(addressFactory); acceptor.setBridgeServiceFactory(serviceFactory); acceptor.setSchedulerProvider(schedulerProvider); base.evaluate(); } finally { + tcpAcceptor.dispose(); acceptor.dispose(); schedulerProvider.shutdownNow(); } diff --git a/transport/nio/src/test/java/org/kaazing/gateway/transport/udp/specification/UdpConnectorRule.java b/transport/nio/src/test/java/org/kaazing/gateway/transport/udp/specification/UdpConnectorRule.java index 3b76204cbc..3880f0d03f 100644 --- a/transport/nio/src/test/java/org/kaazing/gateway/transport/udp/specification/UdpConnectorRule.java +++ b/transport/nio/src/test/java/org/kaazing/gateway/transport/udp/specification/UdpConnectorRule.java @@ -31,6 +31,7 @@ import org.kaazing.gateway.transport.TransportFactory; import org.kaazing.gateway.transport.nio.internal.NioDatagramAcceptor; import org.kaazing.gateway.transport.nio.internal.NioDatagramConnector; +import org.kaazing.gateway.transport.nio.internal.NioSocketAcceptor; import org.kaazing.gateway.util.scheduler.SchedulerProvider; @@ -71,6 +72,7 @@ public ConnectorStatement(Statement base) { @Override public void evaluate() throws Throwable { + NioSocketAcceptor tcpAcceptor = null; try { // Connector setup schedulerProvider = new SchedulerProvider(); @@ -79,18 +81,26 @@ public void evaluate() throws Throwable { TransportFactory transportFactory = TransportFactory.newTransportFactory(Collections.emptyMap()); BridgeServiceFactory serviceFactory = new BridgeServiceFactory(transportFactory); + tcpAcceptor = (NioSocketAcceptor)transportFactory.getTransport("tcp").getAcceptor(); + tcpAcceptor.setResourceAddressFactory(addressFactory); + tcpAcceptor.setBridgeServiceFactory(serviceFactory); + tcpAcceptor.setSchedulerProvider(schedulerProvider); + udpAcceptor = (NioDatagramAcceptor) transportFactory.getTransport("udp").getAcceptor(); udpAcceptor.setResourceAddressFactory(addressFactory); udpAcceptor.setBridgeServiceFactory(serviceFactory); udpAcceptor.setSchedulerProvider(schedulerProvider); + udpAcceptor.setTcpAcceptor(tcpAcceptor); udpConnector = (NioDatagramConnector) transportFactory.getTransport("udp").getConnector(); udpConnector.setResourceAddressFactory(addressFactory); udpConnector.setBridgeServiceFactory(serviceFactory); + udpConnector.setTcpAcceptor(tcpAcceptor); base.evaluate(); } finally { + tcpAcceptor.dispose(); udpAcceptor.dispose(); udpConnector.dispose(); schedulerProvider.shutdownNow(); diff --git a/transport/ws/src/main/java/org/kaazing/gateway/transport/ws/extension/ExtensionHeaderBuilder.java b/transport/ws/src/main/java/org/kaazing/gateway/transport/ws/extension/ExtensionHeaderBuilder.java index bdb5b82603..05a4383593 100644 --- a/transport/ws/src/main/java/org/kaazing/gateway/transport/ws/extension/ExtensionHeaderBuilder.java +++ b/transport/ws/src/main/java/org/kaazing/gateway/transport/ws/extension/ExtensionHeaderBuilder.java @@ -53,7 +53,7 @@ public ExtensionHeaderBuilder(String extension) { // Look for any parameters in the given token int idx = extension.indexOf(';'); if (idx == -1) { - this.extensionToken = extension; + this.extensionToken = extension.trim(); } else { String[] elts = extension.split(";"); diff --git a/transport/wsn/src/main/java/org/kaazing/gateway/transport/wsn/WsnConnector.java b/transport/wsn/src/main/java/org/kaazing/gateway/transport/wsn/WsnConnector.java index c1eaf88b2d..6b9e27385e 100644 --- a/transport/wsn/src/main/java/org/kaazing/gateway/transport/wsn/WsnConnector.java +++ b/transport/wsn/src/main/java/org/kaazing/gateway/transport/wsn/WsnConnector.java @@ -19,6 +19,7 @@ import static java.util.Arrays.asList; import static org.kaazing.gateway.resource.address.ResourceAddress.ALTERNATE; import static org.kaazing.gateway.resource.address.ResourceAddress.NEXT_PROTOCOL; +import static org.kaazing.gateway.transport.ws.util.WsUtils.HEADER_SEC_WEBSOCKET_EXTENSIONS; import static org.kaazing.gateway.transport.wsn.WsnSession.SESSION_KEY; import java.io.IOException; @@ -74,6 +75,7 @@ import org.kaazing.gateway.transport.ws.bridge.filter.WsCodecFilter; import org.kaazing.gateway.transport.ws.bridge.filter.WsFrameBase64Filter; import org.kaazing.gateway.transport.ws.bridge.filter.WsFrameTextFilter; +import org.kaazing.gateway.transport.ws.extension.ExtensionHeaderBuilder; import org.kaazing.gateway.transport.ws.util.WsUtils; import org.kaazing.gateway.util.Encoding; import org.kaazing.gateway.util.Utils; @@ -354,6 +356,17 @@ public void initializeSession(IoSession session, T future) { if (!protocols.isEmpty()) { httpSession.setWriteHeader("Sec-WebSocket-Protocol", Utils.asCommaSeparatedString(protocols)); } + + List extensions = wsnConnectAddress.getOption(WsResourceAddress.EXTENSIONS); + if (extensions != null) { + for (String extension : extensions) { + if (extension != null) { + httpSession.addWriteHeader(HEADER_SEC_WEBSOCKET_EXTENSIONS, + new ExtensionHeaderBuilder(extension).toString()); + } + } + } + WSN_SESSION_INITIALIZER_KEY.set(httpSession, wsnSessionInitializer); WSN_CONNECT_FUTURE_KEY.set(httpSession, wsnConnectFuture); WSN_CONNECT_ADDRESS_KEY.set(httpSession, wsnConnectAddress); @@ -572,6 +585,27 @@ private void doUpgrade(final HttpConnectSession httpSession) { return; } + List negotiatedExtensions = httpSession.getReadHeaders(HEADER_SEC_WEBSOCKET_EXTENSIONS); + List requestedExtensions = httpSession.getWriteHeaders(HEADER_SEC_WEBSOCKET_EXTENSIONS); + if (negotiatedExtensions != null && !negotiatedExtensions.isEmpty()) { + if (requestedExtensions == null || requestedExtensions.isEmpty()) { + logger.warn("WebSocket upgrade failed: Extensions were not requested, but received {}", + negotiatedExtensions); + wsnConnectFuture.setException( + new Exception("WebSocket Upgrade Failed: Invalid " + HEADER_SEC_WEBSOCKET_EXTENSIONS + " header")); + return; + } else { + for (String negociatedExtension : negotiatedExtensions) { + if (!requestedExtensions.contains(new ExtensionHeaderBuilder(negociatedExtension).getExtensionToken())) { + logger.warn("WebSocket upgrade failed: Extension {} was not requested.", negociatedExtension); + wsnConnectFuture.setException(new Exception( + "WebSocket Upgrade Failed: Invalid " + HEADER_SEC_WEBSOCKET_EXTENSIONS + " header")); + return; + } + } + } + } + final IoSessionInitializer wsnSessionInitializer = WSN_SESSION_INITIALIZER_KEY.remove(httpSession); final ConnectFuture wsnConnectFuture = WSN_CONNECT_FUTURE_KEY.get(httpSession); final ResourceAddress wsnConnectAddress = WSN_CONNECT_ADDRESS_KEY.remove(httpSession); diff --git a/transport/wsn/src/test/java/org/kaazing/gateway/transport/wsn/specification/ws/connector/OpeningHandshakeIT.java b/transport/wsn/src/test/java/org/kaazing/gateway/transport/wsn/specification/ws/connector/OpeningHandshakeIT.java index b43006f3f4..978f577d62 100644 --- a/transport/wsn/src/test/java/org/kaazing/gateway/transport/wsn/specification/ws/connector/OpeningHandshakeIT.java +++ b/transport/wsn/src/test/java/org/kaazing/gateway/transport/wsn/specification/ws/connector/OpeningHandshakeIT.java @@ -169,7 +169,7 @@ public void shouldEstablishConnectionWithRequestHeaderOrigin() throws Exception } @Test - @Ignore("Issue# 309: Missing Sec-WebSocket-Extensions header in the handshake request; currently has comma separated which is not support in K3po yet") + @Ignore("K3po issue# 53: Simplify matching syntax for comma-separated list HTTP headers; currently has comma separated which is not support in K3po yet") @Specification({ "request.header.sec.websocket.protocol/handshake.response" }) @@ -192,7 +192,6 @@ public void shouldEstablishConnectionWithRequestHeaderSecWebSocketProtocol() thr } @Test - @Ignore("Issue# 309: Missing Sec-WebSocket-Extensions header in the handshake request") @Specification({ "request.header.sec.websocket.extensions/handshake.response" }) @@ -215,7 +214,6 @@ public void shouldEstablishConnectionWithRequestHeaderSecWebSocketExtensions() t } @Test - @Ignore("Issue# 309: Missing Sec-WebSocket-Extensions header in the handshake request") @Specification({ "response.header.sec.websocket.extensions.partial.agreement/handshake.response" }) @@ -238,7 +236,6 @@ public void shouldEstablishConnectionWithSomeExtensionsNegotiated() throws Excep } @Test - @Ignore("Issue #309: Missing Sec-WebSocket-Extensions header in the handshake request") @Specification({ "response.header.sec.websocket.extensions.reordered/handshake.response" }) @@ -392,8 +389,6 @@ public void shouldFailConnectionWhenResponseHeaderSecWebSocketAcceptMissing() th } @Test - @Ignore("Issues# 309, 314: Missing Sec-WebSocket-Extensions header in the handshake request. connectFuture.isConnected()" - + "must return false as the negotiated extension does not match the supported extensions.") @Specification({ "response.header.sec.websocket.extensions.not.negotiated/handshake.response" }) public void shouldFailConnectionWhenResponseHeaderSecWebSocketExtensionsNotNegotiated() throws Exception { diff --git a/util/src/main/java/org/kaazing/gateway/util/Utils.java b/util/src/main/java/org/kaazing/gateway/util/Utils.java index 807dcea817..d62aaffa56 100644 --- a/util/src/main/java/org/kaazing/gateway/util/Utils.java +++ b/util/src/main/java/org/kaazing/gateway/util/Utils.java @@ -624,19 +624,47 @@ public static long parseTimeInterval(String timeIntervalValue, TimeUnit outputUn try { if (SECONDS_UNITS.contains(unit.toLowerCase())) { - result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.SECONDS)); + if (providedMultiplier < 1) { + result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.SECONDS)); + } else { + result = (long) (outputUnit.convert((long)providedMultiplier, TimeUnit.SECONDS)); + } } else if (MILLISECONDS_UNITS.contains(unit.toLowerCase())) { - result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.MILLISECONDS)); + if (providedMultiplier < 1) { + result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.MILLISECONDS)); + } else { + result = (long) (outputUnit.convert((long)providedMultiplier, TimeUnit.MILLISECONDS)); + } } else if (MINUTES_UNITS.contains(unit.toLowerCase())) { - result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.MINUTES)); + if (providedMultiplier < 1) { + result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.MINUTES)); + } else { + result = (long) (outputUnit.convert((long)providedMultiplier, TimeUnit.MINUTES)); + } } else if (HOURS_UNITS.contains(unit.toLowerCase())) { - result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.HOURS)); + if (providedMultiplier < 1) { + result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.HOURS)); + } else { + result = (long) (outputUnit.convert((long)providedMultiplier, TimeUnit.HOURS)); + } } else if (DAYS_UNITS.contains(unit.toLowerCase())) { - result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.DAYS)); + if (providedMultiplier < 1) { + result = (long) (providedMultiplier * outputUnit.convert(result, TimeUnit.DAYS)); + } else { + result = (long) (outputUnit.convert((long)providedMultiplier, TimeUnit.DAYS)); + } } else if (WEEKS_UNITS.contains(unit.toLowerCase())) { - result = (long) (providedMultiplier * outputUnit.convert(NO_OF_DAYS_IN_A_WEEK * result, TimeUnit.DAYS)); + if (providedMultiplier < 1) { + result = (long) (providedMultiplier * outputUnit.convert(NO_OF_DAYS_IN_A_WEEK * result, TimeUnit.DAYS)); + } else { + result = (long) (outputUnit.convert(NO_OF_DAYS_IN_A_WEEK * (long)providedMultiplier, TimeUnit.DAYS)); + } } else if (YEARS_UNITS.contains(unit.toLowerCase())) { - result = (long) (providedMultiplier * outputUnit.convert(NO_OF_DAYS_IN_A_YEAR * result, TimeUnit.DAYS)); + if (providedMultiplier < 1) { + result = (long) (providedMultiplier * outputUnit.convert(NO_OF_DAYS_IN_A_YEAR * result, TimeUnit.DAYS)); + } else { + result = (long) (outputUnit.convert(NO_OF_DAYS_IN_A_YEAR * (long)providedMultiplier, TimeUnit.DAYS)); + } } if (result < 0) { throw new NumberFormatException("Expected a non-negative time interval, received \"" diff --git a/util/src/main/java/org/kaazing/gateway/util/feature/EarlyAccessFeatures.java b/util/src/main/java/org/kaazing/gateway/util/feature/EarlyAccessFeatures.java index 07d5f3254a..25404e3abb 100644 --- a/util/src/main/java/org/kaazing/gateway/util/feature/EarlyAccessFeatures.java +++ b/util/src/main/java/org/kaazing/gateway/util/feature/EarlyAccessFeatures.java @@ -24,6 +24,7 @@ public interface EarlyAccessFeatures { EarlyAccessFeature WSN_302_REDIRECT = new EarlyAccessFeature("wsn.302.redirect", "Send redirect for wsn via 302", false); EarlyAccessFeature WSX_302_REDIRECT = new EarlyAccessFeature("wsx.302.redirect", "Send redirect for wsx via 302", false); EarlyAccessFeature TURN_REST_SERVICE = new EarlyAccessFeature("turn.rest", "TURN REST Service", false); - EarlyAccessFeature TURN_PROXY = new EarlyAccessFeature("turn.proxy", "TURN Proxy Service", false); + EarlyAccessFeature HTTP_AUTHENTICATOR = new EarlyAccessFeature("http.authenticator", "HTTP Authenticator", false); + EarlyAccessFeature LOGIN_MODULE_EXPIRING_STATE = new EarlyAccessFeature("login.module.expiring.state", "Login Module Expiring State", false); } diff --git a/util/src/main/java/org/kaazing/gateway/util/turn/TurnUtils.java b/util/src/main/java/org/kaazing/gateway/util/turn/TurnUtils.java index b00786a427..00efaf3004 100644 --- a/util/src/main/java/org/kaazing/gateway/util/turn/TurnUtils.java +++ b/util/src/main/java/org/kaazing/gateway/util/turn/TurnUtils.java @@ -19,6 +19,7 @@ import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.util.Base64; @@ -64,4 +65,15 @@ public static char[] generatePassword(String username, Key sharedSecret, String return Base64.getEncoder().encodeToString(hmac.doFinal(username.getBytes())).toCharArray(); } + + + public static byte[] generateKey(String username, String realm, String password, char separator) { + String data = String.format("%s%c%s%c%s", username, separator, realm, separator, password); + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + return md.digest(data.getBytes()); + } catch (NoSuchAlgorithmException e) { + throw new TurnException("Unable to generate key", e); + } + } } \ No newline at end of file