package org.keycloak.testsuite.adapter.example;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.future.ConnectFuture;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.session.ClientSession.ClientSessionEvent;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.adapter.page.HawtioPage;
public abstract class AbstractFuseAdminAdapterTest extends AbstractExampleAdapterTest {
@Page
private HawtioPage hawtioPage;
private SshClient client;
private ClientChannel channel;
private ClientSession session;
enum Result { OK, NOT_FOUND, NO_CREDENTIALS, NO_ROLES };
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation fuseRealm = loadRealm(new File(EXAMPLES_HOME_DIR + "/fuse/demorealm.json"));
testRealms.add(fuseRealm);
}
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
testRealmPage.setAuthRealm(DEMO);
testRealmLoginPage.setAuthRealm(DEMO);
}
@Test
public void hawtioLoginTest() throws Exception {
hawtioPage.navigateTo();
testRealmLoginPage.form().login("user", "invalid-password");
assertCurrentUrlDoesntStartWith(hawtioPage);
testRealmLoginPage.form().login("invalid-user", "password");
assertCurrentUrlDoesntStartWith(hawtioPage);
testRealmLoginPage.form().login("root", "password");
assertCurrentUrlStartsWith(hawtioPage.getDriver(), hawtioPage.toString() + "/welcome");
hawtioPage.logout();
assertCurrentUrlStartsWith(testRealmLoginPage);
hawtioPage.navigateTo();
testRealmLoginPage.form().login("mary", "password");
assertTrue(!driver.getPageSource().contains("welcome"));
}
@Test
public void sshLoginTest() throws Exception {
assertCommand("mary", "password", "shell:date", Result.NO_ROLES);
assertCommand("john", "password", "shell:info", Result.NO_CREDENTIALS);
assertCommand("john", "password", "shell:date", Result.OK);
assertCommand("root", "password", "shell:info", Result.OK);
}
@Test
public void jmxLoginTest() throws Exception {
setJMXAuthentication("keycloak", "password");
ObjectName mbean = new ObjectName("org.apache.karaf:type=config,name=root");
//invalid credentials
try {
getJMXConnector("mary", "password1").getMBeanServerConnection();
Assert.fail();
} catch (SecurityException se) {}
//no role
MBeanServerConnection connection = getJMXConnector("mary", "password").getMBeanServerConnection();
assertJmxInvoke(false, connection, mbean, "listProperties", new Object [] {""}, new String [] {String.class.getName()});
assertJmxInvoke(false, connection, mbean, "setProperty", new Object [] {"", "x", "y"}, new String [] {String.class.getName(), String.class.getName(), String.class.getName()});
//read only role
connection = getJMXConnector("john", "password").getMBeanServerConnection();
assertJmxInvoke(true, connection, mbean, "listProperties", new Object [] {""}, new String [] {String.class.getName()});
assertJmxInvoke(false, connection, mbean, "setProperty", new Object [] {"", "x", "y"}, new String [] {String.class.getName(), String.class.getName(), String.class.getName()});
//read write role
connection = getJMXConnector("root", "password").getMBeanServerConnection();
assertJmxInvoke(true, connection, mbean, "listProperties", new Object [] {""}, new String [] {String.class.getName()});
assertJmxInvoke(true, connection, mbean, "setProperty", new Object [] {"", "x", "y"}, new String [] {String.class.getName(), String.class.getName(), String.class.getName()});
setJMXAuthentication("karaf", "admin");
}
private String assertCommand(String user, String password, String command, Result result) throws Exception, IOException {
if (!command.endsWith("\n"))
command += "\n";
ByteArrayOutputStream out = new ByteArrayOutputStream();
OutputStream pipe = openSshChannel(user, password, out, out);
pipe.write(command.getBytes());
pipe.flush();
closeSshChannel(pipe);
String output = new String(out.toByteArray());
switch(result) {
case OK:
Assert.assertFalse("Should not contain 'Insufficient credentials' or 'Command not found': " + output,
output.contains("Insufficient credentials") || output.contains("Command not found"));
break;
case NOT_FOUND:
Assert.assertTrue("Should contain 'Command not found': " + output,
output.contains("Command not found"));
break;
case NO_CREDENTIALS:
Assert.assertTrue("Should contain 'Insufficient credentials': " + output,
output.contains("Insufficient credentials"));
break;
case NO_ROLES:
Assert.assertTrue("Should contain 'Current user has no associated roles': " + output,
output.contains("Current user has no associated roles"));
break;
default:
Assert.fail("Unexpected enum value: " + result);
}
return output;
}
private OutputStream openSshChannel(String username, String password, OutputStream ... outputs) throws Exception {
client = SshClient.setUpDefaultClient();
client.start();
ConnectFuture future = client.connect(username, "localhost", 8101);
future.await();
session = future.getSession();
Set<ClientSessionEvent> ret = EnumSet.of(ClientSessionEvent.WAIT_AUTH);
while (ret.contains(ClientSessionEvent.WAIT_AUTH)) {
session.addPasswordIdentity(password);
session.auth().verify();
ret = session.waitFor(EnumSet.of(ClientSessionEvent.WAIT_AUTH, ClientSessionEvent.CLOSED, ClientSessionEvent.AUTHED), 0);
}
if (ret.contains(ClientSessionEvent.CLOSED)) {
throw new Exception("Could not open SSH channel");
}
channel = session.createChannel("shell");
PipedOutputStream pipe = new PipedOutputStream();
channel.setIn(new PipedInputStream(pipe));
OutputStream out;
if (outputs.length >= 1) {
out = outputs[0];
} else {
out = new ByteArrayOutputStream();
}
channel.setOut(out);
OutputStream err;
if (outputs.length >= 2) {
err = outputs[1];
} else {
err = new ByteArrayOutputStream();
}
channel.setErr(err);
channel.open();
return pipe;
}
private void setJMXAuthentication(String realm, String password) throws Exception {
assertCommand("admin", "password", "config:edit org.apache.karaf.management; config:propset jmxRealm " + realm + "; config:update", Result.OK);
getMBeanServerConnection(10000, TimeUnit.MILLISECONDS, "admin", password);
}
private void closeSshChannel(OutputStream pipe) throws IOException {
pipe.write("logout\n".getBytes());
pipe.flush();
channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0);
session.close(true);
client.stop();
client = null;
channel = null;
session = null;
}
private Object assertJmxInvoke(boolean expectSuccess, MBeanServerConnection connection, ObjectName mbean, String method,
Object[] params, String[] signature) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException {
try {
Object result = connection.invoke(mbean, method, params, signature);
assertTrue(expectSuccess);
return result;
} catch (SecurityException se) {
assertTrue(!expectSuccess);
return null;
}
}
private MBeanServerConnection getMBeanServerConnection(long timeout, final TimeUnit unit, String username, String password) throws Exception {
Exception lastException = null;
long timeoutMillis = System.currentTimeMillis() + unit.toMillis(timeout);
while (System.currentTimeMillis() < timeoutMillis) {
try {
return getJMXConnector(username, password).getMBeanServerConnection();
} catch (Exception ex) {
lastException = ex;
Thread.sleep(500);
ex.printStackTrace();
}
}
TimeoutException timeoutException = new TimeoutException();
timeoutException.initCause(lastException);
throw timeoutException;
}
private JMXConnector getJMXConnector(String userName, String password) throws Exception {
JMXServiceURL url = new JMXServiceURL(getJmxServiceUrl());
String[] credentials = new String[] { userName, password };
Map<String, ?> env = Collections.singletonMap(JMXConnector.CREDENTIALS, credentials);
JMXConnector connector = JMXConnectorFactory.connect(url, env);
return connector;
}
private String getJmxServiceUrl() throws Exception {
return "service:jmx:rmi://localhost:44444/jndi/rmi://localhost:1099/karaf-root";
}
}