Skip to content

Commit

Permalink
[JENKINS-36871] All the handlers need the client database
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenc committed Aug 3, 2016
1 parent a065e8a commit 6449a73
Show file tree
Hide file tree
Showing 12 changed files with 465 additions and 139 deletions.
Expand Up @@ -14,7 +14,7 @@ public class Jnlp3ConnectionState extends LegacyJnlpConnectionState {
private String newCookie;

protected Jnlp3ConnectionState(@Nonnull Socket socket,
List<JnlpConnectionStateListener> listeners) throws IOException {
List<? extends JnlpConnectionStateListener> listeners) throws IOException {
super(socket, listeners);
}

Expand Down
Expand Up @@ -11,7 +11,7 @@ public class Jnlp4ConnectionState extends JnlpConnectionState {
private X509Certificate certificate;

protected Jnlp4ConnectionState(@Nonnull Socket socket,
List<JnlpConnectionStateListener> listeners) {
List<? extends JnlpConnectionStateListener> listeners) {
super(socket, listeners);
}

Expand Down
@@ -0,0 +1,11 @@
package org.jenkinsci.remoting.engine;

/**
* @author Stephen Connolly
*/
public interface JnlpClientDatabase {

boolean exists(String clientName);

String getSecretOf(String clientName);
}
Expand Up @@ -6,6 +6,7 @@
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -74,8 +75,14 @@ public class JnlpConnectionState {
* The current state in the event lifecycle.
*/
private State lifecycle = State.INITIALIZED;
/**
* Any connection specific state that the listener that has {@link #approve()} for the connection wants to
* track between callbacks.
*/
@CheckForNull
private ListenerState stash;

protected JnlpConnectionState(@Nonnull Socket socket, List<JnlpConnectionStateListener> listeners) {
protected JnlpConnectionState(@Nonnull Socket socket, List<? extends JnlpConnectionStateListener> listeners) {
this.socket = socket;
this.listeners = new ArrayList<JnlpConnectionStateListener>(listeners);
}
Expand Down Expand Up @@ -220,6 +227,20 @@ public void reject(ConnectionRefusalException reason) {
rejection = reason;
}

public <S extends ListenerState> S stash() {
if (lifecycle.compareTo(State.APPROVED) < 0) {
throw new IllegalStateException("The connection has not been approved yet");
}
return (S) stash;
}

public <S extends ListenerState> void stash(S stash) {
if (lifecycle.compareTo(State.APPROVED) < 0) {
throw new IllegalStateException("The connection has not been approved yet");
}
this.stash = stash;
}

/**
* Encapsulates the common event dispatch logic.
*
Expand Down Expand Up @@ -283,6 +304,7 @@ public void invoke(JnlpConnectionStateListener listener, JnlpConnectionState eve
if (lifecycle != State.BEFORE_PROPERTIES) {
throw new IllegalStateException("fireAfterProperties cannot be invoked at lifecycle " + lifecycle);
}
this.properties = new HashMap<String, String>(properties);
lifecycle = State.AFTER_PROPERTIES;
// TODO fire(JnlpConnectionStateListener::afterProperties);
fire(new EventHandler() {
Expand Down Expand Up @@ -350,7 +372,7 @@ public void invoke(JnlpConnectionStateListener listener, JnlpConnectionState eve
* @param cause
*/
/*package*/ void fireChannelClosed(IOException cause) {
if (lifecycle != State.AFTER_CHANNEL) {
if (lifecycle.compareTo(State.BEFORE_CHANNEL) < 0) {
throw new IllegalStateException("fireChannelClosed cannot be invoked at lifecycle " + lifecycle);
}
closeCause = cause;
Expand Down Expand Up @@ -439,4 +461,8 @@ private enum State {
*/
DISCONNECTED
}

public interface ListenerState {

}
}
Expand Up @@ -24,10 +24,7 @@
package org.jenkinsci.remoting.engine;

import hudson.remoting.Channel;
import hudson.remoting.ChannelBuilder;
import hudson.remoting.Engine;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
Expand All @@ -36,6 +33,8 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jenkinsci.remoting.nio.NioChannelHub;
Expand All @@ -51,31 +50,52 @@
* This was the first protocol supported by Jenkins. JNLP slaves will use this
* as a last resort when connecting to old versions of Jenkins masters.
*
* @author Akshay Dayal
* @since FIXME
*/
@Deprecated
public class JnlpProtocol1Handler extends LegacyJnlpProtocolHandler<LegacyJnlpConnectionState> {

static final String NAME = "JNLP-connect";
/**
* Our logger.
*/
private static final Logger LOGGER = Logger.getLogger(JnlpProtocol1Handler.class.getName());

public JnlpProtocol1Handler(@Nonnull ExecutorService threadPool,
/**
* Constructor.
*
* @param clientDatabase the client database to use or {@code null} if client connections will not be required.
* @param threadPool the {@link ExecutorService} to run tasks on.
* @param hub the {@link NioChannelHub} to use or {@code null} to use blocking I/O.
*/
public JnlpProtocol1Handler(@Nullable JnlpClientDatabase clientDatabase, @Nonnull ExecutorService threadPool,
@Nullable NioChannelHub hub) {
super(threadPool, hub);
super(clientDatabase, threadPool, hub);
}

/**
* {@inheritDoc}
*/
@Override
public String getName() {
return NAME;
return "JNLP-connect";
}

/**
* {@inheritDoc}
*/
@Nonnull
@Override
public LegacyJnlpConnectionState createConnectionState(Socket socket, List<JnlpConnectionStateListener> listeners)
public LegacyJnlpConnectionState createConnectionState(@Nonnull Socket socket,
@Nonnull List<? extends JnlpConnectionStateListener> listeners)
throws IOException {
return new LegacyJnlpConnectionState(socket, listeners);
}

/**
* {@inheritDoc}
*/
@Override
void sendHandshake(LegacyJnlpConnectionState state, Map<String, String> headers)
void sendHandshake(@Nonnull LegacyJnlpConnectionState state, @Nonnull Map<String, String> headers)
throws IOException, ConnectionRefusalException {
String secretKey = headers.get(JnlpConnectionState.SECRET_KEY);
if (secretKey == null) {
Expand All @@ -88,38 +108,57 @@ void sendHandshake(LegacyJnlpConnectionState state, Map<String, String> headers)
// Initiate the handshake.
state.fireBeforeProperties();
DataOutputStream outputStream = state.getDataOutputStream();
outputStream.writeUTF(PROTOCOL_PREFIX + NAME);
outputStream.writeUTF(PROTOCOL_PREFIX + getName());
outputStream.writeUTF(secretKey);
outputStream.writeUTF(clientName);
outputStream.flush();

BufferedInputStream inputStream = state.getBufferedInputStream();
DataInputStream inputStream = state.getDataInputStream();
// Check if the server accepted.
String response = EngineUtil.readLine(inputStream);
if (!response.equals(GREETING_SUCCESS)) {
throw new ConnectionRefusalException("Server didn't accept the handshake: " + response);
}
// we don't get any headers from the server in JNLP-connect
state.fireAfterProperties(new HashMap<String,String>());
state.fireAfterProperties(new HashMap<String, String>());
}

/**
* {@inheritDoc}
*/
@Override
void receiveHandshake(LegacyJnlpConnectionState state, Map<String,String> headers) throws IOException {
void receiveHandshake(@Nonnull LegacyJnlpConnectionState state, @Nonnull Map<String, String> headers) throws IOException {
state.fireBeforeProperties();
final String secret = state.getDataInputStream().readUTF();
final String clientName = state.getDataInputStream().readUTF();
Map<String, String> properties = new HashMap<String, String>();
properties.put(JnlpConnectionState.SECRET_KEY, secret);
properties.put(JnlpConnectionState.CLIENT_NAME_KEY, clientName);
JnlpClientDatabase clientDatabase = getClientDatabase();
if (clientDatabase == null || !clientDatabase.exists(clientName)) {
throw new ConnectionRefusalException("Unknown client name: " + clientName);
}
String secretKey = clientDatabase.getSecretOf(clientName);
if (secretKey == null) {
throw new ConnectionRefusalException("Unknown client name: " + clientName);
}
if (!secretKey.equals(secret)) {
LOGGER.log(Level.WARNING, "An attempt was made to connect as {0} from {1} with an incorrect secret",
new Object[]{clientName, state.getSocket().getRemoteSocketAddress()});
throw new ConnectionRefusalException("Authorization failure");
}
state.fireAfterProperties(properties);
PrintWriter out = state.getPrintWriter();
out.println(GREETING_SUCCESS);
out.flush();
}

/**
* {@inheritDoc}
*/
@Nonnull
@Override
Channel buildChannel(LegacyJnlpConnectionState state) throws IOException {
return state.getChannelBuilder().build(state.getBufferedInputStream(), state.getBufferedOutputStream());
Channel buildChannel(@Nonnull LegacyJnlpConnectionState state) throws IOException {
return state.getChannelBuilder().build(state.getSocket());
}

}

0 comments on commit 6449a73

Please sign in to comment.