/*
* Copyright 2015 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*
*
* Copyright (c) 2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*
*/
package io.vertx.ext.shell.term;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.CaseInsensitiveHeaders;
import io.vertx.core.http.HttpClient;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.AbstractUser;
import io.vertx.ext.auth.AuthProvider;
import io.vertx.ext.auth.shiro.ShiroAuthOptions;
import io.vertx.ext.auth.shiro.ShiroAuthRealmType;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
@RunWith(VertxUnitRunner.class)
public abstract class HttpTermServerBase {
protected Vertx vertx;
private TermServer server;
private final String basePath;
public HttpTermServerBase(String basePath) {
this.basePath = basePath;
}
@Before
public void before() throws Exception {
vertx = Vertx.vertx();
}
@After
public void after(TestContext context) {
vertx.close(context.asyncAssertSuccess());
}
protected abstract TermServer createServer(TestContext context, HttpTermOptions options);
@Test
public void testServerWrite(TestContext context) {
Async async = context.async();
server = createServer(context, new HttpTermOptions().setPort(8080));
server.termHandler(term -> {
term.write("hello_from_server");
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", basePath + "/shell/websocket", ws -> {
ws.handler(buf -> {
context.assertEquals("hello_from_server", buf.toString());
async.complete();
});
}, context::fail);
}));
}
@Test
public void testServerRead(TestContext context) {
Async async = context.async();
server = createServer(context, new HttpTermOptions().setPort(8080));
server.termHandler(term -> {
term.stdinHandler(buf -> {
context.assertEquals("hello_from_client", buf);
async.complete();
});
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", basePath + "/shell/websocket", ws -> {
ws.writeFinalTextFrame(new JsonObject().put("action", "read").put("data", "hello_from_client").encode());
}, context::fail);
}));
}
@Test
public void testInitialSize(TestContext context) {
testSize(context, basePath + "/shell/websocket?cols=100&rows=50", 100, 50);
}
@Test
public void testInitialSizeCols(TestContext context) {
testSize(context, basePath + "/shell/websocket?cols=100", 100, 24);
}
@Test
public void testInitialSizeRows(TestContext context) {
testSize(context, basePath + "/shell/websocket?rows=50", 80, 50);
}
@Test
public void testInitialSizeNegative1(TestContext context) {
testSize(context, basePath + "/shell/websocket?cols=100&rows=-50", 80, 24);
}
@Test
public void testInitialSizeNegative2(TestContext context) {
testSize(context, basePath + "/shell/websocket?cols=-100&rows=50", 80, 24);
}
@Test
public void testInitialSizeNegative3(TestContext context) {
testSize(context, basePath + "/shell/websocket?rows=-50", 80, 24);
}
@Test
public void testInitialSizeNegative4(TestContext context) {
testSize(context, basePath + "/shell/websocket?cols=-100", 80, 24);
}
@Test
public void testInitialSizeInvalid1(TestContext context) {
testSize(context, basePath + "/shell/websocket?cols=100&rows=abc", 80, 24);
}
@Test
public void testInitialSizeInvalid2(TestContext context) {
testSize(context, basePath + "/shell/websocket?cols=abc&rows=50", 80, 24);
}
@Test
public void testInitialSizeInvalid3(TestContext context) {
testSize(context, basePath + "/shell/websocket?rows=abc", 80, 24);
}
@Test
public void testInitialSizeInvalid4(TestContext context) {
testSize(context, basePath + "/shell/websocket?cols=abc", 80, 24);
}
private void testSize(TestContext context, String uri, int expectedCols, int expectedRows) {
Async async = context.async();
server = createServer(context, new HttpTermOptions().setPort(8080));;
server.termHandler(term -> {
context.assertEquals(expectedCols, term.width());
context.assertEquals(expectedRows, term.height());
async.complete();
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", uri, ws -> {
}, context::fail);
}));
}
@Test
public void testResize(TestContext context) {
testResize(context, new JsonObject().put("action", "resize").put("cols", 100).put("rows", 50), 100, 50);
}
@Test
public void testResizeCols(TestContext context) {
testResize(context, new JsonObject().put("action", "resize").put("cols", 100), 100, 24);
}
@Test
public void testResizeRows(TestContext context) {
testResize(context, new JsonObject().put("action", "resize").put("rows", 50), 80, 50);
}
private void testResize(TestContext context, JsonObject event, int expectedCols, int expectedRows) {
Async async = context.async();
server = createServer(context, new HttpTermOptions().setPort(8080));;
server.termHandler(term -> {
term.resizehandler(v -> {
context.assertEquals(expectedCols, term.width());
context.assertEquals(expectedRows, term.height());
async.complete();
});
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", basePath + "/shell/websocket", ws -> {
ws.writeFinalTextFrame(event.encode());
}, context::fail);
}));
}
@Test
public void testResizeInvalid(TestContext context) {
Async async = context.async();
server = createServer(context, new HttpTermOptions().setPort(8080));;
server.termHandler(term -> {
term.resizehandler(v -> {
context.fail();
});
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", basePath + "/shell/websocket", ws -> {
ws.writeFinalTextFrame(new JsonObject().put("action", "resize").put("cols", -50).encode());
vertx.setTimer(1000, id -> {
async.complete();
});
}, context::fail);
}));
}
@Test
public void testSecure(TestContext context) {
Async async = context.async();
server = createServer(context, new HttpTermOptions().setAuthOptions(
new ShiroAuthOptions().
setType(ShiroAuthRealmType.PROPERTIES).
setConfig(new JsonObject().put("properties_path", "classpath:test-auth.properties"))).setPort(8080));
server.termHandler(term -> {
term.write("hello");
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", basePath + "/shell/websocket", ws -> {
context.fail();
}, err -> {
// Retry now with auth
client.websocket(8080, "localhost", basePath + "/shell/websocket",
new CaseInsensitiveHeaders().add("Authorization", "Basic " + Base64.getEncoder().encodeToString("tim:sausages".getBytes())),
ws -> {
ws.handler(buf -> {
context.assertEquals("hello", buf.toString());
async.complete();
});
}, context::fail);
});
}));
}
@Test
public void testExternalAuthProvider(TestContext context) throws Exception {
AtomicInteger count = new AtomicInteger();
AuthProvider authProvider = (authInfo, resultHandler) -> {
count.incrementAndGet();
String username = authInfo.getString("username");
String password = authInfo.getString("password");
if (username.equals("paulo") && password.equals("anothersecret")) {
resultHandler.handle(Future.succeededFuture(new AbstractUser() {
@Override
protected void doIsPermitted(String permission, Handler<AsyncResult<Boolean>> resultHandler) {
resultHandler.handle(Future.succeededFuture(true));
}
@Override
public JsonObject principal() {
return new JsonObject().put("username", username);
}
@Override
public void setAuthProvider(AuthProvider authProvider) {
}
}));
} else {
resultHandler.handle(Future.failedFuture("not authenticated"));
}
};
Async async = context.async(2);
server = createServer(context, new HttpTermOptions().setPort(8080));
server.authProvider(authProvider);
server.termHandler(term -> {
context.assertEquals(1, count.get());
async.countDown();
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", basePath + "/shell/websocket", new CaseInsensitiveHeaders().add("Authorization", "Basic " + Base64.getEncoder().encodeToString("paulo:anothersecret".getBytes())), ws -> {
async.countDown();
}, err -> {
context.fail();
});
}));
}
@Test
public void testExternalAuthProviderFails(TestContext context) throws Exception {
AtomicInteger count = new AtomicInteger();
AuthProvider authProvider = (authInfo, resultHandler) -> {
count.incrementAndGet();
resultHandler.handle(Future.failedFuture("not authenticated"));
};
Async async = context.async();
server = createServer(context, new HttpTermOptions().setPort(8080));
server.authProvider(authProvider);
server.termHandler(term -> {
context.fail();
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", basePath + "/shell/websocket", new CaseInsensitiveHeaders().add("Authorization", "Basic " + Base64.getEncoder().encodeToString("paulo:anothersecret".getBytes())), ws -> {
context.fail();
}, err -> {
assertEquals(1, count.get());
async.complete();
});
}));
}
@Test
public void testDifferentCharset(TestContext context) throws Exception {
Async async = context.async();
server = createServer(context, new HttpTermOptions().setPort(8080).setCharset("ISO_8859_1"));
server.termHandler(term -> {
term.write("\u20AC");
term.close();
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", basePath + "/shell/websocket", new CaseInsensitiveHeaders().add("Authorization", "Basic " + Base64.getEncoder().encodeToString("paulo:anothersecret".getBytes())), ws -> {
ws.handler(buf -> {
context.assertTrue(Arrays.equals(new byte[]{63}, buf.getBytes()));
async.complete();
});
}, err -> {
context.fail();
});
}));
}
@Test
public void testKeymapFromFilesystem(TestContext context) throws Exception {
URL url = TermServer.class.getResource(SSHTermOptions.DEFAULT_INPUTRC);
File f = new File(url.toURI());
Async async = context.async();
server = createServer(context, new HttpTermOptions().setPort(8080).setIntputrc(f.getAbsolutePath()));
server.termHandler(term -> {
term.close();
async.complete();
});
server.listen(context.asyncAssertSuccess(server -> {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", basePath + "/shell/websocket", new CaseInsensitiveHeaders().add("Authorization", "Basic " + Base64.getEncoder().encodeToString("paulo:anothersecret".getBytes())), ws -> {
ws.handler(buf -> {
});
}, err -> {
context.fail();
});
}));
}
}