package denominator;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import dagger.Module;
import dagger.Provides;
import denominator.Credentials.AnonymousCredentials;
import denominator.Credentials.ListCredentials;
import static denominator.common.Preconditions.checkNotNull;
import static denominator.common.Util.join;
/**
* use this for providers who need credentials.
*
* ex. for two-part
*
* <pre>
* ultra = Denominator.create(new UltraDNSProvider(), credentials(username, password));
* route53 = Denominator.create(new Route53Provider(), credentials(accesskey, secretkey));
* </pre>
*
* ex. for three-part
*
* <pre>
* dynect = Denominator.create(new DynECTProvider(), credentials(customer, username, password));
* </pre>
*
* ex. for dynamic credentials
*
* <pre>
* final AWSCredentialsProvider provider = // from wherever
* Supplier<Credentials> converter = new Supplier<Credentials>() {
* public Credentials get() {
* AWSCredentials awsCreds = provider.getCredentials();
* return credentials(awsCreds.getAWSAccessKeyId(), awsCreds.getAWSSecretKey());
* }
* };
*
* route53 = Denominator.create(new Route53Provider(), credentials(converter));
* </pre>
*/
public class CredentialsConfiguration {
private CredentialsConfiguration() {
}
/**
* used to set a base case where no credentials are available or needed.
*/
public static Object anonymous() {
return credentials(AnonymousCredentials.INSTANCE);
}
/**
* @param firstPart first part of credentials, such as a username or accessKey
* @param secondPart second part of credentials, such as a password or secretKey
*/
public static Object credentials(Object firstPart, Object secondPart) {
return credentials(ListCredentials.from(firstPart, secondPart));
}
/**
* @param firstPart first part of credentials, such as a customer or tenant
* @param secondPart second part of credentials, such as a username or accessKey
* @param thirdPart third part of credentials, such as a password or secretKey
*/
public static Object credentials(Object firstPart, Object secondPart, Object thirdPart) {
return credentials(ListCredentials.from(firstPart, secondPart, thirdPart));
}
/**
* @param credentials will always be used on the provider
*/
public static Object credentials(Credentials credentials) {
return new ConstantCredentials(credentials);
}
/**
* checks that the supplied input is valid, or throws an {@code IllegalArgumentException} if not.
* Users of this are guaranteed that the {@code input} matches the count of parameters of a
* credential type listed in {@link denominator.Provider#credentialTypeToParameterNames()}.
*
* <br> <br> <b>Coercion to {@code AnonymousCredentials}</b><br>
*
* if {@link denominator.Provider#credentialTypeToParameterNames()} is empty, then no credentials
* are required. When this is true, the following cases will return {@code AnonymousCredentials}.
* <ul> <li>when {@code input} is null</li> <li>when {@code input} is an instance of {@code
* AnonymousCredentials}</li> <li>when {@code input} is an empty instance of {@code Map} or {@code
* List}</li> </ul>
*
* <br> <br> <b>Validation Rules</b><br>
*
* See {@link Credentials} for Validation Rules
*
* @param creds nullable credentials to test
* @param provider context which helps create a useful error message on failure.
* @return correct Credentials value which can be {@link AnonymousCredentials} if {@code input}
* was null and credentials are not needed.
* @throws IllegalArgumentException if provider requires a different amount of credential parts
* than {@code input}
*/
public static Credentials checkValidForProvider(Credentials creds,
denominator.Provider provider) {
checkNotNull(provider, "provider cannot be null");
if (isAnonymous(creds) && provider.credentialTypeToParameterNames().isEmpty()) {
return AnonymousCredentials.INSTANCE;
} else if (creds instanceof Map) {
// check Map first as clojure Map implements List Map.Entry
if (credentialConfigurationHasKeys(provider, Map.class.cast(creds).keySet())) {
return creds;
}
} else if (creds instanceof List) {
if (credentialConfigurationHasPartCount(provider, List.class.cast(creds).size())) {
return creds;
}
}
throw new IllegalArgumentException(exceptionMessage(creds, provider));
}
private final static boolean isAnonymous(Credentials input) {
if (input == null) {
return true;
}
if (input instanceof AnonymousCredentials) {
return true;
}
if (input instanceof Map) {
return Map.class.cast(input).isEmpty();
}
if (input instanceof List) {
return List.class.cast(input).isEmpty();
}
return false;
}
private static boolean credentialConfigurationHasPartCount(denominator.Provider provider,
int size) {
for (String credentialType : provider.credentialTypeToParameterNames().keySet()) {
if (provider.credentialTypeToParameterNames().get(credentialType).size() == size) {
return true;
}
}
return false;
}
private static boolean credentialConfigurationHasKeys(denominator.Provider provider,
Set<?> keys) {
for (String credentialType : provider.credentialTypeToParameterNames().keySet()) {
if (keys.containsAll(provider.credentialTypeToParameterNames().get(credentialType))) {
return true;
}
}
return false;
}
/**
* Use this method to generate a consistent error message when the credentials supplied are not
* valid for the provider. Typically, this will be the message of an {@code
* IllegalArgumentException}
*
* @param input nullable credentials you know are invalid
* @param provider provider they are invalid for
*/
public static String exceptionMessage(Credentials input, denominator.Provider provider) {
StringBuilder msg = new StringBuilder();
if (input == null || input == AnonymousCredentials.INSTANCE) {
msg.append("no credentials supplied. ");
} else {
msg.append("incorrect credentials supplied. ");
}
msg.append(provider.name()).append(" requires ");
Map<String, Collection<String>>
credentialTypeToParameterNames =
provider.credentialTypeToParameterNames();
if (credentialTypeToParameterNames.size() == 1) {
msg.append(join(',', credentialTypeToParameterNames.values().iterator().next().toArray()));
} else {
msg.append("one of the following forms: when type is ");
for (Entry<String, Collection<String>> entry : credentialTypeToParameterNames.entrySet()) {
msg.append(entry.getKey()).append(": ").append(join(',', entry.getValue().toArray()))
.append("; ");
}
msg.trimToSize();
msg.setLength(msg.length() - 2);// remove last '; '
}
return msg.toString();
}
@Module(complete = false,
// only used for dns services that authenticate
library = true,
// override any built-in credentials
overrides = true)
static final class ConstantCredentials {
private final Credentials creds;
private ConstantCredentials(Credentials creds) {
this.creds = checkNotNull(creds, "creds");
}
@Provides
public Credentials get(denominator.Provider provider) {
return checkValidForProvider(creds, provider);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ConstantCredentials) {
ConstantCredentials that = ConstantCredentials.class.cast(obj);
return this.creds.equals(that.creds);
}
return false;
}
@Override
public int hashCode() {
return creds.hashCode();
}
@Override
public String toString() {
return "ConstantCredentials(" + creds + ")";
}
}
}