[Scripting] Add authorization for Maven Resolver

This commit is contained in:
Ilya Muradyan
2021-05-14 19:32:40 +03:00
parent 78c65ab802
commit b77a822822
5 changed files with 103 additions and 29 deletions
@@ -13,12 +13,12 @@ dependencies {
compile("org.eclipse.aether:aether-util:1.1.0") compile("org.eclipse.aether:aether-util:1.1.0")
compile("org.eclipse.aether:aether-connector-basic:1.1.0") compile("org.eclipse.aether:aether-connector-basic:1.1.0")
compile("org.eclipse.aether:aether-transport-wagon:1.1.0") compile("org.eclipse.aether:aether-transport-wagon:1.1.0")
compile("org.eclipse.aether:aether-transport-http:1.1.0")
compile("org.eclipse.aether:aether-transport-file:1.1.0") compile("org.eclipse.aether:aether-transport-file:1.1.0")
compile("org.apache.maven:maven-core:3.8.1") compile("org.apache.maven:maven-core:3.8.1")
compile("org.apache.maven:maven-settings-builder:3.8.1") compile("org.apache.maven:maven-settings-builder:3.8.1")
compile("org.apache.maven:maven-aether-provider:3.3.9") compile("org.apache.maven:maven-aether-provider:3.3.9")
compile("org.apache.maven.wagon:wagon-provider-api:3.4.3") compile("org.apache.maven.wagon:wagon-provider-api:3.4.3")
compile("org.apache.maven.wagon:wagon-http:3.4.3")
testCompile(projectTests(":kotlin-scripting-dependencies")) testCompile(projectTests(":kotlin-scripting-dependencies"))
testCompile(commonDep("junit")) testCompile(commonDep("junit"))
testRuntimeOnly("org.slf4j:slf4j-nop:1.7.30") testRuntimeOnly("org.slf4j:slf4j-nop:1.7.30")
@@ -17,13 +17,14 @@ import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.asSuccess import kotlin.script.experimental.api.asSuccess
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
import kotlin.script.experimental.dependencies.RepositoryCoordinates import kotlin.script.experimental.dependencies.RepositoryCoordinates
import kotlin.script.experimental.dependencies.impl.makeResolveFailureResult import kotlin.script.experimental.dependencies.impl.*
import kotlin.script.experimental.dependencies.impl.toRepositoryUrlOrNull
import kotlin.script.experimental.dependencies.maven.impl.AetherResolveSession import kotlin.script.experimental.dependencies.maven.impl.AetherResolveSession
import kotlin.script.experimental.dependencies.maven.impl.mavenCentral import kotlin.script.experimental.dependencies.maven.impl.mavenCentral
import kotlin.script.experimental.dependencies.impl.dependencyScopes
@Deprecated(
"This class is not functional and left only for compatibility reasons. Use kotlin.script.experimental.dependencies.ExternalDependenciesResolver.Options for passing authorization options",
replaceWith = ReplaceWith("RepositoryCoordinates(url)", "kotlin.script.experimental.dependencies.RepositoryCoordinates")
)
class MavenRepositoryCoordinates( class MavenRepositoryCoordinates(
url: String, url: String,
val username: String?, val username: String?,
@@ -89,21 +90,31 @@ class MavenDependenciesResolver : ExternalDependenciesResolver {
val url = repositoryCoordinates.toRepositoryUrlOrNull() val url = repositoryCoordinates.toRepositoryUrlOrNull()
?: return false.asSuccess() ?: return false.asSuccess()
val repoId = repositoryCoordinates.string.replace(FORBIDDEN_CHARS, "_") val repoId = repositoryCoordinates.string.replace(FORBIDDEN_CHARS, "_")
val repo = RemoteRepository.Builder(repoId, "default", url.toString()) val repo = RemoteRepository.Builder(repoId, "default", url.toString()).apply {
if (repositoryCoordinates is MavenRepositoryCoordinates) { /**
val username = repositoryCoordinates.username?.let(::tryResolveEnvironmentVariable) * Here we set all the authentication information we have, unconditionally.
val password = repositoryCoordinates.password?.let(::tryResolveEnvironmentVariable) * Actual information that will be used (as well as lower-level checks,
if (username != null) { * such as nullability or emptiness) is determined by implementation.
val auth = AuthenticationBuilder().apply { *
addUsername(username) * @see org.eclipse.aether.transport.wagon.WagonTransporter.getProxy
if (password != null) { * @see org.apache.maven.wagon.shared.http.AbstractHttpClientWagon.openConnectionInternal
addPassword(password) */
} setAuthentication(
} AuthenticationBuilder().apply {
repo.setAuthentication(auth.build()) val mavenRepo = repositoryCoordinates as? MavenRepositoryCoordinates
} val username = options.username ?: mavenRepo?.username
} val password = options.password ?: mavenRepo?.password
repos.add(repo.build()) addUsername(username?.let(::tryResolveEnvironmentVariable))
addPassword(password?.let(::tryResolveEnvironmentVariable))
addPrivateKey(
options.privateKeyFile?.let(::tryResolveEnvironmentVariable),
options.privateKeyPassphrase?.let(::tryResolveEnvironmentVariable)
)
}.build()
)
}.build()
repos.add(repo)
return true.asSuccess() return true.asSuccess()
} }
@@ -11,6 +11,7 @@ import org.apache.maven.settings.Settings
import org.apache.maven.settings.SettingsUtils import org.apache.maven.settings.SettingsUtils
import org.apache.maven.settings.TrackableBase import org.apache.maven.settings.TrackableBase
import org.apache.maven.settings.building.* import org.apache.maven.settings.building.*
import org.codehaus.plexus.DefaultPlexusContainer
import org.eclipse.aether.RepositorySystem import org.eclipse.aether.RepositorySystem
import org.eclipse.aether.RepositorySystemSession import org.eclipse.aether.RepositorySystemSession
import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.Artifact
@@ -19,6 +20,8 @@ import org.eclipse.aether.collection.CollectRequest
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
import org.eclipse.aether.graph.Dependency import org.eclipse.aether.graph.Dependency
import org.eclipse.aether.graph.DependencyFilter import org.eclipse.aether.graph.DependencyFilter
import org.eclipse.aether.internal.transport.wagon.PlexusWagonConfigurator
import org.eclipse.aether.internal.transport.wagon.PlexusWagonProvider
import org.eclipse.aether.repository.LocalRepository import org.eclipse.aether.repository.LocalRepository
import org.eclipse.aether.repository.Proxy import org.eclipse.aether.repository.Proxy
import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.repository.RemoteRepository
@@ -29,7 +32,9 @@ import org.eclipse.aether.resolution.DependencyResult
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
import org.eclipse.aether.spi.connector.transport.TransporterFactory import org.eclipse.aether.spi.connector.transport.TransporterFactory
import org.eclipse.aether.transport.file.FileTransporterFactory import org.eclipse.aether.transport.file.FileTransporterFactory
import org.eclipse.aether.transport.http.HttpTransporterFactory import org.eclipse.aether.transport.wagon.WagonConfigurator
import org.eclipse.aether.transport.wagon.WagonProvider
import org.eclipse.aether.transport.wagon.WagonTransporterFactory
import org.eclipse.aether.util.filter.DependencyFilterUtils import org.eclipse.aether.util.filter.DependencyFilterUtils
import org.eclipse.aether.util.repository.AuthenticationBuilder import org.eclipse.aether.util.repository.AuthenticationBuilder
import org.eclipse.aether.util.repository.DefaultMirrorSelector import org.eclipse.aether.util.repository.DefaultMirrorSelector
@@ -48,7 +53,7 @@ class AetherResolveSession(
val remotes by lazy { val remotes by lazy {
val proxySelector = settings.getActiveProxy()?.let { proxy -> val proxySelector = settings.getActiveProxy()?.let { proxy ->
val selector = DefaultProxySelector() val selector = DefaultProxySelector()
val auth = with (AuthenticationBuilder()) { val auth = with(AuthenticationBuilder()) {
addUsername(proxy.username) addUsername(proxy.username)
addPassword(proxy.password) addPassword(proxy.password)
build() build()
@@ -95,8 +100,19 @@ class AetherResolveSession(
) )
locator.addService( locator.addService(
TransporterFactory::class.java, TransporterFactory::class.java,
HttpTransporterFactory::class.java WagonTransporterFactory::class.java
) )
val container = DefaultPlexusContainer()
locator.setServices(
WagonProvider::class.java,
PlexusWagonProvider(container)
)
locator.setServices(
WagonConfigurator::class.java,
PlexusWagonConfigurator(container)
)
locator.getService(RepositorySystem::class.java) locator.getService(RepositorySystem::class.java)
} }
@@ -22,7 +22,7 @@ import kotlin.script.experimental.dependencies.impl.set
@ExperimentalContracts @ExperimentalContracts
class MavenResolverTest : ResolversTestBase() { class MavenResolverTest : ResolversTestBase() {
fun resolveAndCheck( private fun resolveAndCheck(
coordinates: String, coordinates: String,
options: ExternalDependenciesResolver.Options = ExternalDependenciesResolver.Options.Empty, options: ExternalDependenciesResolver.Options = ExternalDependenciesResolver.Options.Empty,
checkBody: (Iterable<File>) -> Boolean = { true } checkBody: (Iterable<File>) -> Boolean = { true }
@@ -39,13 +39,18 @@ class MavenResolverTest : ResolversTestBase() {
files.forEach { it.delete() } files.forEach { it.delete() }
} }
private fun buildOptions(vararg options: Pair<DependenciesResolverOptionsName, String>): ExternalDependenciesResolver.Options {
return makeExternalDependenciesResolverOptions(mutableMapOf<String, String>().apply {
for (option in options) this[option.first] = option.second
})
}
fun testResolveSimple() { fun testResolveSimple() {
resolveAndCheck("org.jetbrains.kotlin:kotlin-annotations-jvm:1.3.50") { files -> resolveAndCheck("org.jetbrains.kotlin:kotlin-annotations-jvm:1.3.50") { files ->
files.any { it.name.startsWith("kotlin-annotations-jvm") } files.any { it.name.startsWith("kotlin-annotations-jvm") }
} }
} }
@ExperimentalStdlibApi
fun testResolveWithRuntime() { fun testResolveWithRuntime() {
val compileOnly = "compile" val compileOnly = "compile"
val compileRuntime = "compile,runtime" val compileRuntime = "compile,runtime"
@@ -54,9 +59,7 @@ class MavenResolverTest : ResolversTestBase() {
listOf(compileOnly, compileRuntime).forEach { scopes -> listOf(compileOnly, compileRuntime).forEach { scopes ->
resolveAndCheck( resolveAndCheck(
"org.uberfire:uberfire-io:7.39.0.Final", "org.uberfire:uberfire-io:7.39.0.Final",
makeExternalDependenciesResolverOptions(buildMap { buildOptions(DependenciesResolverOptionsName.SCOPE to scopes)
this[DependenciesResolverOptionsName.SCOPE] = scopes
})
) { files -> ) { files ->
resolvedFilesCount[scopes] = files.count() resolvedFilesCount[scopes] = files.count()
files.any { it.name.startsWith("uberfire-commons") } files.any { it.name.startsWith("uberfire-commons") }
@@ -81,6 +84,22 @@ class MavenResolverTest : ResolversTestBase() {
} }
} }
// Ignored - tests with custom repos often break the CI due to the caching issues
// TODO: find a way to enable it back
@Ignore
fun ignore_testAuth() {
val resolver = MavenDependenciesResolver()
val options = buildOptions(
DependenciesResolverOptionsName.USERNAME to "<FirstName.LastName>",
DependenciesResolverOptionsName.PASSWORD to "<Space token>",
)
resolver.addRepository("https://packages.jetbrains.team/maven/p/crl/maven/", options)
val files = runBlocking {
resolver.resolve("com.jetbrains:space-sdk:1.0-dev")
}.valueOrThrow()
assertTrue(files.any { it.name.startsWith("space-sdk") })
}
// Ignored - tests with custom repos often break the CI due to the caching issues // Ignored - tests with custom repos often break the CI due to the caching issues
// TODO: find a way to enable it back // TODO: find a way to enable it back
@Ignore @Ignore
@@ -23,7 +23,11 @@ operator fun MutableMap<String, String>.set(key: DependenciesResolverOptionsName
*/ */
enum class DependenciesResolverOptionsName(optionName: String? = null) { enum class DependenciesResolverOptionsName(optionName: String? = null) {
TRANSITIVE, TRANSITIVE,
SCOPE; SCOPE,
USERNAME,
PASSWORD,
KEY_FILE,
KEY_PASSPHRASE;
val key = optionName ?: name.lowercase() val key = optionName ?: name.lowercase()
} }
@@ -33,3 +37,27 @@ val ExternalDependenciesResolver.Options.transitive
val ExternalDependenciesResolver.Options.dependencyScopes val ExternalDependenciesResolver.Options.dependencyScopes
get() = value(DependenciesResolverOptionsName.SCOPE)?.split(",") get() = value(DependenciesResolverOptionsName.SCOPE)?.split(",")
/**
* Username to access repository (should be passed with [password])
*/
val ExternalDependenciesResolver.Options.username
get() = value(DependenciesResolverOptionsName.USERNAME)
/**
* Password to access repository (should be passed with [username])
*/
val ExternalDependenciesResolver.Options.password
get() = value(DependenciesResolverOptionsName.PASSWORD)
/**
* Absolute path to the private key file to access repository
*/
val ExternalDependenciesResolver.Options.privateKeyFile
get() = value(DependenciesResolverOptionsName.KEY_FILE)
/**
* Passphrase to access file passed in [privateKeyFile]
*/
val ExternalDependenciesResolver.Options.privateKeyPassphrase
get() = value(DependenciesResolverOptionsName.KEY_PASSPHRASE)