Skip to content
Merged
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: 2 additions & 0 deletions sdk-core/api/sdk-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,14 @@ public final class org/dexpace/sdk/core/http/auth/KeyCredential : org/dexpace/sd
public final fun getApiKey ()Ljava/lang/String;
public final fun getHeaderName ()Lorg/dexpace/sdk/core/http/common/HttpHeaderName;
public final fun getPrefix ()Ljava/lang/String;
public fun toString ()Ljava/lang/String;
}

public final class org/dexpace/sdk/core/http/auth/NamedKeyCredential : org/dexpace/sdk/core/http/auth/Credential {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public final fun getKey ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
public fun toString ()Ljava/lang/String;
}

public final class org/dexpace/sdk/core/http/common/CommonMediaTypes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,12 @@ public class KeyCredential
init {
require(apiKey.isNotBlank()) { "apiKey must not be blank" }
}

/**
* Redacts the secret [apiKey]. Without this override any log line, exception message,
* or debugger that stringifies the credential would expose the key; this emits
* `apiKey=***` instead while keeping the non-secret [headerName] and [prefix] for
* diagnostics. Identity equality is unaffected.
*/
override fun toString(): String = "KeyCredential(apiKey=***, headerName=$headerName, prefix=$prefix)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,12 @@ public class NamedKeyCredential(public val name: String, public val key: String)
require(name.isNotBlank()) { "name must not be blank" }
require(key.isNotBlank()) { "key must not be blank" }
}

/**
* Redacts the secret [key]. Without this override any log line, exception message, or
* debugger that stringifies the credential would expose the key; this emits `key=***`
* instead while keeping the non-secret [name] for diagnostics. Identity equality is
* unaffected.
*/
override fun toString(): String = "NamedKeyCredential(name=$name, key=***)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import org.dexpace.sdk.core.http.common.HttpHeaderName
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue

class CredentialTest {
// ----------------- KeyCredential -----------------
Expand Down Expand Up @@ -54,6 +57,31 @@ class CredentialTest {
assertEquals("k", (cred as KeyCredential).apiKey)
}

@Test
fun `KeyCredential toString redacts the apiKey but keeps non-secret fields`() {
val cred = KeyCredential("super-secret-key", prefix = "SharedAccessKey")
val rendered = cred.toString()
assertFalse(rendered.contains("super-secret-key"), "toString must not contain the raw apiKey")
assertTrue(rendered.contains("apiKey=***"), "toString must redact the apiKey")
assertTrue(rendered.contains("SharedAccessKey"), "toString should keep the non-secret prefix for diagnostics")
assertTrue(
rendered.contains(HttpHeaderName.AUTHORIZATION.toString()),
"toString should keep the non-secret headerName for diagnostics",
)
}

@Test
fun `KeyCredential toString redacts the apiKey with default fields`() {
val cred = KeyCredential("another-secret")
val rendered = cred.toString()
assertFalse(rendered.contains("another-secret"), "toString must not contain the raw apiKey")
assertTrue(rendered.contains("apiKey=***"), "toString must redact the apiKey")
assertTrue(
rendered.contains(HttpHeaderName.AUTHORIZATION.toString()),
"toString should keep the default headerName for diagnostics",
)
}

// ----------------- NamedKeyCredential -----------------

@Test
Expand Down Expand Up @@ -92,4 +120,30 @@ class CredentialTest {
val cred: Credential = NamedKeyCredential("acct", "secret")
assertEquals("acct", (cred as NamedKeyCredential).name)
}

@Test
fun `NamedKeyCredential toString redacts the key but keeps the name`() {
val cred = NamedKeyCredential("acct-name", "super-secret-key")
val rendered = cred.toString()
assertFalse(rendered.contains("super-secret-key"), "toString must not contain the raw key")
assertTrue(rendered.contains("key=***"), "toString must redact the key")
assertTrue(rendered.contains("acct-name"), "toString should keep the non-secret name for diagnostics")
}

@Test
fun `NamedKeyCredential keeps identity equality - redaction is toString-only`() {
// These are reference types (not data classes): equality is identity-based and the
// redacting toString override must not change that. Same instance equals itself;
// distinct instances with the same fields are not equal.
val cred = NamedKeyCredential("acct", "secret")
assertEquals(cred, cred)
assertNotEquals<NamedKeyCredential>(cred, NamedKeyCredential("acct", "secret"))
}

@Test
fun `KeyCredential keeps identity equality - redaction is toString-only`() {
val cred = KeyCredential("secret-key")
assertEquals(cred, cred)
assertNotEquals<KeyCredential>(cred, KeyCredential("secret-key"))
}
}
Loading