package denominator; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import javax.inject.Inject; import javax.inject.Singleton; import dagger.Provides; import denominator.Credentials.ListCredentials; import denominator.config.GeoUnsupported; import denominator.config.NothingToClose; import denominator.config.OnlyBasicResourceRecordSets; import denominator.config.WeightedUnsupported; import denominator.model.Zone; import static denominator.CredentialsConfiguration.anonymous; import static denominator.CredentialsConfiguration.checkValidForProvider; import static denominator.CredentialsConfiguration.credentials; import static denominator.Denominator.create; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; public class DynamicCredentialsProviderExampleTest { @Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void testCredentialsRequired() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("no credentials supplied. dynamic requires customer,username,password"); // shouldn't exception here, as credentials aren't yet used DNSApiManager mgr = create(new DynamicCredentialsProvider(), anonymous()); // shouldn't exception here, as credentials aren't yet used ZoneApi zones = mgr.api().zones(); zones.iterator(); } @Test public void testThreePartCredentialsRequired() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("incorrect credentials supplied. dynamic requires customer,username,password"); // shouldn't exception here, as credentials aren't yet used DNSApiManager mgr = create(new DynamicCredentialsProvider(), credentials("username", "password")); // shouldn't exception here, as credentials aren't yet used ZoneApi zones = mgr.api().zones(); zones.iterator(); } @Test public void testImplicitDynamicCredentialsUpdate() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("no credentials supplied. dynamic requires customer,username,password"); DNSApiManager mgr = create(new DynamicCredentialsProvider()); ZoneApi zones = mgr.api().zones(); assertThat(zones.iterator()) .containsExactly(Zone.create("acme", "wily", 86400, "coyote")); assertThat(zones.iterator()) .containsExactly(Zone.create("acme", "road", 86400, "runner")); // now, if the supplier doesn't supply a set of credentials, we should // get a correct message zones.iterator(); } /** * example of domain-specific credentials. */ static class CustomerUsernamePassword { private final String customer; private final String username; private final String password; private CustomerUsernamePassword(String customer, String username, String password) { this.customer = customer; this.username = username; this.password = password; } } /** * in this example, the zone api lazy uses domain-specific credentials on each invocation. */ static class DynamicCredentialsZoneApi implements ZoneApi { private final javax.inject.Provider<CustomerUsernamePassword> creds; /** * Supplier facilitates dynamic credentials. */ @Inject DynamicCredentialsZoneApi(javax.inject.Provider<CustomerUsernamePassword> creds) { this.creds = creds; } /** * make sure you note in your javadoc that IllegalArgumentException can arise when the user * supplies the incorrect form of credentials. */ @Override public Iterator<Zone> iterator() { // IllegalArgumentException is possible on lazy get CustomerUsernamePassword cup = creds.get(); // normally, the credentials object would be used to invoke a remote // command. in this case, we don't and say we did :) return asList(Zone.create(cup.customer, cup.username, 86400, cup.password)).iterator(); } @Override public Iterator<Zone> iterateByName(String name) { throw new UnsupportedOperationException(); } @Override public String put(Zone zone) { throw new UnsupportedOperationException(); } @Override public void delete(String idOrName) { throw new UnsupportedOperationException(); } } final static class DynamicCredentialsProvider extends BasicProvider { /** * override name as default would be {@code dynamiccredentials} based on the class name. */ @Override public String name() { return "dynamic"; } /** * inform the user that we need credentials with 3 parameters. */ @Override public Map<String, Collection<String>> credentialTypeToParameterNames() { Map<String, Collection<String>> options = new LinkedHashMap<String, Collection<String>>(); options.put("username", asList("customer", "username", "password")); return options; } @dagger.Module(injects = DNSApiManager.class, complete = false, // denominator.Provider is externally provided includes = {NothingToClose.class, GeoUnsupported.class, WeightedUnsupported.class, OnlyBasicResourceRecordSets.class}) static class Module { final AtomicInteger credentialIndex = new AtomicInteger(); final List<List<String>> credentials = asList( asList("acme", "wily", "coyote"), asList("acme", "road", "runner") ); /** * simulates remote credential management where credentials can change across requests, or * accidentally return null. */ @Provides Credentials dynamicCredentials() { int index = credentialIndex.getAndIncrement(); if (credentials.size() < (index + 1)) { return null; // simulate no credentials } List<String> current = credentials.get(index); return ListCredentials.from(current); } /** * wiring of the zone api requires a Supplier as otherwise credentials would not be able to * dynamically update. */ @Provides @Singleton ZoneApi provideZoneApi(javax.inject.Provider<CustomerUsernamePassword> creds) { return new DynamicCredentialsZoneApi(creds); } /** * using mock as example case is made already with the zone api */ @Provides ResourceRecordSetApi.Factory provideResourceRecordSetApiFactory() { return new ResourceRecordSetApi.Factory() { final DNSApi mock = Denominator.create("mock").api(); @Override public ResourceRecordSetApi create(String id) { return mock.basicRecordSetsInZone(id); } }; } /** * @param credsProvider expected to return customer, username, and password in correct order */ @Provides CustomerUsernamePassword fromListCredentials(Provider provider, javax.inject.Provider<denominator.Credentials> credsProvider) { ListCredentials creds = (ListCredentials) checkValidForProvider(credsProvider.get(), provider); return new CustomerUsernamePassword(creds.get(0).toString(), creds.get(1).toString(), creds.get(2).toString()); } @Provides CheckConnection alwaysOK() { return new CheckConnection() { public boolean ok() { return true; } }; } } } }