diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/TestSuite.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/TestSuite.java index 46be99a6e7a8a87723bd527ecbbd0ea6de3e6d21..c636a1d5619cca878de3938c8dedcb90a6b46dc8 100644 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/TestSuite.java +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/TestSuite.java @@ -15,6 +15,8 @@ */ package ch.ethz.sis.afsserver; +import ch.ethz.sis.afsserver.client.DummyAuthApiClientTest; +import ch.ethz.sis.afsserver.client.OpenBisAuthApiClientTest; import ch.ethz.sis.afsserver.impl.ApiServerAdapterTest; import ch.ethz.sis.afsserver.impl.ApiServerTest; import org.junit.runner.RunWith; @@ -23,7 +25,9 @@ import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ ApiServerTest.class, - ApiServerAdapterTest.class + ApiServerAdapterTest.class, + DummyAuthApiClientTest.class, + OpenBisAuthApiClientTest.class }) public class TestSuite { diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/BaseApiClientTest.java similarity index 70% rename from server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java rename to server-data-store/src/test/java/ch/ethz/sis/afsserver/client/BaseApiClientTest.java index 3d54804e03b3ac3abc1cced7fbe1fb0b68e2a00c..0e2951b612a842d2378a8b33389f07e28b6f38b8 100644 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/BaseApiClientTest.java @@ -15,7 +15,7 @@ * */ -package ch.ethz.sis.afsserver; +package ch.ethz.sis.afsserver.client; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.CoreMatchers.containsString; @@ -33,46 +33,28 @@ import org.junit.*; import ch.ethz.sis.afs.manager.TransactionConnection; import ch.ethz.sis.afsclient.client.AfsClient; import ch.ethz.sis.afsserver.server.Server; -import ch.ethz.sis.afsserver.server.observer.impl.DummyServerObserver; -import ch.ethz.sis.afsserver.startup.AtomicFileSystemServerParameter; -import ch.ethz.sis.shared.startup.Configuration; -public final class ApiClientTest +public abstract class BaseApiClientTest { - private static Server<TransactionConnection, ?> afsServer; + protected static Server<TransactionConnection, ?> afsServer; - private static AfsClient afsClient; + protected static AfsClient afsClient; - private static int httpServerPort; + protected static int httpServerPort; - private static String httpServerPath; + protected static String httpServerPath; - private static String storageRoot; + protected static String storageRoot; - public static final String FILE_A = "A.txt"; + protected static final String FILE_A = "A.txt"; - public static final byte[] DATA = "ABCD".getBytes(); - public static final String FILE_B = "B.txt"; + protected static final byte[] DATA = "ABCD".getBytes(); - public static String owner = UUID.randomUUID().toString(); + protected static final String FILE_B = "B.txt"; - private String testDataRoot; + protected static String owner = UUID.randomUUID().toString(); - - @BeforeClass - public static void classSetUp() throws Exception - { - final Configuration configuration = - new Configuration(List.of(AtomicFileSystemServerParameter.class), - "src/test/resources/test-server-config.properties"); - final DummyServerObserver dummyServerObserver = new DummyServerObserver(); - afsServer = new Server<>(configuration, dummyServerObserver, dummyServerObserver); - httpServerPort = - configuration.getIntegerProperty(AtomicFileSystemServerParameter.httpServerPort); - httpServerPath = - configuration.getStringProperty(AtomicFileSystemServerParameter.httpServerUri); - storageRoot = configuration.getStringProperty(AtomicFileSystemServerParameter.storageRoot); - } + protected String testDataRoot; @AfterClass public static void classTearDown() throws Exception @@ -90,8 +72,7 @@ public final class ApiClientTest IOUtils.write(testDataFile, 0, DATA); afsClient = new AfsClient( - new URI("http", null, "localhost", httpServerPort, - httpServerPath, null, null)); + new URI("http", null, "localhost", httpServerPort, httpServerPath, null, null)); } @After @@ -163,7 +144,8 @@ public final class ApiClientTest } @Test - public void read_getsDataFromTemporaryFile() throws Exception { + public void read_getsDataFromTemporaryFile() throws Exception + { login(); byte[] bytes = afsClient.read(owner, FILE_A, 0L, DATA.length); @@ -171,7 +153,8 @@ public final class ApiClientTest } @Test - public void write_zeroOffset_createsFile() throws Exception { + public void write_zeroOffset_createsFile() throws Exception + { login(); Boolean result = afsClient.write(owner, FILE_B, 0L, DATA, IOUtils.getMD5(DATA)); @@ -182,7 +165,8 @@ public final class ApiClientTest } @Test - public void write_nonZeroOffset_createsFile() throws Exception { + public void write_nonZeroOffset_createsFile() throws Exception + { login(); Long offset = 65L; @@ -194,18 +178,20 @@ public final class ApiClientTest } @Test - public void delete_fileIsGone() throws Exception { + public void delete_fileIsGone() throws Exception + { login(); Boolean deleted = afsClient.delete(owner, FILE_A); assertTrue(deleted); - List<ch.ethz.sis.afs.api.dto.File> list = IOUtils.list(testDataRoot, true); + List<ch.ethz.sis.afs.api.dto.File> list = IOUtils.list(testDataRoot, true); assertEquals(0, list.size()); } @Test - public void copy_newFileIsCreated() throws Exception { + public void copy_newFileIsCreated() throws Exception + { login(); Boolean result = afsClient.copy(owner, FILE_A, owner, FILE_B); @@ -216,13 +202,14 @@ public final class ApiClientTest } @Test - public void move_fileIsRenamed() throws Exception { + public void move_fileIsRenamed() throws Exception + { login(); Boolean result = afsClient.move(owner, FILE_A, owner, FILE_B); assertTrue(result); - List<ch.ethz.sis.afs.api.dto.File> list = IOUtils.list(testDataRoot, true); + List<ch.ethz.sis.afs.api.dto.File> list = IOUtils.list(testDataRoot, true); assertEquals(1, list.size()); assertEquals(FILE_B, list.get(0).getName()); @@ -230,9 +217,7 @@ public final class ApiClientTest assertArrayEquals(DATA, testDataFile); } - - - private String login() throws Exception + protected String login() throws Exception { return afsClient.login("test", "test"); } diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/DummyAuthApiClientTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/DummyAuthApiClientTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3dd4b1e02ce50e03752e32ad5092541646af6ced --- /dev/null +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/DummyAuthApiClientTest.java @@ -0,0 +1,47 @@ +/* + * Copyright ETH 2023 Zürich, Scientific IT Services + * + * Licensed 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 ch.ethz.sis.afsserver.client; + +import java.util.List; + +import org.junit.*; + +import ch.ethz.sis.afsserver.server.Server; +import ch.ethz.sis.afsserver.server.observer.impl.DummyServerObserver; +import ch.ethz.sis.afsserver.startup.AtomicFileSystemServerParameter; +import ch.ethz.sis.shared.startup.Configuration; + +public final class DummyAuthApiClientTest extends BaseApiClientTest +{ + + @BeforeClass + public static void classSetUp() throws Exception + { + final Configuration configuration = + new Configuration(List.of(AtomicFileSystemServerParameter.class), + "src/test/resources/test-server-config.properties"); + final DummyServerObserver dummyServerObserver = new DummyServerObserver(); + afsServer = new Server<>(configuration, dummyServerObserver, dummyServerObserver); + httpServerPort = + configuration.getIntegerProperty(AtomicFileSystemServerParameter.httpServerPort); + httpServerPath = + configuration.getStringProperty(AtomicFileSystemServerParameter.httpServerUri); + storageRoot = configuration.getStringProperty(AtomicFileSystemServerParameter.storageRoot); + } + +} diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/DummyOpenBisServer.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/DummyOpenBisServer.java new file mode 100644 index 0000000000000000000000000000000000000000..b706abc94f37f2643f30ea95e3c0ef10e7894652 --- /dev/null +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/DummyOpenBisServer.java @@ -0,0 +1,129 @@ +/* + * Copyright ETH 2023 Zürich, Scientific IT Services + * + * Licensed 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 ch.ethz.sis.afsserver.client; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Right; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SampleIdentifier; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpServer; +import org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter; +import org.springframework.remoting.support.RemoteInvocation; +import org.springframework.remoting.support.RemoteInvocationResult; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class DummyOpenBisServer +{ + private final HttpServer httpServer; + + private Map<String, Object> responses = Map.of(); + + public DummyOpenBisServer(int httpServerPort, String httpServerPath) throws IOException + { + httpServer = HttpServer.create(new InetSocketAddress(httpServerPort), 0); + httpServer.createContext(httpServerPath, exchange -> + { + DummyInvoker inv = new DummyInvoker(responses); + inv.handle(exchange); + }); + } + + public void start() + { + httpServer.start(); + } + + public void stop() + { + httpServer.stop(0); + } + + public void setResponses(Map<String, Object> responses) + { + this.responses = responses; + } + + private static class DummyInvoker extends SimpleHttpInvokerServiceExporter + { + private final Map<String, Object> result; + + public DummyInvoker(Map<String, Object> result) + { + super(); + this.result = result; + } + + @Override + public void handle(HttpExchange exchange) throws IOException + { + try + { + RemoteInvocation invocation = super.readRemoteInvocation(exchange); + final String method = invocation.getMethodName(); + Object resultObj; + if (result.containsKey(method)) + { + if("getRights".equals(method)) { + Object param = ((List<?>) invocation.getArguments()[1]).get(0); + resultObj = Map.of(param, (Rights) result.get(method)); + } else { + resultObj = result.get(method); + } + } else + { + switch (method) + { + case "login": + resultObj = "test-login-token"; + break; + case "logout": + case "isSessionActive": + resultObj = true; + break; + case "getSamples": + resultObj = Map.of(new SampleIdentifier(""), new Sample()); + break; + case "getRights": + Object param = ((List<?>) invocation.getArguments()[1]).get(0); + resultObj = Map.of(param, new Rights(Set.of(Right.UPDATE))); + break; + default: + throw new IllegalStateException( + "Unknown method: " + invocation.getMethodName()); + } + } + RemoteInvocationResult remoteInvocationResult = + new RemoteInvocationResult(resultObj); + this.writeRemoteInvocationResult(exchange, remoteInvocationResult); + exchange.close(); + } catch (ClassNotFoundException var4) + { + exchange.sendResponseHeaders(500, -1L); + this.logger.error("Class not found during deserialization", var4); + } + } + + } + +} diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/OpenBisAuthApiClientTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/OpenBisAuthApiClientTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e36ca1f2eeb57ec1888285e05ff333ba7f2c44ca --- /dev/null +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/client/OpenBisAuthApiClientTest.java @@ -0,0 +1,161 @@ +/* + * Copyright ETH 2023 Zürich, Scientific IT Services + * + * Licensed 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 ch.ethz.sis.afsserver.client; + +import static org.junit.Assert.*; + +import ch.ethz.sis.afsapi.dto.ExceptionReason; +import ch.ethz.sis.afsapi.exception.ThrowableReason; +import ch.ethz.sis.afsserver.server.Server; +import ch.ethz.sis.afsserver.server.observer.impl.DummyServerObserver; +import ch.ethz.sis.afsserver.startup.AtomicFileSystemServerParameter; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.rights.Rights; +import ch.ethz.sis.shared.io.IOUtils; +import ch.ethz.sis.shared.startup.Configuration; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class OpenBisAuthApiClientTest extends BaseApiClientTest +{ + + /** Match this value with openBISUrl from properties file */ + private static final int OPENBIS_DUMMY_SERVER_PORT = 8084; + + private static final String OPENBIS_DUMMY_SERVER_PATH = "/"; + + private DummyOpenBisServer dummyOpenBisServer; + + @BeforeClass + public static void classSetUp() throws Exception + { + final Configuration configuration = + new Configuration(List.of(AtomicFileSystemServerParameter.class), + "src/test/resources/test-server-with-auth-config.properties"); + final DummyServerObserver dummyServerObserver = new DummyServerObserver(); + + afsServer = new Server<>(configuration, dummyServerObserver, dummyServerObserver); + httpServerPort = + configuration.getIntegerProperty(AtomicFileSystemServerParameter.httpServerPort); + httpServerPath = + configuration.getStringProperty(AtomicFileSystemServerParameter.httpServerUri); + storageRoot = configuration.getStringProperty(AtomicFileSystemServerParameter.storageRoot); + } + + @Before + public void setUpDummyOpenBis() throws Exception + { + dummyOpenBisServer = + new DummyOpenBisServer(OPENBIS_DUMMY_SERVER_PORT, OPENBIS_DUMMY_SERVER_PATH); + dummyOpenBisServer.start(); + } + + @After + public void tearDownDummyOpenBis() + { + dummyOpenBisServer.stop(); + } + + @Test + public void list_callFailsDueToMissingPermissions() throws Exception + { + login(); + + dummyOpenBisServer.setResponses(Map.of("getSamples", Map.of())); + + try + { + afsClient.list(owner, "", Boolean.TRUE); + fail(); + } catch (Exception e) + { + ThrowableReason reason = (ThrowableReason) e.getCause(); + String message = ((ExceptionReason) reason.getReason()).getMessage(); + assertTrue(message.matches( + "(?s).*Session .* don't have rights \\[Read\\] over .*to perform the operation List(?s).*")); + } + + } + + @Test + public void list_failsDueToExpiredSession() throws Exception + { + login(); + dummyOpenBisServer.setResponses(Map.of("isSessionActive", false)); + try + { + afsClient.list(owner, "", Boolean.TRUE); + fail(); + } catch (Exception e) + { + ThrowableReason reason = (ThrowableReason) e.getCause(); + String message = ((ExceptionReason) reason.getReason()).getMessage(); + assertTrue(message.matches("(?s).*Session .* doesn't exist(?s).*")); + } + } + + @Test + public void write_failsDueToMissingPermission_noFileCreated() throws Exception + { + login(); + + dummyOpenBisServer.setResponses(Map.of("getRights", new Rights(Set.of()))); + + try + { + afsClient.write(owner, FILE_B, 0L, DATA, IOUtils.getMD5(DATA)); + fail(); + } catch (Exception e) + { + ThrowableReason reason = (ThrowableReason) e.getCause(); + String message = ((ExceptionReason) reason.getReason()).getMessage(); + assertTrue(message.matches( + "(?s).*Session .* don't have rights \\[Write\\] over .* to perform the operation Write(?s).*")); + } + assertFalse(IOUtils.exists(IOUtils.getPath(testDataRoot, FILE_B))); + + } + + @Test + public void move_failsDueToMissingPermissions() throws Exception + { + login(); + + dummyOpenBisServer.setResponses(Map.of("getRights", new Rights(Set.of()), + "getSamples", Map.of())); + + try + { + afsClient.move(owner, FILE_A, owner, FILE_B); + fail(); + } catch (Exception e) + { + ThrowableReason reason = (ThrowableReason) e.getCause(); + String message = ((ExceptionReason) reason.getReason()).getMessage(); + assertTrue(message.matches( + "(?s).*Session .* don't have rights \\[(Write|Read), (Write|Read)\\] over .* to perform the operation Move(?s).*")); + } + assertFalse(IOUtils.exists(IOUtils.getPath(testDataRoot, FILE_B))); + } + +} diff --git a/server-data-store/src/test/resources/test-server-config.properties b/server-data-store/src/test/resources/test-server-config.properties index 0b988b4160d4a558b42d4d219c337a9e64b0cb6f..430d16f423d418c474557cf5231498484c5a20a6 100644 --- a/server-data-store/src/test/resources/test-server-config.properties +++ b/server-data-store/src/test/resources/test-server-config.properties @@ -1,3 +1,20 @@ +# +# Copyright ETH 2023 Zürich, Scientific IT Services +# +# Licensed 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. +# +# + logFactoryClass=ch.ethz.sis.shared.log.log4j2.Log4J2LogFactory logConfigFile= diff --git a/server-data-store/src/test/resources/test-server-with-auth-config.properties b/server-data-store/src/test/resources/test-server-with-auth-config.properties new file mode 100644 index 0000000000000000000000000000000000000000..cd2e462ccfc3ab3131efe26e436d9b92b930d864 --- /dev/null +++ b/server-data-store/src/test/resources/test-server-with-auth-config.properties @@ -0,0 +1,44 @@ +# +# Copyright ETH 2023 Zürich, Scientific IT Services +# +# Licensed 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. +# +# + +logFactoryClass=ch.ethz.sis.shared.log.log4j2.Log4J2LogFactory +logConfigFile= + +jsonObjectMapperClass=ch.ethz.sis.afsjson.jackson.JacksonObjectMapper +# Where all the transactions information is written until the prepare step +# For performance reasons should be on the save volume as the configured storage +writeAheadLogRoot=./target/tests/transactions +storageRoot=./target/tests/storage + +httpServerClass=ch.ethz.sis.afsserver.http.impl.NettyHttpServer +httpServerPort=8085 +httpServerUri=/fileserver +httpMaxContentLength=1024 + +maxReadSizeInBytes=1024 +authenticationInfoProviderClass=ch.ethz.sis.afsserver.worker.providers.impl.OpenBISAuthenticationInfoProvider +authorizationInfoProviderClass=ch.ethz.sis.afsserver.worker.providers.impl.OpenBISAuthorizationInfoProvider +poolSize=50 +connectionFactoryClass=ch.ethz.sis.afsserver.worker.ConnectionFactory +workerFactoryClass=ch.ethz.sis.afsserver.worker.WorkerFactory +publicApiInterface=ch.ethz.sis.afsapi.api.PublicAPI +apiServerInteractiveSessionKey=1234 +apiServerTransactionManagerKey=5678 +apiServerWorkerTimeout=30000 + +openBISUrl=http://localhost:8084/ +openBISTimeout=30000 \ No newline at end of file