From c6bab1e2df2c70095d083bf5c5f8b5688852be77 Mon Sep 17 00:00:00 2001 From: adriancole Date: Mon, 21 Jan 2013 14:07:02 -0800 Subject: [PATCH 1/2] fixed bug in STS query signing --- .../aws/domain/TemporaryCredentials.java | 2 +- .../org/jclouds/aws/filters/FormSigner.java | 17 +++++++---------- .../jclouds/aws/reference/FormParameters.java | 4 ++++ .../org/jclouds/aws/filters/FormSignerTest.java | 3 +-- 4 files changed, 13 insertions(+), 13 deletions(-) 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 index 73ff43b149..57f8d58f36 100644 --- a/common/aws/src/main/java/org/jclouds/aws/domain/TemporaryCredentials.java +++ b/common/aws/src/main/java/org/jclouds/aws/domain/TemporaryCredentials.java @@ -25,6 +25,7 @@ import java.util.Date; import org.jclouds.domain.Credentials; import com.google.common.base.Objects; +import com.google.common.base.Supplier; /** * AWS credentials for API authentication. @@ -173,5 +174,4 @@ public final class TemporaryCredentials extends Credentials { 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 76e9b716ce..c1f45b590d 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 @@ -26,7 +26,7 @@ import static com.google.common.io.BaseEncoding.base64; import static com.google.common.io.ByteStreams.readBytes; import static org.jclouds.aws.reference.FormParameters.ACTION; import static org.jclouds.aws.reference.FormParameters.AWS_ACCESS_KEY_ID; -import static org.jclouds.aws.reference.FormParameters.SIGNATURE; +import static org.jclouds.aws.reference.FormParameters.*; import static org.jclouds.aws.reference.FormParameters.SIGNATURE_METHOD; import static org.jclouds.aws.reference.FormParameters.SIGNATURE_VERSION; import static org.jclouds.aws.reference.FormParameters.TIMESTAMP; @@ -115,17 +115,9 @@ 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); @@ -211,11 +203,16 @@ public class FormSigner implements HttpRequestFilter, RequestSigner { @VisibleForTesting void addSigningParams(Multimap params) { + params.removeAll(SIGNATURE); + params.removeAll(SECURITY_TOKEN); + Credentials current = creds.get(); + if (current instanceof TemporaryCredentials) { + params.put(SECURITY_TOKEN, TemporaryCredentials.class.cast(current).getSessionToken()); + } params.replaceValues(SIGNATURE_METHOD, ImmutableList.of("HmacSHA256")); params.replaceValues(SIGNATURE_VERSION, ImmutableList.of("2")); params.replaceValues(TIMESTAMP, ImmutableList.of(dateService.get())); params.replaceValues(AWS_ACCESS_KEY_ID, ImmutableList.of(creds.get().identity)); - params.removeAll(SIGNATURE); } public String createStringToSign(HttpRequest input) { diff --git a/common/aws/src/main/java/org/jclouds/aws/reference/FormParameters.java b/common/aws/src/main/java/org/jclouds/aws/reference/FormParameters.java index 7139f1f63d..b87b8094eb 100644 --- a/common/aws/src/main/java/org/jclouds/aws/reference/FormParameters.java +++ b/common/aws/src/main/java/org/jclouds/aws/reference/FormParameters.java @@ -59,6 +59,10 @@ public interface FormParameters { * Guide. Example: Qnpl4Qk/7tINHzfXCiT7VbBatDA= */ public static final String SIGNATURE = "Signature"; + /** + * Temporary access token. + */ + public static final String SECURITY_TOKEN = "SecurityToken"; /** * The hash algorithm you use to create the request signature. Valid values: HmacSHA256 | * HmacSHA1. For more information, go to the Amazon Elastic Compute Cloud Developer Guide. 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 75a620e694..c91fe0b1b4 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 @@ -87,8 +87,7 @@ public class FormSignerTest { 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"); + "Action=DescribeImages&ImageId.1=ami-2bb65342&SecurityToken=AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT&Signature=/8ReFVH1tvyNORsJb%2BSBieT9zvdqREQQr/olwmxC7VY%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE"); } @Test From 0099c708ff9d8688da99c11b1a6903b92597291b Mon Sep 17 00:00:00 2001 From: adriancole Date: Mon, 21 Jan 2013 14:07:31 -0800 Subject: [PATCH 2/2] added STS api for temporary credentials --- labs/aws-sts/pom.xml | 108 ++++++++++ .../aws/sts/AWSSTSProviderMetadata.java | 84 ++++++++ .../org.jclouds.providers.ProviderMetadata | 1 + .../jclouds/aws/sts/AWSSTSApiLiveTest.java | 35 ++++ .../jclouds/aws/sts/AWSSTSProviderTest.java | 37 ++++ labs/pom.xml | 2 + labs/sts/pom.xml | 107 ++++++++++ .../src/main/java/org/jclouds/sts/STSApi.java | 83 ++++++++ .../java/org/jclouds/sts/STSApiMetadata.java | 93 +++++++++ .../java/org/jclouds/sts/STSAsyncApi.java | 113 +++++++++++ .../sts/config/STSRestClientModule.java | 40 ++++ .../java/org/jclouds/sts/domain/User.java | 75 +++++++ .../domain/UserAndTemporaryCredentials.java | 133 +++++++++++++ .../sts/options/AssumeRoleOptions.java | 140 +++++++++++++ .../sts/options/FederatedUserOptions.java | 120 +++++++++++ .../options/TemporaryCredentialsOptions.java | 142 +++++++++++++ .../UserAndTemporaryCredentialsHandler.java | 107 ++++++++++ .../services/org.jclouds.apis.ApiMetadata | 1 + .../org/jclouds/sts/STSApiExpectTest.java | 187 ++++++++++++++++++ .../java/org/jclouds/sts/STSApiLiveTest.java | 88 +++++++++ .../org/jclouds/sts/STSApiMetadataTest.java | 39 ++++ .../sts/internal/BaseSTSApiExpectTest.java | 29 +++ .../sts/internal/BaseSTSApiLiveTest.java | 47 +++++ .../internal/BaseSTSAsyncApiExpectTest.java | 38 ++++ .../sts/internal/BaseSTSExpectTest.java | 51 +++++ .../sts/options/AssumeRoleOptionsTest.java | 69 +++++++ .../sts/options/FederatedUserOptionsTest.java | 58 ++++++ .../TemporaryCredentialsOptionsTest.java | 67 +++++++ .../sts/parse/AssumeRoleResponseTest.java | 57 ++++++ .../parse/GetFederationTokenResponseTest.java | 57 ++++++ .../parse/GetSessionTokenResponseTest.java | 61 ++++++ labs/sts/src/test/resources/assume_role.xml | 18 ++ .../src/test/resources/federation_token.xml | 18 ++ labs/sts/src/test/resources/log4j.xml | 166 ++++++++++++++++ labs/sts/src/test/resources/session_token.xml | 13 ++ 35 files changed, 2484 insertions(+) create mode 100644 labs/aws-sts/pom.xml create mode 100644 labs/aws-sts/src/main/java/org/jclouds/aws/sts/AWSSTSProviderMetadata.java create mode 100644 labs/aws-sts/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata create mode 100644 labs/aws-sts/src/test/java/org/jclouds/aws/sts/AWSSTSApiLiveTest.java create mode 100644 labs/aws-sts/src/test/java/org/jclouds/aws/sts/AWSSTSProviderTest.java create mode 100644 labs/sts/pom.xml create mode 100644 labs/sts/src/main/java/org/jclouds/sts/STSApi.java create mode 100644 labs/sts/src/main/java/org/jclouds/sts/STSApiMetadata.java create mode 100644 labs/sts/src/main/java/org/jclouds/sts/STSAsyncApi.java create mode 100644 labs/sts/src/main/java/org/jclouds/sts/config/STSRestClientModule.java create mode 100644 labs/sts/src/main/java/org/jclouds/sts/domain/User.java create mode 100644 labs/sts/src/main/java/org/jclouds/sts/domain/UserAndTemporaryCredentials.java create mode 100644 labs/sts/src/main/java/org/jclouds/sts/options/AssumeRoleOptions.java create mode 100644 labs/sts/src/main/java/org/jclouds/sts/options/FederatedUserOptions.java create mode 100644 labs/sts/src/main/java/org/jclouds/sts/options/TemporaryCredentialsOptions.java create mode 100644 labs/sts/src/main/java/org/jclouds/sts/xml/UserAndTemporaryCredentialsHandler.java create mode 100644 labs/sts/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata create mode 100644 labs/sts/src/test/java/org/jclouds/sts/STSApiExpectTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/STSApiLiveTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/STSApiMetadataTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSApiExpectTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSApiLiveTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSAsyncApiExpectTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSExpectTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/options/AssumeRoleOptionsTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/options/FederatedUserOptionsTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/options/TemporaryCredentialsOptionsTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/parse/AssumeRoleResponseTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/parse/GetFederationTokenResponseTest.java create mode 100644 labs/sts/src/test/java/org/jclouds/sts/parse/GetSessionTokenResponseTest.java create mode 100644 labs/sts/src/test/resources/assume_role.xml create mode 100644 labs/sts/src/test/resources/federation_token.xml create mode 100644 labs/sts/src/test/resources/log4j.xml create mode 100644 labs/sts/src/test/resources/session_token.xml diff --git a/labs/aws-sts/pom.xml b/labs/aws-sts/pom.xml new file mode 100644 index 0000000000..e20ae8fdd3 --- /dev/null +++ b/labs/aws-sts/pom.xml @@ -0,0 +1,108 @@ + + + + 4.0.0 + + org.jclouds + jclouds-project + 1.6.0-SNAPSHOT + ../../project/pom.xml + + org.jclouds.labs + aws-sts + jclouds Amazon Identity and Access Management (STS) provider + Identity and Access Management (STS) to Amazon Web Services + bundle + + + https://sts.amazonaws.com + 2011-06-15 + + ${test.aws.identity} + ${test.aws.credential} + + org.jclouds.aws.sts*;version="${project.version}" + org.jclouds*;version="${project.version}",* + + + + + org.jclouds.labs + sts + ${project.version} + jar + + + org.jclouds.labs + sts + ${project.version} + test-jar + test + + + org.jclouds + jclouds-core + ${project.version} + test-jar + test + + + org.jclouds.driver + jclouds-log4j + ${project.version} + test + + + + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + ${test.aws-sts.endpoint} + ${test.aws-sts.api-version} + ${test.aws-sts.build-version} + ${test.aws-sts.identity} + ${test.aws-sts.credential} + + + + + + + + + + + diff --git a/labs/aws-sts/src/main/java/org/jclouds/aws/sts/AWSSTSProviderMetadata.java b/labs/aws-sts/src/main/java/org/jclouds/aws/sts/AWSSTSProviderMetadata.java new file mode 100644 index 0000000000..7a6584ba2d --- /dev/null +++ b/labs/aws-sts/src/main/java/org/jclouds/aws/sts/AWSSTSProviderMetadata.java @@ -0,0 +1,84 @@ +/** + * 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.sts; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.sts.STSApiMetadata; +import org.jclouds.providers.ProviderMetadata; +import org.jclouds.providers.internal.BaseProviderMetadata; + +/** + * Implementation of @ link org.jclouds.types.ProviderMetadata} for Amazon's STS + * provider. +* +* @author Adrian Cole +*/ +public class AWSSTSProviderMetadata extends BaseProviderMetadata { + + public static Builder builder() { + return new Builder(); + } + + @Override + public Builder toBuilder() { + return builder().fromProviderMetadata(this); + } + + public AWSSTSProviderMetadata() { + super(builder()); + } + + public AWSSTSProviderMetadata(Builder builder) { + super(builder); + } + + public static Properties defaultProperties() { + Properties properties = new Properties(); + return properties; + } + + public static class Builder extends BaseProviderMetadata.Builder { + + protected Builder(){ + id("aws-sts") + .name("Amazon STS") + .endpoint("https://sts.amazonaws.com") + .homepage(URI.create("http://aws.amazon.com/iam/")) + .console(URI.create("https://console.aws.amazon.com/iam/home")) + .linkedServices("aws-ec2", "aws-elb", "aws-iam", "aws-sts", "aws-cloudwatch", "aws-s3", "aws-sqs", "aws-simpledb") + .iso3166Codes("US-VA") + .apiMetadata(new STSApiMetadata()) + .defaultProperties(AWSSTSProviderMetadata.defaultProperties()); + } + + @Override + public AWSSTSProviderMetadata build() { + return new AWSSTSProviderMetadata(this); + } + + @Override + public Builder fromProviderMetadata(ProviderMetadata in) { + super.fromProviderMetadata(in); + return this; + } + + } +} diff --git a/labs/aws-sts/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata b/labs/aws-sts/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata new file mode 100644 index 0000000000..3c3406f91e --- /dev/null +++ b/labs/aws-sts/src/main/resources/META-INF/services/org.jclouds.providers.ProviderMetadata @@ -0,0 +1 @@ +org.jclouds.aws.sts.AWSSTSProviderMetadata diff --git a/labs/aws-sts/src/test/java/org/jclouds/aws/sts/AWSSTSApiLiveTest.java b/labs/aws-sts/src/test/java/org/jclouds/aws/sts/AWSSTSApiLiveTest.java new file mode 100644 index 0000000000..fee174a6fc --- /dev/null +++ b/labs/aws-sts/src/test/java/org/jclouds/aws/sts/AWSSTSApiLiveTest.java @@ -0,0 +1,35 @@ +/** + * 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.sts; + +import org.jclouds.sts.STSApiLiveTest; +import org.testng.annotations.Test; + +/** + * Tests behavior of {@code STSApi} + * + * @author Adrian Cole + */ +@Test(groups = "live", singleThreaded = true, testName = "AWSSTSApiLiveTest") +public class AWSSTSApiLiveTest extends STSApiLiveTest { + public AWSSTSApiLiveTest() { + provider = "aws-sts"; + } + +} diff --git a/labs/aws-sts/src/test/java/org/jclouds/aws/sts/AWSSTSProviderTest.java b/labs/aws-sts/src/test/java/org/jclouds/aws/sts/AWSSTSProviderTest.java new file mode 100644 index 0000000000..d6d021cd72 --- /dev/null +++ b/labs/aws-sts/src/test/java/org/jclouds/aws/sts/AWSSTSProviderTest.java @@ -0,0 +1,37 @@ +/** + * 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.sts; + +import org.jclouds.aws.sts.AWSSTSProviderMetadata; +import org.jclouds.sts.STSApiMetadata; +import org.jclouds.providers.internal.BaseProviderMetadataTest; +import org.testng.annotations.Test; + +/** + * The AWSSTSProviderTest tests the org.jclouds.providers.AWSSTSProvider class. + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "AWSSTSProviderTest") +public class AWSSTSProviderTest extends BaseProviderMetadataTest { + + public AWSSTSProviderTest() { + super(new AWSSTSProviderMetadata(), new STSApiMetadata()); + } +} diff --git a/labs/pom.xml b/labs/pom.xml index bcb3046938..2e8fbbf1c2 100644 --- a/labs/pom.xml +++ b/labs/pom.xml @@ -63,5 +63,7 @@ oauth openstack-quantum openstack-glance + sts + aws-sts diff --git a/labs/sts/pom.xml b/labs/sts/pom.xml new file mode 100644 index 0000000000..08f965ac30 --- /dev/null +++ b/labs/sts/pom.xml @@ -0,0 +1,107 @@ + + + + 4.0.0 + + org.jclouds + jclouds-project + 1.6.0-SNAPSHOT + ../../project/pom.xml + + org.jclouds.labs + sts + jcloud sts api + jclouds components to access an implementation of Security Token Service (STS) + bundle + + + https://sts.amazonaws.com + 2011-06-15 + + ${test.aws.identity} + ${test.aws.credential} + + org.jclouds.sts*;version="${project.version}" + org.jclouds*;version="${project.version}",* + + + + + org.jclouds.common + aws-common + ${project.version} + jar + + + org.jclouds + jclouds-core + ${project.version} + test-jar + test + + + org.jclouds.driver + jclouds-log4j + ${project.version} + test + + + log4j + log4j + test + + + + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + ${test.sts.endpoint} + ${test.sts.api-version} + ${test.sts.build-version} + ${test.sts.identity} + ${test.sts.credential} + + + + + + + + + + + + diff --git a/labs/sts/src/main/java/org/jclouds/sts/STSApi.java b/labs/sts/src/main/java/org/jclouds/sts/STSApi.java new file mode 100644 index 0000000000..95f084b7bf --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/STSApi.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.sts; + +import org.jclouds.aws.domain.TemporaryCredentials; +import org.jclouds.sts.domain.User; +import org.jclouds.sts.domain.UserAndTemporaryCredentials; +import org.jclouds.sts.options.AssumeRoleOptions; +import org.jclouds.sts.options.FederatedUserOptions; +import org.jclouds.sts.options.TemporaryCredentialsOptions; + +/** + * Provides access to Amazon STS via the Query API + *

+ * + * @see STSAsyncApi + * @see + * @author Adrian Cole + */ +public interface STSApi { + /** + * Returns a set of temporary credentials for an AWS account or IAM user, + * with a default timeout + */ + TemporaryCredentials createTemporaryCredentials(); + + /** + * like {@link #createTemporaryCredentials()}, except you can modify the + * timeout and other parameters. + */ + TemporaryCredentials createTemporaryCredentials(TemporaryCredentialsOptions options); + + /** + * Assumes a role for a specified session. Only IAM users can assume a role. + * + * @param sessionName + * An identifier for the assumed role session, included as part of + * {@link User#getId}. + * @param roleArn + * The Amazon Resource Name (ARN) of the role that the caller is + * assuming. + */ + UserAndTemporaryCredentials assumeRole(String roleArn, String sessionName); + + /** + * like {@link #assumeRole(String, String)}, except you can modify the + * timeout and other parameters. + */ + UserAndTemporaryCredentials assumeRole(String roleArn, String sessionName, AssumeRoleOptions options); + + /** + * Returns a set of temporary credentials for a federated user with the user + * name specified. + * + * @param userName + * The name of the federated user, included as part of + * {@link User#getId}. + */ + UserAndTemporaryCredentials createFederatedUser(String userName); + + /** + * like {@link #createFederatedUser(String)}, except you can modify the + * timeout and other parameters. + */ + UserAndTemporaryCredentials createFederatedUser(String userName, FederatedUserOptions options); + +} diff --git a/labs/sts/src/main/java/org/jclouds/sts/STSApiMetadata.java b/labs/sts/src/main/java/org/jclouds/sts/STSApiMetadata.java new file mode 100644 index 0000000000..4eb7dfb6c0 --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/STSApiMetadata.java @@ -0,0 +1,93 @@ +/** + * 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.sts; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.jclouds.Constants.PROPERTY_TIMEOUTS_PREFIX; +import static org.jclouds.aws.reference.AWSConstants.PROPERTY_AUTH_TAG; +import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.apis.ApiMetadata; +import org.jclouds.sts.config.STSRestClientModule; +import org.jclouds.rest.RestContext; +import org.jclouds.rest.internal.BaseRestApiMetadata; + +import com.google.common.reflect.TypeToken; + +/** + * Implementation of {@link ApiMetadata} for Amazon's STS api. + * + * @author Adrian Cole + */ +public class STSApiMetadata extends BaseRestApiMetadata { + + public static final TypeToken> CONTEXT_TOKEN = new TypeToken>() { + private static final long serialVersionUID = 1L; + }; + + @Override + public Builder toBuilder() { + return new Builder(getApi(), getAsyncApi()).fromApiMetadata(this); + } + + public STSApiMetadata() { + this(new Builder(STSApi.class, STSAsyncApi.class)); + } + + protected STSApiMetadata(Builder builder) { + super(Builder.class.cast(builder)); + } + + public static Properties defaultProperties() { + Properties properties = BaseRestApiMetadata.defaultProperties(); + properties.setProperty(PROPERTY_TIMEOUTS_PREFIX + "default", SECONDS.toMillis(30) + ""); + properties.setProperty(PROPERTY_AUTH_TAG, "AWS"); + properties.setProperty(PROPERTY_HEADER_TAG, "amz"); + return properties; + } + + public static class Builder extends BaseRestApiMetadata.Builder { + + protected Builder(Class api, Class asyncApi) { + super(api, asyncApi); + id("sts") + .name("Amazon STS Api") + .identityName("Access Key ID") + .credentialName("Secret Access Key") + .version("2011-06-15") + .documentation(URI.create("http://docs.amazonwebservices.com/STS/latest/APIReference/")) + .defaultEndpoint("https://sts.amazonaws.com") + .defaultProperties(STSApiMetadata.defaultProperties()) + .defaultModule(STSRestClientModule.class); + } + + @Override + public STSApiMetadata build() { + return new STSApiMetadata(this); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/labs/sts/src/main/java/org/jclouds/sts/STSAsyncApi.java b/labs/sts/src/main/java/org/jclouds/sts/STSAsyncApi.java new file mode 100644 index 0000000000..4a3fdc5482 --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/STSAsyncApi.java @@ -0,0 +1,113 @@ +/** + * 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.sts; + +import javax.inject.Named; +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import org.jclouds.aws.domain.TemporaryCredentials; +import org.jclouds.aws.filters.FormSigner; +import org.jclouds.aws.xml.TemporaryCredentialsHandler; +import org.jclouds.rest.annotations.FormParams; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.VirtualHost; +import org.jclouds.rest.annotations.XMLResponseParser; +import org.jclouds.sts.domain.UserAndTemporaryCredentials; +import org.jclouds.sts.options.AssumeRoleOptions; +import org.jclouds.sts.options.FederatedUserOptions; +import org.jclouds.sts.options.TemporaryCredentialsOptions; +import org.jclouds.sts.xml.UserAndTemporaryCredentialsHandler; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Provides access to Amazon STS via the Query API + *

+ * + * @see + * @author Adrian Cole + */ +@RequestFilters(FormSigner.class) +@VirtualHost +public interface STSAsyncApi { + + /** + * @see STSApi#createTemporaryCredentials() + */ + @Named("GetSessionToken") + @POST + @Path("/") + @XMLResponseParser(TemporaryCredentialsHandler.class) + @FormParams(keys = "Action", values = "GetSessionToken") + ListenableFuture createTemporaryCredentials(); + + /** + * @see STSApi#createTemporaryCredentials(TemporaryCredentialsOptions) + */ + @Named("GetSessionToken") + @POST + @Path("/") + @XMLResponseParser(TemporaryCredentialsHandler.class) + @FormParams(keys = "Action", values = "GetSessionToken") + ListenableFuture createTemporaryCredentials(TemporaryCredentialsOptions options); + + /** + * @see STSApi#assumeRole(String, String) + */ + @Named("AssumeRole") + @POST + @Path("/") + @XMLResponseParser(UserAndTemporaryCredentialsHandler.class) + @FormParams(keys = "Action", values = "AssumeRole") + ListenableFuture assumeRole(@FormParam("RoleArn") String roleArn, + @FormParam("RoleSessionName") String sessionName); + + /** + * @see STSApi#assumeRole(String, String, AssumeRoleOptions) + */ + @Named("AssumeRole") + @POST + @Path("/") + @XMLResponseParser(UserAndTemporaryCredentialsHandler.class) + @FormParams(keys = "Action", values = "AssumeRole") + ListenableFuture assumeRole(@FormParam("RoleArn") String roleArn, + @FormParam("RoleSessionName") String sessionName, AssumeRoleOptions options); + + /** + * @see STSApi#createFederatedUser(String) + */ + @Named("GetFederationToken") + @POST + @Path("/") + @XMLResponseParser(UserAndTemporaryCredentialsHandler.class) + @FormParams(keys = "Action", values = "GetFederationToken") + ListenableFuture createFederatedUser(@FormParam("Name") String userName); + + /** + * @see STSApi#createFederatedUser(FederatedUserOptions) + */ + @Named("GetFederationToken") + @POST + @Path("/") + @XMLResponseParser(UserAndTemporaryCredentialsHandler.class) + @FormParams(keys = "Action", values = "GetFederationToken") + ListenableFuture createFederatedUser(@FormParam("Name") String userName, FederatedUserOptions options); +} diff --git a/labs/sts/src/main/java/org/jclouds/sts/config/STSRestClientModule.java b/labs/sts/src/main/java/org/jclouds/sts/config/STSRestClientModule.java new file mode 100644 index 0000000000..b912dbd8da --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/config/STSRestClientModule.java @@ -0,0 +1,40 @@ +/** + * 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.sts.config; + +import static org.jclouds.reflect.Reflection2.typeToken; + +import org.jclouds.aws.config.FormSigningRestClientModule; +import org.jclouds.rest.ConfiguresRestClient; +import org.jclouds.sts.STSApi; +import org.jclouds.sts.STSAsyncApi; + +/** + * Configures the STS connection. + * + * @author Adrian Cole + */ +@ConfiguresRestClient +public class STSRestClientModule extends FormSigningRestClientModule { + + public STSRestClientModule() { + super(typeToken(STSApi.class), typeToken(STSAsyncApi.class)); + } + +} diff --git a/labs/sts/src/main/java/org/jclouds/sts/domain/User.java b/labs/sts/src/main/java/org/jclouds/sts/domain/User.java new file mode 100644 index 0000000000..d97a4a89cc --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/domain/User.java @@ -0,0 +1,75 @@ +/** + * 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.sts.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Objects; + +/** + * @author Adrian Cole + */ +public final class User { + public static User fromIdAndArn(String id, String arn) { + return new User(id, arn); + } + + private final String id; + private final String arn; + + private User(String id, String arn) { + this.id = checkNotNull(id, "id"); + this.arn = checkNotNull(arn, "arn for %s", id); + } + + /** + * The id of the federated user or assumed role. ex. + * {@code ARO123EXAMPLE123:Bob} + */ + public String getId() { + return id; + } + + /** + * The arn of the federated user or assumed role. + * + * ex. {@code arn:aws:sts::123456789012:federated-user/Bob} or + * {@code arn:aws:sts::123456789012:assumed-role/demo/Bob} + */ + public String getArn() { + return arn; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + User other = (User) obj; + return Objects.equal(this.id, other.id) && Objects.equal(this.arn, other.arn); + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("id", id).add("arn", arn).toString(); + } +} diff --git a/labs/sts/src/main/java/org/jclouds/sts/domain/UserAndTemporaryCredentials.java b/labs/sts/src/main/java/org/jclouds/sts/domain/UserAndTemporaryCredentials.java new file mode 100644 index 0000000000..f2827f178a --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/domain/UserAndTemporaryCredentials.java @@ -0,0 +1,133 @@ +/** + * 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.sts.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.aws.domain.TemporaryCredentials; + +import com.google.common.base.Objects; + +/** + * + * @author Adrian Cole + */ +public final class UserAndTemporaryCredentials { + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().from(this); + } + + public final static class Builder { + private User user; + private TemporaryCredentials credentials; + private int packedPolicySize; + + /** + * @see UserAndTemporaryCredentials#getUser() + */ + public Builder user(User user) { + this.user = user; + return this; + } + + /** + * @see UserAndTemporaryCredentials#getCredentials() + */ + public Builder credentials(TemporaryCredentials credentials) { + this.credentials = credentials; + return this; + } + + /** + * @see UserAndTemporaryCredentials#getPackedPolicySize() + */ + public Builder packedPolicySize(int packedPolicySize) { + this.packedPolicySize = packedPolicySize; + return this; + } + + public UserAndTemporaryCredentials build() { + return new UserAndTemporaryCredentials(user, credentials, packedPolicySize); + } + + public Builder from(UserAndTemporaryCredentials in) { + return this.user(in.user).credentials(in.credentials).packedPolicySize(in.packedPolicySize); + } + } + + private final User user; + private final TemporaryCredentials credentials; + private final int packedPolicySize; + + private UserAndTemporaryCredentials(User user, TemporaryCredentials credentials, int packedPolicySize) { + this.user = checkNotNull(user, "user"); + this.credentials = checkNotNull(credentials, "credentials for %s", user); + this.packedPolicySize = checkNotNull(packedPolicySize, "packedPolicySize for %s", user); + } + + /** + * user correlating to {@link UserAndTemporaryCredentials#getCredentials()} + */ + public User getUser() { + return user; + } + + /** + * The temporary security credentials, which includes an Access Key ID, a + * Secret Access Key, and a security token. + */ + public TemporaryCredentials getCredentials() { + return credentials; + } + + /** + * A percentage value that indicates the size of the policy in packed form. + */ + public int getPackedPolicySize() { + return packedPolicySize; + } + + @Override + public int hashCode() { + return Objects.hashCode(user, credentials, packedPolicySize); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + UserAndTemporaryCredentials other = (UserAndTemporaryCredentials) obj; + return Objects.equal(this.user, other.user) && Objects.equal(this.credentials, other.credentials) + && Objects.equal(this.packedPolicySize, other.packedPolicySize); + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("user", user).add("credentials", credentials) + .add("packedPolicySize", packedPolicySize).toString(); + } +} diff --git a/labs/sts/src/main/java/org/jclouds/sts/options/AssumeRoleOptions.java b/labs/sts/src/main/java/org/jclouds/sts/options/AssumeRoleOptions.java new file mode 100644 index 0000000000..3a07afedf4 --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/options/AssumeRoleOptions.java @@ -0,0 +1,140 @@ +/** + * 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.sts.options; + +import org.jclouds.http.options.BaseHttpRequestOptions; + +import com.google.common.base.Objects; +import com.google.common.collect.Multimap; + +/** + * @see + * + * @author Adrian Cole + */ +public class AssumeRoleOptions extends BaseHttpRequestOptions implements Cloneable { + + // long as this is a more typical unit for duration, hence less casting + private Long durationSeconds; + private String policy; + private String externalId; + + /** + * A unique identifier that is generated by a third party for each of their customers. + */ + public AssumeRoleOptions externalId(String externalId) { + this.externalId = externalId; + return this; + } + + /** + * The duration, in seconds, that the credentials should remain valid. 12 + * hours is default. 15 minutes is current minimum. + */ + public AssumeRoleOptions durationSeconds(long durationSeconds) { + this.durationSeconds = durationSeconds; + return this; + } + + /** + * A supplemental policy that can be associated with the temporary security credentials. + */ + public AssumeRoleOptions policy(String policy) { + this.policy = policy; + return this; + } + + public static class Builder { + + /** + * @see AssumeRoleOptions#externalId + */ + public static AssumeRoleOptions externalId(String externalId) { + return new AssumeRoleOptions().externalId(externalId); + } + + /** + * @see AssumeRoleOptions#durationSeconds + */ + public static AssumeRoleOptions durationSeconds(long durationSeconds) { + return new AssumeRoleOptions().durationSeconds(durationSeconds); + } + + /** + * @see AssumeRoleOptions#policy + */ + public static AssumeRoleOptions policy(String policy) { + return new AssumeRoleOptions().policy(policy); + } + } + + @Override + public Multimap buildFormParameters() { + Multimap params = super.buildFormParameters(); + if (externalId != null) + params.put("ExternalId", externalId.toString()); + if (durationSeconds != null) + params.put("DurationSeconds", durationSeconds.toString()); + if (policy != null) + params.put("Policy", policy); + return params; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(externalId, durationSeconds, policy); + } + + @Override + public AssumeRoleOptions clone() { + return new AssumeRoleOptions().externalId(externalId).durationSeconds(durationSeconds) + .policy(policy); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AssumeRoleOptions other = AssumeRoleOptions.class.cast(obj); + return Objects.equal(this.externalId, other.externalId) + && Objects.equal(this.durationSeconds, other.durationSeconds) + && Objects.equal(this.policy, other.policy); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues().add("externalId", externalId) + .add("durationSeconds", durationSeconds).add("policy", policy).toString(); + } +} diff --git a/labs/sts/src/main/java/org/jclouds/sts/options/FederatedUserOptions.java b/labs/sts/src/main/java/org/jclouds/sts/options/FederatedUserOptions.java new file mode 100644 index 0000000000..8d32faa26a --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/options/FederatedUserOptions.java @@ -0,0 +1,120 @@ +/** + * 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.sts.options; + +import org.jclouds.http.options.BaseHttpRequestOptions; + +import com.google.common.base.Objects; +import com.google.common.collect.Multimap; + +/** + * @see + * + * @author Adrian Cole + */ +public class FederatedUserOptions extends BaseHttpRequestOptions implements Cloneable { + + // long as this is a more typical unit for duration, hence less casting + private Long durationSeconds; + private String policy; + + /** + * The duration, in seconds, that the credentials should remain valid. 12 + * hours is default. 15 minutes is current minimum. + */ + public FederatedUserOptions durationSeconds(long durationSeconds) { + this.durationSeconds = durationSeconds; + return this; + } + + /** + * A supplemental policy that can be associated with the temporary security + * credentials. + */ + public FederatedUserOptions policy(String policy) { + this.policy = policy; + return this; + } + + public static class Builder { + + /** + * @see FederatedUserOptions#durationSeconds + */ + public static FederatedUserOptions durationSeconds(long durationSeconds) { + return new FederatedUserOptions().durationSeconds(durationSeconds); + } + + /** + * @see FederatedUserOptions#policy + */ + public static FederatedUserOptions policy(String policy) { + return new FederatedUserOptions().policy(policy); + } + } + + @Override + public Multimap buildFormParameters() { + Multimap params = super.buildFormParameters(); + if (durationSeconds != null) + params.put("DurationSeconds", durationSeconds.toString()); + if (policy != null) + params.put("Policy", policy); + return params; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(durationSeconds, policy); + } + + @Override + public FederatedUserOptions clone() { + return new FederatedUserOptions().durationSeconds(durationSeconds).policy(policy); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + FederatedUserOptions other = FederatedUserOptions.class.cast(obj); + return Objects.equal(this.durationSeconds, other.durationSeconds) && Objects.equal(this.policy, other.policy); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues().add("durationSeconds", durationSeconds) + .add("policy", policy).toString(); + } +} diff --git a/labs/sts/src/main/java/org/jclouds/sts/options/TemporaryCredentialsOptions.java b/labs/sts/src/main/java/org/jclouds/sts/options/TemporaryCredentialsOptions.java new file mode 100644 index 0000000000..2e9027598a --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/options/TemporaryCredentialsOptions.java @@ -0,0 +1,142 @@ +/** + * 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.sts.options; + +import org.jclouds.http.options.BaseHttpRequestOptions; + +import com.google.common.base.Objects; +import com.google.common.collect.Multimap; + +/** + * Options used to get a session token. + * + * @see + * + * @author Adrian Cole + */ +public class TemporaryCredentialsOptions extends BaseHttpRequestOptions implements Cloneable { + + // long as this is a more typical unit for duration, hence less casting + private Long durationSeconds; + private String tokenCode; + private String serialNumber; + + /** + * The identification number of the MFA device for the user. + */ + public TemporaryCredentialsOptions serialNumber(String serialNumber) { + this.serialNumber = serialNumber; + return this; + } + + /** + * The duration, in seconds, that the credentials should remain valid. 12 + * hours is default. 15 minutes is current minimum. + */ + public TemporaryCredentialsOptions durationSeconds(long durationSeconds) { + this.durationSeconds = durationSeconds; + return this; + } + + /** + * The value provided by the MFA device. + */ + public TemporaryCredentialsOptions tokenCode(String tokenCode) { + this.tokenCode = tokenCode; + return this; + } + + public static class Builder { + + /** + * @see TemporaryCredentialsOptions#serialNumber + */ + public static TemporaryCredentialsOptions serialNumber(String serialNumber) { + return new TemporaryCredentialsOptions().serialNumber(serialNumber); + } + + /** + * @see TemporaryCredentialsOptions#durationSeconds + */ + public static TemporaryCredentialsOptions durationSeconds(long durationSeconds) { + return new TemporaryCredentialsOptions().durationSeconds(durationSeconds); + } + + /** + * @see TemporaryCredentialsOptions#tokenCode + */ + public static TemporaryCredentialsOptions tokenCode(String tokenCode) { + return new TemporaryCredentialsOptions().tokenCode(tokenCode); + } + } + + @Override + public Multimap buildFormParameters() { + Multimap params = super.buildFormParameters(); + if (serialNumber != null) + params.put("SerialNumber", serialNumber.toString()); + if (durationSeconds != null) + params.put("DurationSeconds", durationSeconds.toString()); + if (tokenCode != null) + params.put("TokenCode", tokenCode); + return params; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(serialNumber, durationSeconds, tokenCode); + } + + @Override + public TemporaryCredentialsOptions clone() { + return new TemporaryCredentialsOptions().serialNumber(serialNumber).durationSeconds(durationSeconds) + .tokenCode(tokenCode); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TemporaryCredentialsOptions other = TemporaryCredentialsOptions.class.cast(obj); + return Objects.equal(this.serialNumber, other.serialNumber) + && Objects.equal(this.durationSeconds, other.durationSeconds) + && Objects.equal(this.tokenCode, other.tokenCode); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues().add("serialNumber", serialNumber) + .add("durationSeconds", durationSeconds).add("tokenCode", tokenCode).toString(); + } +} diff --git a/labs/sts/src/main/java/org/jclouds/sts/xml/UserAndTemporaryCredentialsHandler.java b/labs/sts/src/main/java/org/jclouds/sts/xml/UserAndTemporaryCredentialsHandler.java new file mode 100644 index 0000000000..92fffa0219 --- /dev/null +++ b/labs/sts/src/main/java/org/jclouds/sts/xml/UserAndTemporaryCredentialsHandler.java @@ -0,0 +1,107 @@ +/** + * 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.sts.xml; + +import static org.jclouds.util.SaxUtils.currentOrNull; +import static org.jclouds.util.SaxUtils.equalsOrSuffix; + +import org.jclouds.aws.xml.TemporaryCredentialsHandler; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.sts.domain.User; +import org.jclouds.sts.domain.UserAndTemporaryCredentials; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import com.google.inject.Inject; + +/** + * + * @author Adrian Cole + */ +public class UserAndTemporaryCredentialsHandler extends ParseSax.HandlerForGeneratedRequestWithResult { + + private final TemporaryCredentialsHandler credsHandler; + + private StringBuilder currentText = new StringBuilder(); + private UserAndTemporaryCredentials.Builder builder = UserAndTemporaryCredentials.builder(); + + @Inject + public UserAndTemporaryCredentialsHandler(TemporaryCredentialsHandler credsHandler) { + this.credsHandler = credsHandler; + } + + /** + * {@inheritDoc} + */ + @Override + public UserAndTemporaryCredentials getResult() { + try { + return builder.build(); + } finally { + builder = UserAndTemporaryCredentials.builder(); + } + } + + private boolean inCreds; + + private String arn; + private String id; + + @Override + public void startElement(String url, String name, String qName, Attributes attributes) throws SAXException { + if (equalsOrSuffix(qName, "Credentials")) { + inCreds = true; + } + if (inCreds) { + credsHandler.startElement(url, name, qName, attributes); + } + } + + @Override + public void endElement(String uri, String name, String qName) { + if (inCreds) { + if (qName.equals("Credentials")) { + inCreds = false; + builder.credentials(credsHandler.getResult()); + } else { + credsHandler.endElement(uri, name, qName); + } + } else if (qName.equals("Arn")) { + arn = currentOrNull(currentText); + } else if (qName.endsWith("Id")) {// FederatedUserId or AssumedRoleId + id = currentOrNull(currentText); + } else if (qName.endsWith("User")) {// FederatedUser or AssumedRoleUser + builder.user(User.fromIdAndArn(id, arn)); + id = arn = null; + } else if (qName.equals("PackedPolicySize")) { + builder.packedPolicySize(Integer.parseInt(currentOrNull(currentText))); + } + currentText = new StringBuilder(); + } + + @Override + public void characters(char ch[], int start, int length) { + if (inCreds) { + credsHandler.characters(ch, start, length); + } else { + currentText.append(ch, start, length); + } + } + +} diff --git a/labs/sts/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/labs/sts/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata new file mode 100644 index 0000000000..df7b5d89f7 --- /dev/null +++ b/labs/sts/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata @@ -0,0 +1 @@ +org.jclouds.sts.STSApiMetadata diff --git a/labs/sts/src/test/java/org/jclouds/sts/STSApiExpectTest.java b/labs/sts/src/test/java/org/jclouds/sts/STSApiExpectTest.java new file mode 100644 index 0000000000..0ce231d4bf --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/STSApiExpectTest.java @@ -0,0 +1,187 @@ +/** + * 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 + * + * Unles 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 expres or implied. See the License for the + * specific language governing permisions and limitations + * under the License. + */ +package org.jclouds.sts; + +import static org.jclouds.sts.options.AssumeRoleOptions.Builder.externalId; +import static org.jclouds.sts.options.FederatedUserOptions.Builder.policy; +import static org.jclouds.sts.options.TemporaryCredentialsOptions.Builder.serialNumber; +import static org.testng.Assert.assertEquals; + +import java.util.concurrent.TimeUnit; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.sts.internal.BaseSTSApiExpectTest; +import org.jclouds.sts.parse.AssumeRoleResponseTest; +import org.jclouds.sts.parse.GetFederationTokenResponseTest; +import org.jclouds.sts.parse.GetSessionTokenResponseTest; +import org.testng.annotations.Test; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "STSApiExpectTest") +public class STSApiExpectTest extends BaseSTSApiExpectTest { + + HttpRequest createTemporaryCredentials = HttpRequest.builder().method("POST") + .endpoint("https://sts.amazonaws.com/") + .addHeader("Host", "sts.amazonaws.com") + .addFormParam("Action", "GetSessionToken") + .addFormParam("Signature", "ntC%2BPKAcmYTJ5Py5tjICG4KX5y00Pl2L0XJrLbSgLEs%3D") + .addFormParam("SignatureMethod", "HmacSHA256") + .addFormParam("SignatureVersion", "2") + .addFormParam("Timestamp", "2009-11-08T15%3A54%3A08.897Z") + .addFormParam("Version", "2011-06-15") + .addFormParam("AWSAccessKeyId", "identity").build(); + + HttpResponse createTemporaryCredentialsResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/session_token.xml", "text/xml")).build(); + + public void testCreateTemporaryCredentialsWhenResponseIs2xx() { + + STSApi apiWhenWithOptionsExist = requestSendsResponse(createTemporaryCredentials, createTemporaryCredentialsResponse); + + assertEquals( + apiWhenWithOptionsExist.createTemporaryCredentials().toString(), + new GetSessionTokenResponseTest().expected().toString()); + } + + HttpRequest createTemporaryCredentialsWithOptions = HttpRequest.builder().method("POST") + .endpoint("https://sts.amazonaws.com/") + .addHeader("Host", "sts.amazonaws.com") + .addFormParam("Action", "GetSessionToken") + .addFormParam("DurationSeconds", "900") + .addFormParam("SerialNumber", "YourMFADeviceSerialNumber") + .addFormParam("Signature", "e4HEkfKrw7EuLEQhe4/lK1l7ZmaynO3snsIMU/cdarI%3D") + .addFormParam("SignatureMethod", "HmacSHA256") + .addFormParam("SignatureVersion", "2") + .addFormParam("Timestamp", "2009-11-08T15%3A54%3A08.897Z") + .addFormParam("TokenCode", "1234") + .addFormParam("Version", "2011-06-15") + .addFormParam("AWSAccessKeyId", "identity").build(); + + public void testCreateTemporaryCredentialsWithOptionsWhenResponseIs2xx() { + + STSApi apiWhenWithOptionsExist = requestSendsResponse(createTemporaryCredentialsWithOptions, createTemporaryCredentialsResponse); + + assertEquals( + apiWhenWithOptionsExist.createTemporaryCredentials( + serialNumber("YourMFADeviceSerialNumber").tokenCode("1234").durationSeconds(TimeUnit.MINUTES.toSeconds(15))).toString(), + new GetSessionTokenResponseTest().expected().toString()); + } + + HttpRequest assumeRole = HttpRequest.builder().method("POST") + .endpoint("https://sts.amazonaws.com/") + .addHeader("Host", "sts.amazonaws.com") + .addFormParam("Action", "AssumeRole") + .addFormParam("RoleArn", "arn:aws:iam::123456789012:role/demo") + .addFormParam("RoleSessionName", "Bob") + .addFormParam("Signature", "0G1%2B6GX4cSU9Tjf2SyQ9oW5ivFri4BQPif/24FoRiWY%3D") + .addFormParam("SignatureMethod", "HmacSHA256") + .addFormParam("SignatureVersion", "2") + .addFormParam("Timestamp", "2009-11-08T15%3A54%3A08.897Z") + .addFormParam("Version", "2011-06-15") + .addFormParam("AWSAccessKeyId", "identity").build(); + + HttpResponse assumeRoleResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/assume_role.xml", "text/xml")).build(); + + public void testAssumeRoleWhenResponseIs2xx() { + + STSApi apiWhenWithOptionsExist = requestSendsResponse(assumeRole, assumeRoleResponse); + + assertEquals(apiWhenWithOptionsExist.assumeRole("arn:aws:iam::123456789012:role/demo", "Bob").toString(), + new AssumeRoleResponseTest().expected().toString()); + } + + String policy = "{\"Statement\":[{\"Sid\":\"Stmt1\",\"Effect\":\"Allow\",\"Action\":\"s3:*\",\"Resource\":\"*\"}]}"; + + HttpRequest assumeRoleWithOptions = HttpRequest.builder().method("POST") + .endpoint("https://sts.amazonaws.com/") + .addHeader("Host", "sts.amazonaws.com") + .addFormParam("Action", "AssumeRole") + .addFormParam("DurationSeconds", "900") + .addFormParam("ExternalId", "123ABC") + .addFormParam("Policy", policy) + .addFormParam("RoleArn", "arn:aws:iam::123456789012:role/demo") + .addFormParam("RoleSessionName", "Bob") + .addFormParam("Signature", "9qffV6zHRbTX8E9IYbEFeQPWrHEdSbwUfjJpg1SMaBo%3D") + .addFormParam("SignatureMethod", "HmacSHA256") + .addFormParam("SignatureVersion", "2") + .addFormParam("Timestamp", "2009-11-08T15%3A54%3A08.897Z") + .addFormParam("Version", "2011-06-15") + .addFormParam("AWSAccessKeyId", "identity").build(); + + public void testAssumeRoleWithOptionsWhenResponseIs2xx() { + + STSApi apiWhenWithOptionsExist = requestSendsResponse(assumeRoleWithOptions, assumeRoleResponse); + + assertEquals( + apiWhenWithOptionsExist.assumeRole("arn:aws:iam::123456789012:role/demo", "Bob", + externalId("123ABC").policy(policy).durationSeconds(TimeUnit.MINUTES.toSeconds(15))).toString(), + new AssumeRoleResponseTest().expected().toString()); + } + + HttpRequest createFederatedUser = HttpRequest.builder().method("POST") + .endpoint("https://sts.amazonaws.com/") + .addHeader("Host", "sts.amazonaws.com") + .addFormParam("Action", "GetFederationToken") + .addFormParam("Name", "Bob") + .addFormParam("Signature", "Z7AtGK4X9IAx/zMtLD7baNiyltNl%2BF%2BSHqjIGUidzOc%3D") + .addFormParam("SignatureMethod", "HmacSHA256") + .addFormParam("SignatureVersion", "2") + .addFormParam("Timestamp", "2009-11-08T15%3A54%3A08.897Z") + .addFormParam("Version", "2011-06-15") + .addFormParam("AWSAccessKeyId", "identity").build(); + + HttpResponse createFederatedUserResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/federation_token.xml", "text/xml")).build(); + + public void testCreateFederatedUserWhenResponseIs2xx() { + + STSApi apiWhenWithOptionsExist = requestSendsResponse(createFederatedUser, createFederatedUserResponse); + + assertEquals(apiWhenWithOptionsExist.createFederatedUser("Bob").toString(), new GetFederationTokenResponseTest() + .expected().toString()); + } + + HttpRequest createFederatedUserWithOptions = HttpRequest.builder().method("POST") + .endpoint("https://sts.amazonaws.com/") + .addHeader("Host", "sts.amazonaws.com") + .addFormParam("Action", "GetFederationToken") + .addFormParam("DurationSeconds", "900") + .addFormParam("Name", "Bob") + .addFormParam("Policy", policy) + .addFormParam("Signature", "%2BWGvCNtmb1UPmQHxXPMvcK6vH/TJ9r/wCuxdz03n/2w%3D") + .addFormParam("SignatureMethod", "HmacSHA256") + .addFormParam("SignatureVersion", "2") + .addFormParam("Timestamp", "2009-11-08T15%3A54%3A08.897Z") + .addFormParam("Version", "2011-06-15") + .addFormParam("AWSAccessKeyId", "identity").build(); + + public void testCreateFederatedUserWithOptionsWhenResponseIs2xx() { + + STSApi apiWhenWithOptionsExist = requestSendsResponse(createFederatedUserWithOptions, createFederatedUserResponse); + + assertEquals( + apiWhenWithOptionsExist.createFederatedUser("Bob", + policy(policy).durationSeconds(TimeUnit.MINUTES.toSeconds(15))).toString(), + new GetFederationTokenResponseTest().expected().toString()); + } +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/STSApiLiveTest.java b/labs/sts/src/test/java/org/jclouds/sts/STSApiLiveTest.java new file mode 100644 index 0000000000..68f8b9a55e --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/STSApiLiveTest.java @@ -0,0 +1,88 @@ +/** + * 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.sts; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.testng.Assert.assertTrue; + +import org.jclouds.aws.domain.TemporaryCredentials; +import org.jclouds.sts.domain.UserAndTemporaryCredentials; +import org.jclouds.sts.internal.BaseSTSApiLiveTest; +import org.jclouds.sts.options.AssumeRoleOptions; +import org.jclouds.sts.options.FederatedUserOptions; +import org.jclouds.sts.options.TemporaryCredentialsOptions; +import org.testng.SkipException; +import org.testng.annotations.Test; + +/** + * @author Adrian Cole + */ +@Test(groups = "live", singleThreaded = true, testName = "STSApiLiveTest") +public class STSApiLiveTest extends BaseSTSApiLiveTest { + + @Test + protected void testCreateTemporaryCredentials() { + TemporaryCredentials creds = api().createTemporaryCredentials( + new TemporaryCredentialsOptions().durationSeconds(MINUTES.toSeconds(15))); + checkTemporaryCredentials(creds); + // TODO: actually login to some service + // + // context.close(); + // ProviderMetadata pm = createProviderMetadata(); + // + // context = (pm != null ? ContextBuilder.newBuilder(pm) : ContextBuilder.newBuilder(createApiMetadata())) + // .credentialsSupplier(Supplier. of(creds)).modules(setupModules()).build(); + } + + @Test + protected void testCreateFederatedUser() { + UserAndTemporaryCredentials user = api().createFederatedUser("Bob", new FederatedUserOptions().durationSeconds(MINUTES.toSeconds(15))); + checkTemporaryCredentials(user.getCredentials()); + assertTrue(user.getUser().getId().contains("Bob"), user + " id incorrect"); + assertTrue(user.getUser().getArn().contains("Bob"), user + " arn incorrect"); + assertTrue(user.getPackedPolicySize() >= 0, user + " policy size negative"); + } + + @Test + protected void testAssumeRole() { + String arnToAssume = getTestArn(); + UserAndTemporaryCredentials role = api().assumeRole(arnToAssume, "session", + new AssumeRoleOptions().durationSeconds(MINUTES.toSeconds(15))); + checkTemporaryCredentials(role.getCredentials()); + assertTrue(role.getUser().getId().contains("session"), role + " id incorrect"); + assertTrue(role.getUser().getArn().contains("session"), role + " arn incorrect"); + assertTrue(role.getPackedPolicySize() >= 0, role + " policy size negative"); + } + + protected String getTestArn() { + throw new SkipException("TODO: need to query a valid arn to assume"); + } + + private void checkTemporaryCredentials(TemporaryCredentials creds) { + checkNotNull(creds.getAccessKeyId(), "AccessKeyId cannot be null for TemporaryCredentials."); + checkNotNull(creds.getSecretAccessKey(), "SecretAccessKey cannot be null for TemporaryCredentials."); + checkNotNull(creds.getSessionToken(), "SessionToken cannot be null for TemporaryCredentials."); + checkNotNull(creds.getExpiration(), "Expiration cannot be null for TemporaryCredentials."); + } + + protected STSApi api() { + return context.getApi(); + } +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/STSApiMetadataTest.java b/labs/sts/src/test/java/org/jclouds/sts/STSApiMetadataTest.java new file mode 100644 index 0000000000..98c5431b44 --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/STSApiMetadataTest.java @@ -0,0 +1,39 @@ +/** + * 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.sts; + +import org.jclouds.View; +import org.jclouds.rest.internal.BaseRestApiMetadataTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "STSApiMetadataTest") +public class STSApiMetadataTest extends BaseRestApiMetadataTest { + + // no tenant abstraction, yet + public STSApiMetadataTest() { + super(new STSApiMetadata(), ImmutableSet.> of()); + } +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSApiExpectTest.java b/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSApiExpectTest.java new file mode 100644 index 0000000000..829a107dc5 --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSApiExpectTest.java @@ -0,0 +1,29 @@ +/** + * 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.sts.internal; + +import org.jclouds.sts.STSApi; + +/** + * + * @author Adrian Cole + */ +public class BaseSTSApiExpectTest extends BaseSTSExpectTest { + +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSApiLiveTest.java b/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSApiLiveTest.java new file mode 100644 index 0000000000..fb1008556c --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSApiLiveTest.java @@ -0,0 +1,47 @@ +/** + * 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.sts.internal; + +import org.jclouds.apis.BaseContextLiveTest; +import org.jclouds.sts.STSApiMetadata; +import org.jclouds.sts.STSAsyncApi; +import org.jclouds.sts.STSApi; +import org.jclouds.rest.RestContext; +import org.testng.annotations.Test; + +import com.google.common.reflect.TypeToken; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "live") +public class BaseSTSApiLiveTest extends + BaseContextLiveTest> { + + public BaseSTSApiLiveTest() { + provider = "sts"; + } + + @Override + protected TypeToken> contextType() { + return STSApiMetadata.CONTEXT_TOKEN; + } + +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSAsyncApiExpectTest.java b/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSAsyncApiExpectTest.java new file mode 100644 index 0000000000..e466fee86c --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSAsyncApiExpectTest.java @@ -0,0 +1,38 @@ +/** + * 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.sts.internal; + +import java.util.Properties; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.sts.STSAsyncApi; + +import com.google.common.base.Function; +import com.google.inject.Module; + +/** + * + * @author Adrian Cole + */ +public class BaseSTSAsyncApiExpectTest extends BaseSTSExpectTest { + public STSAsyncApi createApi(Function fn, Module module, Properties props) { + return createInjector(fn, module, props).getInstance(STSAsyncApi.class); + } +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSExpectTest.java b/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSExpectTest.java new file mode 100644 index 0000000000..b4004180e1 --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/internal/BaseSTSExpectTest.java @@ -0,0 +1,51 @@ +/** + * 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.sts.internal; + +import org.jclouds.date.DateService; +import org.jclouds.sts.config.STSRestClientModule; +import org.jclouds.rest.ConfiguresRestClient; +import org.jclouds.rest.internal.BaseRestApiExpectTest; + +import com.google.inject.Module; + +/** + * + * @author Adrian Cole + */ +public class BaseSTSExpectTest extends BaseRestApiExpectTest { + + public BaseSTSExpectTest() { + provider = "sts"; + } + + @ConfiguresRestClient + private static final class TestSTSRestClientModule extends STSRestClientModule { + + @Override + protected String provideTimeStamp(final DateService dateService) { + return "2009-11-08T15:54:08.897Z"; + } + } + + @Override + protected Module createModule() { + return new TestSTSRestClientModule(); + } +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/options/AssumeRoleOptionsTest.java b/labs/sts/src/test/java/org/jclouds/sts/options/AssumeRoleOptionsTest.java new file mode 100644 index 0000000000..bb1faeb4d8 --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/options/AssumeRoleOptionsTest.java @@ -0,0 +1,69 @@ +/** + * 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.sts.options; + +import static org.jclouds.sts.options.AssumeRoleOptions.Builder.externalId; +import static org.jclouds.sts.options.AssumeRoleOptions.Builder.durationSeconds; +import static org.jclouds.sts.options.AssumeRoleOptions.Builder.policy; +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "AssumeRoleOptionsTest") +public class AssumeRoleOptionsTest { + + public void testExternalId() { + AssumeRoleOptions options = new AssumeRoleOptions().externalId("123ABC"); + assertEquals(ImmutableSet.of("123ABC"), options.buildFormParameters().get("ExternalId")); + } + + public void testExternalIdStatic() { + AssumeRoleOptions options = externalId("123ABC"); + assertEquals(ImmutableSet.of("123ABC"), options.buildFormParameters().get("ExternalId")); + } + + public void testDurationSeconds() { + AssumeRoleOptions options = new AssumeRoleOptions().durationSeconds(3600); + assertEquals(ImmutableSet.of("3600"), options.buildFormParameters().get("DurationSeconds")); + } + + public void testDurationSecondsStatic() { + AssumeRoleOptions options = durationSeconds(3600); + assertEquals(ImmutableSet.of("3600"), options.buildFormParameters().get("DurationSeconds")); + } + + String policy = "{\"Statement\":[{\"Sid\":\"Stmt1\",\"Effect\":\"Allow\",\"Action\":\"s3:*\",\"Resource\":\"*\"}]}"; + + public void testPolicy() { + AssumeRoleOptions options = new AssumeRoleOptions().policy(policy); + assertEquals(ImmutableSet.of(policy), options.buildFormParameters().get("Policy")); + } + + public void testPolicyStatic() { + AssumeRoleOptions options = policy(policy); + assertEquals(ImmutableSet.of(policy), options.buildFormParameters().get("Policy")); + } + +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/options/FederatedUserOptionsTest.java b/labs/sts/src/test/java/org/jclouds/sts/options/FederatedUserOptionsTest.java new file mode 100644 index 0000000000..b89f64d6e2 --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/options/FederatedUserOptionsTest.java @@ -0,0 +1,58 @@ +/** + * 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.sts.options; + +import static org.jclouds.sts.options.FederatedUserOptions.Builder.durationSeconds; +import static org.jclouds.sts.options.FederatedUserOptions.Builder.policy; +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "FederatedUserOptionsTest") +public class FederatedUserOptionsTest { + + public void testDurationSeconds() { + FederatedUserOptions options = new FederatedUserOptions().durationSeconds(3600); + assertEquals(ImmutableSet.of("3600"), options.buildFormParameters().get("DurationSeconds")); + } + + public void testDurationSecondsStatic() { + FederatedUserOptions options = durationSeconds(3600); + assertEquals(ImmutableSet.of("3600"), options.buildFormParameters().get("DurationSeconds")); + } + + String policy = "{\"Statement\":[{\"Sid\":\"Stmt1\",\"Effect\":\"Allow\",\"Action\":\"s3:*\",\"Resource\":\"*\"}]}"; + + public void testPolicy() { + FederatedUserOptions options = new FederatedUserOptions().policy(policy); + assertEquals(ImmutableSet.of(policy), options.buildFormParameters().get("Policy")); + } + + public void testPolicyStatic() { + FederatedUserOptions options = policy(policy); + assertEquals(ImmutableSet.of(policy), options.buildFormParameters().get("Policy")); + } + +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/options/TemporaryCredentialsOptionsTest.java b/labs/sts/src/test/java/org/jclouds/sts/options/TemporaryCredentialsOptionsTest.java new file mode 100644 index 0000000000..548bce3a59 --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/options/TemporaryCredentialsOptionsTest.java @@ -0,0 +1,67 @@ +/** + * 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.sts.options; + +import static org.jclouds.sts.options.TemporaryCredentialsOptions.Builder.serialNumber; +import static org.jclouds.sts.options.TemporaryCredentialsOptions.Builder.durationSeconds; +import static org.jclouds.sts.options.TemporaryCredentialsOptions.Builder.tokenCode; +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "TemporaryCredentialsOptionsTest") +public class TemporaryCredentialsOptionsTest { + + public void testSerialNumber() { + TemporaryCredentialsOptions options = new TemporaryCredentialsOptions().serialNumber("YourMFADeviceSerialNumber"); + assertEquals(ImmutableSet.of("YourMFADeviceSerialNumber"), options.buildFormParameters().get("SerialNumber")); + } + + public void testSerialNumberStatic() { + TemporaryCredentialsOptions options = serialNumber("YourMFADeviceSerialNumber"); + assertEquals(ImmutableSet.of("YourMFADeviceSerialNumber"), options.buildFormParameters().get("SerialNumber")); + } + + public void testDurationSeconds() { + TemporaryCredentialsOptions options = new TemporaryCredentialsOptions().durationSeconds(3600); + assertEquals(ImmutableSet.of("3600"), options.buildFormParameters().get("DurationSeconds")); + } + + public void testDurationSecondsStatic() { + TemporaryCredentialsOptions options = durationSeconds(3600); + assertEquals(ImmutableSet.of("3600"), options.buildFormParameters().get("DurationSeconds")); + } + + public void testTokenCode() { + TemporaryCredentialsOptions options = new TemporaryCredentialsOptions().tokenCode("123456"); + assertEquals(ImmutableSet.of("123456"), options.buildFormParameters().get("TokenCode")); + } + + public void testTokenCodeStatic() { + TemporaryCredentialsOptions options = tokenCode("123456"); + assertEquals(ImmutableSet.of("123456"), options.buildFormParameters().get("TokenCode")); + } + +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/parse/AssumeRoleResponseTest.java b/labs/sts/src/test/java/org/jclouds/sts/parse/AssumeRoleResponseTest.java new file mode 100644 index 0000000000..b8a3e74b5d --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/parse/AssumeRoleResponseTest.java @@ -0,0 +1,57 @@ +/** + * 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.sts.parse; + +import static org.testng.Assert.assertEquals; + +import java.io.InputStream; + +import org.jclouds.http.functions.BaseHandlerTest; +import org.jclouds.sts.domain.UserAndTemporaryCredentials; +import org.jclouds.sts.domain.User; +import org.jclouds.sts.xml.UserAndTemporaryCredentialsHandler; +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 = "AssumeRoleResponseTest") +public class AssumeRoleResponseTest extends BaseHandlerTest { + + public void test() { + InputStream is = getClass().getResourceAsStream("/assume_role.xml"); + + UserAndTemporaryCredentials expected = expected(); + + UserAndTemporaryCredentialsHandler handler = injector.getInstance(UserAndTemporaryCredentialsHandler.class); + UserAndTemporaryCredentials result = factory.create(handler).parse(is); + + assertEquals(result, expected); + assertEquals(result.getUser(), expected.getUser()); + assertEquals(result.getPackedPolicySize(), expected.getPackedPolicySize()); + } + + public UserAndTemporaryCredentials expected() { + return UserAndTemporaryCredentials.builder() + .credentials(new GetSessionTokenResponseTest().expected()) + .user(User.fromIdAndArn("ARO123EXAMPLE123:Bob", "arn:aws:sts::123456789012:assumed-role/demo/Bob")) + .packedPolicySize(6).build(); + } +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/parse/GetFederationTokenResponseTest.java b/labs/sts/src/test/java/org/jclouds/sts/parse/GetFederationTokenResponseTest.java new file mode 100644 index 0000000000..0a5e6e3b9d --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/parse/GetFederationTokenResponseTest.java @@ -0,0 +1,57 @@ +/** + * 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.sts.parse; + +import static org.testng.Assert.assertEquals; + +import java.io.InputStream; + +import org.jclouds.http.functions.BaseHandlerTest; +import org.jclouds.sts.domain.UserAndTemporaryCredentials; +import org.jclouds.sts.domain.User; +import org.jclouds.sts.xml.UserAndTemporaryCredentialsHandler; +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 = "GetFederationTokenResponseTest") +public class GetFederationTokenResponseTest extends BaseHandlerTest { + + public void test() { + InputStream is = getClass().getResourceAsStream("/federation_token.xml"); + + UserAndTemporaryCredentials expected = expected(); + + UserAndTemporaryCredentialsHandler handler = injector.getInstance(UserAndTemporaryCredentialsHandler.class); + UserAndTemporaryCredentials result = factory.create(handler).parse(is); + + assertEquals(result, expected); + assertEquals(result.getUser(), expected.getUser()); + assertEquals(result.getPackedPolicySize(), expected.getPackedPolicySize()); + } + + public UserAndTemporaryCredentials expected() { + return UserAndTemporaryCredentials.builder() + .credentials(new GetSessionTokenResponseTest().expected()) + .user(User.fromIdAndArn("123456789012:Bob", "arn:aws:sts::123456789012:federated-user/Bob")) + .packedPolicySize(6).build(); + } +} diff --git a/labs/sts/src/test/java/org/jclouds/sts/parse/GetSessionTokenResponseTest.java b/labs/sts/src/test/java/org/jclouds/sts/parse/GetSessionTokenResponseTest.java new file mode 100644 index 0000000000..bd2aeec8c9 --- /dev/null +++ b/labs/sts/src/test/java/org/jclouds/sts/parse/GetSessionTokenResponseTest.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.sts.parse; + +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 = "GetSessionTokenResponseTest") +public class GetSessionTokenResponseTest extends BaseHandlerTest { + + public void test() { + InputStream is = getClass().getResourceAsStream("/session_token.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/labs/sts/src/test/resources/assume_role.xml b/labs/sts/src/test/resources/assume_role.xml new file mode 100644 index 0000000000..442dc147c1 --- /dev/null +++ b/labs/sts/src/test/resources/assume_role.xml @@ -0,0 +1,18 @@ + + + + AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT + wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY + 2011-07-11T19:55:29.611Z + AKIAIOSFODNN7EXAMPLE + + + arn:aws:sts::123456789012:assumed-role/demo/Bob + ARO123EXAMPLE123:Bob + + 6 + + + c6104cbe-af31-11e0-8154-cbc7ccf896c7 + + diff --git a/labs/sts/src/test/resources/federation_token.xml b/labs/sts/src/test/resources/federation_token.xml new file mode 100644 index 0000000000..651f559a04 --- /dev/null +++ b/labs/sts/src/test/resources/federation_token.xml @@ -0,0 +1,18 @@ + + + + AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT + wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY + 2011-07-11T19:55:29.611Z + AKIAIOSFODNN7EXAMPLE + + + arn:aws:sts::123456789012:federated-user/Bob + 123456789012:Bob + + 6 + + + c6104cbe-af31-11e0-8154-cbc7ccf896c7 + + \ No newline at end of file diff --git a/labs/sts/src/test/resources/log4j.xml b/labs/sts/src/test/resources/log4j.xml new file mode 100644 index 0000000000..7527432931 --- /dev/null +++ b/labs/sts/src/test/resources/log4j.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/labs/sts/src/test/resources/session_token.xml b/labs/sts/src/test/resources/session_token.xml new file mode 100644 index 0000000000..062ea2dfa7 --- /dev/null +++ b/labs/sts/src/test/resources/session_token.xml @@ -0,0 +1,13 @@ + + + + AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT + wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY + 2011-07-11T19:55:29.611Z + AKIAIOSFODNN7EXAMPLE + + + + 58c5dbae-abef-11e0-8cfe-09039844ac7d + + \ No newline at end of file