Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add batch authorization test #1326

Open
wants to merge 1 commit into
base: v2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -58,9 +62,20 @@ public Implementation11() {
private static final List<ObservedProperty> OBSERVED_PROPS = new ArrayList<>();
private static final Map<EntityType, IdType> 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();
}

Expand All @@ -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<String, Object> properties = new HashMap<>();
properties.put("int", i + 8);
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -421,7 +450,8 @@ void test06JsonBatchRequest() {
+ "\"id\": \"3\","
+ "\"method\": \"get\","
+ "\"url\": \"Things(null)\""
+ "}]}");
+ "}]}",
"write");
String thingId = getLastestEntityIdForPath(EntityType.THING);

try {
Expand Down Expand Up @@ -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);

Expand All @@ -497,23 +528,46 @@ 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());
return null;
}
}

/**
* @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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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));
Expand Down