From 81ae74033e0c503c2abcbb9ebc9b269d36cdc0ac Mon Sep 17 00:00:00 2001 From: Pierre Date: Tue, 6 Dec 2022 18:11:45 +0100 Subject: [PATCH] Add batch authorization test --- .../ilt/statests/c04batch/BatchTests.java | 160 ++++++++++++------ .../iosb/ilt/statests/util/EntityHelper.java | 20 ++- .../iosb/ilt/statests/util/HTTPMethods.java | 37 ++++ 3 files changed, 162 insertions(+), 55 deletions(-) diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c04batch/BatchTests.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c04batch/BatchTests.java index 0010ebe92..43d4b40c6 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c04batch/BatchTests.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c04batch/BatchTests.java @@ -7,6 +7,8 @@ import de.fraunhofer.iosb.ilt.sta.model.Thing; import de.fraunhofer.iosb.ilt.statests.AbstractTestClass; import de.fraunhofer.iosb.ilt.statests.ServerVersion; +import de.fraunhofer.iosb.ilt.statests.TestSuite; +import de.fraunhofer.iosb.ilt.statests.f01auth.BasicAuthTests; import de.fraunhofer.iosb.ilt.statests.util.EntityHelper; import de.fraunhofer.iosb.ilt.statests.util.EntityType; import de.fraunhofer.iosb.ilt.statests.util.EntityUtils; @@ -17,9 +19,11 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import org.json.JSONException; import org.junit.jupiter.api.AfterAll; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -58,9 +62,20 @@ public Implementation11() { private static final List OBSERVED_PROPS = new ArrayList<>(); private static final Map ID_TYPES = new HashMap<>(); private final ObjectMapper mapper; + private static final Properties SERVER_PROPERTIES = new Properties(); + static { + SERVER_PROPERTIES.put("auth_provider", "de.fraunhofer.iosb.ilt.frostserver.auth.basic.BasicAuthProvider"); + SERVER_PROPERTIES.put("auth_allowAnonymousRead", "false"); + SERVER_PROPERTIES.put("auth_autoUpdateDatabase", "true"); + final String dbName = "basicauth"; + SERVER_PROPERTIES.put("auth.db.url", TestSuite.createDbUrl(dbName)); + SERVER_PROPERTIES.put("auth_db_driver", "org.postgresql.Driver"); + SERVER_PROPERTIES.put("auth_db_username", TestSuite.VAL_PG_USER); + SERVER_PROPERTIES.put("auth_db_password", TestSuite.VAL_PG_PASS); + } public BatchTests(ServerVersion version) { - super(version); + super(version, SERVER_PROPERTIES); mapper = new ObjectMapper(); } @@ -86,12 +101,13 @@ public static void tearDown() throws ServiceFailureException { } private static void cleanup() throws ServiceFailureException { - EntityUtils.deleteAll(version, serverSettings, service); + EntityUtils.deleteAll(version, serverSettings, BasicAuthTests.setAuth(service, "admin", "admin")); THINGS.clear(); OBSERVED_PROPS.clear(); } private static void createEntities() throws ServiceFailureException, URISyntaxException { + BasicAuthTests.setAuth(service, "write", "write"); for (int i = 0; i < 6; i++) { Map properties = new HashMap<>(); properties.put("int", i + 8); @@ -109,6 +125,52 @@ private static void createEntities() throws ServiceFailureException, URISyntaxEx ID_TYPES.put(EntityType.OBSERVED_PROPERTY, IdType.findFor(OBSERVED_PROPS.get(0).getId().getValue())); } + private String makeBatchBody() { + return "--batch_36522ad7-fc75-4b56-8c71-56071383e77b\r\n" + + "Content-Type: application/http\r\n" + + "\r\n" + + "GET /" + version.urlPart + "/Things(" + THINGS.get(0).getId().getUrl() + + ")?$select=name HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n" + + "\r\n" + + "--batch_36522ad7-fc75-4b56-8c71-56071383e77b\r\n" + + "Content-Type: multipart/mixed;boundary=changeset_77162fcd-b8da-41ac-a9f8-9357efbbd\r\n" + + "\r\n" + + "--changeset_77162fcd-b8da-41ac-a9f8-9357efbbd\r\n" + + "Content-Type: application/http\r\n" + + "Content-ID: 1\r\n" + + "\r\n" + + "POST /" + version.urlPart + "/Things HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-Type: application/json\r\n" + + "Content-Length: 36\r\n" + + "\r\n" + + "{\"name\":\"New\",\"description\":\"Thing\"}\r\n" + + "--changeset_77162fcd-b8da-41ac-a9f8-9357efbbd\r\n" + + "Content-Type: application/http\r\n" + + "Content-ID: 2\r\n" + + "\r\n" + + "PATCH /" + version.urlPart + "/Things(" + THINGS.get(0).getId().getUrl() + + ") HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-Type: application/json\r\n" + + "Content-Length: 18\r\n" + + "\r\n" + + "{\"name\":\"Patched\"}\r\n" + + "--changeset_77162fcd-b8da-41ac-a9f8-9357efbbd--\r\n" + + "--batch_36522ad7-fc75-4b56-8c71-56071383e77b\r\n" + + "Content-Type: application/http\r\n" + + "\r\n" + + "GET /" + version.urlPart + "/Things(" + + Utils.quoteIdForUrl(ID_TYPES.get(EntityType.THING).generateUnlikely()) + + ") HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n" + + "\r\n" + + "--batch_36522ad7-fc75-4b56-8c71-56071383e77b--"; + } + /** * Test batch request body example from "OGC SensorThings API Part 1, * Sensing Version 1.1, 11.2.1. Batch request body example" except changes @@ -118,45 +180,8 @@ private static void createEntities() throws ServiceFailureException, URISyntaxEx void test01BatchRequest() { LOGGER.info(" test01BatchRequest"); String response = postBatch("batch_36522ad7-fc75-4b56-8c71-56071383e77b", - "--batch_36522ad7-fc75-4b56-8c71-56071383e77b\r\n" - + "Content-Type: application/http\r\n" - + "\r\n" - + "GET /" + version.urlPart + "/Things(" + THINGS.get(0).getId().getUrl() + ")?$select=name HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "\r\n" - + "\r\n" - + "--batch_36522ad7-fc75-4b56-8c71-56071383e77b\r\n" - + "Content-Type: multipart/mixed;boundary=changeset_77162fcd-b8da-41ac-a9f8-9357efbbd\r\n" - + "\r\n" - + "--changeset_77162fcd-b8da-41ac-a9f8-9357efbbd\r\n" - + "Content-Type: application/http\r\n" - + "Content-ID: 1\r\n" - + "\r\n" - + "POST /" + version.urlPart + "/Things HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "Content-Type: application/json\r\n" - + "Content-Length: 36\r\n" - + "\r\n" - + "{\"name\":\"New\",\"description\":\"Thing\"}\r\n" - + "--changeset_77162fcd-b8da-41ac-a9f8-9357efbbd\r\n" - + "Content-Type: application/http\r\n" - + "Content-ID: 2\r\n" - + "\r\n" - + "PATCH /" + version.urlPart + "/Things(" + THINGS.get(0).getId().getUrl() + ") HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "Content-Type: application/json\r\n" - + "Content-Length: 18\r\n" - + "\r\n" - + "{\"name\":\"Patched\"}\r\n" - + "--changeset_77162fcd-b8da-41ac-a9f8-9357efbbd--\r\n" - + "--batch_36522ad7-fc75-4b56-8c71-56071383e77b\r\n" - + "Content-Type: application/http\r\n" - + "\r\n" - + "GET /" + version.urlPart + "/Things(" + Utils.quoteIdForUrl(ID_TYPES.get(EntityType.THING).generateUnlikely()) + ") HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "\r\n" - + "\r\n" - + "--batch_36522ad7-fc75-4b56-8c71-56071383e77b--"); + makeBatchBody(), + "write"); String thingId = getLastestEntityIdForPath(EntityType.THING); String batchBoundary = response.split("\n", 2)[0]; int mixedBoundaryStart = response.indexOf("boundary=") + 9; @@ -254,7 +279,8 @@ void test02BatchRequestWithChangeSetReferencingNewEntities() { + "\r\n" + post2 + "\r\n" + "--changeset_77162fcd-b8da-41ac-a9f8-9357efbbd--\r\n" - + "--batch_36522ad7-fc75-4b56-8c71-56071383e77b--"); + + "--batch_36522ad7-fc75-4b56-8c71-56071383e77b--", + "write"); String sensorId = getLastestEntityIdForPath(EntityType.SENSOR); String datastreamId = getLastestEntityIdForPath(EntityType.DATASTREAM); @@ -293,7 +319,8 @@ void test03BatchRequestWithEncodedCharsInUrl() { + "GET Things?$filter=properties/int%20eq%2010&$select=name HTTP/1.1\r\n" + "\r\n" + "\r\n" - + "--batch_test--"); + + "--batch_test--", + "write"); String batchBoundary = response.split("\n", 2)[0]; assertEquals(batchBoundary + "\n" + "Content-Type: application/http\n" @@ -333,7 +360,8 @@ void test04BatchRequestWithAbsoluteUri() { + "/Things?$filter=properties/int%20eq%2011&$select=name HTTP/1.1\r\n" + "\r\n" + "\r\n" - + "--batch_test--"); + + "--batch_test--", + "write"); String batchBoundary = response.split("\n", 2)[0]; assertEquals(batchBoundary + "\n" + "Content-Type: application/http\n" @@ -377,7 +405,8 @@ void test05BatchRequestWithResourcePathRelativeToBatchRequest() { + "GET Things?$filter=properties/int%20eq%2011&$select=name HTTP/1.1\r\n" + "\r\n" + "\r\n" - + "--batch_test--"); + + "--batch_test--", + "write"); String batchBoundary = response.split("\n", 2)[0]; assertEquals(batchBoundary + "\n" + "Content-Type: application/http\n" @@ -421,7 +450,8 @@ void test06JsonBatchRequest() { + "\"id\": \"3\"," + "\"method\": \"get\"," + "\"url\": \"Things(null)\"" - + "}]}"); + + "}]}", + "write"); String thingId = getLastestEntityIdForPath(EntityType.THING); try { @@ -478,7 +508,8 @@ void test07JsonBatchRequestWithChangeSetReferencingNewEntities() { + "\"url\": \"Things(" + THINGS.get(0).getId().getUrl() + ")/Datastreams\"," + "\"body\":" + post2 - + "}]}"); + + "}]}", + "write"); String sensorId = getLastestEntityIdForPath(EntityType.SENSOR); String datastreamId = getLastestEntityIdForPath(EntityType.DATASTREAM); @@ -497,13 +528,28 @@ void test07JsonBatchRequestWithChangeSetReferencingNewEntities() { } - private String postBatch(String boundary, String body) { + @Test + void test08BatchRequestFailedAuthorization() { + LOGGER.info(" test08BatchRequestFailedAuthorization"); + String response = postBatch("batch_36522ad7-fc75-4b56-8c71-56071383e77b", + makeBatchBody(), + "read", + 401); + assertEquals("", response); + } + + private String postBatch(String boundary, String body, String user) { + return postBatch(boundary, body, user, 200); + } + + private String postBatch(String boundary, String body, String user, int responseCode) { String urlString = serverSettings.getServiceUrl(version) + "/$batch"; try { - HttpResponse httpResponse = HTTPMethods.doPost(urlString, body, - boundary == null ? "application/json" : "multipart/mixed;boundary=" + boundary); - assertEquals(200, httpResponse.code, "Batch response should be 200"); - return httpResponse.response; + HttpResponse httpResponse = HTTPMethods.doPost(urlString, body, + boundary == null ? "application/json" : "multipart/mixed;boundary=" + boundary, + basicAuth(user)); + assertEquals(responseCode, httpResponse.code, "Batch response should be " + responseCode); + return httpResponse.response; } catch (JSONException e) { LOGGER.error("Exception: ", e); fail("An Exception occurred during testing: " + e.getMessage()); @@ -511,9 +557,17 @@ private String postBatch(String boundary, String body) { } } + /** + * @param user + * @return Authorization header with password identical to user + */ + private String basicAuth(String user) { + return "Basic " + Base64.getEncoder().encodeToString((user + ':' + user).getBytes()); + } + private String getLastestEntityIdForPath(EntityType entityType) { EntityHelper entityHelper = new EntityHelper(version, serverSettings); - Object id = entityHelper.getAnyEntity(entityType, "$orderBy=id%20desc", 1).get("@iot.id"); + Object id = entityHelper.getAnyEntity(entityType, "$orderBy=id%20desc", 1, basicAuth("read")).get("@iot.id"); if (id instanceof Number) { return id.toString(); } else { diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/EntityHelper.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/EntityHelper.java index bdf62c1df..74ec2b69f 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/EntityHelper.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/EntityHelper.java @@ -465,14 +465,30 @@ public Object getLastestEntityId(EntityType entityType) { * @return The first entity found, or null after all retries. */ public JSONObject getAnyEntity(EntityType entityType, String queryOptions, int retries) { + return getAnyEntity(entityType, queryOptions, retries, null); + } + + /** + * Tries to fetch the given entity type, with the given query options, + * retrying a maximum of retries times, waiting MqttHelper.WAIT_AFTER_INSERT + * milliseconds between retries. + * + * @param entityType The entity type to fetch the first entity of. + * @param queryOptions The query options to use while fetching. + * @param retries The maximum number of retries. + * @param authorization When not null, the authorization header value + * @return The first entity found, or null after all retries. + */ + public JSONObject getAnyEntity(EntityType entityType, String queryOptions, int retries, String authorization) { String urlString = ServiceUrlHelper.buildURLString(rootUri, entityType, null, null, null) + "?$top=1"; if (queryOptions != null && !queryOptions.isEmpty()) { urlString += "&" + queryOptions; } + String json = null; try { int retry = 0; while (retry < retries) { - String json = HTTPMethods.doGet(urlString).response; + json = HTTPMethods.doGet(urlString, authorization).response; JSONArray items = new JSONObject(json).getJSONArray("value"); if (!items.isEmpty()) { return items.getJSONObject(0); @@ -485,7 +501,7 @@ public JSONObject getAnyEntity(EntityType entityType, String queryOptions, int r LOGGER.error("Failed to read an entity from url after {} tries: {}", retries, urlString); return null; } catch (JSONException e) { - LOGGER.error("Failed while reading from url {}", urlString); + LOGGER.error("Failed while reading from url {}: {}", urlString, json); LOGGER.error("Exception:", e); fail("An Exception occurred during testing!: " + e.getMessage()); return null; diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/HTTPMethods.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/HTTPMethods.java index 71d78fbd6..ba4211489 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/HTTPMethods.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/HTTPMethods.java @@ -87,12 +87,29 @@ public static void logStats() { * be empty. */ public static HttpResponse doGet(String urlString) { + return doGet(urlString, null); + } + + /** + * Send HTTP GET request to the urlString and return response code and + * response body + * + * @param urlString The URL that the GET request should be sent to + * @param authorization When not null, the Authorization header value + * @return response-code and response(response body) of the HTTP GET in the + * MAP format. If the response is not 200, the response(response body) will + * be empty. + */ + public static HttpResponse doGet(String urlString, String authorization) { HttpResponse result = null; LOGGER.debug("Getting: {}", urlString); countGet++; try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpGet request = new HttpGet(urlString); + if (authorization != null){ + request.setHeader("Authorization", authorization); + } try (CloseableHttpResponse response = httpClient.execute(request)) { result = new HttpResponse(response.getStatusLine().getStatusCode()); if (result.code == 200) { @@ -147,6 +164,23 @@ public static HttpResponse doPost(String urlString, String postBody) { * If response is 200, it will be the HTTP response body String. */ public static HttpResponse doPost(String urlString, String postBody, String contentType) { + return doPost(urlString, postBody, contentType, null); + } + + /** + * Send HTTP POST request to the urlString with postBody and return response + * code and response body + * + * @param urlString The URL that the POST request should be sent to + * @param postBody The body of the POST request + * @param contentType The POST request content type header value + * @param authorization When not null, the Authorization header value + * @return response-code and response of the HTTP POST in the MAP format. If + * the response is 201, the response will contain the self-link to the + * created entity from location header if present, or the response string. + * If response is 200, it will be the HTTP response body String. + */ + public static HttpResponse doPost(String urlString, String postBody, String contentType, String authorization) { HttpURLConnection connection = null; try { LOGGER.debug("Posting: {}", urlString); @@ -159,6 +193,9 @@ public static HttpResponse doPost(String urlString, String postBody, String cont connection.setDoOutput(true); connection.setInstanceFollowRedirects(false); connection.setRequestMethod("POST"); + if (authorization != null){ + connection.setRequestProperty("Authorization", authorization); + } connection.setRequestProperty("Content-Type", contentType); connection.setRequestProperty("charset", "utf-8"); connection.setRequestProperty("Content-Length", Integer.toString(postDataLength));