This is an automated email from the ASF dual-hosted git repository. tibordigana pushed a commit to branch SUREFIRE-1796 in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
commit 08740026d6a2feb6746b5dcd88490f1a24b17003 Author: tibordigana <[email protected]> AuthorDate: Fri Jun 5 00:28:20 2020 +0200 [SUREFIRE-1796] The session of TCP channel should be authenticated --- .../plugin/surefire/booterclient/ForkStarter.java | 15 ++- .../extensions/InvalidSessionIdException.java | 34 ++---- .../surefire/extensions/SurefireForkChannel.java | 30 +++++- ...BooterDeserializerStartupConfigurationTest.java | 4 +- .../maven/plugin/surefire/extensions/E2ETest.java | 83 ++++++++++++++- .../maven/surefire/extensions/ForkChannelTest.java | 19 +++- .../api/util/internal/AsyncSocketTest.java | 3 +- ...refireMasterProcessChannelProcessorFactory.java | 28 +++++ .../surefire/booter/ForkedBooterMockTest.java | 116 +++++++++++++++++---- .../surefire/extensions/ForkNodeArguments.java | 3 + .../spi/MasterProcessChannelProcessorFactory.java | 2 +- 11 files changed, 277 insertions(+), 60 deletions(-) diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java index 3a8cd4f..0a333f6 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java @@ -83,6 +83,7 @@ import static java.lang.StrictMath.min; import static java.lang.System.currentTimeMillis; import static java.lang.Thread.currentThread; import static java.util.Collections.addAll; +import static java.util.UUID.randomUUID; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -573,7 +574,8 @@ public class ForkStarter File dumpLogDir = replaceForkThreadsInPath( startupReportConfiguration.getReportsDirectory(), forkNumber ); try { - forkChannel = forkNodeFactory.createForkChannel( new ForkedNodeArg( forkNumber, dumpLogDir ) ); + ForkNodeArguments forkNodeArguments = new ForkedNodeArg( forkNumber, dumpLogDir, randomUUID().toString() ); + forkChannel = forkNodeFactory.createForkChannel( forkNodeArguments ); closer.addCloseable( forkChannel ); tempDir = forkConfiguration.getTempDirectory().getCanonicalPath(); BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration ); @@ -881,11 +883,20 @@ public class ForkStarter { private final int forkChannelId; private final File dumpLogDir; + private final String sessionId; - ForkedNodeArg( int forkChannelId, File dumpLogDir ) + ForkedNodeArg( int forkChannelId, File dumpLogDir, String sessionId ) { this.forkChannelId = forkChannelId; this.dumpLogDir = dumpLogDir; + this.sessionId = sessionId; + } + + @Nonnull + @Override + public String getSessionId() + { + return sessionId; } @Override diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java similarity index 54% copy from surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java copy to maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java index bdd0b2e..2ceaf12 100644 --- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/InvalidSessionIdException.java @@ -1,4 +1,4 @@ -package org.apache.maven.surefire.extensions; +package org.apache.maven.plugin.surefire.extensions; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -19,33 +19,21 @@ package org.apache.maven.surefire.extensions; * under the License. */ -import org.apache.maven.plugin.surefire.log.api.ConsoleLogger; +import org.apache.maven.surefire.extensions.ForkChannel; +import org.apache.maven.surefire.extensions.util.CommandlineExecutor; -import javax.annotation.Nonnegative; -import javax.annotation.Nonnull; -import java.io.File; +import java.io.IOException; /** - * The properties related to the current JVM. + * After the authentication has failed, {@link ForkChannel#connectToClient()} throws {@link InvalidSessionIdException} + * and {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter} should close {@link CommandlineExecutor}. * - * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> * @since 3.0.0-M5 */ -public interface ForkNodeArguments +public class InvalidSessionIdException extends IOException { - /** - * The index of the forked JVM, from 1 to N. - * - * @return index of the forked JVM - */ - @Nonnegative - int getForkChannelId(); - - @Nonnull - File dumpStreamText( @Nonnull String text ); - - void logWarningAtEnd( @Nonnull String text ); - - @Nonnull - ConsoleLogger getConsoleLogger(); + public InvalidSessionIdException( String actualSessionId, String expectedSessionId ) + { + super( "The actual sessionId '" + actualSessionId + "' does not match '" + expectedSessionId + "'." ); + } } diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java index cf522e8..5049dcf 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/extensions/SurefireForkChannel.java @@ -32,10 +32,10 @@ import org.apache.maven.surefire.extensions.util.LineConsumerThread; import javax.annotation.Nonnull; import java.io.Closeable; import java.io.IOException; -import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketOption; +import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.ReadableByteChannel; @@ -49,6 +49,7 @@ import static java.net.StandardSocketOptions.SO_REUSEADDR; import static java.net.StandardSocketOptions.TCP_NODELAY; import static java.nio.channels.AsynchronousChannelGroup.withThreadPool; import static java.nio.channels.AsynchronousServerSocketChannel.open; +import static java.nio.charset.StandardCharsets.US_ASCII; import static org.apache.maven.surefire.api.util.internal.Channels.newBufferedChannel; import static org.apache.maven.surefire.api.util.internal.Channels.newChannel; import static org.apache.maven.surefire.api.util.internal.Channels.newInputStream; @@ -76,6 +77,7 @@ final class SurefireForkChannel extends ForkChannel private final AsynchronousServerSocketChannel server; private final String localHost; private final int localPort; + private final String sessionId; private volatile AsynchronousSocketChannel worker; private volatile LineConsumerThread out; @@ -84,11 +86,12 @@ final class SurefireForkChannel extends ForkChannel super( arguments ); server = open( withThreadPool( THREAD_POOL ) ); setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE ); - InetAddress ip = Inet4Address.getLocalHost(); + InetAddress ip = InetAddress.getLocalHost(); server.bind( new InetSocketAddress( ip, 0 ), 1 ); InetSocketAddress localAddress = (InetSocketAddress) server.getLocalAddress(); localHost = localAddress.getHostString(); localPort = localAddress.getPort(); + sessionId = arguments.getSessionId(); } @Override @@ -102,6 +105,7 @@ final class SurefireForkChannel extends ForkChannel try { worker = server.accept().get(); + verifySessionId(); } catch ( InterruptedException e ) { @@ -113,6 +117,26 @@ final class SurefireForkChannel extends ForkChannel } } + private void verifySessionId() throws InterruptedException, ExecutionException, IOException + { + ByteBuffer buffer = ByteBuffer.allocate( sessionId.length() ); + int read; + do + { + read = worker.read( buffer ).get(); + } while ( read != -1 && buffer.hasRemaining() ); + if ( read == -1 ) + { + throw new IOException( "Channel closed while verifying the client." ); + } + buffer.flip(); + String clientSessionId = new String( buffer.array(), US_ASCII ); + if ( !clientSessionId.equals( sessionId ) ) + { + throw new InvalidSessionIdException( clientSessionId, sessionId ); + } + } + @SafeVarargs private final void setTrueOptions( SocketOption<Boolean>... options ) throws IOException @@ -129,7 +153,7 @@ final class SurefireForkChannel extends ForkChannel @Override public String getForkNodeConnectionString() { - return "tcp://" + localHost + ":" + localPort; + return "tcp://" + localHost + ":" + localPort + "?sessionId=" + sessionId; } @Override diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java index 6fe2ddc..669e73c 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java @@ -179,10 +179,10 @@ public class BooterDeserializerStartupConfigurationTest BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration ); String aTest = "aTest"; File propsTest = booterSerializer.serialize( props, getProviderConfiguration(), startupConfiguration, aTest, - false, null, 1, "tcp://127.0.0.1:63003" ); + false, null, 1, "tcp://localhost:63003" ); BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) ); assertNull( booterDeserializer.getPluginPid() ); - assertEquals( "tcp://127.0.0.1:63003", booterDeserializer.getConnectionString() ); + assertEquals( "tcp://localhost:63003", booterDeserializer.getConnectionString() ); return booterDeserializer.getStartupConfiguration(); } diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java index 30517f3..34946ef 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/extensions/E2ETest.java @@ -21,24 +21,31 @@ package org.apache.maven.plugin.surefire.extensions; import org.apache.maven.plugin.surefire.log.api.ConsoleLogger; import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder; -import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory; import org.apache.maven.surefire.api.event.Event; +import org.apache.maven.surefire.api.report.ConsoleOutputReceiver; +import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory; import org.apache.maven.surefire.extensions.EventHandler; import org.apache.maven.surefire.extensions.ForkNodeArguments; import org.apache.maven.surefire.extensions.util.CountdownCloseable; -import org.apache.maven.surefire.api.report.ConsoleOutputReceiver; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import javax.annotation.Nonnull; import java.io.Closeable; +import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; +import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import static org.fest.assertions.Assertions.assertThat; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -52,13 +59,17 @@ public class E2ETest private static final String LONG_STRING = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; + @Rule + public final ExpectedException e = ExpectedException.none(); + @Test - public void test() throws Exception + public void endToEndTest() throws Exception { ConsoleLogger logger = mock( ConsoleLogger.class ); ForkNodeArguments arguments = mock( ForkNodeArguments.class ); when( arguments.getForkChannelId() ).thenReturn( 1 ); when( arguments.getConsoleLogger() ).thenReturn( logger ); + when( arguments.getSessionId() ).thenReturn( UUID.randomUUID().toString() ); final SurefireForkChannel server = new SurefireForkChannel( arguments ); final String connection = server.getForkNodeConnectionString(); @@ -184,4 +195,70 @@ public class E2ETest .isPositive() .isLessThanOrEqualTo( 6_000L ); } + + @Test( timeout = 10_000L ) + public void shouldVerifyClient() throws Exception + { + ForkNodeArguments forkNodeArguments = mock( ForkNodeArguments.class ); + when( forkNodeArguments.getSessionId() ).thenReturn( UUID.randomUUID().toString() ); + + try ( SurefireForkChannel server = new SurefireForkChannel( forkNodeArguments ); + SurefireMasterProcessChannelProcessorFactory client = new SurefireMasterProcessChannelProcessorFactory() ) + { + FutureTask<String> task = new FutureTask<>( new Callable<String>() + { + @Override + public String call() throws Exception + { + client.connect( server.getForkNodeConnectionString() ); + return "client connected"; + } + } ); + + Thread t = new Thread( task ); + t.setDaemon( true ); + t.start(); + + server.connectToClient(); + + assertThat( task.get() ) + .isEqualTo( "client connected" ); + } + } + + @Test( timeout = 10_000L ) + public void shouldNotVerifyClient() throws Exception + { + ForkNodeArguments forkNodeArguments = mock( ForkNodeArguments.class ); + String serverSessionId = UUID.randomUUID().toString(); + when( forkNodeArguments.getSessionId() ).thenReturn( serverSessionId ); + + try ( SurefireForkChannel server = new SurefireForkChannel( forkNodeArguments ); + SurefireMasterProcessChannelProcessorFactory client = new SurefireMasterProcessChannelProcessorFactory() ) + { + FutureTask<String> task = new FutureTask<>( new Callable<String>() + { + @Override + public String call() throws Exception + { + URI connectionUri = new URI( server.getForkNodeConnectionString() ); + client.connect( "tcp://" + connectionUri.getHost() + ":" + connectionUri.getPort() + + "?sessionId=6ba7b812-9dad-11d1-80b4-00c04fd430c8" ); + return "client connected"; + } + } ); + + Thread t = new Thread( task ); + t.setDaemon( true ); + t.start(); + + e.expect( InvalidSessionIdException.class ); + e.expectMessage( "The actual sessionId '6ba7b812-9dad-11d1-80b4-00c04fd430c8' does not match '" + + serverSessionId + "'." ); + + server.connectToClient(); + + fail( task.get() ); + } + } } diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java index 01bb8f3..fec93c8 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/extensions/ForkChannelTest.java @@ -39,6 +39,7 @@ import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.Queue; +import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -62,8 +63,16 @@ public class ForkChannelTest @Test( timeout = TESTCASE_TIMEOUT ) public void shouldRequestReplyMessagesViaTCP() throws Exception { + final String sessionId = UUID.randomUUID().toString(); ForkNodeArguments forkNodeArguments = new ForkNodeArguments() { + @Nonnull + @Override + public String getSessionId() + { + return sessionId; + } + @Override public int getForkChannelId() { @@ -102,7 +111,8 @@ public class ForkChannelTest String localHost = InetAddress.getLocalHost().getHostAddress(); assertThat( channel.getForkNodeConnectionString() ) .startsWith( "tcp://" + localHost + ":" ) - .isNotEqualTo( "tcp://" + localHost + ":" ); + .isNotEqualTo( "tcp://" + localHost + ":" ) + .endsWith( "?sessionId=" + sessionId ); URI uri = new URI( channel.getForkNodeConnectionString() ); @@ -123,7 +133,7 @@ public class ForkChannelTest CountdownCloseable cc = new CountdownCloseable( closeable, 2 ); Consumer consumer = new Consumer(); - Client client = new Client( uri.getPort() ); + Client client = new Client( uri.getPort(), sessionId.toString() ); client.start(); channel.connectToClient(); @@ -164,10 +174,12 @@ public class ForkChannelTest private final class Client extends Thread { private final int port; + private final String sessionId; - private Client( int port ) + private Client( int port, String sessionId ) { this.port = port; + this.sessionId = sessionId; } @Override @@ -175,6 +187,7 @@ public class ForkChannelTest { try ( Socket socket = new Socket( InetAddress.getLocalHost().getHostAddress(), port ) ) { + socket.getOutputStream().write( sessionId.getBytes( US_ASCII ) ); byte[] data = new byte[128]; int readLength = socket.getInputStream().read( data ); String token = new String( data, 0, readLength, US_ASCII ); diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/api/util/internal/AsyncSocketTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/api/util/internal/AsyncSocketTest.java index 004d743..f4cb773 100644 --- a/surefire-api/src/test/java/org/apache/maven/surefire/api/util/internal/AsyncSocketTest.java +++ b/surefire-api/src/test/java/org/apache/maven/surefire/api/util/internal/AsyncSocketTest.java @@ -80,7 +80,8 @@ public class AsyncSocketTest AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool( executorService ); AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open( group ); setTrueOptions( server, SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE ); - server.bind( null, 1 ); + InetAddress ip = InetAddress.getLocalHost(); + server.bind( new InetSocketAddress( ip, 0 ), 1 ); address = (InetSocketAddress) server.getLocalAddress(); System.gc(); diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java index f27aa0c..9efff25 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/spi/SurefireMasterProcessChannelProcessorFactory.java @@ -29,7 +29,9 @@ import java.net.MalformedURLException; import java.net.SocketOption; import java.net.URI; import java.net.URISyntaxException; +import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; +import java.util.StringTokenizer; import java.util.concurrent.ExecutionException; import static java.net.StandardSocketOptions.SO_KEEPALIVE; @@ -37,6 +39,7 @@ import static java.net.StandardSocketOptions.SO_REUSEADDR; import static java.net.StandardSocketOptions.TCP_NODELAY; import static java.nio.channels.AsynchronousChannelGroup.withFixedThreadPool; import static java.nio.channels.AsynchronousSocketChannel.open; +import static java.nio.charset.StandardCharsets.US_ASCII; import static org.apache.maven.surefire.api.util.internal.Channels.newBufferedChannel; import static org.apache.maven.surefire.api.util.internal.Channels.newInputStream; import static org.apache.maven.surefire.api.util.internal.Channels.newOutputStream; @@ -75,6 +78,12 @@ public class SurefireMasterProcessChannelProcessorFactory clientSocketChannel = open( withFixedThreadPool( 2, newDaemonThreadFactory() ) ); setTrueOptions( SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE ); clientSocketChannel.connect( hostAddress ).get(); + String sessionId = extractSessionId( uri ); + if ( sessionId != null ) + { + ByteBuffer buff = ByteBuffer.wrap( sessionId.getBytes( US_ASCII ) ); + clientSocketChannel.write( buff ); + } } catch ( URISyntaxException | InterruptedException e ) { @@ -119,4 +128,23 @@ public class SurefireMasterProcessChannelProcessorFactory } } } + + private static String extractSessionId( URI uri ) + { + String query = uri.getQuery(); + if ( query == null ) + { + return null; + } + for ( StringTokenizer tokenizer = new StringTokenizer( query, "&" ); tokenizer.hasMoreTokens(); ) + { + String token = tokenizer.nextToken(); + int delimiter = token.indexOf( '=' ); + if ( delimiter != -1 && "sessionId".equals( token.substring( 0, delimiter ) ) ) + { + return token.substring( delimiter + 1 ); + } + } + return null; + } } diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java index 11a3df2..1982f7e 100644 --- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java +++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java @@ -21,11 +21,11 @@ package org.apache.maven.surefire.booter; import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder; import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder; +import org.apache.maven.surefire.api.report.StackTraceWriter; import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder; import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder; import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory; import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory; -import org.apache.maven.surefire.api.report.StackTraceWriter; import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils; import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory; import org.junit.Rule; @@ -45,11 +45,16 @@ import org.powermock.modules.junit4.PowerMockRunner; import java.io.IOException; import java.net.InetSocketAddress; import java.net.MalformedURLException; +import java.net.URI; +import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; -import static java.net.StandardSocketOptions.SO_KEEPALIVE; -import static java.net.StandardSocketOptions.SO_REUSEADDR; -import static java.net.StandardSocketOptions.TCP_NODELAY; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; import static org.mockito.ArgumentMatchers.any; @@ -300,31 +305,16 @@ public class ForkedBooterMockTest try ( ServerSocketChannel server = ServerSocketChannel.open() ) { - if ( server.supportedOptions().contains( SO_REUSEADDR ) ) - { - server.setOption( SO_REUSEADDR, true ); - } - - if ( server.supportedOptions().contains( TCP_NODELAY ) ) - { - server.setOption( TCP_NODELAY, true ); - } - - if ( server.supportedOptions().contains( SO_KEEPALIVE ) ) - { - server.setOption( SO_KEEPALIVE, true ); - } - server.bind( new InetSocketAddress( 0 ) ); int serverPort = ( (InetSocketAddress) server.getLocalAddress() ).getPort(); try ( MasterProcessChannelProcessorFactory factory = - invokeMethod( ForkedBooter.class, "lookupDecoderFactory", "tcp://127.0.0.1:" + serverPort ) ) + invokeMethod( ForkedBooter.class, "lookupDecoderFactory", "tcp://localhost:" + serverPort ) ) { assertThat( factory ) .isInstanceOf( SurefireMasterProcessChannelProcessorFactory.class ); - assertThat( factory.canUse( "tcp://127.0.0.1:" + serverPort ) ) + assertThat( factory.canUse( "tcp://localhost:" + serverPort ) ) .isTrue(); assertThat( factory.canUse( "-- whatever --" ) ) @@ -350,7 +340,7 @@ public class ForkedBooterMockTest } } ); - factory.connect( "tcp://127.0.0.1:" + serverPort ); + factory.connect( "tcp://localhost:" + serverPort ); MasterProcessChannelDecoder decoder = factory.createDecoder(); assertThat( decoder ) @@ -363,6 +353,68 @@ public class ForkedBooterMockTest } @Test + public void shouldAuthenticate() throws Exception + { + mockStatic( ForkedBooter.class ); + + doCallRealMethod() + .when( ForkedBooter.class, "lookupDecoderFactory", anyString() ); + + try ( final ServerSocketChannel server = ServerSocketChannel.open() ) + { + server.bind( new InetSocketAddress( 0 ) ); + int serverPort = ( (InetSocketAddress) server.getLocalAddress() ).getPort(); + final String uuid = UUID.randomUUID().toString(); + String url = "tcp://localhost:" + serverPort + "?sessionId=" + uuid; + try ( final MasterProcessChannelProcessorFactory factory = + invokeMethod( ForkedBooter.class, "lookupDecoderFactory", url ) ) + { + assertThat( factory ) + .isInstanceOf( SurefireMasterProcessChannelProcessorFactory.class ); + + FutureTask<Boolean> task = new FutureTask<>( new Callable<Boolean>() + { + @Override + public Boolean call() + { + try + { + SocketChannel channel = server.accept(); + ByteBuffer bb = ByteBuffer.allocate( uuid.length() ); + int read = channel.read( bb ); + assertThat( read ) + .isEqualTo( uuid.length() ); + bb.flip(); + assertThat( new String( bb.array(), US_ASCII ) ) + .isEqualTo( uuid ); + return true; + } + catch ( IOException e ) + { + return false; + } + } + } ); + + Thread t = new Thread( task ); + t.setDaemon( true ); + t.start(); + + factory.connect( url ); + + try + { + task.get( 10, SECONDS ); + } + finally + { + factory.close(); + } + } + } + } + + @Test public void testFlushEventChannelOnExit() throws Exception { mockStatic( ShutdownHookUtils.class ); @@ -385,4 +437,24 @@ public class ForkedBooterMockTest } ).when( ShutdownHookUtils.class, "addShutDownHook", any( Thread.class ) ); invokeMethod( booter, "flushEventChannelOnExit" ); } + + @Test + public void shouldParseUUID() throws Exception + { + UUID uuid = UUID.randomUUID(); + URI uri = new URI( "tcp://localhost:12345?sessionId=" + uuid ); + String parsed = invokeMethod( SurefireMasterProcessChannelProcessorFactory.class, "extractSessionId", uri ); + assertThat( parsed ) + .isEqualTo( uuid.toString() ); + } + + @Test + public void shouldNotParseUUID() throws Exception + { + UUID uuid = UUID.randomUUID(); + URI uri = new URI( "tcp://localhost:12345?xxx=" + uuid ); + String parsed = invokeMethod( SurefireMasterProcessChannelProcessorFactory.class, "extractSessionId", uri ); + assertThat( parsed ) + .isNull(); + } } diff --git a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java index bdd0b2e..53da2bd 100644 --- a/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java +++ b/surefire-extensions-api/src/main/java/org/apache/maven/surefire/extensions/ForkNodeArguments.java @@ -33,6 +33,9 @@ import java.io.File; */ public interface ForkNodeArguments { + @Nonnull + String getSessionId(); + /** * The index of the forked JVM, from 1 to N. * diff --git a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java index 93b61e5..8da6c11 100644 --- a/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java +++ b/surefire-extensions-spi/src/main/java/org/apache/maven/surefire/spi/MasterProcessChannelProcessorFactory.java @@ -41,7 +41,7 @@ public interface MasterProcessChannelProcessorFactory extends Closeable /** * Open a new connection. * - * @param channelConfig e.g. "pipe://3" or "tcp://127.0.0.1:65035" + * @param channelConfig e.g. "pipe://3" or "tcp://localhost:65035" * @throws IOException if cannot connect */ void connect( String channelConfig ) throws IOException;
