diff --git a/common/aws/src/main/java/org/jclouds/aws/domain/TemporaryCredentials.java b/common/aws/src/main/java/org/jclouds/aws/domain/TemporaryCredentials.java
new file mode 100644
index 0000000000..73ff43b149
--- /dev/null
+++ b/common/aws/src/main/java/org/jclouds/aws/domain/TemporaryCredentials.java
@@ -0,0 +1,177 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you 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 org.jclouds.aws.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Date;
+
+import org.jclouds.domain.Credentials;
+
+import com.google.common.base.Objects;
+
+/**
+ * AWS credentials for API authentication.
+ *
+ * @see
+ *
+ * @author Adrian Cole
+ */
+public final class TemporaryCredentials extends Credentials {
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public Builder toBuilder() {
+ return builder().from(this);
+ }
+
+ public final static class Builder extends Credentials.Builder {
+ private String accessKeyId;
+ private String secretAccessKey;
+ private String sessionToken;
+ private Date expiration;
+
+ @Override
+ public Builder identity(String identity) {
+ return accessKeyId(identity);
+ }
+
+ @Override
+ public Builder credential(String credential) {
+ return secretAccessKey(credential);
+ }
+
+ /**
+ * @see TemporaryCredentials#getAccessKeyId()
+ */
+ public Builder accessKeyId(String accessKeyId) {
+ this.accessKeyId = accessKeyId;
+ return this;
+ }
+
+ /**
+ * @see TemporaryCredentials#getSecretAccessKey()
+ */
+ public Builder secretAccessKey(String secretAccessKey) {
+ this.secretAccessKey = secretAccessKey;
+ return this;
+ }
+
+ /**
+ * @see TemporaryCredentials#getSessionToken()
+ */
+ public Builder sessionToken(String sessionToken) {
+ this.sessionToken = sessionToken;
+ return this;
+ }
+
+ /**
+ * @see TemporaryCredentials#getExpiration()
+ */
+ public Builder expiration(Date expiration) {
+ this.expiration = expiration;
+ return this;
+ }
+
+ public TemporaryCredentials build() {
+ return new TemporaryCredentials(accessKeyId, secretAccessKey, sessionToken, expiration);
+ }
+
+ public Builder from(TemporaryCredentials in) {
+ return this.accessKeyId(in.identity).secretAccessKey(in.credential).sessionToken(in.sessionToken)
+ .expiration(in.expiration);
+ }
+ }
+
+ private final String sessionToken;
+ private final Date expiration;
+
+ private TemporaryCredentials(String accessKeyId, String secretAccessKey, String sessionToken, Date expiration) {
+ super(checkNotNull(accessKeyId, "accessKeyId"), checkNotNull(secretAccessKey, "secretAccessKey for %s",
+ accessKeyId));
+ this.sessionToken = checkNotNull(sessionToken, "sessionToken for %s", accessKeyId);
+ this.expiration = checkNotNull(expiration, "expiration for %s", accessKeyId);
+ }
+
+ /**
+ * AccessKeyId ID that identifies the temporary credentials.
+ */
+ public String getAccessKeyId() {
+ return identity;
+ }
+
+ /**
+ * The Secret Access Key to sign requests.
+ */
+ public String getSecretAccessKey() {
+ return credential;
+ }
+
+ /**
+ * The security token that users must pass to the service API to use the
+ * temporary credentials.
+ */
+ public String getSessionToken() {
+ return sessionToken;
+ }
+
+ /**
+ * The date on which these credentials expire.
+ */
+ public Date getExpiration() {
+ return expiration;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(identity, credential, sessionToken, expiration);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ TemporaryCredentials other = (TemporaryCredentials) obj;
+ return Objects.equal(this.identity, other.identity) && Objects.equal(this.credential, other.credential)
+ && Objects.equal(this.sessionToken, other.sessionToken) && Objects.equal(this.expiration, other.expiration);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this).add("accessKeyId", identity).add("sessionToken", sessionToken)
+ .add("expiration", expiration).toString();
+ }
+
+}
diff --git a/common/aws/src/main/java/org/jclouds/aws/filters/FormSigner.java b/common/aws/src/main/java/org/jclouds/aws/filters/FormSigner.java
index f957b9b6a6..76e9b716ce 100644
--- a/common/aws/src/main/java/org/jclouds/aws/filters/FormSigner.java
+++ b/common/aws/src/main/java/org/jclouds/aws/filters/FormSigner.java
@@ -47,6 +47,7 @@ import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants;
+import org.jclouds.aws.domain.TemporaryCredentials;
import org.jclouds.crypto.Crypto;
import org.jclouds.date.TimeStamp;
import org.jclouds.domain.Credentials;
@@ -114,9 +115,17 @@ public class FormSigner implements HttpRequestFilter, RequestSigner {
String signature = sign(stringToSign);
addSignature(decodedParams, signature);
request = setPayload(request, decodedParams);
+ Credentials current = creds.get();
+ if (current instanceof TemporaryCredentials) {
+ request = replaceSecurityTokenHeader(request, TemporaryCredentials.class.cast(current));
+ }
utils.logRequest(signatureLog, request, "<<");
return request;
}
+
+ HttpRequest replaceSecurityTokenHeader(HttpRequest request, TemporaryCredentials current) {
+ return request.toBuilder().replaceHeader("SecurityToken", current.getSessionToken()).build();
+ }
HttpRequest setPayload(HttpRequest request, Multimap decodedParams) {
String queryLine = buildQueryLine(decodedParams);
diff --git a/common/aws/src/main/java/org/jclouds/aws/xml/TemporaryCredentialsHandler.java b/common/aws/src/main/java/org/jclouds/aws/xml/TemporaryCredentialsHandler.java
new file mode 100644
index 0000000000..65f9e7eb4e
--- /dev/null
+++ b/common/aws/src/main/java/org/jclouds/aws/xml/TemporaryCredentialsHandler.java
@@ -0,0 +1,83 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you 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 org.jclouds.aws.xml;
+
+import javax.inject.Inject;
+
+import org.jclouds.aws.domain.TemporaryCredentials;
+import org.jclouds.date.DateService;
+import org.jclouds.http.functions.ParseSax;
+import org.jclouds.util.SaxUtils;
+
+/**
+ * @see
+ *
+ * @author Adrian Cole
+ */
+public class TemporaryCredentialsHandler extends ParseSax.HandlerForGeneratedRequestWithResult {
+ private final DateService dateService;
+
+ @Inject
+ protected TemporaryCredentialsHandler(DateService dateService) {
+ this.dateService = dateService;
+ }
+
+ private StringBuilder currentText = new StringBuilder();
+ private TemporaryCredentials.Builder builder = TemporaryCredentials.builder();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public TemporaryCredentials getResult() {
+ try {
+ return builder.build();
+ } finally {
+ builder = TemporaryCredentials.builder();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void endElement(String uri, String name, String qName) {
+ if (qName.equals("AccessKeyId")) {
+ builder.accessKeyId(SaxUtils.currentOrNull(currentText));
+ } else if (qName.equals("SecretAccessKey")) {
+ builder.secretAccessKey(SaxUtils.currentOrNull(currentText));
+ } else if (qName.equals("SessionToken")) {
+ builder.sessionToken(SaxUtils.currentOrNull(currentText));
+ } else if (qName.equals("Expiration")) {
+ builder.expiration(dateService.iso8601DateParse(SaxUtils.currentOrNull(currentText)));
+ }
+ currentText = new StringBuilder();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void characters(char ch[], int start, int length) {
+ currentText.append(ch, start, length);
+ }
+
+}
diff --git a/common/aws/src/test/java/org/jclouds/aws/filters/FormSignerTest.java b/common/aws/src/test/java/org/jclouds/aws/filters/FormSignerTest.java
index 6f7d551470..75a620e694 100644
--- a/common/aws/src/test/java/org/jclouds/aws/filters/FormSignerTest.java
+++ b/common/aws/src/test/java/org/jclouds/aws/filters/FormSignerTest.java
@@ -17,13 +17,16 @@
* under the License.
*/
package org.jclouds.aws.filters;
+
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
import static org.testng.Assert.assertEquals;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.ContextBuilder;
+import org.jclouds.aws.xml.TemporaryCredentialsHandlerTest;
import org.jclouds.date.TimeStamp;
+import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.IntegrationTestAsyncClient;
import org.jclouds.http.IntegrationTestClient;
@@ -33,6 +36,7 @@ import org.jclouds.rest.RequestSigner;
import org.jclouds.rest.internal.BaseRestApiTest.MockModule;
import org.testng.annotations.Test;
+import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.inject.AbstractModule;
@@ -45,49 +49,63 @@ import com.google.inject.name.Names;
*
* @author Adrian Cole
*/
-// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
-@Test(groups = "unit", testName = "FormSignerTest")
+// NOTE:without testName, this will not call @Before* and fail w/NPE during
+// surefire
+@Test(groups = "unit", singleThreaded = true, testName = "FormSignerTest")
public class FormSignerTest {
- public static final Injector INJECTOR = ContextBuilder
- .newBuilder(
- AnonymousProviderMetadata.forClientMappedToAsyncClientOnEndpoint(IntegrationTestClient.class, IntegrationTestAsyncClient.class,
- "http://localhost"))
- .credentials("identity", "credential")
- .apiVersion("apiVersion")
- .modules(ImmutableList. of(new MockModule(), new NullLoggingModule(),
- new AbstractModule() {
- @Override
- protected void configure() {
- bind(RequestSigner.class).to(FormSigner.class);
- bind(String.class).annotatedWith(Names.named(PROPERTY_HEADER_TAG)).toInstance("amz");
- bind(String.class).annotatedWith(TimeStamp.class).toInstance("2009-11-08T15:54:08.897Z");
- }
+ public static Injector injector(Credentials creds) {
+ return ContextBuilder
+ .newBuilder(
+ AnonymousProviderMetadata.forClientMappedToAsyncClientOnEndpoint(IntegrationTestClient.class,
+ IntegrationTestAsyncClient.class, "http://localhost"))
+ .credentialsSupplier(Suppliers. ofInstance(creds)).apiVersion("apiVersion")
+ .modules(ImmutableList. of(new MockModule(), new NullLoggingModule(), new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(RequestSigner.class).to(FormSigner.class);
+ bind(String.class).annotatedWith(Names.named(PROPERTY_HEADER_TAG)).toInstance("amz");
+ bind(String.class).annotatedWith(TimeStamp.class).toInstance("2009-11-08T15:54:08.897Z");
+ }
- })).buildInjector();
- FormSigner filter = INJECTOR.getInstance(FormSigner.class);
+ })).buildInjector();
+ }
+
+ public static FormSigner filter(Credentials creds) {
+ return injector(creds).getInstance(FormSigner.class);
+ }
+
+ public static FormSigner staticCredentialsFilter = filter(new Credentials("identity", "credential"));
+
+ HttpRequest request = HttpRequest.builder().method("GET")
+ .endpoint("http://localhost")
+ .addHeader(HttpHeaders.HOST, "localhost")
+ .addFormParam("Action", "DescribeImages")
+ .addFormParam("ImageId.1", "ami-2bb65342").build();
+
+ @Test
+ void testAddsSecurityToken() {
+ HttpRequest filtered = filter(new TemporaryCredentialsHandlerTest().expected()).filter(request);
+ assertEquals(
+ filtered.getPayload().getRawContent(),
+ "Action=DescribeImages&ImageId.1=ami-2bb65342&Signature=waV%2B%2BIdRwHRlnK2126CqgHHd4FZb%2B5wAeRueidjFc/M%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE");
+ assertEquals(filtered.getFirstHeaderOrNull("SecurityToken"), "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT");
+ }
@Test
void testBuildCanonicalizedStringSetsVersion() {
-
- assertEquals(
- filter.filter(
- HttpRequest.builder()
- .method("GET")
- .endpoint("http://localhost")
- .addHeader(HttpHeaders.HOST, "localhost")
- .payload("Action=DescribeImages&ImageId.1=ami-2bb65342").build())
- .getPayload().getRawContent(),
- "Action=DescribeImages&ImageId.1=ami-2bb65342&Signature=ugnt4m2eHE7Ka/vXTr9EhKZq7bhxOfvW0y4pAEqF97w%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=identity");
+ HttpRequest filtered = staticCredentialsFilter.filter(request);
+ assertEquals(filtered.getPayload().getRawContent(),
+ "Action=DescribeImages&ImageId.1=ami-2bb65342&Signature=ugnt4m2eHE7Ka/vXTr9EhKZq7bhxOfvW0y4pAEqF97w%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=identity");
}
@Test
void testBuildCanonicalizedString() {
assertEquals(
- filter.buildCanonicalizedString(new ImmutableMultimap.Builder().put("AWSAccessKeyId",
- "foo").put("Action", "DescribeImages").put("Expires", "2008-02-10T12:00:00Z").put("ImageId.1",
- "ami-2bb65342").put("SignatureMethod", "HmacSHA256").put("SignatureVersion", "2").put(
- "Version", "2010-06-15").build()),
- "AWSAccessKeyId=foo&Action=DescribeImages&Expires=2008-02-10T12%3A00%3A00Z&ImageId.1=ami-2bb65342&SignatureMethod=HmacSHA256&SignatureVersion=2&Version=2010-06-15");
+ staticCredentialsFilter.buildCanonicalizedString(new ImmutableMultimap.Builder()
+ .put("AWSAccessKeyId", "foo").put("Action", "DescribeImages").put("Expires", "2008-02-10T12:00:00Z")
+ .put("ImageId.1", "ami-2bb65342").put("SignatureMethod", "HmacSHA256").put("SignatureVersion", "2")
+ .put("Version", "2010-06-15").build()),
+ "AWSAccessKeyId=foo&Action=DescribeImages&Expires=2008-02-10T12%3A00%3A00Z&ImageId.1=ami-2bb65342&SignatureMethod=HmacSHA256&SignatureVersion=2&Version=2010-06-15");
}
}
diff --git a/common/aws/src/test/java/org/jclouds/aws/util/AWSUtilsTest.java b/common/aws/src/test/java/org/jclouds/aws/util/AWSUtilsTest.java
index fa9b565981..70c5c16f5d 100644
--- a/common/aws/src/test/java/org/jclouds/aws/util/AWSUtilsTest.java
+++ b/common/aws/src/test/java/org/jclouds/aws/util/AWSUtilsTest.java
@@ -29,6 +29,7 @@ import java.io.InputStream;
import org.jclouds.aws.domain.AWSError;
import org.jclouds.aws.filters.FormSignerTest;
+import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
@@ -49,7 +50,7 @@ public class AWSUtilsTest {
@BeforeTest
protected void setUpInjector() throws IOException {
- utils = FormSignerTest.INJECTOR.getInstance(AWSUtils.class);
+ utils = FormSignerTest.injector(new Credentials("identity", "credential")).getInstance(AWSUtils.class);
command = createMock(HttpCommand.class);
expect(command.getCurrentRequest()).andReturn(createMock(HttpRequest.class)).atLeastOnce();
diff --git a/common/aws/src/test/java/org/jclouds/aws/xml/TemporaryCredentialsHandlerTest.java b/common/aws/src/test/java/org/jclouds/aws/xml/TemporaryCredentialsHandlerTest.java
new file mode 100644
index 0000000000..5c11266516
--- /dev/null
+++ b/common/aws/src/test/java/org/jclouds/aws/xml/TemporaryCredentialsHandlerTest.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you 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 org.jclouds.aws.xml;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+
+import org.jclouds.aws.domain.TemporaryCredentials;
+import org.jclouds.aws.xml.TemporaryCredentialsHandler;
+import org.jclouds.date.internal.SimpleDateFormatDateService;
+import org.jclouds.http.functions.BaseHandlerTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
+@Test(groups = "unit", testName = "TemporaryCredentialsHandlerTest")
+public class TemporaryCredentialsHandlerTest extends BaseHandlerTest {
+
+ public void test() {
+ InputStream is = getClass().getResourceAsStream("/credentials.xml");
+
+ TemporaryCredentials expected = expected();
+
+ TemporaryCredentialsHandler handler = injector.getInstance(TemporaryCredentialsHandler.class);
+ TemporaryCredentials result = factory.create(handler).parse(is);
+
+ assertEquals(result, expected);
+ assertEquals(result.getAccessKeyId(), expected.getAccessKeyId());
+ assertEquals(result.getSecretAccessKey(), expected.getSecretAccessKey());
+ assertEquals(result.getSessionToken(), expected.getSessionToken());
+ assertEquals(result.getExpiration(), expected.getExpiration());
+ }
+
+ public TemporaryCredentials expected() {
+ return TemporaryCredentials.builder()
+ .accessKeyId("AKIAIOSFODNN7EXAMPLE")
+ .secretAccessKey("wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY")
+ .sessionToken("AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT")
+ .expiration(new SimpleDateFormatDateService().iso8601DateParse("2011-07-11T19:55:29.611Z")).build();
+ }
+
+}
diff --git a/common/aws/src/test/resources/credentials.xml b/common/aws/src/test/resources/credentials.xml
new file mode 100644
index 0000000000..615ba138af
--- /dev/null
+++ b/common/aws/src/test/resources/credentials.xml
@@ -0,0 +1,6 @@
+
+ AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT
+ wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY
+ 2011-07-11T19:55:29.611Z
+ AKIAIOSFODNN7EXAMPLE
+
\ No newline at end of file