diff --git a/src/main/java/com/google/devtools/build/lib/authandtls/BUILD b/src/main/java/com/google/devtools/build/lib/authandtls/BUILD index 1908736e03c8f3..23d2909923954d 100644 --- a/src/main/java/com/google/devtools/build/lib/authandtls/BUILD +++ b/src/main/java/com/google/devtools/build/lib/authandtls/BUILD @@ -4,7 +4,9 @@ package(default_visibility = ["//src:__subpackages__"]) filegroup( name = "srcs", - srcs = glob(["**"]), + srcs = glob(["**"]) + [ + "//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper:srcs", + ], visibility = ["//src:__subpackages__"], ) diff --git a/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD new file mode 100644 index 00000000000000..caed6e13c058d3 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD @@ -0,0 +1,21 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//src:__subpackages__"]) + +licenses(["notice"]) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src:__subpackages__"], +) + +java_library( + name = "credentialhelper", + srcs = glob(["*.java"]), + deps = [ + "//src/main/java/com/google/devtools/build/lib/vfs", + "//third_party:error_prone_annotations", + "//third_party:guava", + ], +) 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 new file mode 100644 index 00000000000000..c6b60b1fb1a0b4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelper.java @@ -0,0 +1,39 @@ +// 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.credentialhelper; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.vfs.Path; +import com.google.errorprone.annotations.Immutable; + +/** Wraps an external tool used to obtain credentials. */ +@Immutable +public final class CredentialHelper { + // `Path` is immutable, but not annotated. + @SuppressWarnings("Immutable") + private final Path path; + + CredentialHelper(Path path) { + this.path = Preconditions.checkNotNull(path); + } + + @VisibleForTesting + Path getPath() { + return path; + } + + // TODO(yannic): Implement running the helper subprocess. +} diff --git a/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelperProvider.java b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelperProvider.java new file mode 100644 index 00000000000000..ded7e5eab3c0d0 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelperProvider.java @@ -0,0 +1,190 @@ +// 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.credentialhelper; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.vfs.Path; +import com.google.errorprone.annotations.Immutable; +import java.io.IOException; +import java.net.IDN; +import java.net.URI; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +/** + * A provider for {@link CredentialHelper}s. + * + *
This class is used to find the right {@link CredentialHelper} for a {@link URI}, using the
+ * most specific match.
+ */
+@Immutable
+public final class CredentialHelperProvider {
+ // `Path` is immutable, but not annotated.
+ @SuppressWarnings("Immutable")
+ private final Optional As of 2022-06-20, only matching based on (wildcard) domain name is supported.
+ *
+ * If {@code pattern} starts with {@code *.}, it is considered a wildcard pattern matching
+ * all subdomains in addition to the domain itself. For example {@code *.example.com} would
+ * match {@code example.com}, {@code foo.example.com}, {@code bar.example.com}, {@code
+ * baz.bar.example.com} and so on, but not anything that isn't a subdomain of {@code
+ * example.com}.
+ */
+ public Builder add(String pattern, Path helper) throws IOException {
+ Preconditions.checkNotNull(pattern);
+ checkHelper(helper);
+
+ String punycodePattern = toPunycodePattern(pattern);
+ Preconditions.checkArgument(
+ DOMAIN_PATTERN.matcher(punycodePattern).matches(),
+ "Pattern '%s' is not a valid (wildcard) DNS name",
+ pattern);
+
+ if (pattern.startsWith("*.")) {
+ suffixToHelper.put(punycodePattern.substring(2), helper);
+ } else {
+ hostToHelper.put(punycodePattern, helper);
+ }
+
+ return this;
+ }
+
+ /** Converts a pattern to Punycode (see https://en.wikipedia.org/wiki/Punycode). */
+ private final String toPunycodePattern(String pattern) {
+ Preconditions.checkNotNull(pattern);
+
+ try {
+ return IDN.toASCII(pattern);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(
+ String.format(Locale.US, "Could not convert '%s' to punycode", pattern), e);
+ }
+ }
+
+ public CredentialHelperProvider build() {
+ return new CredentialHelperProvider(
+ defaultHelper, ImmutableMap.copyOf(hostToHelper), ImmutableMap.copyOf(suffixToHelper));
+ }
+ }
+}
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 7bef4b2873c15e..f9125da2d92771 100644
--- a/src/test/java/com/google/devtools/build/lib/authandtls/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/authandtls/BUILD
@@ -10,7 +10,9 @@ licenses(["notice"])
filegroup(
name = "srcs",
testonly = 0,
- srcs = glob(["**"]),
+ srcs = glob(["**"]) + [
+ "//src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper:srcs",
+ ],
visibility = ["//src:__subpackages__"],
)
diff --git a/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD
new file mode 100644
index 00000000000000..e904e6d99349fb
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/BUILD
@@ -0,0 +1,33 @@
+load("@rules_java//java:defs.bzl", "java_test")
+
+package(
+ default_testonly = 1,
+ default_visibility = ["//src:__subpackages__"],
+)
+
+licenses(["notice"])
+
+filegroup(
+ name = "srcs",
+ testonly = 0,
+ srcs = glob(["**"]),
+ visibility = ["//src:__subpackages__"],
+)
+
+java_test(
+ name = "credentialhelper",
+ srcs = glob(["*.java"]),
+ test_class = "com.google.devtools.build.lib.AllTests",
+ runtime_deps = [
+ "//src/test/java/com/google/devtools/build/lib:test_runner",
+ ],
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper",
+ "//src/main/java/com/google/devtools/build/lib/vfs",
+ "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
+ "//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelperProviderTest.java b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelperProviderTest.java
new file mode 100644
index 00000000000000..25b6c03ff8e6b2
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/authandtls/credentialhelper/CredentialHelperProviderTest.java
@@ -0,0 +1,376 @@
+// 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.credentialhelper;
+
+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.common.base.Preconditions;
+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.PathFragment;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import java.io.OutputStream;
+import java.net.URI;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link CredentialHelperProvider}. */
+@RunWith(JUnit4.class)
+public class CredentialHelperProviderTest {
+ private static final PathFragment DEFAULT_HELPER_PATH =
+ PathFragment.create("/path/to/default/helper");
+ private static final PathFragment EXAMPLE_COM_HELPER_PATH =
+ PathFragment.create("/path/to/example/com/helper");
+ private static final PathFragment EXAMPLE_COM_WILDCARD_HELPER_PATH =
+ PathFragment.create("/path/to/example/com/wildcard/helper");
+ private static final PathFragment SUB_EXAMPLE_COM_WILDCARD_HELPER_PATH =
+ PathFragment.create("/path/to/sub/example/com/wildcard/helper");
+
+ private final FileSystem fileSystem = new InMemoryFileSystem(DigestHashFunction.SHA256);
+
+ @Before
+ public void setUp() throws Exception {
+ setUpHelper(fileSystem.getPath(DEFAULT_HELPER_PATH));
+ setUpHelper(fileSystem.getPath(EXAMPLE_COM_HELPER_PATH));
+ setUpHelper(fileSystem.getPath(EXAMPLE_COM_WILDCARD_HELPER_PATH));
+ setUpHelper(fileSystem.getPath(SUB_EXAMPLE_COM_WILDCARD_HELPER_PATH));
+ }
+
+ private void setUpHelper(Path path) throws Exception {
+ Preconditions.checkNotNull(path);
+
+ path.getParentDirectory().createDirectoryAndParents();
+ try (OutputStream stream = path.getOutputStream()) {
+ // Just create an empty file, nothing to do.
+ }
+ path.setExecutable(true);
+ }
+
+ @Test
+ public void noHelpersConfigured() {
+ CredentialHelperProvider provider = CredentialHelperProvider.builder().build();
+
+ assertThat(provider.findCredentialHelper(URI.create("http://example.com/foo"))).isEmpty();
+ assertThat(provider.findCredentialHelper(URI.create("https://example.com/foo"))).isEmpty();
+ assertThat(provider.findCredentialHelper(URI.create("grpc://example.com/foo"))).isEmpty();
+ assertThat(provider.findCredentialHelper(URI.create("grpcs://example.com/foo"))).isEmpty();
+ assertThat(provider.findCredentialHelper(URI.create("custom://example.com/foo"))).isEmpty();
+
+ assertThat(provider.findCredentialHelper(URI.create("https://subdomain.example.com/bar")))
+ .isEmpty();
+ assertThat(provider.findCredentialHelper(URI.create("https://other-domain.com"))).isEmpty();
+ }
+
+ private void assertInvalidPattern(String pattern) {
+ Preconditions.checkNotNull(pattern);
+
+ IllegalArgumentException exception =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ CredentialHelperProvider.builder()
+ .add(pattern, fileSystem.getPath(DEFAULT_HELPER_PATH)));
+ assertThat(exception).hasMessageThat().contains(pattern);
+ }
+
+ @Test
+ public void invalidPattern() throws Exception {
+ assertInvalidPattern("foo.*.example.com");
+ assertInvalidPattern("*.foo.*.example.com");
+ assertInvalidPattern("*-foo.example.com");
+ assertInvalidPattern("example.*");
+ assertInvalidPattern("*.example.*");
+
+ // Punycode
+ assertInvalidPattern("foo.*.münchen.de");
+ assertInvalidPattern(".*.münchen.de");
+ assertInvalidPattern("foo-*.münchen.de");
+ }
+
+ @Test
+ public void addNonExecutableDefaultHelper() throws Exception {
+ Path helper = fileSystem.getPath("/path/to/non/executable");
+ setUpHelper(helper);
+ helper.setExecutable(false);
+ CredentialHelperProvider.Builder provider = CredentialHelperProvider.builder();
+
+ IllegalArgumentException exception =
+ assertThrows(IllegalArgumentException.class, () -> provider.add(helper));
+ assertThat(exception).hasMessageThat().contains("is not executable");
+ }
+
+ @Test
+ public void onlyDefaultHelper() throws Exception {
+ Path helper = fileSystem.getPath(DEFAULT_HELPER_PATH);
+ CredentialHelperProvider provider = CredentialHelperProvider.builder().add(helper).build();
+
+ assertThat(provider.findCredentialHelper(URI.create("http://example.com/foo")).get().getPath())
+ .isEqualTo(helper);
+ assertThat(provider.findCredentialHelper(URI.create("https://example.com/foo")).get().getPath())
+ .isEqualTo(helper);
+ assertThat(provider.findCredentialHelper(URI.create("grpc://example.com/foo")).get().getPath())
+ .isEqualTo(helper);
+ assertThat(provider.findCredentialHelper(URI.create("grpcs://example.com/foo")).get().getPath())
+ .isEqualTo(helper);
+ assertThat(
+ provider.findCredentialHelper(URI.create("custom://example.com/foo")).get().getPath())
+ .isEqualTo(helper);
+
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://subdomain.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(helper);
+ assertThat(
+ provider.findCredentialHelper(URI.create("https://other-domain.com")).get().getPath())
+ .isEqualTo(helper);
+ }
+
+ @Test
+ public void withHostHelpersAndDefaultFallback() throws Exception {
+ Path defaultHelper = fileSystem.getPath(DEFAULT_HELPER_PATH);
+ Path exampleComHelper = fileSystem.getPath(EXAMPLE_COM_HELPER_PATH);
+ CredentialHelperProvider provider =
+ CredentialHelperProvider.builder()
+ .add(defaultHelper)
+ .add("example.com", exampleComHelper)
+ .build();
+
+ assertThat(provider.findCredentialHelper(URI.create("http://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+ assertThat(provider.findCredentialHelper(URI.create("https://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+ assertThat(provider.findCredentialHelper(URI.create("grpc://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+ assertThat(provider.findCredentialHelper(URI.create("grpcs://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+ assertThat(
+ provider.findCredentialHelper(URI.create("custom://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://subdomain.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(defaultHelper);
+ assertThat(
+ provider.findCredentialHelper(URI.create("https://other-domain.com")).get().getPath())
+ .isEqualTo(defaultHelper);
+ }
+
+ @Test
+ public void wildcardMatching() throws Exception {
+ Path defaultHelper = fileSystem.getPath(DEFAULT_HELPER_PATH);
+ Path exampleComWildcardHelper = fileSystem.getPath(EXAMPLE_COM_WILDCARD_HELPER_PATH);
+ CredentialHelperProvider provider =
+ CredentialHelperProvider.builder()
+ .add(defaultHelper)
+ .add("*.example.com", exampleComWildcardHelper)
+ .build();
+
+ assertThat(provider.findCredentialHelper(URI.create("http://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(provider.findCredentialHelper(URI.create("https://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(provider.findCredentialHelper(URI.create("grpc://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(provider.findCredentialHelper(URI.create("grpcs://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(
+ provider.findCredentialHelper(URI.create("custom://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComWildcardHelper);
+
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://subdomain.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://subdomain2.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://sub.subdomain.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://subdomain.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(exampleComWildcardHelper);
+
+ assertThat(
+ provider.findCredentialHelper(URI.create("https://other-domain.com")).get().getPath())
+ .isEqualTo(defaultHelper);
+ }
+
+ @Test
+ public void preferExactMatchOverWildcardMatching() throws Exception {
+ Path defaultHelper = fileSystem.getPath(DEFAULT_HELPER_PATH);
+ Path exampleComHelper = fileSystem.getPath(EXAMPLE_COM_HELPER_PATH);
+ Path exampleComWildcardHelper = fileSystem.getPath(EXAMPLE_COM_WILDCARD_HELPER_PATH);
+ CredentialHelperProvider provider =
+ CredentialHelperProvider.builder()
+ .add(defaultHelper)
+ .add("example.com", exampleComHelper)
+ .add("*.example.com", exampleComWildcardHelper)
+ .build();
+
+ assertThat(provider.findCredentialHelper(URI.create("http://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+ assertThat(provider.findCredentialHelper(URI.create("https://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+ assertThat(provider.findCredentialHelper(URI.create("grpc://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+ assertThat(provider.findCredentialHelper(URI.create("grpcs://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+ assertThat(
+ provider.findCredentialHelper(URI.create("custom://example.com/foo")).get().getPath())
+ .isEqualTo(exampleComHelper);
+
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://subdomain.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://subdomain2.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://sub.subdomain.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://subdomain.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(exampleComWildcardHelper);
+
+ assertThat(
+ provider.findCredentialHelper(URI.create("https://other-domain.com")).get().getPath())
+ .isEqualTo(defaultHelper);
+ }
+
+ @Test
+ public void preferMostSpecificWildcardMatch() throws Exception {
+ Path exampleComWildcardHelper = fileSystem.getPath(EXAMPLE_COM_WILDCARD_HELPER_PATH);
+ Path subExampleComWildcardHelper = fileSystem.getPath(SUB_EXAMPLE_COM_WILDCARD_HELPER_PATH);
+ CredentialHelperProvider provider =
+ CredentialHelperProvider.builder()
+ .add("*.example.com", exampleComWildcardHelper)
+ .add("*.sub.example.com", subExampleComWildcardHelper)
+ .build();
+
+ assertThat(provider.findCredentialHelper(URI.create("https://example.com/bar")).get().getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://foo.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(exampleComWildcardHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://sub.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(subExampleComWildcardHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("https://foo.sub.example.com/bar"))
+ .get()
+ .getPath())
+ .isEqualTo(subExampleComWildcardHelper);
+ }
+
+ @Test
+ public void punycodeMatching() throws Exception {
+ Path defaultHelper = fileSystem.getPath(DEFAULT_HELPER_PATH);
+ Path specificHelper = fileSystem.getPath(EXAMPLE_COM_HELPER_PATH);
+ Path subdomainHelper = fileSystem.getPath(EXAMPLE_COM_HELPER_PATH);
+
+ CredentialHelperProvider provider =
+ CredentialHelperProvider.builder()
+ .add(defaultHelper)
+ .add("münchen.de", specificHelper)
+ .add("*.köln.de", subdomainHelper)
+ .build();
+
+ // münchen.de
+ assertThat(
+ provider.findCredentialHelper(URI.create("http://xn--mnchen-3ya.de")).get().getPath())
+ .isEqualTo(specificHelper);
+ assertThat(
+ provider
+ .findCredentialHelper(URI.create("http://foo.xn--mnchen-3ya.de"))
+ .get()
+ .getPath())
+ .isEqualTo(defaultHelper);
+ assertThat(provider.findCredentialHelper(URI.create("http://muenchen.de")).get().getPath())
+ .isEqualTo(defaultHelper);
+
+ // köln.de
+ assertThat(provider.findCredentialHelper(URI.create("http://xn--kln-sna.de")).get().getPath())
+ .isEqualTo(subdomainHelper);
+ assertThat(
+ provider.findCredentialHelper(URI.create("http://foo.xn--kln-sna.de")).get().getPath())
+ .isEqualTo(subdomainHelper);
+ assertThat(
+ provider.findCredentialHelper(URI.create("http://bar.xn--kln-sna.de")).get().getPath())
+ .isEqualTo(subdomainHelper);
+ assertThat(provider.findCredentialHelper(URI.create("http://koeln.de")).get().getPath())
+ .isEqualTo(defaultHelper);
+
+ // småland.se
+ assertThat(
+ provider.findCredentialHelper(URI.create("http://xn--smland-jua.se")).get().getPath())
+ .isEqualTo(defaultHelper);
+ }
+
+ @Test
+ public void parentDomain() {
+ assertThat(CredentialHelperProvider.parentDomain("com")).isEmpty();
+
+ assertThat(CredentialHelperProvider.parentDomain("foo.example.com")).hasValue("example.com");
+ assertThat(CredentialHelperProvider.parentDomain("example.com")).hasValue("com");
+
+ // Punycode URIs (münchen.de).
+ assertThat(CredentialHelperProvider.parentDomain("foo.xn--mnchen-3ya.de"))
+ .hasValue("xn--mnchen-3ya.de");
+ assertThat(CredentialHelperProvider.parentDomain("bar.foo.xn--mnchen-3ya.de"))
+ .hasValue("foo.xn--mnchen-3ya.de");
+ assertThat(CredentialHelperProvider.parentDomain("xn--mnchen-3ya.de")).hasValue("de");
+ }
+}