Repository: ImFlog/schema-registry-plugin
Pull request #19 (2019-03-03 00:25:39)

Adding support for Basic Auth for schema registry
Changes are per
https://github.com/confluentinc/schema-registry/pull/907

gvishnutej: @ImFlog Please review this pull request, I can update the README too if the current changes are good.
ImFlog: Sorry for the late answer. I will take a look at this during the week. 
I've spotted some small issues and if you don't mind I would like to commit on your branch. Could you open the maintainer edit Plz ? There is a button on the github PR which allows that. 
gvishnutej: I have already selected the maintainer edit option, please feel free commit changes. Thanks
ImFlog: I successfully did all the change I wanted. Unfortunately, I can't find the doc about the way to launch a schema-registry with basic auth.
@gvishnutej, could you try and see if everything works as expected on a real use case ?
If it does, I'll merge this before doing some cleanup on the whole code base. Then I'll release a new version.

Thank you
gvishnutej: @ImFlog sure, I will validate and let you know. Thanks 
ImFlog: @gvishnutej I am merging this as I want to work a bit on code cleanup of this repository.
If you find any bug, please tell me and I'll fix it :)
Thanks for your help on this !

Patch:
diff --git a/README.md b/README.md
index b90cdcb..230e492 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,11 @@ A DSL is available to configure the task:
 ```groovy
 schemaRegistry {
     url = 'http://localhost:8081/'
+    credentials {
+        username = 'basicauthentication-username'
+        password = 'basicauthentication-password'
+    } //optional
+    
     download {
         subject('topic1-key', 'src/main/avro')
         subject('topic1-value', 'src/main/avro/values')
@@ -36,6 +41,10 @@ A DSL is available to specify what to test:
 ```groovy
 schemaRegistry {
     url = 'http://localhost:8081'
+    credentials {
+        username = 'basicauthentication-username'
+        password = 'basicauthentication-password'
+    } //optional
     compatibility {
         subject('mySubject', 'file/path.avsc')
         subject('otherSubject', 'other/path.avsc')
@@ -68,6 +77,10 @@ A DSL is available to specify what to register:
 ```groovy
 schemaRegistry {
     url = 'http://localhost:8081'
+    credentials {
+        username = 'basicauthentication-username'
+        password = 'basicauthentication-password'
+    } //optional
     register {
         subject('mySubject', 'file/path.avsc')
         subject('otherSubject', 'other/path.avsc')
diff --git a/build.gradle.kts b/build.gradle.kts
index 6d469b8..338153e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -24,13 +24,18 @@ val wiremockVersion = "2.18.0"
 
 repositories {
     jcenter()
+    mavenCentral()
     maven("http://packages.confluent.io/maven/")
 }
 
+tasks.withType<Test> {
+    useJUnitPlatform()
+}
+
 dependencies {
     compileOnly(gradleApi())
     implementation("org.jetbrains.kotlin", "kotlin-stdlib", kotlinVersion)
-    implementation("io.confluent", "kafka-avro-serializer", "3.2.1")
+    implementation("io.confluent", "kafka-avro-serializer", "5.0.0")
             .exclude("org.slf4j", "slf4j-log4j12")
     implementation("org.apache.avro", "avro", "1.8.2")
 
diff --git a/src/main/kotlin/com/github/imflog/schema/registry/RegistryClientWrapper.kt b/src/main/kotlin/com/github/imflog/schema/registry/RegistryClientWrapper.kt
index 83c8e53..7e318d3 100644
--- a/src/main/kotlin/com/github/imflog/schema/registry/RegistryClientWrapper.kt
+++ b/src/main/kotlin/com/github/imflog/schema/registry/RegistryClientWrapper.kt
@@ -2,6 +2,8 @@ package com.github.imflog.schema.registry
 
 import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient
 import io.confluent.kafka.schemaregistry.client.SchemaRegistryClient
+import io.confluent.kafka.schemaregistry.client.SchemaRegistryClientConfig
+
 
 /**
  * This is a singleton.
@@ -9,12 +11,24 @@ import io.confluent.kafka.schemaregistry.client.SchemaRegistryClient
  */
 object RegistryClientWrapper {
 
-    private var registryClient: SchemaRegistryClient? = null
+    private const val BASIC_AUTH_SOURCE: String = "USER_INFO"
+
+    fun client(url: String, auth: SchemaRegistryBasicAuthExtension): SchemaRegistryClient =
+            CachedSchemaRegistryClient(url, 100, getConfig(auth))
 
-    fun client(url: String): SchemaRegistryClient? {
-        if (registryClient == null) {
-            registryClient = CachedSchemaRegistryClient(url, 100)
+    /**
+     * Retrieves configuration from the plugin extension.
+     * Note that BASIC_AUTH_CREDENTIALS_SOURCE is not configurable as the plugin only supports
+     * a single schema registry URL, so there is no additional utility of the URL source.
+     */
+    private fun getConfig(auth: SchemaRegistryBasicAuthExtension): Map<String, String> {
+        return if (auth.username == null || auth.password == null) {
+            mapOf()
+        } else {
+            mapOf(
+                    SchemaRegistryClientConfig.BASIC_AUTH_CREDENTIALS_SOURCE to BASIC_AUTH_SOURCE,
+                    SchemaRegistryClientConfig.USER_INFO_CONFIG to auth.getBasicAuthCredentials()
+            )
         }
-        return registryClient
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/github/imflog/schema/registry/SchemaRegistryBasicAuthExtension.kt b/src/main/kotlin/com/github/imflog/schema/registry/SchemaRegistryBasicAuthExtension.kt
new file mode 100644
index 0000000..832c858
--- /dev/null
+++ b/src/main/kotlin/com/github/imflog/schema/registry/SchemaRegistryBasicAuthExtension.kt
@@ -0,0 +1,9 @@
+package com.github.imflog.schema.registry
+
+open class SchemaRegistryBasicAuthExtension {
+
+    var username: String? = null
+    var password: String? = null
+
+    fun getBasicAuthCredentials() = "${this.username}:${this.password}"
+}
diff --git a/src/main/kotlin/com/github/imflog/schema/registry/SchemaRegistryPlugin.kt b/src/main/kotlin/com/github/imflog/schema/registry/SchemaRegistryPlugin.kt
index d8e0029..71e5651 100644
--- a/src/main/kotlin/com/github/imflog/schema/registry/SchemaRegistryPlugin.kt
+++ b/src/main/kotlin/com/github/imflog/schema/registry/SchemaRegistryPlugin.kt
@@ -27,26 +27,33 @@ class SchemaRegistryPlugin : Plugin<Project> {
             val compatibilityExtension = extensions.create(
                     "compatibility",
                     CompatibilitySubjectExtension::class.java)
+            val authExtension = extensions.create(
+                    "credentials",
+                    SchemaRegistryBasicAuthExtension::class.java
+            )
 
             afterEvaluate {
                 tasks.create(
                         DOWNLOAD_SCHEMAS_TASK, DownloadTask::class.java).apply {
                     url = globalExtension.url
+                    basicAuth = authExtension
                     subjects = downloadExtension.subjects
                 }
 
                 tasks.create(
                         REGISTER_SCHEMAS_TASK, RegisterSchemasTask::class.java).apply {
                     url = globalExtension.url
+                    auth = authExtension
                     subjects = registerExtension.subjects
                 }
 
                 tasks.create(
                         TEST_SCHEMAS_TASK, CompatibilityTask::class.java).apply {
                     url = globalExtension.url
+                    auth = authExtension
                     subjects = compatibilityExtension.subjects
                 }
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTask.kt b/src/main/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTask.kt
index fe33899..172c44a 100644
--- a/src/main/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTask.kt
+++ b/src/main/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTask.kt
@@ -1,6 +1,7 @@
 package com.github.imflog.schema.registry.compatibility
 
 import com.github.imflog.schema.registry.RegistryClientWrapper
+import com.github.imflog.schema.registry.SchemaRegistryBasicAuthExtension
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleScriptException
 import org.gradle.api.tasks.Input
@@ -16,13 +17,17 @@ open class CompatibilityTask : DefaultTask() {
 
     @Input
     lateinit var url: String
+
+    @Input
+    lateinit var auth: SchemaRegistryBasicAuthExtension
+
     @Input
     lateinit var subjects: List<Triple<String, String, List<String>>>
 
     @TaskAction
     fun testCompatibility() {
         val errorCount = CompatibilityTaskAction(
-                RegistryClientWrapper.client(url)!!,
+                RegistryClientWrapper.client(url, auth),
                 subjects,
                 project.rootDir
         ).run()
@@ -30,4 +35,4 @@ open class CompatibilityTask : DefaultTask() {
             throw GradleScriptException("$errorCount schemas not compatible, see logs for details.", Throwable())
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/github/imflog/schema/registry/download/DownloadTask.kt b/src/main/kotlin/com/github/imflog/schema/registry/download/DownloadTask.kt
index 615ca5a..e9e70e9 100644
--- a/src/main/kotlin/com/github/imflog/schema/registry/download/DownloadTask.kt
+++ b/src/main/kotlin/com/github/imflog/schema/registry/download/DownloadTask.kt
@@ -1,6 +1,7 @@
 package com.github.imflog.schema.registry.download
 
 import com.github.imflog.schema.registry.RegistryClientWrapper
+import com.github.imflog.schema.registry.SchemaRegistryBasicAuthExtension
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleScriptException
 import org.gradle.api.tasks.Input
@@ -17,13 +18,16 @@ open class DownloadTask : DefaultTask() {
     @Input
     lateinit var subjects: List<Pair<String, String>>
 
+    @Input
+    lateinit var basicAuth: SchemaRegistryBasicAuthExtension
+
     @Input
     lateinit var url: String
 
     @TaskAction
     fun downloadSchemas() {
         val errorCount = DownloadTaskAction(
-                RegistryClientWrapper.client(url)!!,
+                RegistryClientWrapper.client(url, basicAuth),
                 subjects,
                 project.rootDir)
                 .run()
@@ -31,4 +35,4 @@ open class DownloadTask : DefaultTask() {
             throw GradleScriptException("$errorCount schemas not downloaded, see logs for details", Throwable())
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/github/imflog/schema/registry/register/RegisterTask.kt b/src/main/kotlin/com/github/imflog/schema/registry/register/RegisterTask.kt
index 3c931be..81a6e9e 100644
--- a/src/main/kotlin/com/github/imflog/schema/registry/register/RegisterTask.kt
+++ b/src/main/kotlin/com/github/imflog/schema/registry/register/RegisterTask.kt
@@ -1,6 +1,7 @@
 package com.github.imflog.schema.registry.register
 
 import com.github.imflog.schema.registry.RegistryClientWrapper
+import com.github.imflog.schema.registry.SchemaRegistryBasicAuthExtension
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleScriptException
 import org.gradle.api.tasks.Input
@@ -17,13 +18,16 @@ open class RegisterSchemasTask : DefaultTask() {
     @Input
     lateinit var url: String
 
+    @Input
+    lateinit var auth: SchemaRegistryBasicAuthExtension
+
     @Input
     lateinit var subjects: List<Triple<String, String, List<String>>>
 
     @TaskAction
     fun registerSchemas() {
         val errorCount = RegisterTaskAction(
-                RegistryClientWrapper.client(url)!!,
+                RegistryClientWrapper.client(url, auth),
                 subjects,
                 project.rootDir
         ).run()
@@ -33,4 +37,4 @@ open class RegisterSchemasTask : DefaultTask() {
     }
 
 
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/github/imflog/schema/registry/register/RegisterTaskAction.kt b/src/main/kotlin/com/github/imflog/schema/registry/register/RegisterTaskAction.kt
index 37890cf..794ac11 100644
--- a/src/main/kotlin/com/github/imflog/schema/registry/register/RegisterTaskAction.kt
+++ b/src/main/kotlin/com/github/imflog/schema/registry/register/RegisterTaskAction.kt
@@ -3,7 +3,6 @@ package com.github.imflog.schema.registry.register
 import io.confluent.kafka.schemaregistry.client.SchemaRegistryClient
 import org.apache.avro.Schema
 import org.gradle.api.logging.Logging
-import sun.misc.ExtensionDependency
 import java.io.File
 
 class RegisterTaskAction(


Test patch:
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/SchemaRegistryPluginTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/SchemaRegistryPluginTest.kt
index 9db712f..2b20c22 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/SchemaRegistryPluginTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/SchemaRegistryPluginTest.kt
@@ -14,6 +14,7 @@ import org.junit.jupiter.api.Test
 import java.io.File
 
 const val REGISTRY_FAKE_PORT = 6666
+const val REGISTRY_FAKE_AUTH_PORT = 7777
 
 class SchemaRegistryPluginTest {
     lateinit var project: Project
@@ -44,7 +45,7 @@ class SchemaRegistryPluginTest {
     }
 
     @Test
-    fun `plugin should fail with wrong extension configuration`() {
+    fun `plugin should fail with wrong url extension configuration`() {
         folderRule.create()
         buildFile = folderRule.newFile("build.gradle")
         buildFile.writeText("""
@@ -73,4 +74,40 @@ class SchemaRegistryPluginTest {
             Assertions.assertThat(ex.message).containsIgnoringCase("unknown property 'urlFoo'")
         }
     }
-}
\ No newline at end of file
+
+    @Test
+    fun `plugin should fail with wrong credentials extension configuration`() {
+        folderRule.create()
+        buildFile = folderRule.newFile("build.gradle")
+        buildFile.writeText("""
+            plugins {
+                id 'java'
+                id 'com.github.imflog.kafka-schema-registry-gradle-plugin'
+            }
+
+            schemaRegistry {
+                url = 'http://localhost:$REGISTRY_FAKE_PORT/'
+                credentials {
+                    username = 'user'
+                    password = 'pass'
+                }
+                credentialsBar.username = 'user'
+                output = 'src/main/avro'
+                subjects = ['$subject']
+            }
+        """)
+
+        try {
+            GradleRunner.create()
+                    .withGradleVersion("4.9")
+                    .withProjectDir(folderRule.root)
+                    .withArguments(DOWNLOAD_SCHEMAS_TASK)
+                    .withPluginClasspath()
+                    .withDebug(true)
+                    .build()
+            Assertions.fail("Should not reach this point")
+        } catch (ex: UnexpectedBuildFailure) {
+            Assertions.assertThat(ex.message).containsIgnoringCase("unknown property 'credentialsBar'")
+        }
+    }
+}
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTaskActionTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTaskActionTest.kt
index 6afbdbf..d63fe44 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTaskActionTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTaskActionTest.kt
@@ -69,7 +69,7 @@ class CompatibilityTaskActionTest {
              "name": "test",
              "fields": [
                 {"name": "name", "type": "string" },
-                {"name": "address", "type": "Address"}
+                {"name": "address", "type": [ "null", "Address" ], "default": null}
              ]
             }
         """.trimIndent())
@@ -79,7 +79,7 @@ class CompatibilityTaskActionTest {
              "name": "Address",
              "fields": [
                 {"name": "city", "type": "string" },
-                {"name": "street", "type": "Street" }
+                {"name": "street", "type": [ "null", "Street" ] }
              ]
             }
         """.trimIndent())
@@ -88,7 +88,7 @@ class CompatibilityTaskActionTest {
             {"type": "record",
              "name": "Street",
              "fields": [
-                {"name": "street", "type": "string" }
+                {"name": "street", "type": "string", "default": "" }
              ]
             }
         """.trimIndent())
@@ -144,4 +144,4 @@ class CompatibilityTaskActionTest {
         Assertions.assertThat(errorCount).isEqualTo(1)
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTaskTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTaskTest.kt
index b9025d3..6be73be 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTaskTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/compatibility/CompatibilityTaskTest.kt
@@ -1,6 +1,8 @@
 package com.github.imflog.schema.registry.compatibility
 
+import com.github.imflog.schema.registry.REGISTRY_FAKE_AUTH_PORT
 import com.github.imflog.schema.registry.REGISTRY_FAKE_PORT
+import com.github.imflog.schema.registry.RegistryClientWrapper
 import com.github.tomakehurst.wiremock.WireMockServer
 import com.github.tomakehurst.wiremock.client.WireMock
 import com.github.tomakehurst.wiremock.common.ConsoleNotifier
@@ -10,15 +12,24 @@ import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder
 import org.gradle.testkit.runner.BuildResult
 import org.gradle.testkit.runner.GradleRunner
 import org.gradle.testkit.runner.TaskOutcome
-import org.junit.jupiter.api.*
+import org.junit.jupiter.api.AfterAll
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
 import java.io.File
 
 class CompatibilityTaskTest {
     lateinit var folderRule: TemporaryFolder
     lateinit var buildFile: File
 
+    val username: String = "user"
+
+    val password: String = "pass"
+
     companion object {
         lateinit var wireMockServerItem: WireMockServer
+        lateinit var wireMockAuthServerItem: WireMockServer
 
         @BeforeAll
         @JvmStatic
@@ -29,18 +40,28 @@ class CompatibilityTaskTest {
                             .port(REGISTRY_FAKE_PORT)
                             .notifier(ConsoleNotifier(true)))
             wireMockServerItem.start()
+
+            wireMockAuthServerItem = WireMockServer(
+                    WireMockConfiguration
+                            .wireMockConfig()
+                            .port(REGISTRY_FAKE_AUTH_PORT)
+                            .notifier(ConsoleNotifier(true)))
+            wireMockAuthServerItem.start()
         }
 
         @AfterAll
         @JvmStatic
         fun tearDown() {
             wireMockServerItem.stop()
+            wireMockAuthServerItem.stop()
         }
     }
 
     @BeforeEach
     fun init() {
+        // Reset the client before each test
         folderRule = TemporaryFolder()
+        // Stub without authentication configuration
         wireMockServerItem.stubFor(
                 WireMock.post(WireMock
                         .urlMatching("/compatibility/subjects/.*/versions/.*"))
@@ -48,6 +69,16 @@ class CompatibilityTaskTest {
                                 .withStatus(200)
                                 .withHeader("Content-Type", "application/vnd.schemaregistry.v1+json")
                                 .withBody("{\"is_compatible\": true}")))
+
+        // Stub with authentication configuration
+        wireMockAuthServerItem.stubFor(
+                WireMock.post(WireMock
+                        .urlMatching("/compatibility/subjects/.*/versions/.*"))
+                        .withBasicAuth(username, password)
+                        .willReturn(WireMock.aResponse()
+                                .withStatus(200)
+                                .withHeader("Content-Type", "application/vnd.schemaregistry.v1+json")
+                                .withBody("{\"is_compatible\": true}")))
     }
 
     @AfterEach
@@ -85,7 +116,11 @@ class CompatibilityTaskTest {
             }
 
             schemaRegistry {
-                url = 'http://localhost:$REGISTRY_FAKE_PORT/'
+                url = 'http://localhost:$REGISTRY_FAKE_AUTH_PORT/'
+                credentials {
+                    username = '$username'
+                    password = '$password'
+                }
                 compatibility {
                     subject('testSubject1', 'avro/test.avsc')
                     subject('testSubject2', 'avro/other_test.avsc')
@@ -94,7 +129,7 @@ class CompatibilityTaskTest {
         """)
 
         val result: BuildResult? = GradleRunner.create()
-                .withGradleVersion("4.9")
+                .withGradleVersion("5.2")
                 .withProjectDir(folderRule.root)
                 .withArguments(TEST_SCHEMAS_TASK)
                 .withPluginClasspath()
@@ -114,7 +149,11 @@ class CompatibilityTaskTest {
             }
 
             schemaRegistry {
-                url = 'http://localhost:$REGISTRY_FAKE_PORT/'
+                url = 'http://localhost:$REGISTRY_FAKE_AUTH_PORT/'
+                credentials {
+                    username = '$username'
+                    password = '$password'
+                }
                 compatibility {
                     subject('testSubject', 'avro/core.avsc', ['avro/dependency.avsc'])
                 }
@@ -137,7 +176,7 @@ class CompatibilityTaskTest {
         """.trimIndent()
         coreAvsc.writeText(coreSchema)
 
-        val depAvsc = folderRule.newFile("avro/dependency_test.avsc")
+        val depAvsc = folderRule.newFile("avro/dependency.avsc")
         val depSchema = """
             {
                 "type":"record",
@@ -153,7 +192,107 @@ class CompatibilityTaskTest {
         depAvsc.writeText(depSchema)
 
         val result: BuildResult? = GradleRunner.create()
-                .withGradleVersion("4.9")
+                .withGradleVersion("5.2")
+                .withProjectDir(folderRule.root)
+                .withArguments(TEST_SCHEMAS_TASK)
+                .withPluginClasspath()
+                .withDebug(true)
+                .build()
+        Assertions.assertThat(result?.task(":testSchemasTask")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
+    }
+
+    @Test
+    internal fun `Compatibility task should fail if credential not provided on authenticated registry`() {
+        folderRule.create()
+        folderRule.newFolder("avro")
+        val testAvsc = folderRule.newFile("avro/other_test.avsc")
+        val schemaTest = """
+            {
+                "type":"record",
+                "name":"Blah",
+                "fields":[
+                    {
+                        "name":"name",
+                        "type":"string"
+                    }
+                ]
+            }
+        """.trimIndent()
+        testAvsc.writeText(schemaTest)
+
+        val testAvsc2 = folderRule.newFile("avro/test.avsc")
+        testAvsc2.writeText(schemaTest)
+
+        buildFile = folderRule.newFile("build.gradle")
+        // Auth configuration is omited to test the failure
+        buildFile.writeText("""
+            plugins {
+                id 'java'
+                id 'com.github.imflog.kafka-schema-registry-gradle-plugin'
+            }
+
+            schemaRegistry {
+                url = 'http://localhost:$REGISTRY_FAKE_AUTH_PORT/'
+                compatibility {
+                    subject('testSubject1', 'avro/test.avsc')
+                }
+            }
+        """)
+
+        val result: BuildResult? = GradleRunner.create()
+                .withGradleVersion("5.2")
+                .withProjectDir(folderRule.root)
+                .withArguments(TEST_SCHEMAS_TASK)
+                .withPluginClasspath()
+                .withDebug(true)
+                .buildAndFail()
+        Assertions.assertThat(result?.task(":testSchemasTask")?.outcome).isEqualTo(TaskOutcome.FAILED)
+    }
+
+    @Test
+    fun `CompatibilityTask should validate input schema with dependencies without credentials`() {
+        folderRule.create()
+        folderRule.newFolder("avro")
+        val testAvsc = folderRule.newFile("avro/other_test.avsc")
+        val schemaTest = """
+            {
+                "type":"record",
+                "name":"Blah",
+                "fields":[
+                    {
+                        "name":"name",
+                        "type":"string"
+                    }
+                ]
+            }
+        """.trimIndent()
+        testAvsc.writeText(schemaTest)
+
+        val testAvsc2 = folderRule.newFile("avro/test.avsc")
+        testAvsc2.writeText(schemaTest)
+
+        buildFile = folderRule.newFile("build.gradle")
+        buildFile.writeText("""
+            plugins {
+                id 'java'
+                id 'com.github.imflog.kafka-schema-registry-gradle-plugin'
+            }
+
+            schemaRegistry {
+                url = 'http://localhost:$REGISTRY_FAKE_PORT/'
+                credentials {
+                    username = '$username'
+                    password = '$password'
+                }
+                compatibility {
+                    subject('testSubject1', 'avro/test.avsc')
+                    subject('testSubject2', 'avro/other_test.avsc')
+                }
+            }
+        """)
+
+        val result: BuildResult? = GradleRunner.create()
+                .withGradleVersion("5.2")
                 .withProjectDir(folderRule.root)
                 .withArguments(TEST_SCHEMAS_TASK)
                 .withPluginClasspath()
@@ -161,4 +300,4 @@ class CompatibilityTaskTest {
                 .build()
         Assertions.assertThat(result?.task(":testSchemasTask")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/download/DownloadTaskTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/download/DownloadTaskTest.kt
index d8ef647..6263f71 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/download/DownloadTaskTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/download/DownloadTaskTest.kt
@@ -1,13 +1,16 @@
 package com.github.imflog.schema.registry.download
 
 import com.fasterxml.jackson.databind.ObjectMapper
+import com.github.imflog.schema.registry.REGISTRY_FAKE_AUTH_PORT
 import com.github.imflog.schema.registry.REGISTRY_FAKE_PORT
+import com.github.imflog.schema.registry.compatibility.CompatibilityTaskTest
 import com.github.tomakehurst.wiremock.WireMockServer
 import com.github.tomakehurst.wiremock.client.WireMock
 import com.github.tomakehurst.wiremock.common.ConsoleNotifier
 import com.github.tomakehurst.wiremock.core.WireMockConfiguration
 import io.confluent.kafka.schemaregistry.client.rest.entities.Schema
 import org.assertj.core.api.Assertions.assertThat
+import org.assertj.core.api.Assertions.useDefaultDateFormatsOnly
 import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder
 import org.gradle.testkit.runner.BuildResult
 import org.gradle.testkit.runner.GradleRunner
@@ -20,13 +23,19 @@ class DownloadTaskTest {
     lateinit var folderRule: TemporaryFolder
     val subject = "test-subject"
 
+    val username: String = "user"
+
+    val password: String = "pass"
+
     val schema = "{\"type\": \"record\", \"name\": \"Blah\", \"fields\": [{ \"name\": \"name\", \"type\": \"string\" }]}"
+
     lateinit var buildFile: File
 
     val mapper = ObjectMapper()
 
     companion object {
         lateinit var wiremockServerItem: WireMockServer
+        lateinit var wiremockAuthServerItem: WireMockServer
 
         @BeforeAll
         @JvmStatic
@@ -37,12 +46,20 @@ class DownloadTaskTest {
                             .port(REGISTRY_FAKE_PORT)
                             .notifier(ConsoleNotifier(true)))
             wiremockServerItem.start()
+
+            wiremockAuthServerItem = WireMockServer(
+                    WireMockConfiguration
+                            .wireMockConfig()
+                            .port(REGISTRY_FAKE_AUTH_PORT)
+                            .notifier(ConsoleNotifier(true)))
+            wiremockAuthServerItem.start()
         }
 
         @AfterAll
         @JvmStatic
         fun tearDown() {
             wiremockServerItem.stop()
+            wiremockAuthServerItem.stop()
         }
     }
 
@@ -53,9 +70,19 @@ class DownloadTaskTest {
 
         // Register schema
         val avroSchema = Schema(subject, 1, 1, schema)
+        // Stub without authentication configuration
         wiremockServerItem.stubFor(
                 WireMock.get(WireMock
-                        .urlMatching("/subjects/.*/versions/latest"))
+                        .urlMatching("/subjects/test-subject/versions/latest"))
+                        .willReturn(WireMock.aResponse()
+                                .withStatus(200)
+                                .withHeader("Accept", "application/json")
+                                .withBody(mapper.writeValueAsString(avroSchema))))
+        // Stub with authentication configuration
+        wiremockAuthServerItem.stubFor(
+                WireMock.get(WireMock
+                        .urlMatching("/subjects/test-subject/versions/latest"))
+                        .withBasicAuth(username, password)
                         .willReturn(WireMock.aResponse()
                                 .withStatus(200)
                                 .withHeader("Accept", "application/json")
@@ -69,6 +96,40 @@ class DownloadTaskTest {
 
     @Test
     fun `DownloadSchemaTask should download last schema version`() {
+        buildFile = folderRule.newFile("build.gradle")
+        buildFile.writeText("""
+            plugins {
+                id 'java'
+                id 'com.github.imflog.kafka-schema-registry-gradle-plugin'
+            }
+
+            schemaRegistry {
+                url = 'http://localhost:$REGISTRY_FAKE_AUTH_PORT/'
+                credentials {
+                    username = '$username'
+                    password = '$password'
+                }
+                download {
+                    subject('test-subject', 'src/main/avro/test')
+                }
+            }
+        """)
+
+        val result: BuildResult? = GradleRunner.create()
+                .withGradleVersion("4.9")
+                .withProjectDir(folderRule.root)
+                .withArguments(DOWNLOAD_SCHEMAS_TASK)
+                .withPluginClasspath()
+                .withDebug(true)
+                .build()
+
+        assertThat(File(folderRule.root, "src/main/avro/test")).exists()
+        assertThat(File(folderRule.root, "src/main/avro/test/test-subject.avsc")).exists()
+        assertThat(result?.task(":downloadSchemasTask")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
+    }
+
+    @Test
+    fun `DownloadSchemaTask should download last schema version without credentials`() {
         buildFile = folderRule.newFile("build.gradle")
         buildFile.writeText("""
             plugins {
@@ -96,4 +157,58 @@ class DownloadTaskTest {
         assertThat(File(folderRule.root, "src/main/avro/test/test-subject.avsc")).exists()
         assertThat(result?.task(":downloadSchemasTask")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun `DownloadSchemaTask should fail download when schema does not exist`() {
+        buildFile = folderRule.newFile("build.gradle")
+        buildFile.writeText("""
+            plugins {
+                id 'java'
+                id 'com.github.imflog.kafka-schema-registry-gradle-plugin'
+            }
+
+            schemaRegistry {
+                url = 'http://localhost:$REGISTRY_FAKE_PORT/'
+                download {
+                    subject('UNKNOWN', 'src/main/avro/test')
+                }
+            }
+        """)
+
+        val result: BuildResult? = GradleRunner.create()
+                .withGradleVersion("4.9")
+                .withProjectDir(folderRule.root)
+                .withArguments(DOWNLOAD_SCHEMAS_TASK)
+                .withPluginClasspath()
+                .withDebug(true)
+                .buildAndFail()
+        assertThat(result?.task(":downloadSchemasTask")?.outcome).isEqualTo(TaskOutcome.FAILED)
+    }
+
+    @Test
+    fun `DownloadSchemaTask should fail download when credentials not setup and required`() {
+        buildFile = folderRule.newFile("build.gradle")
+        buildFile.writeText("""
+            plugins {
+                id 'java'
+                id 'com.github.imflog.kafka-schema-registry-gradle-plugin'
+            }
+
+            schemaRegistry {
+                url = 'http://localhost:$REGISTRY_FAKE_AUTH_PORT/'
+                download {
+                    subject('test-subject', 'src/main/avro/test')
+                }
+            }
+        """)
+
+        val result: BuildResult? = GradleRunner.create()
+                .withGradleVersion("4.9")
+                .withProjectDir(folderRule.root)
+                .withArguments(DOWNLOAD_SCHEMAS_TASK)
+                .withPluginClasspath()
+                .withDebug(true)
+                .buildAndFail()
+        assertThat(result?.task(":downloadSchemasTask")?.outcome).isEqualTo(TaskOutcome.FAILED)
+    }
+}
diff --git a/src/test/kotlin/com/github/imflog/schema/registry/register/RegisterTaskTest.kt b/src/test/kotlin/com/github/imflog/schema/registry/register/RegisterTaskTest.kt
index 6c9b458..30df354 100644
--- a/src/test/kotlin/com/github/imflog/schema/registry/register/RegisterTaskTest.kt
+++ b/src/test/kotlin/com/github/imflog/schema/registry/register/RegisterTaskTest.kt
@@ -1,5 +1,6 @@
 package com.github.imflog.schema.registry.register
 
+import com.github.imflog.schema.registry.REGISTRY_FAKE_AUTH_PORT
 import com.github.imflog.schema.registry.REGISTRY_FAKE_PORT
 import com.github.tomakehurst.wiremock.WireMockServer
 import com.github.tomakehurst.wiremock.client.WireMock
@@ -10,7 +11,11 @@ import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder
 import org.gradle.testkit.runner.BuildResult
 import org.gradle.testkit.runner.GradleRunner
 import org.gradle.testkit.runner.TaskOutcome
-import org.junit.jupiter.api.*
+import org.junit.jupiter.api.AfterAll
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
 import java.io.File
 
 class RegisterTaskTest {
@@ -18,8 +23,12 @@ class RegisterTaskTest {
 
     lateinit var buildFile: File
 
+    val username: String = "user"
+    val password: String = "pass"
+
     companion object {
         lateinit var wiremockServerItem: WireMockServer
+        lateinit var wiremockAuthServerItem: WireMockServer
 
         @BeforeAll
         @JvmStatic
@@ -30,25 +39,43 @@ class RegisterTaskTest {
                             .port(REGISTRY_FAKE_PORT)
                             .notifier(ConsoleNotifier(true)))
             wiremockServerItem.start()
+
+
+            wiremockAuthServerItem = WireMockServer(
+                    WireMockConfiguration
+                            .wireMockConfig()
+                            .port(REGISTRY_FAKE_AUTH_PORT)
+                            .notifier(ConsoleNotifier(true)))
+            wiremockAuthServerItem.start()
         }
 
         @AfterAll
         @JvmStatic
         fun tearDown() {
             wiremockServerItem.stop()
+            wiremockAuthServerItem.stop()
         }
     }
 
     @BeforeEach
     fun init() {
         folderRule = TemporaryFolder()
-        // Register schema
+        // Stub without authentication configuration
         wiremockServerItem.stubFor(
                 WireMock.post(WireMock
                         .urlMatching("/subjects/.*/versions"))
                         .willReturn(WireMock.aResponse()
                                 .withStatus(200)
                                 .withBody("{\"id\": 1}")))
+        // Stub with authentication configuration
+        wiremockAuthServerItem.stubFor(
+                WireMock.post(WireMock
+                        .urlMatching("/subjects/.*/versions"))
+                        .withBasicAuth(username, password)
+                        .willReturn(WireMock.aResponse()
+                                .withStatus(200)
+                                .withBody("{\"id\": 1}")))
+
     }
 
     @AfterEach
@@ -58,6 +85,74 @@ class RegisterTaskTest {
 
     @Test
     fun `RegisterSchemasTask should register schemas`() {
+        folderRule.create()
+        buildFile = folderRule.newFile("build.gradle")
+        buildFile.writeText("""
+            plugins {
+                id 'java'
+                id 'com.github.imflog.kafka-schema-registry-gradle-plugin'
+            }
+
+            schemaRegistry {
+                url = 'http://localhost:$REGISTRY_FAKE_AUTH_PORT/'
+                credentials {
+                    username = '$username'
+                    password = '$password'
+                }
+                register {
+                    subject('testSubject1', 'avro/test.avsc')
+                    subject('testSubject2', 'avro/other_test.avsc')
+                    subject('testSubject3', 'avro/dependency_test.avsc', ['avro/test.avsc'])
+                }
+            }
+        """)
+
+        folderRule.newFolder("avro")
+        var testAvsc = folderRule.newFile("avro/test.avsc")
+        val schemaTest = """
+            {
+                "type":"record",
+                "name":"Blah",
+                "fields":[
+                    {
+                        "name":"name",
+                        "type":"string"
+                    }
+                ]
+            }
+        """.trimIndent()
+        testAvsc.writeText(schemaTest)
+
+        var testAvsc2 = folderRule.newFile("avro/other_test.avsc")
+        testAvsc2.writeText(schemaTest)
+
+        var depAvsc = folderRule.newFile("avro/dependency_test.avsc")
+        val depSchema = """
+            {
+                "type":"record",
+                "name":"Dependency",
+                "fields":[
+                    {
+                        "name":"blah",
+                        "type":"Blah"
+                    }
+                ]
+            }
+        """.trimIndent()
+        depAvsc.writeText(depSchema)
+
+        val result: BuildResult? = GradleRunner.create()
+                .withGradleVersion("4.9")
+                .withProjectDir(folderRule.root)
+                .withArguments(REGISTER_SCHEMAS_TASK)
+                .withPluginClasspath()
+                .withDebug(true)
+                .build()
+        Assertions.assertThat(result?.task(":registerSchemasTask")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
+    }
+
+    @Test
+    fun `RegisterSchemasTask should register schemas without credentials`() {
         folderRule.create()
         buildFile = folderRule.newFile("build.gradle")
         buildFile.writeText("""
@@ -119,4 +214,48 @@ class RegisterTaskTest {
                 .build()
         Assertions.assertThat(result?.task(":registerSchemasTask")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun `RegisterSchemasTask should fail register schemas without credentials when required`() {
+        folderRule.create()
+        buildFile = folderRule.newFile("build.gradle")
+        buildFile.writeText("""
+            plugins {
+                id 'java'
+                id 'com.github.imflog.kafka-schema-registry-gradle-plugin'
+            }
+
+            schemaRegistry {
+                url = 'http://localhost:$REGISTRY_FAKE_AUTH_PORT/'
+                register {
+                    subject('testSubject1', 'avro/test.avsc')
+                }
+            }
+        """)
+
+        folderRule.newFolder("avro")
+        val testAvsc = folderRule.newFile("avro/test.avsc")
+        val schemaTest = """
+            {
+                "type":"record",
+                "name":"Blah",
+                "fields":[
+                    {
+                        "name":"name",
+                        "type":"string"
+                    }
+                ]
+            }
+        """.trimIndent()
+        testAvsc.writeText(schemaTest)
+
+        val result: BuildResult? = GradleRunner.create()
+                .withGradleVersion("4.9")
+                .withProjectDir(folderRule.root)
+                .withArguments(REGISTER_SCHEMAS_TASK)
+                .withPluginClasspath()
+                .withDebug(true)
+                .buildAndFail()
+        Assertions.assertThat(result?.task(":registerSchemasTask")?.outcome).isEqualTo(TaskOutcome.FAILED)
+    }
+}