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

backmerge main to develop #1054

Closed
wants to merge 2 commits into from
Closed
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ subprojects {

allprojects {
group = "org.stellar.anchor-sdk"
version = "2.1.2"
version = "2.1.3"

tasks.jar {
manifest {
Expand Down
4 changes: 1 addition & 3 deletions core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java
Original file line number Diff line number Diff line change
@@ -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 java.io.IOException;
Expand All @@ -23,10 +22,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:
Expand Down
32 changes: 29 additions & 3 deletions core/src/main/java/org/stellar/anchor/util/NetUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,44 @@
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.
*
* <p>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(
"Unsuccessful response code: " + response.code() + ", message: " + response.message());
}

// Since fetch expects a response body, we will throw IOException if its null
if (response.body() == null) {
throw new IOException(
"Null response body. Response code: "
+ response.code()
+ ", message: "
+ response.message());
}

return response.body().string();
}

@SuppressWarnings("BooleanMethodIsAlwaysInverted")
Expand Down
31 changes: 22 additions & 9 deletions core/src/main/java/org/stellar/anchor/util/Sep1Helper.java
Original file line number Diff line number Diff line change
@@ -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);
}
Expand Down
25 changes: 19 additions & 6 deletions core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,31 +38,42 @@ 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()
}
}

@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
Expand Down
2 changes: 1 addition & 1 deletion docs/00 - Stellar Anchor Platform.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![License](https://badgen.net/badge/license/Apache%202/blue?icon=github&label=License)](https://github.com/stellar/java-stellar-anchor-sdk/blob/develop/LICENSE)
[![GitHub Version](https://badgen.net/github/release/stellar/java-stellar-anchor-sdk?icon=github&label=Latest%20release)](https://github.com/stellar/java-stellar-anchor-sdk/releases)
[![Docker](https://badgen.net/badge/Latest%20Release/v2.1.2/blue?icon=docker)](https://hub.docker.com/r/stellar/anchor-platform/tags?page=1&name=release-2.1.2)
[![Docker](https://badgen.net/badge/Latest%20Release/v2.1.2/blue?icon=docker)](https://hub.docker.com/r/stellar/anchor-platform/tags?page=1&name=release-2.1.3)
![Develop Branch](https://github.com/stellar/java-stellar-anchor-sdk/actions/workflows/wk_push_to_develop.yml/badge.svg?branch=develop)

<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -40,7 +41,7 @@ public RedirectView landingPage() throws SepNotFoundException {
@RequestMapping(
value = "/.well-known/stellar.toml",
method = {RequestMethod.GET, RequestMethod.OPTIONS})
public ResponseEntity<String> getToml() throws SepNotFoundException {
public ResponseEntity<String> getToml() throws IOException, SepNotFoundException {
if (!sep1Config.isEnabled()) {
throw new SepNotFoundException("Not Found");
}
Expand Down
59 changes: 55 additions & 4 deletions platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package org.stellar.anchor

import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.stellar.anchor.config.Sep1Config.TomlType.*
import org.stellar.anchor.platform.config.PropertySep1Config
Expand All @@ -13,8 +16,9 @@ import org.stellar.anchor.platform.config.Sep1ConfigTest
import org.stellar.anchor.sep1.Sep1Service

class Sep1ServiceTest {

lateinit var sep1: Sep1Service
val stellarToml =
private val stellarToml =
"""
ACCOUNTS = [ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE" ]
VERSION = "0.1.0"
Expand Down Expand Up @@ -47,7 +51,6 @@ class Sep1ServiceTest {

@Test
fun `test Sep1Service reading toml from inline string`() {

val config = PropertySep1Config(true, TomlConfig(STRING, stellarToml))
sep1 = Sep1Service(config)
assertEquals(sep1.stellarToml, stellarToml)
Expand All @@ -61,14 +64,62 @@ class Sep1ServiceTest {
}

@Test
fun `test Sep1Service reading toml from url`() {
fun `getStellarToml fetches data during re-direct`() {
val mockServer = MockWebServer()
mockServer.start()
val mockAnchorUrl = mockServer.url("").toString()
mockServer.enqueue(MockResponse().setBody(stellarToml))

val config = PropertySep1Config(true, TomlConfig(URL, mockAnchorUrl))
sep1 = Sep1Service(config)
assertEquals(sep1.stellarToml, stellarToml)
}

// this test is not expected to raise an exception. given the re-direct to a malicious
// endpoint still returns a 200 the exception will be raised/obfuscated
// when the toml is parsed.
@Test
fun `getStellarTomlthrows exception when redirect is encountered`() {
val mockServer = MockWebServer()
mockServer.start()
val mockAnchorUrl = mockServer.url("").toString()
val metadata =
"{\n" +
" \"ami-id\": \"ami-12345678\",\n" +
" \"instance-id\": \"i-1234567890abcdef\",\n" +
" \"instance-type\": \"t2.micro\"\n" +
" // ... other metadata ...\n" +
"}"

// Enqueue a response with a 302 status and a Location header to simulate a redirect.
mockServer.enqueue(
MockResponse()
.setResponseCode(302)
.setHeader("Location", mockServer.url("/new_location").toString())
)

// Enqueue a response at the redirect location that simulates AWS metadata leak.
mockServer.enqueue(MockResponse().setResponseCode(200).setBody(metadata))

val config = PropertySep1Config(true, TomlConfig(URL, mockAnchorUrl))
val sep1 = Sep1Service(config)
assertEquals(sep1.getStellarToml(), metadata)
mockServer.shutdown()
}

@Test
fun `getStellarToml throws exception when redirected location results in error`() {
val mockServer = MockWebServer()
mockServer.start()
val mockAnchorUrl = mockServer.url("/new_location").toString()

// Enqueue a response that provides a server error.
mockServer.enqueue(MockResponse().setResponseCode(500))

val config = PropertySep1Config(true, TomlConfig(URL, mockAnchorUrl))
val exception = assertThrows(IOException::class.java) { sep1 = Sep1Service(config) }
assertTrue(
exception.message?.contains("Unsuccessful response code: 500, message: Server Error") == true
)
mockServer.shutdown()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ class LogAppenderTest {
"debug" -> Log.debug(wantMessage)
"trace" -> Log.trace(wantMessage)
}

// Verify that the append method was called, which ensures that the event was captured
verify { appender.append(any()) }

assertEquals(LogAppenderTest::class.qualifiedName, capturedLogEvent.captured.loggerName)
assertEquals(wantLevelName, capturedLogEvent.captured.level.toString())
assertEquals(wantMessage, capturedLogEvent.captured.message.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ class TestProfileExecutor(val config: TestConfig) {
val envMap = config.env

envMap["assets.value"] = getResourceFile(envMap["assets.value"]!!).absolutePath
envMap["sep1.toml.value"] = getResourceFile(envMap["sep1.toml.value"]!!).absolutePath

if (envMap["sep1.toml.type"] != "url") {
envMap["sep1.toml.value"] = getResourceFile(envMap["sep1.toml.value"]!!).absolutePath
}
// Start servers
val jobs = mutableListOf<Job>()
val scope = CoroutineScope(Dispatchers.Default)
Expand Down
Loading