Skip to content
Merged
11 changes: 1 addition & 10 deletions core/src/main/scala/kafka/utils/Json.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,7 @@ object Json {
*/
def parseFull(input: String): Option[JsonValue] =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm probably missing something obvious, but I found the following callers of this function: DeleteRecordsCommand, LeaderElectionCommand, PreferredReplicaLeaderElectionCommand, and ReassignPartitionsCommand. As far as I can tell, none of these uses involve the parsing of ACLs. Did we lose this compatibility handling at some point or is there some magic invocation?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compatibility fix was only required for ACLs, but we added it here to keep the fix small for backporting. The idea was to move to the appropriate place soon after, but it got stuck for a while. Do you have some concerns?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed this offline with @hachikuji. I had misunderstood his point. It looks like the compatibility code has not been used since 1.1.0:

9b44c3e#diff-81f2ec590f4e047be7e3a7e5267cbc9aR60

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should just drop this code.

@viktorsomogyi viktorsomogyi May 5, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looks like parseFull is used in a couple of places as Json mentioned but I can perhaps just delegate this to parseBytes like this below and can continue using that in the mentioned callers.

def parseFull(input: String): Option[JsonValue] = parseBytes(input.getBytes(Charset.defaultCharset()))

Or shall I just get rid of this code and use parseBytes everywhere?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is fine as it is. We are suggesting that we don't need parseBytesWithAclFallback in AclEntry.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then I misunderstood it too. :)
However isn't it possible that this issue resurfaces again if we remove this? I suspect the user who raised it may still use 1.0 where it got fixed and when they upgrade they'd run into this again. Or am I missing something else too?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@viktorsomogyi I think the argument is that if no-one complained since 1.1 which was released more than 2 years ago, then maybe this issue is very rare.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that's fair. I've uploaded the changes.

try Option(mapper.readTree(input)).map(JsonValue(_))
catch {
case _: JsonProcessingException =>
// Before 1.0.1, Json#encode did not escape backslash or any other special characters. SSL principals
// stored in ACLs may contain backslash as an escape char, making the JSON generated in earlier versions invalid.
// Escape backslash and retry to handle these strings which may have been persisted in ZK.
// Note that this does not handle all special characters (e.g. non-escaped double quotes are not supported)
val escapedInput = input.replaceAll("\\\\", "\\\\\\\\")
try Option(mapper.readTree(escapedInput)).map(JsonValue(_))
catch { case _: JsonProcessingException => None }
}
catch { case _: JsonProcessingException => None }

/**
* Parse a JSON string into either a generic type T, or a JsonProcessingException in the case of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@ import org.apache.kafka.common.acl.AclOperation.READ
import org.apache.kafka.common.acl.AclPermissionType.{ALLOW, DENY}
import org.apache.kafka.common.security.auth.KafkaPrincipal
import org.junit.{Assert, Test}
import org.scalatestplus.junit.JUnitSuite

import scala.jdk.CollectionConverters._

class AclEntryTest extends JUnitSuite {
class AclEntryTest {

val AclJson = "{\"version\": 1, \"acls\": [{\"host\": \"host1\",\"permissionType\": \"Deny\",\"operation\": \"READ\", \"principal\": \"User:alice\" }, " +
"{ \"host\": \"*\" , \"permissionType\": \"Allow\", \"operation\": \"Read\", \"principal\": \"User:bob\" }, " +
"{ \"host\": \"host1\", \"permissionType\": \"Deny\", \"operation\": \"Read\" , \"principal\": \"User:bob\"} ]}"
val AclJson = """{"version": 1, "acls": [{"host": "host1","permissionType": "Deny","operation": "READ", "principal": "User:alice" },
{ "host": "*" , "permissionType": "Allow", "operation": "Read", "principal": "User:bob" },
{ "host": "host1", "permissionType": "Deny", "operation": "Read" , "principal": "User:bob"}]}"""

@Test
def testAclJsonConversion(): Unit = {
Expand All @@ -40,10 +39,8 @@ class AclEntryTest extends JUnitSuite {
val acl3 = AclEntry(new KafkaPrincipal(KafkaPrincipal.USER_TYPE, "bob"), DENY, "host1", READ)

val acls = Set[AclEntry](acl1, acl2, acl3)
val jsonAcls = Json.encodeAsBytes(AclEntry.toJsonCompatibleMap(acls).asJava)

Assert.assertEquals(acls, AclEntry.fromBytes(jsonAcls))
Assert.assertEquals(acls, AclEntry.fromBytes(Json.encodeAsBytes(AclEntry.toJsonCompatibleMap(acls).asJava)))
Assert.assertEquals(acls, AclEntry.fromBytes(AclJson.getBytes(UTF_8)))
}

}
4 changes: 0 additions & 4 deletions core/src/test/scala/unit/kafka/utils/JsonTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ class JsonTest {
val encoded = Json.legacyEncodeAsString(map)
val decoded = Json.parseFull(encoded)
assertEquals(Json.parseFull("""{"foo1":"bar1\\,bar2", "foo2":"\\bar"}"""), decoded)

// Test strings with non-escaped backslash and quotes. This is to verify that ACLs
// containing non-escaped chars persisted using 1.0 can be parsed.
assertEquals(decoded, Json.parseFull("""{"foo1":"bar1\,bar2", "foo2":"\bar"}"""))
}

@Test
Expand Down