1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15
16 package software.amazon.awssdk.auth.credentials;
17
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.List;
23 import software.amazon.awssdk.annotations.SdkPublicApi;
24 import software.amazon.awssdk.core.exception.SdkClientException;
25 import software.amazon.awssdk.utils.IoUtils;
26 import software.amazon.awssdk.utils.Logger;
27 import software.amazon.awssdk.utils.SdkAutoCloseable;
28 import software.amazon.awssdk.utils.ToString;
29 import software.amazon.awssdk.utils.Validate;
30
31 /**
32  * {@link AwsCredentialsProvider} implementation that chains together multiple credentials providers.
33  *
34  * <p>When a caller first requests credentials from this provider, it calls all the providers in the chain, in the original order
35  * specified, until one can provide credentials, and then returns those credentials. If all of the credential providers in the
36  * chain have been called, and none of them can provide credentials, then this class will throw an exception indicated that no
37  * credentials are available.</p>
38  *
39  * <p>By defaultthis class will remember the first credentials provider in the chain that was able to provide credentials, and
40  * will continue to use that provider when credentials are requested in the future, instead of traversing the chain each time.
41  * This behavior can be controlled through the {@link Builder#reuseLastProviderEnabled(Boolean)} method.</p>
42  *
43  * <p>This chain implements {@link AutoCloseable}. When closed, it will call the {@link AutoCloseable#close()} on any credential
44  * providers in the chain that need to be closed.</p>
45  */

46 @SdkPublicApi
47 public final class AwsCredentialsProviderChain implements AwsCredentialsProvider, SdkAutoCloseable {
48     private static final Logger log = Logger.loggerFor(AwsCredentialsProviderChain.class);
49
50     private final List<AwsCredentialsProvider> credentialsProviders;
51
52     private final boolean reuseLastProviderEnabled;
53
54     private volatile AwsCredentialsProvider lastUsedProvider;
55
56     /**
57      * @see #builder()
58      */

59     private AwsCredentialsProviderChain(BuilderImpl builder) {
60         this.reuseLastProviderEnabled = builder.reuseLastProviderEnabled;
61         this.credentialsProviders = Collections.unmodifiableList(
62                 Validate.notEmpty(builder.credentialsProviders, "No credential providers were specified."));
63     }
64
65     /**
66      * Get a new builder for creating a {@link AwsCredentialsProviderChain}.
67      */

68     public static Builder builder() {
69         return new BuilderImpl();
70     }
71
72     /**
73      * Create an AWS credentials provider chain with default configuration that checks the given credential providers.
74      * @param awsCredentialsProviders The credentials providers that should be checked for credentials, in the order they should
75      *                                be checked.
76      * @return A credential provider chain that checks the provided credential providers in order.
77      */

78     public static AwsCredentialsProviderChain of(AwsCredentialsProvider... awsCredentialsProviders) {
79         return builder().credentialsProviders(awsCredentialsProviders).build();
80     }
81
82     @Override
83     public AwsCredentials resolveCredentials() {
84         if (reuseLastProviderEnabled && lastUsedProvider != null) {
85             return lastUsedProvider.resolveCredentials();
86         }
87
88         List<String> exceptionMessages = null;
89         for (AwsCredentialsProvider provider : credentialsProviders) {
90             try {
91                 AwsCredentials credentials = provider.resolveCredentials();
92
93                 log.debug(() -> "Loading credentials from " + provider);
94
95                 lastUsedProvider = provider;
96                 return credentials;
97             } catch (RuntimeException e) {
98                 // Ignore any exceptions and move onto the next provider
99                 String message = provider + ": " + e.getMessage();
100                 log.debug(() -> "Unable to load credentials from " + message , e);
101
102                 if (exceptionMessages == null) {
103                     exceptionMessages = new ArrayList<>();
104                 }
105                 exceptionMessages.add(message);
106             }
107         }
108
109         throw SdkClientException.builder()
110                                 .message("Unable to load credentials from any of the providers in the chain " +
111                                          this + " : " + exceptionMessages)
112                                 .build();
113     }
114
115     @Override
116     public void close() {
117         credentialsProviders.forEach(c -> IoUtils.closeIfCloseable(c, null));
118     }
119
120     @Override
121     public String toString() {
122         return ToString.builder("AwsCredentialsProviderChain")
123                        .add("credentialsProviders", credentialsProviders)
124                        .build();
125     }
126
127     /**
128      * A builder for a {@link AwsCredentialsProviderChain} that allows controlling its behavior.
129      */

130     public interface Builder {
131
132         /**
133          * Controls whether the chain should reuse the last successful credentials provider in the chain. Reusing the last
134          * successful credentials provider will typically return credentials faster than searching through the chain.
135          *
136          * <p>
137          * By defaultthis is enabled
138          */

139         Builder reuseLastProviderEnabled(Boolean reuseLastProviderEnabled);
140
141         /**
142          * Configure the credentials providers that should be checked for credentials, in the order they should be checked.
143          */

144         Builder credentialsProviders(Collection<? extends AwsCredentialsProvider> credentialsProviders);
145
146         /**
147          * Configure the credentials providers that should be checked for credentials, in the order they should be checked.
148          */

149         Builder credentialsProviders(AwsCredentialsProvider... credentialsProviders);
150
151         /**
152          * Add a credential provider to the chain, after the credential providers that have already been configured.
153          */

154         Builder addCredentialsProvider(AwsCredentialsProvider credentialsProviders);
155
156         AwsCredentialsProviderChain build();
157     }
158
159     private static final class BuilderImpl implements Builder {
160         private Boolean reuseLastProviderEnabled = true;
161         private List<AwsCredentialsProvider> credentialsProviders = new ArrayList<>();
162
163         private BuilderImpl() {
164         }
165
166         @Override
167         public Builder reuseLastProviderEnabled(Boolean reuseLastProviderEnabled) {
168             this.reuseLastProviderEnabled = reuseLastProviderEnabled;
169             return this;
170         }
171
172         public void setReuseLastProviderEnabled(Boolean reuseLastProviderEnabled) {
173             reuseLastProviderEnabled(reuseLastProviderEnabled);
174         }
175
176         @Override
177         public Builder credentialsProviders(Collection<? extends AwsCredentialsProvider> credentialsProviders) {
178             this.credentialsProviders = new ArrayList<>(credentialsProviders);
179             return this;
180         }
181
182         public void setCredentialsProviders(Collection<? extends AwsCredentialsProvider> credentialsProviders) {
183             credentialsProviders(credentialsProviders);
184         }
185
186         @Override
187         public Builder credentialsProviders(AwsCredentialsProvider... credentialsProviders) {
188             return credentialsProviders(Arrays.asList(credentialsProviders));
189         }
190
191         @Override
192         public Builder addCredentialsProvider(AwsCredentialsProvider credentialsProviders) {
193             this.credentialsProviders.add(credentialsProviders);
194             return this;
195         }
196
197         @Override
198         public AwsCredentialsProviderChain build() {
199             return new AwsCredentialsProviderChain(this);
200         }
201     }
202 }
203