Mercurial > hg > release > thermostat-0.15
view web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java @ 1223:1fd7faa8502a
Separate add-pojo/replace-pojo entry points.
Reviewed-by: neugens
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-August/007977.html
author | Severin Gehwolf <sgehwolf@redhat.com> |
---|---|
date | Mon, 19 Aug 2013 14:11:20 +0200 |
parents | 71b72b655b84 |
children | 19edbf121251 |
line wrap: on
line source
/* * Copyright 2012, 2013 Red Hat, Inc. * * This file is part of Thermostat. * * Thermostat is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * Thermostat is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Thermostat; see the file COPYING. If not see * <http://www.gnu.org/licenses/>. * * Linking this code with other modules is making a combined work * based on this code. Thus, the terms and conditions of the GNU * General Public License cover the whole combination. * * As a special exception, the copyright holders of this code give * you permission to link this code with independent modules to * produce an executable, regardless of the license terms of these * independent modules, and to copy and distribute the resulting * executable under terms of your choice, provided that you also * meet, for each linked independent module, the terms and conditions * of the license of that module. An independent module is a module * which is not derived from or based on this code. If you modify * this code, you may extend this exception to your version of the * library, but you are not obligated to do so. If you do not wish * to do so, delete this exception statement from your version. */ package com.redhat.thermostat.web.server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; import java.net.URLEncoder; import java.security.Principal; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.security.auth.Subject; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.binary.Base64; import org.eclipse.jetty.plus.jaas.JAASLoginService; import org.eclipse.jetty.security.DefaultUserIdentity; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.MappedLoginService; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.security.Password; import org.eclipse.jetty.webapp.WebAppContext; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.ArgumentCaptor; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.redhat.thermostat.storage.core.Add; import com.redhat.thermostat.storage.core.AggregateQuery; import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction; import com.redhat.thermostat.storage.core.BackingStorage; import com.redhat.thermostat.storage.core.Categories; import com.redhat.thermostat.storage.core.Category; import com.redhat.thermostat.storage.core.CategoryAdapter; import com.redhat.thermostat.storage.core.Cursor; import com.redhat.thermostat.storage.core.Entity; import com.redhat.thermostat.storage.core.Key; import com.redhat.thermostat.storage.core.ParsedStatement; import com.redhat.thermostat.storage.core.Persist; import com.redhat.thermostat.storage.core.PreparedParameter; import com.redhat.thermostat.storage.core.PreparedStatement; import com.redhat.thermostat.storage.core.Query; import com.redhat.thermostat.storage.core.Remove; import com.redhat.thermostat.storage.core.Replace; import com.redhat.thermostat.storage.core.StatementDescriptor; import com.redhat.thermostat.storage.core.Update; import com.redhat.thermostat.storage.core.auth.DescriptorMetadata; import com.redhat.thermostat.storage.core.auth.CategoryRegistration; import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration; import com.redhat.thermostat.storage.dao.HostInfoDAO; import com.redhat.thermostat.storage.model.AggregateCount; import com.redhat.thermostat.storage.model.BasePojo; import com.redhat.thermostat.storage.model.HostInfo; import com.redhat.thermostat.storage.model.Pojo; import com.redhat.thermostat.storage.query.BinarySetMembershipExpression; import com.redhat.thermostat.storage.query.Expression; import com.redhat.thermostat.storage.query.ExpressionFactory; import com.redhat.thermostat.storage.query.Operator; import com.redhat.thermostat.test.FreePortFinder; import com.redhat.thermostat.test.FreePortFinder.TryPort; import com.redhat.thermostat.web.common.ExpressionSerializer; import com.redhat.thermostat.web.common.OperatorSerializer; import com.redhat.thermostat.web.common.PreparedParameterSerializer; import com.redhat.thermostat.web.common.StorageWrapper; import com.redhat.thermostat.web.common.ThermostatGSONConverter; import com.redhat.thermostat.web.common.WebInsert; import com.redhat.thermostat.web.common.WebPreparedStatement; import com.redhat.thermostat.web.common.WebPreparedStatementResponse; import com.redhat.thermostat.web.common.WebPreparedStatementSerializer; import com.redhat.thermostat.web.common.WebQueryResponse; import com.redhat.thermostat.web.common.WebQueryResponseSerializer; import com.redhat.thermostat.web.common.WebRemove; import com.redhat.thermostat.web.common.WebUpdate; import com.redhat.thermostat.web.server.auth.BasicRole; import com.redhat.thermostat.web.server.auth.RolePrincipal; import com.redhat.thermostat.web.server.auth.Roles; import com.redhat.thermostat.web.server.auth.UserPrincipal; import com.redhat.thermostat.web.server.auth.WebStoragePathHandler; public class WebStorageEndpointTest { @Entity public static class TestClass extends BasePojo { private String key1; private int key2; @Persist public String getKey1() { return key1; } @Persist public void setKey1(String key1) { this.key1 = key1; } @Persist public int getKey2() { return key2; } @Persist public void setKey2(int key2) { this.key2 = key2; } public boolean equals(Object o) { if (! (o instanceof TestClass)) { return false; } TestClass other = (TestClass) o; return key1.equals(other.key1) && key2 == other.key2; } } private Server server; private int port; private BackingStorage mockStorage; private Integer categoryId; private static Key<String> key1; private static Key<Integer> key2; private static Category<TestClass> category; private static String categoryName = "test"; private ExpressionFactory factory; @BeforeClass public static void setupCategory() { key1 = new Key<>("key1", true); key2 = new Key<>("key2", false); category = new Category<>(categoryName, TestClass.class, key1, key2); } @AfterClass public static void cleanupCategory() { Categories.remove(category); category = null; key2 = null; key1 = null; } @Before public void setUp() throws Exception { // Set thermostat home to something existing and readable File fakeHome = new File(getClass().getResource("/broken_test_roles.properties").getFile()); // fakeHome does not need to be a real THERMOSTAT_HOME, but needs to // be readable and must exist. assertTrue(fakeHome.canRead()); System.setProperty("THERMOSTAT_HOME", fakeHome.getAbsolutePath()); mockStorage = mock(BackingStorage.class); StorageWrapper.setStorage(mockStorage); factory = new ExpressionFactory(); } private void startServer(int port, LoginService loginService) throws Exception { server = new Server(port); WebAppContext ctx = new WebAppContext("src/main/webapp", "/"); ctx.getSecurityHandler().setLoginService(loginService); server.setHandler(ctx); server.start(); } @After public void tearDown() throws Exception { System.clearProperty("THERMOSTAT_HOME"); // some tests don't use server if (server != null) { server.stop(); server.join(); } KnownCategoryRegistryFactory.setInstance(null); KnownDescriptorRegistryFactory.setKnownDescriptorRegistry(null); } /** * Makes sure that all paths we dispatch to, dispatch to * {@link WebStoragePathHandler} annotated methods. * * @throws Exception */ @Test public void ensureAuthorizationCovered() throws Exception { // manually maintained list of path handlers which should include // authorization checks final String[] authPaths = new String[] { "prepare-statement", "query-execute", "add-pojo", "replace-pojo", "register-category", "remove-pojo", "update-pojo", "save-file", "load-file", "purge", "ping", "generate-token", "verify-token" }; Map<String, Boolean> checkedAutPaths = new HashMap<>(); for (String path: authPaths) { checkedAutPaths.put(path, false); } int methodsReqAuthorization = 0; for (Method method: WebStorageEndPoint.class.getDeclaredMethods()) { if (method.isAnnotationPresent(WebStoragePathHandler.class)) { methodsReqAuthorization++; WebStoragePathHandler annot = method.getAnnotation(WebStoragePathHandler.class); try { // this may NPE if there is something funny going on in // WebStorageEndPoint (e.g. one method annotated but this // reference list has not been updated). if (!checkedAutPaths.get(annot.path())) { // mark path as covered checkedAutPaths.put(annot.path(), true); } else { throw new AssertionError( "method " + method + " annotated as web storage path handler (path '" + annot.path() + "'), but not in reference list we know about!"); } } catch (NullPointerException e) { throw new AssertionError("Don't know about path '" + annot.path() + "'"); } } } // at this point we should have all dispatched paths covered for (String path: authPaths) { assertTrue( "Is " + path + " marked with @WebStoragePathHandler and have proper authorization checks been included?", checkedAutPaths.get(path)); } assertEquals(authPaths.length, methodsReqAuthorization); } @Test public void authorizedPrepareQueryWithUnTrustedDescriptor() throws Exception { String strDescriptor = "QUERY " + category.getName() + " WHERE '" + key1.getName() + "' = ?s SORT '" + key1.getName() + "' DSC LIMIT 42"; // setup a statement descriptor set so as to mimic a not trusted desc String wrongDescriptor = "QUERY something-other WHERE 'a' = true"; setupTrustedStatementRegistry(wrongDescriptor, null); String[] roleNames = new String[] { Roles.REGISTER_CATEGORY, Roles.PREPARE_STATEMENT, Roles.ACCESS_REALM, }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory(testuser, password); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/prepare-statement"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoInput(true); conn.setDoOutput(true); Gson gson = new GsonBuilder() .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>()) .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter()) .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer()) .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId; out.write(body + "\n"); out.flush(); Reader in = new InputStreamReader(conn.getInputStream()); WebPreparedStatementResponse response = gson.fromJson(in, WebPreparedStatementResponse.class); assertEquals("descriptor not trusted, so expected number should be negative!", -1, response.getNumFreeVariables()); assertEquals(WebPreparedStatementResponse.ILLEGAL_STATEMENT, response.getStatementId()); assertEquals("application/json; charset=UTF-8", conn.getContentType()); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void authorizedPrepareQueryWithTrustedDescriptor() throws Exception { String strDescriptor = "QUERY " + category.getName() + " WHERE '" + key1.getName() + "' = ?s SORT '" + key1.getName() + "' DSC LIMIT 42"; // metadata which basically does no filtering. There's another test which // asserts only allowed data (via ACL) gets returned. DescriptorMetadata metadata = new DescriptorMetadata(); setupTrustedStatementRegistry(strDescriptor, metadata); Set<BasicRole> roles = new HashSet<>(); roles.add(new RolePrincipal(Roles.REGISTER_CATEGORY)); roles.add(new RolePrincipal(Roles.PREPARE_STATEMENT)); roles.add(new RolePrincipal(Roles.READ)); roles.add(new RolePrincipal(Roles.ACCESS_REALM)); UserPrincipal testUser = new UserPrincipal("ignored1"); testUser.setRoles(roles); final LoginService loginService = new TestJAASLoginService(testUser); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory("ignored1", "ignored2"); TestClass expected1 = new TestClass(); expected1.setKey1("fluff1"); expected1.setKey2(42); TestClass expected2 = new TestClass(); expected2.setKey1("fluff2"); expected2.setKey2(43); // prepare-statement does this under the hood Query<TestClass> mockMongoQuery = mock(Query.class); when(mockStorage.createQuery(eq(category))).thenReturn(mockMongoQuery); Cursor<TestClass> cursor = mock(Cursor.class); when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false); when(cursor.next()).thenReturn(expected1).thenReturn(expected2); PreparedStatement mockPreparedQuery = mock(PreparedStatement.class); when(mockStorage.prepareStatement(any(StatementDescriptor.class))).thenReturn(mockPreparedQuery); ParsedStatement mockParsedStatement = mock(ParsedStatement.class); when(mockParsedStatement.getNumParams()).thenReturn(1); when(mockParsedStatement.patchStatement(any(PreparedParameter[].class))).thenReturn(mockMongoQuery); when(mockPreparedQuery.getParsedStatement()).thenReturn(mockParsedStatement); // The web layer when(mockPreparedQuery.executeQuery()).thenReturn(cursor); // And the mongo layer when(mockMongoQuery.execute()).thenReturn(cursor); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/prepare-statement"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, "ignored1", "ignored2"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoInput(true); conn.setDoOutput(true); Gson gson = new GsonBuilder() .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>()) .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter()) .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer()) .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId; out.write(body + "\n"); out.flush(); Reader in = new InputStreamReader(conn.getInputStream()); WebPreparedStatementResponse response = gson.fromJson(in, WebPreparedStatementResponse.class); assertEquals(1, response.getNumFreeVariables()); assertEquals(0, response.getStatementId()); assertEquals("application/json; charset=UTF-8", conn.getContentType()); // now execute the query we've just prepared WebPreparedStatement<TestClass> stmt = new WebPreparedStatement<>(1, 0); stmt.setString(0, "fluff"); url = new URL(endpoint + "/query-execute"); HttpURLConnection conn2 = (HttpURLConnection) url.openConnection(); conn2.setRequestMethod("POST"); sendAuthentication(conn2, "ignored1", "ignored2"); conn2.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn2.setDoInput(true); conn2.setDoOutput(true); out = new OutputStreamWriter(conn2.getOutputStream()); body = "prepared-stmt=" + gson.toJson(stmt, WebPreparedStatement.class); out.write(body + "\n"); out.flush(); in = new InputStreamReader(conn2.getInputStream()); Type typeToken = new TypeToken<WebQueryResponse<TestClass>>(){}.getType(); WebQueryResponse<TestClass> result = gson.fromJson(in, typeToken); TestClass[] results = result.getResultList(); assertEquals(2, results.length); assertEquals("fluff1", results[0].getKey1()); assertEquals(42, results[0].getKey2()); assertEquals("fluff2", results[1].getKey1()); assertEquals(43, results[1].getKey2()); assertEquals("application/json; charset=UTF-8", conn2.getContentType()); verify(mockMongoQuery).execute(); verify(mockMongoQuery).getWhereExpression(); verifyNoMoreInteractions(mockMongoQuery); } /* * * This test simulates a case where the mongo query would return more than * a user can see. In this case only records matching agentIds which are * allowed via roles should get returned. */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void authorizedFilteredQuery() throws Exception { Category oldCategory = category; String categoryName = "test-authorizedFilteredQuery"; // redefine category to include the agentId key in the category. // undone via a the try-finally block. category = new Category(categoryName, TestClass.class, key1, key2, Key.AGENT_ID); try { String strDescriptor = "QUERY " + category.getName() + " WHERE '" + key1.getName() + "' = ?s SORT '" + key1.getName() + "' DSC LIMIT 42"; DescriptorMetadata metadata = new DescriptorMetadata(); setupTrustedStatementRegistry(strDescriptor, metadata); Set<BasicRole> roles = new HashSet<>(); roles.add(new RolePrincipal(Roles.REGISTER_CATEGORY)); roles.add(new RolePrincipal(Roles.PREPARE_STATEMENT)); roles.add(new RolePrincipal(Roles.READ)); roles.add(new RolePrincipal(Roles.ACCESS_REALM)); String fakeAgentId = "someAgentId"; roles.add(new RolePrincipal("thermostat-agents-grant-read-agentId-" + fakeAgentId)); UserPrincipal testUser = new UserPrincipal("ignored1"); testUser.setRoles(roles); final LoginService loginService = new TestJAASLoginService(testUser); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory("ignored1", "ignored2"); TestClass expected1 = new TestClass(); expected1.setKey1("fluff1"); expected1.setKey2(42); TestClass expected2 = new TestClass(); expected2.setKey1("fluff2"); expected2.setKey2(43); // prepare-statement does this under the hood Query<TestClass> mockMongoQuery = mock(Query.class); when(mockStorage.createQuery(eq(category))).thenReturn(mockMongoQuery); Cursor<TestClass> cursor = mock(Cursor.class); when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false); when(cursor.next()).thenReturn(expected1).thenReturn(expected2); PreparedStatement mockPreparedQuery = mock(PreparedStatement.class); when(mockStorage.prepareStatement(any(StatementDescriptor.class))).thenReturn(mockPreparedQuery); ParsedStatement mockParsedStatement = mock(ParsedStatement.class); when(mockParsedStatement.getNumParams()).thenReturn(1); when(mockParsedStatement.patchStatement(any(PreparedParameter[].class))).thenReturn(mockMongoQuery); when(mockPreparedQuery.getParsedStatement()).thenReturn(mockParsedStatement); // The web layer when(mockPreparedQuery.executeQuery()).thenReturn(cursor); // And the mongo layer when(mockMongoQuery.execute()).thenReturn(cursor); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/prepare-statement"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, "ignored1", "ignored2"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoInput(true); conn.setDoOutput(true); Gson gson = new GsonBuilder() .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>()) .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter()) .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer()) .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId; out.write(body + "\n"); out.flush(); Reader in = new InputStreamReader(conn.getInputStream()); WebPreparedStatementResponse response = gson.fromJson(in, WebPreparedStatementResponse.class); assertEquals(1, response.getNumFreeVariables()); assertEquals(0, response.getStatementId()); assertEquals("application/json; charset=UTF-8", conn.getContentType()); // now execute the query we've just prepared WebPreparedStatement<TestClass> stmt = new WebPreparedStatement<>(1, 0); stmt.setString(0, "fluff"); url = new URL(endpoint + "/query-execute"); HttpURLConnection conn2 = (HttpURLConnection) url.openConnection(); conn2.setRequestMethod("POST"); sendAuthentication(conn2, "ignored1", "ignored2"); conn2.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn2.setDoInput(true); conn2.setDoOutput(true); out = new OutputStreamWriter(conn2.getOutputStream()); body = "prepared-stmt=" + gson.toJson(stmt, WebPreparedStatement.class); out.write(body + "\n"); out.flush(); in = new InputStreamReader(conn2.getInputStream()); Type typeToken = new TypeToken<WebQueryResponse<TestClass>>(){}.getType(); WebQueryResponse<TestClass> result = gson.fromJson(in, typeToken); TestClass[] results = result.getResultList(); assertEquals(2, results.length); assertEquals("fluff1", results[0].getKey1()); assertEquals(42, results[0].getKey2()); assertEquals("fluff2", results[1].getKey1()); assertEquals(43, results[1].getKey2()); assertEquals("application/json; charset=UTF-8", conn2.getContentType()); verify(mockMongoQuery).execute(); verify(mockMongoQuery).getWhereExpression(); ArgumentCaptor<Expression> expressionCaptor = ArgumentCaptor.forClass(Expression.class); verify(mockMongoQuery).where(expressionCaptor.capture()); verifyNoMoreInteractions(mockMongoQuery); Expression capturedExpression = expressionCaptor.getValue(); assertTrue(capturedExpression instanceof BinarySetMembershipExpression); Set<String> agentIds = new HashSet<>(); agentIds.add(fakeAgentId); Expression expectedExpression = new ExpressionFactory().in(Key.AGENT_ID, agentIds, String.class); assertEquals(expectedExpression, capturedExpression); } finally { category = oldCategory; } } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void authorizedPreparedAggregateQuery() throws Exception { String strDescriptor = "QUERY-COUNT " + category.getName(); DescriptorMetadata metadata = new DescriptorMetadata(); setupTrustedStatementRegistry(strDescriptor, metadata); Set<BasicRole> roles = new HashSet<>(); roles.add(new RolePrincipal(Roles.REGISTER_CATEGORY)); roles.add(new RolePrincipal(Roles.PREPARE_STATEMENT)); roles.add(new RolePrincipal(Roles.READ)); roles.add(new RolePrincipal(Roles.ACCESS_REALM)); UserPrincipal testUser = new UserPrincipal("ignored1"); testUser.setRoles(roles); final LoginService loginService = new TestJAASLoginService(testUser); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); AggregateCount count = new AggregateCount(); count.setCount(500); // prepare-statement does this under the hood Query<AggregateCount> mockMongoQuery = mock(AggregateQuery.class); Category<AggregateCount> adapted = new CategoryAdapter(category).getAdapted(AggregateCount.class); registerCategory(adapted, "no-matter", "no-matter"); when(mockStorage.createAggregateQuery(eq(AggregateFunction.COUNT), eq(adapted))).thenReturn(mockMongoQuery); Cursor<AggregateCount> cursor = mock(Cursor.class); when(cursor.hasNext()).thenReturn(true).thenReturn(false); when(cursor.next()).thenReturn(count); PreparedStatement mockPreparedQuery = mock(PreparedStatement.class); when(mockStorage.prepareStatement(any(StatementDescriptor.class))).thenReturn(mockPreparedQuery); ParsedStatement mockParsedStatement = mock(ParsedStatement.class); when(mockParsedStatement.getNumParams()).thenReturn(0); when(mockParsedStatement.patchStatement(any(PreparedParameter[].class))).thenReturn(mockMongoQuery); when(mockPreparedQuery.getParsedStatement()).thenReturn(mockParsedStatement); // The web layer when(mockPreparedQuery.executeQuery()).thenReturn(cursor); // And the mongo layer when(mockMongoQuery.execute()).thenReturn(cursor); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/prepare-statement"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, "no-matter", "no-matter"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoInput(true); conn.setDoOutput(true); Gson gson = new GsonBuilder() .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>()) .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter()) .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer()) .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId; out.write(body + "\n"); out.flush(); Reader in = new InputStreamReader(conn.getInputStream()); WebPreparedStatementResponse response = gson.fromJson(in, WebPreparedStatementResponse.class); assertEquals(0, response.getNumFreeVariables()); assertEquals(0, response.getStatementId()); assertEquals("application/json; charset=UTF-8", conn.getContentType()); // now execute the query we've just prepared WebPreparedStatement<AggregateCount> stmt = new WebPreparedStatement<>(0, 0); url = new URL(endpoint + "/query-execute"); HttpURLConnection conn2 = (HttpURLConnection) url.openConnection(); conn2.setRequestMethod("POST"); sendAuthentication(conn2, "no-matter", "no-matter"); conn2.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn2.setDoInput(true); conn2.setDoOutput(true); out = new OutputStreamWriter(conn2.getOutputStream()); body = "prepared-stmt=" + gson.toJson(stmt, WebPreparedStatement.class); out.write(body + "\n"); out.flush(); in = new InputStreamReader(conn2.getInputStream()); Type typeToken = new TypeToken<WebQueryResponse<AggregateCount>>(){}.getType(); WebQueryResponse<AggregateCount> result = gson.fromJson(in, typeToken); AggregateCount[] results = result.getResultList(); assertEquals(1, results.length); assertEquals(500, results[0].getCount()); assertEquals("application/json; charset=UTF-8", conn2.getContentType()); verify(mockMongoQuery).execute(); verify(mockMongoQuery).getWhereExpression(); verifyNoMoreInteractions(mockMongoQuery); } private void setupTrustedCategory(String categoryName) { Set<String> descs = new HashSet<>(); descs.add(categoryName); CategoryRegistration reg = mock(CategoryRegistration.class); when(reg.getCategoryNames()).thenReturn(descs); List<CategoryRegistration> regs = new ArrayList<>(1); regs.add(reg); KnownCategoryRegistry registry = new KnownCategoryRegistry(regs); KnownCategoryRegistryFactory.setInstance(registry); } private void setupTrustedStatementRegistry(String strDescriptor, DescriptorMetadata metadata) { Set<String> descs = new HashSet<>(); descs.add(strDescriptor); StatementDescriptorRegistration reg = new TestStatementDescriptorRegistration(descs, metadata); List<StatementDescriptorRegistration> regs = new ArrayList<>(1); regs.add(reg); KnownDescriptorRegistry registry = new KnownDescriptorRegistry(regs); KnownDescriptorRegistryFactory.setKnownDescriptorRegistry(registry); } @Test public void cannotRegisterCategoryWithoutRegistrationOnInit() throws Exception { // need this in order to pass basic permissions. String[] roleNames = new String[] { Roles.REGISTER_CATEGORY }; String username = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(username, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); try { String endpoint = getEndpoint(); URL url = new URL(endpoint + "/register-category"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); String enc = "UTF-8"; conn.setRequestProperty("Content-Encoding", enc); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestMethod("POST"); sendAuthentication(conn, username, password); OutputStream out = conn.getOutputStream(); Gson gson = new Gson(); OutputStreamWriter writer = new OutputStreamWriter(out); writer.write("name="); writer.write(URLEncoder.encode(category.getName(), enc)); writer.write("&category="); writer.write(URLEncoder.encode(gson.toJson(category), enc)); writer.flush(); // "test" category name not registered, expecting forbidden. assertEquals(HttpServletResponse.SC_FORBIDDEN, conn.getResponseCode()); } catch (IOException e) { fail("Should not throw exception! " + e.getMessage()); } } @Test public void unauthorizedPrepareStmt() throws Exception { String failMsg = "thermostat-prepare-statement role missing, expected Forbidden!"; doUnauthorizedTest("prepare-statement", failMsg); } @Test public void unauthorizedExecutePreparedQuery() throws Exception { String failMsg = "thermostat-read role missing, expected Forbidden!"; doUnauthorizedTest("query-execute", failMsg); } private void doUnauthorizedTest(String pathForEndPoint, String failMessage) throws Exception { String[] insufficientRoleNames = new String[] { Roles.REGISTER_CATEGORY, Roles.ACCESS_REALM }; doUnauthorizedTest(pathForEndPoint, failMessage, insufficientRoleNames, true); } private void doUnauthorizedTest(String pathForEndPoint, String failMessage, String[] insufficientRoles, boolean doRegisterCategory) throws Exception, MalformedURLException, IOException, ProtocolException { String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, insufficientRoles); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); if (doRegisterCategory) { // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory(testuser, password); } String endpoint = getEndpoint(); URL url = new URL(endpoint + "/" + pathForEndPoint); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); assertEquals(failMessage, HttpServletResponse.SC_FORBIDDEN, conn.getResponseCode()); } @Test public void authorizedRegisterCategoryTest() throws Exception { Set<BasicRole> roles = new HashSet<>(); roles.add(new RolePrincipal(Roles.REGISTER_CATEGORY)); roles.add(new RolePrincipal(Roles.ACCESS_REALM)); UserPrincipal testUser = new UserPrincipal("ignored1"); testUser.setRoles(roles); final LoginService loginService = new TestJAASLoginService(testUser); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); Category<HostInfo> wantedCategory = HostInfoDAO.hostInfoCategory; Category<AggregateCount> aggregate = new CategoryAdapter<HostInfo, AggregateCount>(wantedCategory).getAdapted(AggregateCount.class); // First the originating category has to be registered, then the adapted // one. Integer realId = registerCategoryAndGetId(wantedCategory, "no-matter", "no-matter"); Integer aggregateId = registerCategoryAndGetId(aggregate, "no-matter", "no-matter"); assertTrue("Aggregate categories need their own ID", aggregateId != realId); verify(mockStorage).registerCategory(eq(wantedCategory)); verifyNoMoreInteractions(mockStorage); } private Integer registerCategoryAndGetId(Category<?> cat, String username, String password) throws Exception { String endpoint = getEndpoint(); URL url = new URL(endpoint + "/register-category"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, username, password); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); Gson gson = new Gson(); out.write("name=" + cat.getName() + "&data-class=" + cat.getDataClass().getName() + "&category=" + gson.toJson(cat)); out.flush(); assertEquals(200, conn.getResponseCode()); Reader reader = new InputStreamReader(conn.getInputStream()); Integer id = gson.fromJson(reader, Integer.class); return id; } @Test public void authorizedReplacePojo() throws Exception { String[] roleNames = new String[] { Roles.REPLACE, Roles.REGISTER_CATEGORY, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory(testuser, password); Replace replace = mock(Replace.class); when(mockStorage.createReplace(any(Category.class))).thenReturn(replace); TestClass expected1 = new TestClass(); expected1.setKey1("fluff1"); expected1.setKey2(42); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/replace-pojo"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); WebInsert insert = new WebInsert(categoryId); Gson gson = new Gson(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("insert="); gson.toJson(insert, out); out.flush(); out.write("&pojo="); gson.toJson(expected1, out); out.write("\n"); out.flush(); assertEquals(200, conn.getResponseCode()); verify(mockStorage).createReplace(category); verify(replace).setPojo(expected1); verify(replace).apply(); } @Test public void unauthorizedReplacePojo() throws Exception { String[] insufficientRoleNames = new String[] { Roles.REGISTER_CATEGORY, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, insufficientRoleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory(testuser, password); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/replace-pojo"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // replace WebInsert insert = new WebInsert(categoryId); Gson gson = new Gson(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("insert="); gson.toJson(insert, out); out.flush(); out.write("&pojo="); TestClass expected1 = new TestClass(); gson.toJson(expected1, out); out.write("\n"); out.flush(); assertEquals("thermostat-replace role missing", HttpServletResponse.SC_FORBIDDEN, conn.getResponseCode()); } @Test public void authorizedAddPojo() throws Exception { String[] roleNames = new String[] { Roles.APPEND, Roles.REGISTER_CATEGORY, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory(testuser, password); Add insert = mock(Add.class); when(mockStorage.createAdd(any(Category.class))).thenReturn(insert); TestClass expected1 = new TestClass(); expected1.setKey1("fluff1"); expected1.setKey2(42); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/add-pojo"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); WebInsert ins = new WebInsert(categoryId); Gson gson = new Gson(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("insert="); gson.toJson(ins, out); out.flush(); out.write("&pojo="); gson.toJson(expected1, out); out.write("\n"); out.flush(); assertEquals(200, conn.getResponseCode()); verify(mockStorage).createAdd(category); verify(insert).setPojo(expected1); verify(insert).apply(); } @Test public void unauthorizedAddPojo() throws Exception { String[] insufficientRoleNames = new String[] { Roles.REGISTER_CATEGORY, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, insufficientRoleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory(testuser, password); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/add-pojo"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // replace WebInsert insert = new WebInsert(categoryId); Gson gson = new Gson(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("insert="); gson.toJson(insert, out); out.flush(); out.write("&pojo="); TestClass expected1 = new TestClass(); gson.toJson(expected1, out); out.write("\n"); out.flush(); assertEquals("thermostat-add role missing", HttpServletResponse.SC_FORBIDDEN, conn.getResponseCode()); } private void sendAuthentication(HttpURLConnection conn, String username, String passwd) { String userpassword = username + ":" + passwd; String encodedAuthorization = Base64.encodeBase64String(userpassword.getBytes()); conn.setRequestProperty("Authorization", "Basic "+ encodedAuthorization); } @Test public void authorizedRemovePojo() throws Exception { String[] roleNames = new String[] { Roles.DELETE, Roles.REGISTER_CATEGORY, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory(testuser, password); Remove mockRemove = mock(Remove.class); when(mockStorage.createRemove(category)).thenReturn(mockRemove); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/remove-pojo"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); Expression expr = factory.equalTo(key1, "test"); WebRemove remove = new WebRemove(categoryId); remove.where(expr); Gson gson = new GsonBuilder() .registerTypeHierarchyAdapter(Expression.class, new ExpressionSerializer()) .registerTypeHierarchyAdapter(Operator.class, new OperatorSerializer()).create(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("remove="); gson.toJson(remove, out); out.write("\n"); out.flush(); assertEquals(200, conn.getResponseCode()); verify(mockStorage).createRemove(eq(category)); verify(mockRemove).where(eq(expr)); verify(mockRemove).apply(); } @Test public void unauthorizedRemovePojo() throws Exception { String failMsg = "thermostat-remove role missing, expected Forbidden!"; doUnauthorizedTest("remove-pojo", failMsg); } @Test public void authorizedUpdatePojo() throws Exception { String[] roleNames = new String[] { Roles.UPDATE, Roles.REGISTER_CATEGORY, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); // This makes register category work for the "test" category. // Undone via @After setupTrustedCategory(categoryName); registerCategory(testuser, password); Update mockUpdate = mock(Update.class); when(mockStorage.createUpdate(any(Category.class))).thenReturn(mockUpdate); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/update-pojo"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); WebUpdate update = new WebUpdate(); update.setCategoryId(categoryId); Expression expr = factory.equalTo(key1, "test"); update.where(expr); update.set(key1, "fluff"); update.set(key2, 42); Gson gson = new GsonBuilder() .registerTypeHierarchyAdapter(Expression.class, new ExpressionSerializer()) .registerTypeHierarchyAdapter(Operator.class, new OperatorSerializer()).create(); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("update="); gson.toJson(update, out); out.write("&values="); gson.toJson(new Object[] {"fluff", 42 }, out); out.write("\n"); out.flush(); assertEquals(200, conn.getResponseCode()); verify(mockStorage).createUpdate(category); verify(mockUpdate).where(eq(expr)); verify(mockUpdate).set(key1, "fluff"); verify(mockUpdate).set(key2, 42); verify(mockUpdate).apply(); verifyNoMoreInteractions(mockUpdate); } @Test public void unauthorizedUpdatePojo() throws Exception { String failMsg = "thermostat-update role missing, expected Forbidden!"; doUnauthorizedTest("update-pojo", failMsg); } @Test public void authorizedSaveFile() throws Exception { String[] roleNames = new String[] { Roles.SAVE_FILE, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/save-file"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=fluff"); conn.setRequestProperty("Transfer-Encoding", "chunked"); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("--fluff\r\n"); out.write("Content-Disposition: form-data; name=\"file\"; filename=\"fluff\"\r\n"); out.write("Content-Type: application/octet-stream\r\n"); out.write("Content-Transfer-Encoding: binary\r\n"); out.write("\r\n"); out.write("Hello World\r\n"); out.write("--fluff--\r\n"); out.flush(); // needed in order to trigger inCaptor interaction with mock conn.getResponseCode(); ArgumentCaptor<InputStream> inCaptor = ArgumentCaptor.forClass(InputStream.class); verify(mockStorage).saveFile(eq("fluff"), inCaptor.capture()); InputStream in = inCaptor.getValue(); byte[] data = new byte[11]; int totalRead = 0; while (totalRead < 11) { int read = in.read(data, totalRead, 11 - totalRead); if (read < 0) { fail(); } totalRead += read; } assertEquals("Hello World", new String(data)); } @Test public void unauthorizedSaveFile() throws Exception { String failMsg = "thermostat-save-file role missing, expected Forbidden!"; String[] insufficientRoles = new String[0]; doUnauthorizedTest("save-file", failMsg, insufficientRoles, false); } @Test public void authorizedLoadFile() throws Exception { String[] roleNames = new String[] { Roles.LOAD_FILE, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); byte[] data = "Hello World".getBytes(); InputStream in = new ByteArrayInputStream(data); when(mockStorage.loadFile("fluff")).thenReturn(in); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/load-file"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setDoInput(true); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("file=fluff"); out.flush(); in = conn.getInputStream(); data = new byte[11]; int totalRead = 0; while (totalRead < 11) { int read = in.read(data, totalRead, 11 - totalRead); if (read < 0) { fail(); } totalRead += read; } assertEquals("Hello World", new String(data)); verify(mockStorage).loadFile("fluff"); } @Test public void unauthorizedLoadFile() throws Exception { String failMsg = "thermostat-load-file role missing, expected Forbidden!"; String[] insufficientRoles = new String[0]; doUnauthorizedTest("load-file", failMsg, insufficientRoles, false); } @Test public void authorizedPurge() throws Exception { String[] roleNames = new String[] { Roles.PURGE, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/purge"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setRequestMethod("POST"); sendAuthentication(conn, testuser, password); conn.getOutputStream().write("agentId=fluff".getBytes()); int status = conn.getResponseCode(); assertEquals(200, status); verify(mockStorage).purge("fluff"); } @Test public void unauthorizedPurge() throws Exception { String failMsg = "thermostat-purge role missing, expected Forbidden!"; String[] insufficientRoles = new String[0]; doUnauthorizedTest("purge", failMsg, insufficientRoles, false); } private void registerCategory(String username, String password) { registerCategory(category, username, password); } private void registerCategory(Category<?> category, String username, String password) { try { String endpoint = getEndpoint(); URL url = new URL(endpoint + "/register-category"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); String enc = "UTF-8"; conn.setRequestProperty("Content-Encoding", enc); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestMethod("POST"); sendAuthentication(conn, username, password); OutputStream out = conn.getOutputStream(); Gson gson = new Gson(); OutputStreamWriter writer = new OutputStreamWriter(out); writer.write("name="); writer.write(URLEncoder.encode(category.getName(), enc)); writer.write("&category="); writer.write(URLEncoder.encode(gson.toJson(category), enc)); writer.write("&data-class="); writer.write(URLEncoder.encode(category.getDataClass().getName(), enc)); writer.flush(); InputStream in = conn.getInputStream(); Reader reader = new InputStreamReader(in); Integer id = gson.fromJson(reader, Integer.class); categoryId = id; } catch (IOException ex) { throw new RuntimeException(ex); } } private String getEndpoint() { return "http://localhost:" + port + "/storage"; } @Test public void authorizedGenerateToken() throws Exception { String actionName = "testing"; String[] roleNames = new String[] { Roles.CMD_CHANNEL_GENERATE, Roles.ACCESS_REALM, // grant the "testing" action WebStorageEndPoint.CMDC_AUTHORIZATION_GRANT_ROLE_PREFIX + actionName }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); verifyAuthorizedGenerateToken(testuser, password, actionName); } @Test public void unauthorizedGenerateToken() throws Exception { String failMsg = "thermostat-cmdc-generate role missing, expected Forbidden!"; String[] insufficientRoles = new String[] { Roles.ACCESS_REALM }; doUnauthorizedTest("generate-token", failMsg, insufficientRoles, false); } @Test public void authorizedGenerateVerifyToken() throws Exception { String actionName = "someAction"; String[] roleNames = new String[] { Roles.CMD_CHANNEL_GENERATE, Roles.CMD_CHANNEL_VERIFY, Roles.ACCESS_REALM, // grant "someAction" to be performed WebStorageEndPoint.CMDC_AUTHORIZATION_GRANT_ROLE_PREFIX + actionName, }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); byte[] token = verifyAuthorizedGenerateToken(testuser, password, actionName); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/verify-token"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setDoInput(true); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("client-token=fluff&action-name=" + actionName + "&token=" + URLEncoder.encode(Base64.encodeBase64String(token), "UTF-8")); out.flush(); assertEquals(200, conn.getResponseCode()); } @Test public void unAuthorizedGenerateVerifyToken() throws Exception { String testuser = "testuser"; String password = "testpassword"; String actionName = "someAction"; String[] roleNames = new String[] { Roles.CMD_CHANNEL_GENERATE, Roles.CMD_CHANNEL_VERIFY, Roles.ACCESS_REALM, // missing the thermostat-cmdc-grant-someAction role }; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); byte[] result = verifyAuthorizedGenerateToken(testuser, password, actionName, 403); assertNull(result); } @Test public void authenticatedGenerateVerifyTokenWithActionNameMismatch() throws Exception { String actionName = "someAction"; String[] roleNames = new String[] { Roles.CMD_CHANNEL_GENERATE, WebStorageEndPoint.CMDC_AUTHORIZATION_GRANT_ROLE_PREFIX + actionName, Roles.CMD_CHANNEL_VERIFY, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); byte[] token = verifyAuthorizedGenerateToken(testuser, password, actionName); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/verify-token"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setDoInput(true); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); // expected action-name parameter is "someAction". This should not // verify => 403. out.write("client-token=fluff&action-name=wrongAction&token=" + URLEncoder.encode(Base64.encodeBase64String(token), "UTF-8")); out.flush(); assertEquals(403, conn.getResponseCode()); } @Test public void authenticatedTokenTimeout() throws Exception { String actionName = "someAction"; String[] roleNames = new String[] { Roles.CMD_CHANNEL_GENERATE, Roles.CMD_CHANNEL_VERIFY, Roles.ACCESS_REALM, // Grant "someAction", this test tests the time-out WebStorageEndPoint.CMDC_AUTHORIZATION_GRANT_ROLE_PREFIX + actionName }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); byte[] token = verifyAuthorizedGenerateToken(testuser, password, actionName); Thread.sleep(700); // Timeout is set to 500ms for tests, 700ms should be enough for everybody. ;-) String endpoint = getEndpoint(); URL url = new URL(endpoint + "/verify-token"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setDoInput(true); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("client-token=fluff&action-name=" + actionName + "&token=" + URLEncoder.encode(Base64.encodeBase64String(token), "UTF-8")); out.flush(); assertEquals(403, conn.getResponseCode()); } @Test public void authenticatedVerifyNonExistentToken() throws Exception { String[] roleNames = new String[] { Roles.CMD_CHANNEL_VERIFY, Roles.ACCESS_REALM }; String testuser = "testuser"; String password = "testpassword"; final LoginService loginService = new TestLoginService(testuser, password, roleNames); port = FreePortFinder.findFreePort(new TryPort() { @Override public void tryPort(int port) throws Exception { startServer(port, loginService); } }); byte[] token = "fluff".getBytes(); String endpoint = getEndpoint(); URL url = new URL(endpoint + "/verify-token"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); sendAuthentication(conn, testuser, password); conn.setDoOutput(true); conn.setDoInput(true); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("client-token=fluff&action-name=someAction&token=" + URLEncoder.encode(Base64.encodeBase64String(token), "UTF-8")); out.flush(); assertEquals(403, conn.getResponseCode()); } @Test public void unauthorizedVerifyToken() throws Exception { String failMsg = "thermostat-cmdc-verify role missing, expected Forbidden!"; String[] insufficientRoles = new String[] { Roles.ACCESS_REALM }; doUnauthorizedTest("verify-token", failMsg, insufficientRoles, false); } @Test public void initThrowsRuntimeExceptionIfThermostatHomeNotSet() { // setup sets this, but we don't want to have it set for this test System.clearProperty("THERMOSTAT_HOME"); WebStorageEndPoint endpoint = new WebStorageEndPoint(); ServletConfig config = mock(ServletConfig.class); try { endpoint.init(config); fail("Thermostat home was not set in config, should not get here!"); } catch (RuntimeException e) { // pass assertTrue(e.getMessage().contains("THERMOSTAT_HOME")); } catch (ServletException e) { fail(e.getMessage()); } // set config with non-existing dir when(config.getInitParameter("THERMOSTAT_HOME")).thenReturn("not-existing"); try { endpoint.init(config); fail("Thermostat home was set in config but file does not exist, should have died!"); } catch (RuntimeException e) { // pass assertTrue(e.getMessage().contains("THERMOSTAT_HOME")); } catch (ServletException e) { fail(e.getMessage()); } } private byte[] verifyAuthorizedGenerateToken(String username, String password, String actionName) throws IOException { return verifyAuthorizedGenerateToken(username, password, actionName, 200); } private byte[] verifyAuthorizedGenerateToken(String username, String password, String actionName, int expectedResponseCode) throws IOException { return verifyAuthorizedGenerateToken(username, password, expectedResponseCode, actionName); } private byte[] verifyAuthorizedGenerateToken(String username, String password, int expectedResponseCode, String actionName) throws IOException { String endpoint = getEndpoint(); URL url = new URL(endpoint + "/generate-token"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); sendAuthentication(conn, username, password); conn.setDoOutput(true); conn.setDoInput(true); OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write("client-token=fluff&action-name=" + actionName); out.flush(); int actualResponseCode = conn.getResponseCode(); assertEquals(expectedResponseCode, actualResponseCode); if (actualResponseCode == 200) { InputStream in = conn.getInputStream(); int length = conn.getContentLength(); byte[] token = new byte[length]; assertEquals(256, length); int totalRead = 0; while (totalRead < length) { int read = in.read(token, totalRead, length - totalRead); if (read < 0) { fail(); } totalRead += read; } return token; } else { return null; } } private static class TestLoginService extends MappedLoginService { private final String[] roleNames; private final String username; private final String password; private TestLoginService(String username, String password, String[] roleNames) { this.username = username; this.password = password; this.roleNames = roleNames; } @Override protected void loadUsers() throws IOException { putUser(username, new Password(password), roleNames); } @Override protected UserIdentity loadUser(String username) { return new DefaultUserIdentity(null, null, roleNames); } } private static class TestJAASLoginService extends JAASLoginService { private final UserPrincipal userPrincipal; private TestJAASLoginService(UserPrincipal userPrincipal) { this.userPrincipal = userPrincipal; } @Override public UserIdentity login(String username, Object credentials) { return new TestUserIdentity(userPrincipal); } private static class TestUserIdentity implements UserIdentity { private final UserPrincipal userPrincipal; private TestUserIdentity(UserPrincipal principal) { this.userPrincipal = principal; } @Override public Subject getSubject() { throw new IllegalStateException("Not implemented"); } @Override public Principal getUserPrincipal() { return userPrincipal; } @Override public boolean isUserInRole(String role, Scope scope) { RolePrincipal rolePrincipal = new RolePrincipal(role); return userPrincipal.getRoles().contains(rolePrincipal); } } } private static class TestStatementDescriptorRegistration implements StatementDescriptorRegistration { private final Set<String> descriptorSet; private final DescriptorMetadata metadata; private TestStatementDescriptorRegistration(Set<String> descriptorSet, DescriptorMetadata metadata) { assertEquals(1, descriptorSet.size()); this.descriptorSet = descriptorSet; this.metadata = metadata; } @Override public DescriptorMetadata getDescriptorMetadata(String descriptor, PreparedParameter[] params) { return metadata; } @Override public Set<String> getStatementDescriptors() { return descriptorSet; } } }