From aa94b1b9de8a7f100a958677dac20af589c55f31 Mon Sep 17 00:00:00 2001 From: Yannic Bonenberger Date: Wed, 20 Jul 2022 02:56:42 -0700 Subject: [PATCH] [credentialhelper] Add parser for flag syntax Progress on #15856 Closes #15906. PiperOrigin-RevId: 462095541 Change-Id: I4d31ad2ae185b63f0945119483ffafd3ba121d3d --- .../lib/authandtls/AuthAndTLSOptions.java | 56 ++++ .../credentialhelper/CredentialHelper.java | 2 +- .../google/devtools/build/lib/remote/BUILD | 2 + .../build/lib/remote/RemoteModule.java | 38 +++ .../devtools/build/lib/authandtls/BUILD | 1 + ...edScopedCredentialHelperConverterTest.java | 108 +++++++ .../google/devtools/build/lib/remote/BUILD | 1 + .../build/lib/remote/RemoteModuleTest.java | 264 ++++++++++++++++++ 8 files changed, 471 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/google/devtools/build/lib/authandtls/UnresolvedScopedCredentialHelperConverterTest.java diff --git a/src/main/java/com/google/devtools/build/lib/authandtls/AuthAndTLSOptions.java b/src/main/java/com/google/devtools/build/lib/authandtls/AuthAndTLSOptions.java index 2a54e3d7f96297..30bbe3711e568d 100644 --- a/src/main/java/com/google/devtools/build/lib/authandtls/AuthAndTLSOptions.java +++ b/src/main/java/com/google/devtools/build/lib/authandtls/AuthAndTLSOptions.java @@ -14,6 +14,10 @@ package com.google.devtools.build.lib.authandtls; +import com.google.auto.value.AutoValue; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.devtools.common.options.Converter; import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter; import com.google.devtools.common.options.Converters.DurationConverter; import com.google.devtools.common.options.Option; @@ -21,8 +25,11 @@ import com.google.devtools.common.options.OptionEffectTag; import com.google.devtools.common.options.OptionMetadataTag; import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsParsingException; import java.time.Duration; import java.util.List; +import java.util.Optional; +import javax.annotation.Nullable; /** * Common options for authentication and TLS. @@ -131,4 +138,53 @@ public class AuthAndTLSOptions extends OptionsBase { + "granularity; it is an error to set a value less than one second. If keep-alive " + "pings are disabled, then this setting is ignored.") public Duration grpcKeepaliveTimeout; + + /** One of the values of the `--credential_helper` flag. */ + @AutoValue + public abstract static class UnresolvedScopedCredentialHelper { + /** Returns the scope of the credential helper (if any). */ + public abstract Optional getScope(); + + /** Returns the (unparsed) path of the credential helper. */ + public abstract String getPath(); + } + + /** A {@link Converter} for the `--credential_helper` flag. */ + public static final class UnresolvedScopedCredentialHelperConverter + extends Converter.Contextless { + public static final UnresolvedScopedCredentialHelperConverter INSTANCE = + new UnresolvedScopedCredentialHelperConverter(); + + @Override + public String getTypeDescription() { + return "An (unresolved) path to a credential helper for a scope."; + } + + @Override + public UnresolvedScopedCredentialHelper convert(String input) throws OptionsParsingException { + Preconditions.checkNotNull(input); + + int pos = input.indexOf('='); + if (pos >= 0) { + String scope = input.substring(0, pos); + if (Strings.isNullOrEmpty(scope)) { + throw new OptionsParsingException("Scope of credential helper must not be empty"); + } + String path = checkPath(input.substring(pos + 1)); + return new AutoValue_AuthAndTLSOptions_UnresolvedScopedCredentialHelper( + Optional.of(scope), path); + } + + // `input` does not specify a scope. + return new AutoValue_AuthAndTLSOptions_UnresolvedScopedCredentialHelper( + Optional.empty(), checkPath(input)); + } + + private String checkPath(@Nullable String input) throws OptionsParsingException { + if (Strings.isNullOrEmpty(input)) { + throw new OptionsParsingException("Path to credential helper must not be empty"); + } + return input; + } + } } diff --git a/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelper.java b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelper.java index c82417b0034383..e410c0b52bc771 100644 --- a/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelper.java +++ b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelper.java @@ -49,7 +49,7 @@ public final class CredentialHelper { } @VisibleForTesting - Path getPath() { + public Path getPath() { return path; } diff --git a/src/main/java/com/google/devtools/build/lib/remote/BUILD b/src/main/java/com/google/devtools/build/lib/remote/BUILD index ed33a59565c956..1ed01f28639a1b 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/BUILD +++ b/src/main/java/com/google/devtools/build/lib/remote/BUILD @@ -59,6 +59,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/analysis:top_level_artifact_context", "//src/main/java/com/google/devtools/build/lib/analysis/platform:platform_utils", "//src/main/java/com/google/devtools/build/lib/authandtls", + "//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper", "//src/main/java/com/google/devtools/build/lib/bazel/repository/downloader", "//src/main/java/com/google/devtools/build/lib/buildeventstream", "//src/main/java/com/google/devtools/build/lib/clock", @@ -102,6 +103,7 @@ java_library( "//src/main/java/com/google/devtools/common/options", "//src/main/protobuf:failure_details_java_proto", "//third_party:auth", + "//third_party:auto_value", "//third_party:caffeine", "//third_party:flogger", "//third_party:guava", diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java index 19a2b7d0a85ecc..2f219457a2d712 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java +++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java @@ -19,6 +19,7 @@ import build.bazel.remote.execution.v2.DigestFunction; import build.bazel.remote.execution.v2.ServerCapabilities; import com.google.auth.Credentials; +import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Ascii; import com.google.common.base.Preconditions; @@ -45,11 +46,14 @@ import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget; import com.google.devtools.build.lib.analysis.test.TestProvider; import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions; +import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions.UnresolvedScopedCredentialHelper; import com.google.devtools.build.lib.authandtls.CallCredentialsProvider; import com.google.devtools.build.lib.authandtls.GoogleAuthUtils; import com.google.devtools.build.lib.authandtls.Netrc; import com.google.devtools.build.lib.authandtls.NetrcCredentials; import com.google.devtools.build.lib.authandtls.NetrcParser; +import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperEnvironment; +import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperProvider; import com.google.devtools.build.lib.bazel.repository.downloader.Downloader; import com.google.devtools.build.lib.buildeventstream.BuildEventArtifactUploader; import com.google.devtools.build.lib.buildeventstream.LocalFilesArtifactUploader; @@ -78,6 +82,7 @@ import com.google.devtools.build.lib.runtime.BuildEventArtifactUploaderFactory; import com.google.devtools.build.lib.runtime.Command; import com.google.devtools.build.lib.runtime.CommandEnvironment; +import com.google.devtools.build.lib.runtime.CommandLinePathFactory; import com.google.devtools.build.lib.runtime.RepositoryRemoteExecutor; import com.google.devtools.build.lib.runtime.RepositoryRemoteExecutorFactory; import com.google.devtools.build.lib.runtime.ServerBuilder; @@ -1130,4 +1135,37 @@ static Credentials newCredentials( return creds; } + + @VisibleForTesting + static CredentialHelperProvider newCredentialHelperProvider( + CredentialHelperEnvironment environment, + CommandLinePathFactory pathFactory, + List helpers) + throws IOException { + Preconditions.checkNotNull(environment); + Preconditions.checkNotNull(pathFactory); + Preconditions.checkNotNull(helpers); + + CredentialHelperProvider.Builder builder = CredentialHelperProvider.builder(); + for (UnresolvedScopedCredentialHelper helper : helpers) { + Optional scope = helper.getScope(); + Path path = pathFactory.create(environment.getClientEnvironment(), helper.getPath()); + if (scope.isPresent()) { + builder.add(scope.get(), path); + } else { + builder.add(path); + } + } + return builder.build(); + } + + @VisibleForTesting + @AutoValue + abstract static class ScopedCredentialHelper { + /** Returns the scope of the credential helper (if any). */ + public abstract Optional getScope(); + + /** Returns the path of the credential helper. */ + public abstract Path getPath(); + } } diff --git a/src/test/java/com/google/devtools/build/lib/authandtls/BUILD b/src/test/java/com/google/devtools/build/lib/authandtls/BUILD index f9125da2d92771..90634ffd83f0b4 100644 --- a/src/test/java/com/google/devtools/build/lib/authandtls/BUILD +++ b/src/test/java/com/google/devtools/build/lib/authandtls/BUILD @@ -25,6 +25,7 @@ java_library( ), deps = [ "//src/main/java/com/google/devtools/build/lib/authandtls", + "//src/main/java/com/google/devtools/common/options", "//third_party:guava", "//third_party:junit4", "//third_party:truth", diff --git a/src/test/java/com/google/devtools/build/lib/authandtls/UnresolvedScopedCredentialHelperConverterTest.java b/src/test/java/com/google/devtools/build/lib/authandtls/UnresolvedScopedCredentialHelperConverterTest.java new file mode 100644 index 00000000000000..e4708348719207 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/authandtls/UnresolvedScopedCredentialHelperConverterTest.java @@ -0,0 +1,108 @@ +// Copyright 2022 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.authandtls; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions.UnresolvedScopedCredentialHelper; +import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions.UnresolvedScopedCredentialHelperConverter; +import com.google.devtools.common.options.OptionsParsingException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link UnresolvedScopedCredentialHelperConverter}. */ +@RunWith(JUnit4.class) +public class UnresolvedScopedCredentialHelperConverterTest { + @Test + public void convertAbsolutePath() throws Exception { + UnresolvedScopedCredentialHelper helper1 = + UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("/absolute/path"); + assertThat(helper1.getScope()).isEmpty(); + assertThat(helper1.getPath()).isEqualTo("/absolute/path"); + + UnresolvedScopedCredentialHelper helper2 = + UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("example.com=/absolute/path"); + assertThat(helper2.getScope()).hasValue("example.com"); + assertThat(helper2.getPath()).isEqualTo("/absolute/path"); + + UnresolvedScopedCredentialHelper helper3 = + UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("*.example.com=/absolute/path"); + assertThat(helper3.getScope()).hasValue("*.example.com"); + assertThat(helper3.getPath()).isEqualTo("/absolute/path"); + } + + @Test + public void convertRootRelativePath() throws Exception { + UnresolvedScopedCredentialHelper helper1 = + UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("%workspace%/path"); + assertThat(helper1.getScope()).isEmpty(); + assertThat(helper1.getPath()).isEqualTo("%workspace%/path"); + + UnresolvedScopedCredentialHelper helper2 = + UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("example.com=%workspace%/path"); + assertThat(helper2.getScope()).hasValue("example.com"); + assertThat(helper2.getPath()).isEqualTo("%workspace%/path"); + + UnresolvedScopedCredentialHelper helper3 = + UnresolvedScopedCredentialHelperConverter.INSTANCE.convert( + "*.example.com=%workspace%/path"); + assertThat(helper3.getScope()).hasValue("*.example.com"); + assertThat(helper3.getPath()).isEqualTo("%workspace%/path"); + } + + @Test + public void convertPathLookup() throws Exception { + UnresolvedScopedCredentialHelper helper1 = + UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("foo"); + assertThat(helper1.getScope()).isEmpty(); + assertThat(helper1.getPath()).isEqualTo("foo"); + + UnresolvedScopedCredentialHelper helper2 = + UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("example.com=foo"); + assertThat(helper2.getScope()).hasValue("example.com"); + assertThat(helper2.getPath()).isEqualTo("foo"); + + UnresolvedScopedCredentialHelper helper3 = + UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("*.example.com=foo"); + assertThat(helper3.getScope()).hasValue("*.example.com"); + assertThat(helper3.getPath()).isEqualTo("foo"); + } + + @Test + public void emptyPath() { + assertThrows( + OptionsParsingException.class, + () -> UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("")); + assertThrows( + OptionsParsingException.class, + () -> UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("foo=")); + assertThrows( + OptionsParsingException.class, + () -> UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("=")); + } + + @Test + public void emptyScope() { + assertThrows( + OptionsParsingException.class, + () -> UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("=/foo")); + assertThrows( + OptionsParsingException.class, + () -> UnresolvedScopedCredentialHelperConverter.INSTANCE.convert("=")); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/remote/BUILD b/src/test/java/com/google/devtools/build/lib/remote/BUILD index 44df625842371a..0102233157caee 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/BUILD +++ b/src/test/java/com/google/devtools/build/lib/remote/BUILD @@ -56,6 +56,7 @@ java_test( "//src/main/java/com/google/devtools/build/lib/analysis:server_directories", "//src/main/java/com/google/devtools/build/lib/analysis/platform:platform_utils", "//src/main/java/com/google/devtools/build/lib/authandtls", + "//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper", "//src/main/java/com/google/devtools/build/lib/buildeventstream", "//src/main/java/com/google/devtools/build/lib/clock", "//src/main/java/com/google/devtools/build/lib/collect/nestedset", diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteModuleTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteModuleTest.java index bfc0171b6b84f6..e15cdf3ecbe2c4 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/RemoteModuleTest.java +++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteModuleTest.java @@ -14,6 +14,7 @@ package com.google.devtools.build.lib.remote; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; @@ -27,6 +28,7 @@ import build.bazel.remote.execution.v2.GetCapabilitiesRequest; import build.bazel.remote.execution.v2.ServerCapabilities; import com.google.auth.Credentials; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -35,7 +37,10 @@ import com.google.devtools.build.lib.analysis.ServerDirectories; import com.google.devtools.build.lib.analysis.config.CoreOptions; import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions; +import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions.UnresolvedScopedCredentialHelper; import com.google.devtools.build.lib.authandtls.BasicHttpAuthenticationEncoder; +import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperEnvironment; +import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperProvider; import com.google.devtools.build.lib.events.Reporter; import com.google.devtools.build.lib.exec.BinTools; import com.google.devtools.build.lib.exec.ExecutionOptions; @@ -47,15 +52,18 @@ import com.google.devtools.build.lib.runtime.ClientOptions; import com.google.devtools.build.lib.runtime.Command; import com.google.devtools.build.lib.runtime.CommandEnvironment; +import com.google.devtools.build.lib.runtime.CommandLinePathFactory; import com.google.devtools.build.lib.runtime.CommonCommandOptions; import com.google.devtools.build.lib.runtime.commands.BuildCommand; import com.google.devtools.build.lib.testutil.Scratch; import com.google.devtools.build.lib.util.AbruptExitException; import com.google.devtools.build.lib.vfs.DigestHashFunction; import com.google.devtools.build.lib.vfs.FileSystem; +import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; import com.google.devtools.common.options.Options; import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.OptionsParsingException; import com.google.devtools.common.options.OptionsParsingResult; import io.grpc.BindableService; import io.grpc.Server; @@ -65,7 +73,9 @@ import io.grpc.stub.StreamObserver; import io.grpc.util.MutableHandlerRegistry; import java.io.IOException; +import java.io.OutputStream; import java.net.URI; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -576,4 +586,258 @@ private static void assertRequestMetadata( assertThat(Iterables.getOnlyElement(requestMetadata.values())) .containsExactly(BasicHttpAuthenticationEncoder.encode(username, password, UTF_8)); } + + @Test + public void testCredentialHelperProvider() throws Exception { + FileSystem fileSystem = new InMemoryFileSystem(DigestHashFunction.SHA256); + + Path workspace = fileSystem.getPath("/workspace"); + Path pathValue = fileSystem.getPath("/usr/local/bin"); + pathValue.createDirectoryAndParents(); + + CredentialHelperEnvironment credentialHelperEnvironment = + CredentialHelperEnvironment.newBuilder() + .setEventReporter(new Reporter(new EventBus())) + .setWorkspacePath(workspace) + .setClientEnvironment(ImmutableMap.of("PATH", pathValue.getPathString())) + .setHelperExecutionTimeout(Duration.ZERO) + .build(); + CommandLinePathFactory commandLinePathFactory = + new CommandLinePathFactory(fileSystem, ImmutableMap.of("workspace", workspace)); + + Path unusedHelper = createExecutable(fileSystem, "/unused/helper"); + + Path defaultHelper = createExecutable(fileSystem, "/default/helper"); + Path exampleComHelper = createExecutable(fileSystem, "/example/com/helper"); + Path fooExampleComHelper = createExecutable(fileSystem, "/foo/example/com/helper"); + Path exampleComWildcardHelper = createExecutable(fileSystem, "/example/com/wildcard/helper"); + + Path exampleOrgHelper = createExecutable(workspace.getRelative("helpers/example-org")); + + // No helpers. + CredentialHelperProvider credentialHelperProvider1 = + newCredentialHelperProvider( + credentialHelperEnvironment, commandLinePathFactory, ImmutableList.of()); + assertThat(credentialHelperProvider1.findCredentialHelper(URI.create("https://example.com"))) + .isEmpty(); + assertThat( + credentialHelperProvider1.findCredentialHelper(URI.create("https://foo.example.com"))) + .isEmpty(); + + // Default helper only. + CredentialHelperProvider credentialHelperProvider2 = + newCredentialHelperProvider( + credentialHelperEnvironment, + commandLinePathFactory, + ImmutableList.of(defaultHelper.getPathString())); + assertThat( + credentialHelperProvider2 + .findCredentialHelper(URI.create("https://example.com")) + .get() + .getPath()) + .isEqualTo(defaultHelper); + assertThat( + credentialHelperProvider2 + .findCredentialHelper(URI.create("https://foo.example.com")) + .get() + .getPath()) + .isEqualTo(defaultHelper); + + // Default and exact match. + CredentialHelperProvider credentialHelperProvider3 = + newCredentialHelperProvider( + credentialHelperEnvironment, + commandLinePathFactory, + ImmutableList.of( + defaultHelper.getPathString(), "example.com=" + exampleComHelper.getPathString())); + assertThat( + credentialHelperProvider3 + .findCredentialHelper(URI.create("https://example.com")) + .get() + .getPath()) + .isEqualTo(exampleComHelper); + assertThat( + credentialHelperProvider3 + .findCredentialHelper(URI.create("https://foo.example.com")) + .get() + .getPath()) + .isEqualTo(defaultHelper); + + // Exact match without default. + CredentialHelperProvider credentialHelperProvider4 = + newCredentialHelperProvider( + credentialHelperEnvironment, + commandLinePathFactory, + ImmutableList.of("example.com=" + exampleComHelper.getPathString())); + assertThat( + credentialHelperProvider4 + .findCredentialHelper(URI.create("https://example.com")) + .get() + .getPath()) + .isEqualTo(exampleComHelper); + assertThat( + credentialHelperProvider4.findCredentialHelper(URI.create("https://foo.example.com"))) + .isEmpty(); + + // Multiple scoped helpers with default. + CredentialHelperProvider credentialHelperProvider5 = + newCredentialHelperProvider( + credentialHelperEnvironment, + commandLinePathFactory, + ImmutableList.of( + defaultHelper.getPathString(), + "example.com=" + exampleComHelper.getPathString(), + "*.foo.example.com=" + fooExampleComHelper.getPathString(), + "*.example.com=" + exampleComWildcardHelper.getPathString(), + "example.org=%workspace%/helpers/example-org")); + assertThat( + credentialHelperProvider5 + .findCredentialHelper(URI.create("https://anotherdomain.com")) + .get() + .getPath()) + .isEqualTo(defaultHelper); + assertThat( + credentialHelperProvider5 + .findCredentialHelper(URI.create("https://example.com")) + .get() + .getPath()) + .isEqualTo(exampleComHelper); + assertThat( + credentialHelperProvider5 + .findCredentialHelper(URI.create("https://foo.example.com")) + .get() + .getPath()) + .isEqualTo(fooExampleComHelper); + assertThat( + credentialHelperProvider5 + .findCredentialHelper(URI.create("https://abc.foo.example.com")) + .get() + .getPath()) + .isEqualTo(fooExampleComHelper); + assertThat( + credentialHelperProvider5 + .findCredentialHelper(URI.create("https://bar.example.com")) + .get() + .getPath()) + .isEqualTo(exampleComWildcardHelper); + assertThat( + credentialHelperProvider5 + .findCredentialHelper(URI.create("https://abc.bar.example.com")) + .get() + .getPath()) + .isEqualTo(exampleComWildcardHelper); + assertThat( + credentialHelperProvider5 + .findCredentialHelper(URI.create("https://example.org")) + .get() + .getPath()) + .isEqualTo(exampleOrgHelper); + + // Helpers override. + CredentialHelperProvider credentialHelperProvider6 = + newCredentialHelperProvider( + credentialHelperEnvironment, + commandLinePathFactory, + ImmutableList.of( + // + unusedHelper.getPathString(), + + // + defaultHelper.getPathString(), + "example.com=" + unusedHelper.getPathString(), + "*.example.com=" + unusedHelper.getPathString(), + "example.org=" + unusedHelper.getPathString(), + "*.example.org=" + exampleOrgHelper.getPathString(), + + // + "*.example.com=" + exampleComWildcardHelper.getPathString(), + "example.org=" + exampleOrgHelper.getPathString(), + "*.foo.example.com=" + unusedHelper.getPathString(), + + // + "example.com=" + exampleComHelper.getPathString(), + "*.foo.example.com=" + fooExampleComHelper.getPathString())); + assertThat( + credentialHelperProvider6 + .findCredentialHelper(URI.create("https://anotherdomain.com")) + .get() + .getPath()) + .isEqualTo(defaultHelper); + assertThat( + credentialHelperProvider6 + .findCredentialHelper(URI.create("https://example.com")) + .get() + .getPath()) + .isEqualTo(exampleComHelper); + assertThat( + credentialHelperProvider6 + .findCredentialHelper(URI.create("https://foo.example.com")) + .get() + .getPath()) + .isEqualTo(fooExampleComHelper); + assertThat( + credentialHelperProvider6 + .findCredentialHelper(URI.create("https://bar.example.com")) + .get() + .getPath()) + .isEqualTo(exampleComWildcardHelper); + assertThat( + credentialHelperProvider6 + .findCredentialHelper(URI.create("https://example.org")) + .get() + .getPath()) + .isEqualTo(exampleOrgHelper); + assertThat( + credentialHelperProvider6 + .findCredentialHelper(URI.create("https://foo.example.org")) + .get() + .getPath()) + .isEqualTo(exampleOrgHelper); + } + + private static Path createExecutable(FileSystem fileSystem, String path) throws IOException { + Preconditions.checkNotNull(fileSystem); + Preconditions.checkNotNull(path); + + return createExecutable(fileSystem.getPath(path)); + } + + private static Path createExecutable(Path path) throws IOException { + Preconditions.checkNotNull(path); + + path.getParentDirectory().createDirectoryAndParents(); + try (OutputStream unused = path.getOutputStream()) { + // Nothing to do. + } + path.setExecutable(true); + + return path; + } + + private static CredentialHelperProvider newCredentialHelperProvider( + CredentialHelperEnvironment credentialHelperEnvironment, + CommandLinePathFactory commandLinePathFactory, + ImmutableList inputs) + throws Exception { + Preconditions.checkNotNull(credentialHelperEnvironment); + Preconditions.checkNotNull(commandLinePathFactory); + Preconditions.checkNotNull(inputs); + + return RemoteModule.newCredentialHelperProvider( + credentialHelperEnvironment, + commandLinePathFactory, + ImmutableList.copyOf( + Iterables.transform(inputs, s -> createUnresolvedScopedCredentialHelper(s)))); + } + + private static UnresolvedScopedCredentialHelper createUnresolvedScopedCredentialHelper( + String input) { + Preconditions.checkNotNull(input); + + try { + return AuthAndTLSOptions.UnresolvedScopedCredentialHelperConverter.INSTANCE.convert(input); + } catch (OptionsParsingException e) { + throw new IllegalStateException(e); + } + } }