blob: 1af0bde4aeb7b3e00feef1f862c5a98395398ae1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.qpid.protonj2.engine.impl.sasl;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.charset.StandardCharsets;
import org.apache.qpid.protonj2.buffer.ProtonBuffer;
import org.apache.qpid.protonj2.engine.Engine;
import org.apache.qpid.protonj2.engine.EngineFactory;
import org.apache.qpid.protonj2.engine.exceptions.ProtocolViolationException;
import org.apache.qpid.protonj2.engine.impl.ProtonEngineTestSupport;
import org.apache.qpid.protonj2.engine.sasl.SaslOutcome;
import org.apache.qpid.protonj2.engine.sasl.SaslServerContext;
import org.apache.qpid.protonj2.engine.sasl.SaslServerListener;
import org.apache.qpid.protonj2.test.driver.ProtonTestConnector;
import org.apache.qpid.protonj2.test.driver.codec.security.SaslCode;
import org.apache.qpid.protonj2.types.Symbol;
import org.apache.qpid.protonj2.types.transport.AMQPHeader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
/**
* Test proton engine from the perspective of a SASL client
*/
@Timeout(20)
public class ProtonSaslServerTest extends ProtonEngineTestSupport {
@Test
public void testEngineFailsIfAMQPHeaderArrivesWhenSASLHeaderExpected() throws Exception {
Engine engine = EngineFactory.PROTON.createEngine();
engine.errorHandler(result -> failure = result.failureCause());
ProtonTestConnector peer = createTestPeer(engine);
// Setup basic SASL server which only allows ANONYMOUS
engine.saslDriver().server().setListener(createAnonymousSaslServerListener());
engine.connection().openHandler((conn) -> conn.open());
engine.connection().closeHandler((conn) -> conn.close());
engine.start();
peer.expectSASLHeader();
try {
peer.remoteHeader(AMQPHeader.getAMQPHeader().toArray()).now();
} catch (AssertionError pve) {
assertTrue(pve.getCause() instanceof ProtocolViolationException);
}
peer.waitForScriptToCompleteIgnoreErrors();
assertNotNull(failure);
}
@Test
public void testSaslAnonymousConnection() throws Exception {
Engine engine = EngineFactory.PROTON.createEngine();
engine.errorHandler(result -> failure = result.failureCause());
ProtonTestConnector peer = createTestPeer(engine);
// Setup basic SASL server which only allows ANONYMOUS
engine.saslDriver().server().setListener(createAnonymousSaslServerListener());
engine.connection().openHandler((conn) -> conn.open());
engine.connection().closeHandler((conn) -> conn.close());
engine.start();
peer.expectSASLHeader();
peer.expectSaslMechanisms().withSaslServerMechanisms("ANONYMOUS");
peer.remoteHeader(AMQPHeader.getSASLHeader().toArray()).now();
peer.waitForScriptToComplete();
peer.expectSaslOutcome().withCode(SaslCode.OK);
peer.remoteSaslInit().withMechanism("ANONYMOUS").now();
peer.waitForScriptToComplete();
peer.expectAMQPHeader();
peer.expectOpen();
peer.remoteHeader(AMQPHeader.getAMQPHeader().toArray()).now();
peer.remoteOpen().now();
peer.waitForScriptToComplete();
peer.expectClose();
peer.remoteClose().now();
peer.waitForScriptToComplete();
assertNull(failure);
}
@Test
public void testSaslPlainConnection() throws Exception {
Engine engine = EngineFactory.PROTON.createEngine();
engine.errorHandler(result -> failure = result.failureCause());
ProtonTestConnector peer = createTestPeer(engine);
// Setup basic SASL server which only allows ANONYMOUS
engine.saslDriver().server().setListener(createPlainSaslServerListener());
engine.connection().openHandler((conn) -> conn.open());
engine.connection().closeHandler((conn) -> conn.close());
engine.start();
peer.expectSASLHeader();
peer.expectSaslMechanisms().withSaslServerMechanisms("PLAIN");
peer.remoteHeader(AMQPHeader.getSASLHeader().toArray()).now();
peer.waitForScriptToComplete();
peer.expectSaslOutcome().withCode(SaslCode.OK);
peer.remoteSaslInit().withMechanism("PLAIN")
.withInitialResponse(saslPlainInitialResponse("user", "pass")).now();
peer.waitForScriptToComplete();
peer.expectAMQPHeader();
peer.expectOpen();
peer.remoteHeader(AMQPHeader.getAMQPHeader().toArray()).now();
peer.remoteOpen().now();
peer.waitForScriptToComplete();
peer.expectClose();
peer.remoteClose().now();
peer.waitForScriptToComplete();
assertNull(failure);
}
@Test
public void testSaslPlainConnectionFailedWhenAnonymousOffered() throws Exception {
Engine engine = EngineFactory.PROTON.createEngine();
engine.errorHandler(result -> failure = result.failureCause());
ProtonTestConnector peer = createTestPeer(engine);
// Setup basic SASL server which only allows ANONYMOUS
engine.saslDriver().server().setListener(createPlainSaslServerListener());
engine.connection().openHandler((conn) -> conn.open());
engine.connection().closeHandler((conn) -> conn.close());
engine.start();
peer.expectSASLHeader();
peer.expectSaslMechanisms().withSaslServerMechanisms("PLAIN");
peer.remoteHeader(AMQPHeader.getSASLHeader().toArray()).now();
peer.waitForScriptToComplete();
peer.expectSaslOutcome().withCode(SaslCode.SYS_PERM);
peer.remoteSaslInit().withMechanism("ANONYMOUS").now();
peer.waitForScriptToComplete();
assertNull(failure);
}
@Test
public void testEngineFailsForUnexpecetedNonSaslFrameDuringSaslExchange() throws Exception {
Engine engine = EngineFactory.PROTON.createEngine();
engine.errorHandler(result -> failure = result.failureCause());
ProtonTestConnector peer = createTestPeer(engine);
// Setup basic SASL server which only allows ANONYMOUS
engine.saslDriver().server().setListener(createPlainSaslServerListener());
engine.connection().openHandler((conn) -> conn.open());
engine.connection().closeHandler((conn) -> conn.close());
engine.start();
peer.expectSASLHeader();
peer.expectSaslMechanisms().withSaslServerMechanisms("PLAIN");
peer.remoteHeader(AMQPHeader.getSASLHeader().toArray()).now();
peer.waitForScriptToComplete();
try {
peer.remoteOpen().now();
} catch (AssertionError pve) {
assertTrue(pve.getCause() instanceof ProtocolViolationException);
}
peer.waitForScriptToCompleteIgnoreErrors();
assertNotNull(failure);
}
public byte[] saslPlainInitialResponse(String username, String password) {
byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8);
byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
byte[] initialResponse = new byte[usernameBytes.length + passwordBytes.length + 2];
System.arraycopy(usernameBytes, 0, initialResponse, 1, usernameBytes.length);
System.arraycopy(passwordBytes, 0, initialResponse, 2 + usernameBytes.length, passwordBytes.length);
return initialResponse;
}
private SaslServerListener createAnonymousSaslServerListener() {
return new SaslServerListener() {
@Override
public void handleSaslResponse(SaslServerContext context, ProtonBuffer response) {
throw new RuntimeException("Not expecting any SASL Response frames");
}
@Override
public void handleSaslInit(SaslServerContext context, Symbol mechanism, ProtonBuffer initResponse) {
if (mechanism.equals(Symbol.valueOf("ANONYMOUS"))) {
context.sendOutcome(SaslOutcome.SASL_OK, null);
} else {
context.sendOutcome(SaslOutcome.SASL_PERM, null);
}
}
@Override
public void handleSaslHeader(SaslServerContext context, AMQPHeader header) {
context.sendMechanisms(new Symbol[] { Symbol.valueOf("ANONYMOUS") });
}
};
}
private SaslServerListener createPlainSaslServerListener() {
return new SaslServerListener() {
@Override
public void handleSaslResponse(SaslServerContext context, ProtonBuffer response) {
throw new RuntimeException("Not expecting any SASL Response frames");
}
@Override
public void handleSaslInit(SaslServerContext context, Symbol mechanism, ProtonBuffer initResponse) {
if (mechanism.equals(Symbol.valueOf("PLAIN"))) {
context.sendOutcome(SaslOutcome.SASL_OK, null);
} else {
context.sendOutcome(SaslOutcome.SASL_PERM, null);
}
}
@Override
public void handleSaslHeader(SaslServerContext context, AMQPHeader header) {
context.sendMechanisms(new Symbol[] { Symbol.valueOf("PLAIN") });
}
};
}
}