diff --git a/build.gradle.kts b/build.gradle.kts index 50e1166762..c850b30da5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -135,7 +135,7 @@ subprojects { allprojects { group = "org.stellar.anchor-sdk" - version = "2.1.2" + version = "2.1.3" tasks.jar { manifest { diff --git a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java index 03c6813b01..9edf5efcd3 100644 --- a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java +++ b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java @@ -1,6 +1,5 @@ package org.stellar.anchor.sep1; -import static org.stellar.anchor.util.Log.debug; import static org.stellar.anchor.util.Log.debugF; import static org.stellar.anchor.util.MetricConstants.SEP1_TOML_ACCESSED; @@ -27,10 +26,9 @@ public class Sep1Service { */ public Sep1Service(Sep1Config sep1Config) throws IOException, InvalidConfigException { if (sep1Config.isEnabled()) { - debug("sep1Config:", sep1Config); switch (sep1Config.getType()) { case STRING: - debug("reading stellar.toml from config[sep1.toml.value]"); + debugF("reading stellar.toml from config[sep1.toml.value]"); tomlValue = sep1Config.getValue(); break; case FILE: diff --git a/core/src/main/java/org/stellar/anchor/util/NetUtil.java b/core/src/main/java/org/stellar/anchor/util/NetUtil.java index 35e2e1ee06..e8c3545146 100644 --- a/core/src/main/java/org/stellar/anchor/util/NetUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/NetUtil.java @@ -7,18 +7,39 @@ import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; -import java.util.Objects; import okhttp3.Call; import okhttp3.Request; import okhttp3.Response; public class NetUtil { + + /** + * Fetches the content from the specified URL using an HTTP GET request. + * + *
This method expects a response body to be present in the HTTP response. If the response is
+ * unsuccessful (i.e., not a 2xx status code) or if the response body is null, an IOException will
+ * be thrown.
+ *
+ * @param url The URL to fetch content from.
+ * @return The content of the response body as a string.
+ * @throws IOException If the response is unsuccessful, or if the response body is null.
+ */
public static String fetch(String url) throws IOException {
+
Request request = OkHttpUtil.buildGetRequest(url);
Response response = getCall(request).execute();
- if (response.body() == null) return "";
- return Objects.requireNonNull(response.body()).string();
+ // Check if response was unsuccessful (ie not status code 2xx) and throw IOException
+ if (!response.isSuccessful()) {
+ throw new IOException(response.toString());
+ }
+
+ // Since fetch expects a response body, we will throw IOException if its null
+ if (response.body() == null) {
+ throw new IOException(response.toString());
+ }
+
+ return response.body().string();
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
diff --git a/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java b/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java
index 517a0864f3..9aca02d869 100644
--- a/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java
+++ b/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java
@@ -1,26 +1,39 @@
package org.stellar.anchor.util;
+import static org.stellar.anchor.util.Log.*;
+
import com.moandjiezana.toml.Toml;
import java.io.IOException;
-import java.net.URL;
+import org.stellar.anchor.api.exception.InvalidConfigException;
public class Sep1Helper {
public static TomlContent readToml(String url) throws IOException {
- return new TomlContent(new URL(url));
+ try {
+ String tomlValue = NetUtil.fetch(url);
+ return new TomlContent(tomlValue);
+ } catch (IOException e) {
+ String obfuscatedMessage =
+ String.format("An error occurred while fetching the TOML from %s", url);
+ Log.error(e.toString());
+ throw new IOException(obfuscatedMessage); // Preserve the original exception as the cause
+ }
}
- public static TomlContent parse(String tomlString) {
- return new TomlContent(tomlString);
+ public static TomlContent parse(String tomlString) throws InvalidConfigException {
+ try {
+ return new TomlContent(tomlString);
+ } catch (Exception e) {
+ // Obfuscate the message and rethrow
+ String obfuscatedMessage = "Failed to parse TOML content. Invalid Config.";
+ Log.error(e.toString()); // Log the parsing exception
+ throw new InvalidConfigException(
+ obfuscatedMessage); // Preserve the original exception as the cause
+ }
}
public static class TomlContent {
private final Toml toml;
- TomlContent(URL url) throws IOException {
- String tomlValue = NetUtil.fetch(url.toString());
- toml = new Toml().read(tomlValue);
- }
-
TomlContent(String tomlString) {
toml = new Toml().read(tomlString);
}
diff --git a/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt
index 593d890aa7..fe8fd63808 100644
--- a/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt
+++ b/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt
@@ -4,11 +4,13 @@ package org.stellar.anchor.util
import io.mockk.*
import io.mockk.impl.annotations.MockK
+import java.io.IOException
import java.net.MalformedURLException
import okhttp3.Response
import okhttp3.ResponseBody
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
@@ -36,15 +38,17 @@ internal class NetUtilTest {
}
@Test
- fun `test fetch()`() {
+ fun `test fetch successful response`() {
mockkStatic(NetUtil::class)
every { NetUtil.getCall(any()) } returns mockCall
every { mockCall.execute() } returns mockResponse
+ every { mockResponse.isSuccessful } returns true
every { mockResponse.body } returns mockResponseBody
every { mockResponseBody.string() } returns "result"
val result = NetUtil.fetch("http://hello")
- assert(result.equals("result"))
+ assertEquals("result", result)
+
verify {
NetUtil.getCall(any())
mockCall.execute()
@@ -52,15 +56,24 @@ internal class NetUtilTest {
}
@Test
- fun `test fetch() throws exception`() {
+ fun `test fetch unsuccessful response`() {
+ mockkStatic(NetUtil::class)
+ every { NetUtil.getCall(any()) } returns mockCall
+ every { mockCall.execute() } returns mockResponse
+ every { mockResponse.isSuccessful } returns false
+
+ assertThrows(IOException::class.java) { NetUtil.fetch("http://hello") }
+ }
+
+ @Test
+ fun `test fetch null response body`() {
mockkStatic(NetUtil::class)
every { NetUtil.getCall(any()) } returns mockCall
every { mockCall.execute() } returns mockResponse
+ every { mockResponse.isSuccessful } returns true
every { mockResponse.body } returns null
- every { mockResponseBody.string() } returns "result"
- val result = NetUtil.fetch("http://hello")
- assert(result.equals(""))
+ assertThrows(IOException::class.java) { NetUtil.fetch("http://hello") }
}
@Test
diff --git a/core/src/test/kotlin/org/stellar/anchor/util/Sep1HelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/Sep1HelperTest.kt
new file mode 100644
index 0000000000..7ae179320c
--- /dev/null
+++ b/core/src/test/kotlin/org/stellar/anchor/util/Sep1HelperTest.kt
@@ -0,0 +1,75 @@
+package org.stellar.anchor.util
+
+import java.io.IOException
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.stellar.anchor.api.exception.InvalidConfigException
+
+class Sep1HelperTest {
+ val stellarToml =
+ """
+ ACCOUNTS = [ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE" ]
+ VERSION = "0.1.0"
+ SIGNING_KEY = "GBDYDBJKQBJK4GY4V7FAONSFF2IBJSKNTBYJ65F5KCGBY2BIGPGGLJOH"
+ NETWORK_PASSPHRASE = "Test SDF Network ; September 2015"
+
+ WEB_AUTH_ENDPOINT = "http://localhost:8080/auth"
+ KYC_SERVER = "http://localhost:8080/sep12"
+ TRANSFER_SERVER_SEP0024 = "http://localhost:8080/sep24"
+ DIRECT_PAYMENT_SERVER = "http://localhost:8080/sep31"
+ ANCHOR_QUOTE_SERVER = "http://localhost:8080/sep38"
+
+ [[CURRENCIES]]
+ code = "SRT"
+ issuer = "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B"
+ status = "test"
+ is_asset_anchored = false
+ anchor_asset_type = "other"
+ desc = "A fake anchored asset to use with this example anchor server."
+
+ [DOCUMENTATION]
+ ORG_NAME = "Stellar Development Foundation"
+ ORG_URL = "https://stellar.org"
+ ORG_DESCRIPTION = "SEP 24 reference server."
+ ORG_KEYBASE = "stellar.public"
+ ORG_TWITTER = "StellarOrg"
+ ORG_GITHUB = "stellar"
+ """
+ .trimIndent()
+
+ private val mockWebServer = MockWebServer()
+
+ @BeforeEach
+ fun setup() {
+ mockWebServer.start()
+ }
+
+ @AfterEach
+ fun teardown() {
+ mockWebServer.shutdown()
+ }
+
+ @Test
+ fun `test Read Toml with IOException`() {
+ // Enqueue a response with an HTTP error status code
+ mockWebServer.enqueue(MockResponse().setResponseCode(500))
+ val exception =
+ assertThrows(IOException::class.java) {
+ Sep1Helper.readToml(mockWebServer.url("/").toString())
+ }
+ // You may need to adjust the assertion based on the specific behavior of Sep1Helper
+ assertTrue(exception.message!!.contains("An error occurred while fetching the TOML from"))
+ }
+
+ @Test
+ fun `test Parse Invalid Toml String`() {
+ val invalidTomlString = "key = value" // An invalid TOML string without quotes
+ val exception =
+ assertThrows(InvalidConfigException::class.java) { Sep1Helper.parse(invalidTomlString) }
+ assertEquals("Failed to parse TOML content. Invalid Config.", exception.message)
+ }
+}
diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java
index 3d40b342bb..70381da1d7 100644
--- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java
+++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java
@@ -6,6 +6,7 @@
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
+import org.stellar.anchor.api.exception.InvalidConfigException;
import org.stellar.anchor.config.Sep1Config;
import org.stellar.anchor.util.NetUtil;
import org.stellar.anchor.util.Sep1Helper;
@@ -59,12 +60,11 @@ void validateTomlTypeAndValue(PropertySep1Config config, Errors errors) {
case STRING:
try {
Sep1Helper.parse(config.getToml().getValue());
- } catch (IllegalStateException isex) {
+ } catch (InvalidConfigException e) {
errors.rejectValue(
"value",
"sep1-toml-value-string-invalid",
- String.format(
- "sep1.toml.value does not contain a valid TOML. %s", isex.getMessage()));
+ String.format("sep1.toml.value does not contain a valid TOML. %s", e.getMessage()));
}
break;
case URL:
diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/sep/Sep1Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/sep/Sep1Controller.java
index 7351d2b430..fb20aa5695 100644
--- a/platform/src/main/java/org/stellar/anchor/platform/controller/sep/Sep1Controller.java
+++ b/platform/src/main/java/org/stellar/anchor/platform/controller/sep/Sep1Controller.java
@@ -1,5 +1,6 @@
package org.stellar.anchor.platform.controller.sep;
+import java.io.IOException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -40,7 +41,7 @@ public RedirectView landingPage() throws SepNotFoundException {
@RequestMapping(
value = "/.well-known/stellar.toml",
method = {RequestMethod.GET, RequestMethod.OPTIONS})
- public ResponseEntity