diff --git a/README.md b/README.md index b5b5047..12ab2a0 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,17 @@ mvn -Dkotlin.version=1.4.10 -DskipTests package To start the shell, run `bin/ki.sh` on Linux and macOS. On Windows, use `bin\ki.bat` instead. To exit the shell, type `:q` or `:quit`. + +## Adding maven repositories that require auth + +The following options are supported: + +```bash +:repository https://myrepo.org username=user password=pwd +:repository https://myrepo.org username:user password:pwd + +:repository https:myrepo.org ./path/to/file +# properties file should contain +# username=john +# password=johnpwd +``` diff --git a/ki-shell/src/main/kotlin/org/jetbrains/kotlinx/ki/shell/plugins/DependenciesPlugin.kt b/ki-shell/src/main/kotlin/org/jetbrains/kotlinx/ki/shell/plugins/DependenciesPlugin.kt index f69f831..de51228 100644 --- a/ki-shell/src/main/kotlin/org/jetbrains/kotlinx/ki/shell/plugins/DependenciesPlugin.kt +++ b/ki-shell/src/main/kotlin/org/jetbrains/kotlinx/ki/shell/plugins/DependenciesPlugin.kt @@ -6,11 +6,20 @@ import org.jetbrains.kotlinx.ki.shell.Command import org.jetbrains.kotlinx.ki.shell.Plugin import org.jetbrains.kotlinx.ki.shell.Shell import org.jetbrains.kotlinx.ki.shell.configuration.ReplConfiguration +import java.io.File +import java.util.Properties import kotlin.script.experimental.api.* import kotlin.script.experimental.dependencies.* import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver import kotlin.script.experimental.jvm.JvmDependency +data class RepoCredentials(val username: String, val password: String) + +private const val CREDENTIALS_USERNAME_PROPERTY = "username" +private const val CREDENTIALS_PASSWORD_PROPERTY = "password" +private const val MANDATORY_REPO_COMMAND_ARGS = 2 +private const val OPTIONAL_REPO_COMMAND_ARGS = 2 + class DependenciesPlugin : Plugin { inner class DependsOnCommand(conf: ReplConfiguration): BaseCommand() { @@ -34,10 +43,79 @@ class DependenciesPlugin : Plugin { override val params = "" + private val emptyRepoCredentials = RepoCredentials("", "") + override fun execute(line: String): Command.Result { - val p = line.indexOf(' ') - val repo = line.substring(p + 1).trim() - return Command.Result.RunSnippets(listOf("@file:Repository(\"$repo\")")) + val args = line.split(' ') + + return Command.Result.RunSnippets(buildSnippet(args)) + } + + private fun buildSnippet(args: List): List { + val repo = if (args.size > 1) args[1].trim() else "" + + if (args.size <= MANDATORY_REPO_COMMAND_ARGS) { + return listOf("@file:Repository(\"$repo\")") + } + + val (username, password) = getCredentials( + args.drop(MANDATORY_REPO_COMMAND_ARGS).take(OPTIONAL_REPO_COMMAND_ARGS) + ) + + return listOf("@file:Repository(\"$repo\", options = [\"username=$username\", \"password=$password\"])") + } + + private fun getCredentials(args: List): RepoCredentials { + return when (args.size) { + 2 -> buildCredentialsFromUsernameAndPassword(args) + 1 -> buildCredentialsFromFile(args) + else -> emptyRepoCredentials + } + } + + private fun buildCredentialsFromUsernameAndPassword(args: List): RepoCredentials { + val props = Properties() + + return args.joinToString("\n").reader().use { reader -> + props.load(reader) + + RepoCredentials( + props.getProperty(CREDENTIALS_USERNAME_PROPERTY, ""), + props.getProperty(CREDENTIALS_PASSWORD_PROPERTY, "") + ) + } + } + + private fun buildCredentialsFromFile(args: List): RepoCredentials { + val path = args[0] + val credFile = File(path) + + return when { + !credFile.exists() -> { + println("File $path not found.") + emptyRepoCredentials + } + !credFile.canRead() -> { + println("Cannot open $path.") + emptyRepoCredentials + } + else -> { + try { + credFile.reader().use { reader -> + val props = Properties() + props.load(reader) + + RepoCredentials( + props.getProperty(CREDENTIALS_USERNAME_PROPERTY, ""), + props.getProperty(CREDENTIALS_PASSWORD_PROPERTY, "") + ) + } + } catch (e: Exception) { + println(e.message) + emptyRepoCredentials + } + } + } } } diff --git a/ki-shell/src/test/kotlin/org/jetbrains/kotlinx/ki/shell/plugins/DependenciesPluginTest.kt b/ki-shell/src/test/kotlin/org/jetbrains/kotlinx/ki/shell/plugins/DependenciesPluginTest.kt new file mode 100644 index 0000000..d85382f --- /dev/null +++ b/ki-shell/src/test/kotlin/org/jetbrains/kotlinx/ki/shell/plugins/DependenciesPluginTest.kt @@ -0,0 +1,191 @@ +package org.jetbrains.kotlinx.ki.shell.plugins + +import junit.framework.TestCase +import org.jetbrains.kotlinx.ki.shell.Command +import org.jetbrains.kotlinx.ki.shell.configuration.ReplConfigurationBase +import org.junit.Test + +class DependenciesPluginTest : TestCase() { + + private val repoCommand = DependenciesPlugin().RepositoryCommand(object : ReplConfigurationBase() {}) + + private fun extractSnippetFromCommand(command: Command.Result.RunSnippets): String { + return command.snippetsToRun.toList()[0] + } + + @Test + fun testWhenRepoUrlNotPassed() { + val command = repoCommand.execute(":repository ") as Command.Result.RunSnippets + + assertEquals("@file:Repository(\"\")", extractSnippetFromCommand(command)) + } + + @Test + fun testWhenRepoUrlPassed() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\")", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testUsernameAndPasswordEqualSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven username=user password=pass") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=user\", \"password=pass\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testUsernameOnlyEqualSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven username=user barepassword") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=user\", \"password=\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testPasswordOnlyEqualSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven bareusername password=pass") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=\", \"password=pass\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testUsernameWithEqualEqualSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven username=my=user password=pass") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=my=user\", \"password=pass\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testPasswordWithEqualEqualSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven username=user password=my=pass") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=user\", \"password=my=pass\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testUsernameAndPasswordSemicolonSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven username:user password:pass") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=user\", \"password=pass\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testUsernameOnlySemicolonSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven username:user barepassword") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=user\", \"password=\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testPasswordOnlySemicolonSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven bareusername password:pass") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=\", \"password=pass\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testUsernameWithSemicolonSemicolonSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven username:my:user password:pass") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=my:user\", \"password=pass\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testPasswordWithSemicolonSemicolonSeparator() { + val command = repoCommand.execute(":repository https://packages.team/path/to/maven username:user password:my:pass") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=user\", \"password=my:pass\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testFilenamePassed() { + val filePath = "./src/test/resources/valid.credentials" + val command = repoCommand.execute(":repository https://packages.team/path/to/maven $filePath") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=fileusername\", \"password=file password\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testNotFoundFilename() { + val filePath = "blabla" + val command = repoCommand.execute(":repository https://packages.team/path/to/maven $filePath") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=\", \"password=\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testMissingCredentialsFile() { + val filePath = "./src/test/resources/missing.credentials" + val command = repoCommand.execute(":repository https://packages.team/path/to/maven $filePath") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=\", \"password=\"])", + extractSnippetFromCommand(command) + ) + } + + @Test + fun testCredentialsFileIsDirectory() { + val filePath = "./src/test/resources" + val command = repoCommand.execute(":repository https://packages.team/path/to/maven $filePath") + as Command.Result.RunSnippets + + assertEquals( + "@file:Repository(\"https://packages.team/path/to/maven\", options = [\"username=\", \"password=\"])", + extractSnippetFromCommand(command) + ) + } +} diff --git a/ki-shell/src/test/resources/missing.credentials b/ki-shell/src/test/resources/missing.credentials new file mode 100644 index 0000000..e8e4106 --- /dev/null +++ b/ki-shell/src/test/resources/missing.credentials @@ -0,0 +1 @@ +blabla \ No newline at end of file diff --git a/ki-shell/src/test/resources/valid.credentials b/ki-shell/src/test/resources/valid.credentials new file mode 100644 index 0000000..57848a2 --- /dev/null +++ b/ki-shell/src/test/resources/valid.credentials @@ -0,0 +1,2 @@ +username=fileusername +password=file password \ No newline at end of file