| // 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.github.benmanes.caffeine.cache.Cache; |
| import com.google.auth.Credentials; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Implementation of {@link Credentials} which fetches credentials by invoking a {@code credential |
| * helper} as subprocess, falling back to another {@link Credentials} if no suitable helper exists. |
| */ |
| public class CredentialHelperCredentials extends Credentials { |
| private final CredentialHelperProvider credentialHelperProvider; |
| private final CredentialHelperEnvironment credentialHelperEnvironment; |
| private final Cache<URI, ImmutableMap<String, ImmutableList<String>>> credentialCache; |
| private final Optional<Credentials> fallbackCredentials; |
| |
| /** Wraps around an {@link IOException} so we can smuggle it through {@link Cache#get}. */ |
| public static final class WrappedIOException extends RuntimeException { |
| private final IOException wrapped; |
| |
| WrappedIOException(IOException e) { |
| super(e); |
| this.wrapped = e; |
| } |
| |
| IOException getWrapped() { |
| return wrapped; |
| } |
| } |
| |
| public CredentialHelperCredentials( |
| CredentialHelperProvider credentialHelperProvider, |
| CredentialHelperEnvironment credentialHelperEnvironment, |
| Cache<URI, ImmutableMap<String, ImmutableList<String>>> credentialCache, |
| Optional<Credentials> fallbackCredentials) { |
| this.credentialHelperProvider = Preconditions.checkNotNull(credentialHelperProvider); |
| this.credentialHelperEnvironment = Preconditions.checkNotNull(credentialHelperEnvironment); |
| this.credentialCache = Preconditions.checkNotNull(credentialCache); |
| this.fallbackCredentials = Preconditions.checkNotNull(fallbackCredentials); |
| } |
| |
| @Override |
| public String getAuthenticationType() { |
| if (fallbackCredentials.isPresent()) { |
| return "credential-helper-with-fallback-" + fallbackCredentials.get().getAuthenticationType(); |
| } |
| |
| return "credential-helper"; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") // Map<String, ImmutableList<String>> to Map<String<List<String>> |
| public Map<String, List<String>> getRequestMetadata(URI uri) throws IOException { |
| Preconditions.checkNotNull(uri); |
| |
| ImmutableMap<String, ImmutableList<String>> credentials; |
| try { |
| credentials = credentialCache.get(uri, this::getCredentialsFromHelper); |
| } catch (WrappedIOException e) { |
| throw e.getWrapped(); |
| } |
| if (credentials != null) { |
| return (Map) credentials; |
| } |
| |
| if (fallbackCredentials.isPresent()) { |
| return fallbackCredentials.get().getRequestMetadata(uri); |
| } |
| |
| return ImmutableMap.of(); |
| } |
| |
| @Nullable |
| private ImmutableMap<String, ImmutableList<String>> getCredentialsFromHelper(URI uri) { |
| Preconditions.checkNotNull(uri); |
| |
| Optional<CredentialHelper> maybeCredentialHelper = |
| credentialHelperProvider.findCredentialHelper(uri); |
| if (maybeCredentialHelper.isEmpty()) { |
| return null; |
| } |
| CredentialHelper credentialHelper = maybeCredentialHelper.get(); |
| |
| GetCredentialsResponse response; |
| try { |
| response = credentialHelper.getCredentials(credentialHelperEnvironment, uri); |
| } catch (IOException e) { |
| throw new WrappedIOException(e); |
| } |
| if (response == null) { |
| return null; |
| } |
| |
| return response.getHeaders(); |
| } |
| |
| @Override |
| public boolean hasRequestMetadata() { |
| return true; |
| } |
| |
| @Override |
| public boolean hasRequestMetadataOnly() { |
| return false; |
| } |
| |
| @Override |
| public void refresh() throws IOException { |
| if (fallbackCredentials.isPresent()) { |
| fallbackCredentials.get().refresh(); |
| } |
| |
| credentialCache.invalidateAll(); |
| } |
| } |