From 638834089ab919c471f37c264c2f75a37402acb0 Mon Sep 17 00:00:00 2001 From: Vladimir Orany Date: Tue, 30 Apr 2019 14:40:57 +0200 Subject: [PATCH 1/2] Micronaut 1.1.0 upgrade --- gradle.properties | 14 +++++++------- .../aws/apigateway/ws/LambdaEchoClient.java | 3 ++- .../aws/apigateway/ws/LambdaEchoJavaClient.java | 3 ++- .../micronaut/http/basic/BasicRequestHandler.java | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/gradle.properties b/gradle.properties index ddf46b9..36c2ee3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,11 @@ -version = 1.0.4.4 -micronautVersion = 1.0.4 -gruVersion = 0.7.1 -druVersion = 0.5.0 +version = 1.1.0 +micronautVersion = 1.1.0 +gruVersion = 0.8.0 +druVersion = 0.6.0 groovyVersion = 2.5.6 -spockVersion = 1.2-groovy-2.5 -awsSdkVersion = 1.11.500 -testcontainersVersion = 1.10.6 +spockVersion = 1.3-groovy-2.5 +awsSdkVersion = 1.11.542 +testcontainersVersion = 1.11.2 # this should be aligned to Micronaut version # required for AWS CBOR marshalling diff --git a/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/apigateway/ws/LambdaEchoClient.java b/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/apigateway/ws/LambdaEchoClient.java index 216f2e0..6dc03e9 100644 --- a/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/apigateway/ws/LambdaEchoClient.java +++ b/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/apigateway/ws/LambdaEchoClient.java @@ -4,7 +4,8 @@ import com.agorapulse.micronaut.aws.apigateway.ws.event.WebSocketResponse; import io.micronaut.function.client.FunctionClient; import io.reactivex.Single; -import org.testcontainers.shaded.javax.inject.Named; + +import javax.inject.Named; @FunctionClient interface LambdaEchoClient { diff --git a/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/apigateway/ws/LambdaEchoJavaClient.java b/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/apigateway/ws/LambdaEchoJavaClient.java index 66ab3dc..9a2fc5e 100644 --- a/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/apigateway/ws/LambdaEchoJavaClient.java +++ b/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/apigateway/ws/LambdaEchoJavaClient.java @@ -4,7 +4,8 @@ import com.agorapulse.micronaut.aws.apigateway.ws.event.WebSocketResponse; import io.micronaut.function.client.FunctionClient; import io.reactivex.Single; -import org.testcontainers.shaded.javax.inject.Named; + +import javax.inject.Named; @FunctionClient interface LambdaEchoJavaClient { diff --git a/micronaut-http-server-basic/src/main/groovy/com/agorapulse/micronaut/http/basic/BasicRequestHandler.java b/micronaut-http-server-basic/src/main/groovy/com/agorapulse/micronaut/http/basic/BasicRequestHandler.java index 5fbfe37..6b6b238 100644 --- a/micronaut-http-server-basic/src/main/groovy/com/agorapulse/micronaut/http/basic/BasicRequestHandler.java +++ b/micronaut-http-server-basic/src/main/groovy/com/agorapulse/micronaut/http/basic/BasicRequestHandler.java @@ -15,8 +15,8 @@ import io.micronaut.http.filter.HttpFilter; import io.micronaut.http.filter.HttpServerFilter; import io.micronaut.http.filter.ServerFilterChain; -import io.micronaut.http.hateos.JsonError; -import io.micronaut.http.hateos.Link; +import io.micronaut.http.hateoas.JsonError; +import io.micronaut.http.hateoas.Link; import io.micronaut.http.server.binding.RequestArgumentSatisfier; import io.micronaut.http.server.exceptions.ExceptionHandler; import io.micronaut.http.server.exceptions.InternalServerException; From f3d3c5149d05074f824ae1db269f31a1d55e937f Mon Sep 17 00:00:00 2001 From: Vladimir Orany Date: Mon, 6 May 2019 16:10:25 +0200 Subject: [PATCH 2/2] back-ported changes from Grails plugin --- build.gradle | 1 + docs/aws.adoc | 13 +++ docs/sts.adoc | 25 ++++++ micronaut-aws-sdk/build.gradle | 1 + .../aws/s3/DefaultSimpleStorageService.groovy | 87 +++++++++++++++++++ .../aws/s3/SimpleStorageService.java | 25 ++++++ .../aws/sqs/DefaultSimpleQueueService.groovy | 21 +++++ .../micronaut/aws/sqs/SimpleQueueService.java | 56 ++++++++++++ .../aws/sts/DefaultSecurityTokenService.java | 32 +++++++ .../aws/sts/SecurityTokenService.java | 40 +++++++++ .../aws/sts/SecurityTokenServiceFactory.java | 35 ++++++++ .../SimpleStorageServiceWithMockSpec.groovy | 40 +++++++++ .../aws/sts/SecurityTokenServiceSpec.groovy | 42 +++++++++ 13 files changed, 418 insertions(+) create mode 100644 docs/sts.adoc create mode 100644 micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/DefaultSecurityTokenService.java create mode 100644 micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenService.java create mode 100644 micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenServiceFactory.java create mode 100644 micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenServiceSpec.groovy diff --git a/build.gradle b/build.gradle index ca30599..86727bf 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,7 @@ subprojects { Project subproject -> entry 'aws-java-sdk-ses' entry 'aws-java-sdk-sns' entry 'aws-java-sdk-sqs' + entry 'aws-java-sdk-sts' entry 'jmespath' } dependency group: 'com.amazonaws', name: 'amazon-dax-client', version: '1.0.202017.0' diff --git a/docs/aws.adoc b/docs/aws.adoc index 4f75d61..ca9469c 100644 --- a/docs/aws.adoc +++ b/docs/aws.adoc @@ -11,6 +11,7 @@ Provided integrations: * <> * <> * <> +* <> * <> NOTE: <> is handled separately in its own library. @@ -60,6 +61,9 @@ compile group: 'com.amazonaws', name: 'aws-java-sdk-sns', version: '1.11.500' // only required for SQS integration compile group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '1.11.500' + +// only required for STS integration +compile group: 'com.amazonaws', name: 'aws-java-sdk-sts', version: '1.11.500' ---- [source,xml,indent=0,role="secondary",subs='verbatim,attributes'] @@ -125,6 +129,13 @@ compile group: 'com.amazonaws', name: 'aws-java-sdk-sqs', version: '1.11.500' aws-java-sdk-sqs 1.11.500 + + + + com.amazonaws + aws-java-sdk-sts + 1.11.500 + ---- include::dynamodb.adoc[] @@ -139,4 +150,6 @@ include::sns.adoc[] include::sqs.adoc[] +include::sts.adoc[] + include::websockets.adoc[] diff --git a/docs/sts.adoc b/docs/sts.adoc new file mode 100644 index 0000000..5db7812 --- /dev/null +++ b/docs/sts.adoc @@ -0,0 +1,25 @@ +=== Security Token Service (STS) + +> The AWS Security Token Service (STS) is a web service that enables you to request temporary, limited-privilege credentials for AWS Identity and Access Management (IAM) users or for users that you authenticate (federated users). + +This library provides basic support for Amazon STS using <> + +==== Security Token Service + +`SecurityTokenService` provides only one method (with multiple variations) to create credentials +which assumes usage of a certain IAM role. + +Following example shows how to create credentials for assumed role. + +[source,groovy,indent=0,options="nowrap",role="primary"] +.Assume Role +---- +include::../micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenServiceSpec.groovy[tag=usage] +---- + +Please, see https://agorapulse.github.io/micronaut-libraries/docs/javadoc/micronaut-aws-sdk/com/agorapulse/micronaut/aws/sts/SecurityTokenService.html[SecurityTokenService] for the full reference. + +==== Testing +It is recommended just to mock the `SecurityTokenService` in your tests as it only contains single abstract method. + + diff --git a/micronaut-aws-sdk/build.gradle b/micronaut-aws-sdk/build.gradle index 5c4734b..a6ef378 100644 --- a/micronaut-aws-sdk/build.gradle +++ b/micronaut-aws-sdk/build.gradle @@ -11,6 +11,7 @@ dependencies { optional group: 'com.amazonaws', name: 'aws-java-sdk-ses' optional group: 'com.amazonaws', name: 'aws-java-sdk-sns' optional group: 'com.amazonaws', name: 'aws-java-sdk-sqs' + optional group: 'com.amazonaws', name: 'aws-java-sdk-sts' optional 'javax.mail:mail:1.4.4' diff --git a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/s3/DefaultSimpleStorageService.groovy b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/s3/DefaultSimpleStorageService.groovy index 19a0830..37b23f2 100644 --- a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/s3/DefaultSimpleStorageService.groovy +++ b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/s3/DefaultSimpleStorageService.groovy @@ -82,6 +82,45 @@ class DefaultSimpleStorageService implements SimpleStorageService { metadata } + @SuppressWarnings([ + 'DuplicateStringLiteral', + 'UnnecessarySubstring', + ]) + static String getBucketFromUri(String aURI) { + URI uri = new URI(aURI) + String path = uri.path.startsWith('/') ? uri.path.substring(1, uri.path.length()) : uri.path + if (uri.host) { + // direct bucket URI not using any CNAME + if (uri.host.endsWith('amazonaws.com')) { + if (uri.host.startsWith('s3')) { + return path.substring(0, path.indexOf('/')) + } + return uri.host.substring(0, uri.host.indexOf('.s3.')) + } + return uri.host + } + // consider the bucket name is using CNAME of the same name as the bucket + return path.substring(0, path.indexOf('/')) + } + + @SuppressWarnings([ + 'DuplicateStringLiteral', + 'UnnecessarySubstring', + ]) + static String getKeyFromUri(String aURI) { + URI uri = new URI(aURI) + String path = uri.path.startsWith('/') ? uri.path.substring(1, uri.path.length()) : uri.path + if (uri.host) { + // direct bucket URI not using any CNAME + if (uri.host.endsWith('amazonaws.com') && uri.host.startsWith('s3')) { + return path.substring(path.indexOf('/') + 1, path.length()) + } + return path + } + // consider the bucket name is using CNAME of the same name as the bucket + return path.substring(path.indexOf('/') + 1, path.length()) + } + /** * * @param bucketName @@ -153,6 +192,7 @@ class DefaultSimpleStorageService implements SimpleStorageService { * @param prefix * @return */ + @SuppressWarnings('DuplicateStringLiteral') boolean deleteFiles(String bucketName, String prefix) { assert prefix.tokenize('/').size() >= 2, 'Multiple delete are only allowed in sub/sub directories' @@ -313,6 +353,53 @@ class DefaultSimpleStorageService implements SimpleStorageService { return defaultBucketName } + /** + * Move S3 object to different location (key). + * + * Moving objects is useful in combination with + * + * S3 Lifecycle Configurations + * for prefixes. + * + * @param sourceBucketName the name of the source bucket + * @param sourceKey the key of the source object + * @param destinationBucketName the name of the destination bucket + * @param destinationKey the key of the destination object + * @return the destination URL or null if the file wasn't moved + */ + String moveObject( + String sourceBucketName, + String sourceKey, + String destinationBucketName, + String destinationKey + ) { + try { + CopyObjectRequest request = new CopyObjectRequest(sourceBucketName, sourceKey, destinationBucketName, destinationKey) + + S3Object object = client.getObject(sourceBucketName, sourceKey) + + if (object.taggingCount) { + GetObjectTaggingRequest taggingRequest = new GetObjectTaggingRequest(sourceBucketName, sourceKey) + GetObjectTaggingResult taggingResult = client.getObjectTagging(taggingRequest) + request.withNewObjectTagging(new ObjectTagging(taggingResult.tagSet)) + } + + request.withNewObjectMetadata(object.objectMetadata) + + AccessControlList acl = client.getObjectAcl(sourceBucketName, sourceKey) + AccessControlList newAcls = new AccessControlList() + newAcls.grantAllPermissions(acl.grantsAsList as Grant[]) + request.withAccessControlList(newAcls) + + client.copyObject(request) + client.deleteObject(sourceBucketName, sourceKey) + return client.getUrl(destinationBucketName, destinationKey) + } catch (AmazonClientException e) { + log.error("Exception moving object $sourceBucketName/$sourceKey to $destinationBucketName/$destinationKey", e) + } + return null + } + // PRIVATE private void assertDefaultBucketName() { assert defaultBucketName, 'Default bucket must be defined' diff --git a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/s3/SimpleStorageService.java b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/s3/SimpleStorageService.java index 5967bf8..f762cc1 100644 --- a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/s3/SimpleStorageService.java +++ b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/s3/SimpleStorageService.java @@ -18,6 +18,13 @@ */ public interface SimpleStorageService { + static String getBucketFromUri(String aURI) { + return DefaultSimpleStorageService.getBucketFromUri(aURI); + } + + static String getKeyFromUri(String aURI) { + return DefaultSimpleStorageService.getKeyFromUri(aURI); + } /** * @return default name of the bucket @@ -407,4 +414,22 @@ default String storeMultipartFile(String path, PartData multipartFile, CannedAcc return storeMultipartFile(getDefaultBucketName(), path, multipartFile, cannedAcl, metadata); } + /** + * Move S3 object to different location (key). + * + * Moving objects is useful in combination with S3 Lifecycle Configurations for prefixes. + * + * @param sourceBucketName the name of the source bucket + * @param sourceKey the key of the source object + * @param destinationBucketName the name of the destination bucket + * @param destinationKey the key of the destination object + * @return the destination URL or null if the file wasn't moved + */ + String moveObject( + String sourceBucketName, + String sourceKey, + String destinationBucketName, + String destinationKey + ); + } diff --git a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sqs/DefaultSimpleQueueService.groovy b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sqs/DefaultSimpleQueueService.groovy index b663d6e..d8c6919 100644 --- a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sqs/DefaultSimpleQueueService.groovy +++ b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sqs/DefaultSimpleQueueService.groovy @@ -8,6 +8,8 @@ import groovy.transform.CompileStatic import groovy.transform.Synchronized import groovy.util.logging.Slf4j +import java.util.function.Consumer + /** * Default simple queue service implementation. */ @@ -232,6 +234,25 @@ class DefaultSimpleQueueService implements SimpleQueueService { messageId } + /** + * + * @param queueName + * @param messageBody + * @param delaySeconds + * @return + */ + String sendMessage(String queueName, String messageBody, Consumer messageConfiguration) { + String queueUrl = getQueueUrl(queueName) + + SendMessageRequest request = new SendMessageRequest(queueUrl, messageBody) + + messageConfiguration.accept(request) + + String messageId = client.sendMessage(request).messageId + log.debug "Message sent (messageId=$messageId)" + messageId + } + void assertDefaultQueueName() { assert configuration.queue, 'Default queue must be defined' } diff --git a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sqs/SimpleQueueService.java b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sqs/SimpleQueueService.java index adba8a7..c723a9a 100644 --- a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sqs/SimpleQueueService.java +++ b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sqs/SimpleQueueService.java @@ -1,9 +1,16 @@ package com.agorapulse.micronaut.aws.sqs; import com.amazonaws.services.sqs.model.Message; +import com.amazonaws.services.sqs.model.SendMessageRequest; +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import groovy.transform.stc.ClosureParams; +import groovy.transform.stc.FromString; +import space.jasan.support.groovy.closure.ConsumerWithDelegate; import java.util.List; import java.util.Map; +import java.util.function.Consumer; /** * Amazon SQS services @@ -250,4 +257,53 @@ default String sendMessage(String messageBody, int delaySeconds, String groupId) return sendMessage(getDefaultQueueName(), messageBody, delaySeconds, null); } + /** + * Sends message with additional configuration into the default queue. + * @param messageBody message body + * @param messageConfiguration additional configuration + * @return message id + */ + default String sendMessage(String messageBody, Consumer messageConfiguration) { + return sendMessage(getDefaultQueueName(), messageBody, messageConfiguration); + } + + /** + * Sends message with additional configuration into the default queue. + * @param messageBody message body + * @param messageConfiguration additional configuration + * @return message id + */ + default String sendMessage(String messageBody, + @DelegatesTo(value = SendMessageRequest.class, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = FromString.class, options = "com.amazonaws.services.sqs.model.SendMessageRequest") + Closure messageConfiguration + ) { + return sendMessage(getDefaultQueueName(), messageBody, ConsumerWithDelegate.create(messageConfiguration)); + } + + /** + * Sends message with additional configuration into the given queue. + * @param queueName name of the queue + * @param messageBody message body + * @param messageConfiguration additional configuration + * @return message id + */ + default String sendMessage( + String queueName, + String messageBody, + @DelegatesTo(value = SendMessageRequest.class, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = FromString.class, options = "com.amazonaws.services.sqs.model.SendMessageRequest") + Closure messageConfiguration + ) { + return sendMessage(queueName, messageBody, ConsumerWithDelegate.create(messageConfiguration)); + } + + /** + * Sends message with additional configuration into the given queue. + * @param queueName name of the queue + * @param messageBody message body + * @param messageConfiguration additional configuration + * @return message id + */ + String sendMessage(String queueName, String messageBody, Consumer messageConfiguration); } diff --git a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/DefaultSecurityTokenService.java b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/DefaultSecurityTokenService.java new file mode 100644 index 0000000..7b72fbf --- /dev/null +++ b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/DefaultSecurityTokenService.java @@ -0,0 +1,32 @@ +package com.agorapulse.micronaut.aws.sts; + +import com.amazonaws.services.securitytoken.AWSSecurityTokenService; +import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; +import com.amazonaws.services.securitytoken.model.AssumeRoleResult; +import io.micronaut.context.annotation.Requires; + +import javax.inject.Singleton; +import java.util.function.Consumer; + +@Singleton +@Requires(classes = AWSSecurityTokenService.class) +public class DefaultSecurityTokenService implements SecurityTokenService { + + private final AWSSecurityTokenService client; + + public DefaultSecurityTokenService(AWSSecurityTokenService client) { + this.client = client; + } + + @Override + public AssumeRoleResult assumeRole(String sessionName, String roleArn, int durationInSeconds, Consumer additionParameters) { + AssumeRoleRequest request = new AssumeRoleRequest() + .withRoleSessionName(sessionName) + .withRoleArn(roleArn) + .withDurationSeconds(durationInSeconds); + + additionParameters.accept(request); + + return client.assumeRole(request); + } +} diff --git a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenService.java b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenService.java new file mode 100644 index 0000000..3b1d126 --- /dev/null +++ b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenService.java @@ -0,0 +1,40 @@ +package com.agorapulse.micronaut.aws.sts; + +import com.amazonaws.services.securitytoken.model.AssumeRoleRequest; +import com.amazonaws.services.securitytoken.model.AssumeRoleResult; +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import groovy.transform.stc.ClosureParams; +import groovy.transform.stc.SimpleType; +import space.jasan.support.groovy.closure.ConsumerWithDelegate; + +import java.util.function.Consumer; + +public interface SecurityTokenService { + + default AssumeRoleResult assumeRole(String sessionName, String roleArn, int durationInSeconds) { + return assumeRole(sessionName, roleArn, durationInSeconds, (p) -> { }); + } + + default AssumeRoleResult assumeRole( + String sessionName, + String roleArn, + int durationInSeconds, + @DelegatesTo(value = AssumeRoleRequest.class, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = SimpleType.class, options = "com.amazonaws.services.securitytoken.model.AssumeRoleRequest") + Closure additionParameters + ) { + return assumeRole(sessionName, roleArn, durationInSeconds, ConsumerWithDelegate.create(additionParameters)); + } + + /** + * Create credentials for role assumption. + * @param sessionName the name of the session + * @param roleArn the role ARN + * @param durationInSeconds the duration in seconds + * @param additionParameters the additional parameters + * @return the assumption result containing the provisioned credentials + */ + AssumeRoleResult assumeRole(String sessionName, String roleArn, int durationInSeconds, Consumer additionParameters); + +} diff --git a/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenServiceFactory.java b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenServiceFactory.java new file mode 100644 index 0000000..734cd86 --- /dev/null +++ b/micronaut-aws-sdk/src/main/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenServiceFactory.java @@ -0,0 +1,35 @@ +package com.agorapulse.micronaut.aws.sts; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.regions.AwsRegionProvider; +import com.amazonaws.services.securitytoken.AWSSecurityTokenService; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; +import io.micronaut.configuration.aws.AWSClientConfiguration; +import io.micronaut.context.annotation.Bean; +import io.micronaut.context.annotation.Factory; +import io.micronaut.context.annotation.Requires; +import io.micronaut.context.annotation.Value; + +import javax.inject.Singleton; +import java.util.Optional; + +@Factory +@Requires(classes = AWSSecurityTokenService.class) +public class SecurityTokenServiceFactory { + + @Bean + @Singleton + AWSSecurityTokenService awsSecurityTokenService( + AWSClientConfiguration clientConfiguration, + AWSCredentialsProvider credentialsProvider, + AwsRegionProvider awsRegionProvider, + @Value("${aws.sts.region}") Optional region + ) { + return AWSSecurityTokenServiceClientBuilder.standard() + .withCredentials(credentialsProvider) + .withRegion(region.orElseGet(awsRegionProvider::getRegion)) + .withClientConfiguration(clientConfiguration.getClientConfiguration()) + .build(); + } + +} diff --git a/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/s3/SimpleStorageServiceWithMockSpec.groovy b/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/s3/SimpleStorageServiceWithMockSpec.groovy index f38498f..ffe4a6a 100644 --- a/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/s3/SimpleStorageServiceWithMockSpec.groovy +++ b/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/s3/SimpleStorageServiceWithMockSpec.groovy @@ -2,14 +2,20 @@ package com.agorapulse.micronaut.aws.s3 import com.amazonaws.AmazonClientException import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.model.AccessControlList import com.amazonaws.services.s3.model.AmazonS3Exception import com.amazonaws.services.s3.model.Bucket import com.amazonaws.services.s3.model.CannedAccessControlList +import com.amazonaws.services.s3.model.CopyObjectRequest +import com.amazonaws.services.s3.model.CopyObjectResult +import com.amazonaws.services.s3.model.GetObjectTaggingResult import com.amazonaws.services.s3.model.ObjectListing import com.amazonaws.services.s3.model.ObjectMetadata import com.amazonaws.services.s3.model.PutObjectResult import com.amazonaws.services.s3.model.Region +import com.amazonaws.services.s3.model.S3Object import com.amazonaws.services.s3.model.S3ObjectSummary +import com.amazonaws.services.s3.model.Tag import com.amazonaws.services.s3.transfer.Upload import com.amazonaws.services.s3.transfer.model.UploadResult import io.micronaut.http.MediaType @@ -24,6 +30,7 @@ import java.nio.ByteBuffer /** * Tests for simple storage service. */ +@SuppressWarnings('MethodCount') class SimpleStorageServiceWithMockSpec extends Specification { private static final String BUCKET_NAME = 'bucket' @@ -402,6 +409,39 @@ class SimpleStorageServiceWithMockSpec extends Specification { 1 * client.putObject(BUCKET_NAME, 'stored.txt', _, _) } + void 'Moving object'() { + when: + String newUrl = service.moveObject(BUCKET_NAME, 'uploads/key', BUCKET_NAME.reverse(), 'files/key') + then: + newUrl == "https://s3-eu-west-1.amazonaws.com/${BUCKET_NAME}/files/key" + + 1 * client.copyObject({ CopyObjectRequest request -> + request.sourceBucketName == BUCKET_NAME && + request.sourceKey == 'uploads/key' && + request.destinationBucketName == BUCKET_NAME.reverse() && + request.destinationKey == 'files/key' + } as CopyObjectRequest) >> new CopyObjectResult() + 1 * client.deleteObject(BUCKET_NAME, 'uploads/key') + 1 * client.getUrl(BUCKET_NAME.reverse(), 'files/key') >> new URL("https://s3-eu-west-1.amazonaws.com/${BUCKET_NAME}/files/key") + 1 * client.getObject(BUCKET_NAME, 'uploads/key') >> new S3Object(taggingCount: 1) + 1 * client.getObjectAcl(BUCKET_NAME, 'uploads/key') >> new AccessControlList() + 1 * client.getObjectTagging(_) >> new GetObjectTaggingResult([new Tag('key', 'value')]) + } + + @Unroll + void 'extract bucket name from uri #uri'() { + expect: + SimpleStorageService.getBucketFromUri(uri) == 'publishing.agorapulse.com' + SimpleStorageService.getKeyFromUri(uri) == 'publishingItemMedia/109098/f02f2a8c-1b80-50cb-f9ef-2d7850ed0525.png' + where: + uri << [ + 'https://s3.eu-west-1.amazonaws.com/publishing.agorapulse.com/publishingItemMedia/109098/f02f2a8c-1b80-50cb-f9ef-2d7850ed0525.png', + 'publishing.agorapulse.com/publishingItemMedia/109098/f02f2a8c-1b80-50cb-f9ef-2d7850ed0525.png', + 'https://publishing.agorapulse.com/publishingItemMedia/109098/f02f2a8c-1b80-50cb-f9ef-2d7850ed0525.png', + 'http://publishing.agorapulse.com.s3.eu-west-1.amazonaws.com/publishingItemMedia/109098/f02f2a8c-1b80-50cb-f9ef-2d7850ed0525.png', + ] + } + } class MockPartData implements PartData { diff --git a/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenServiceSpec.groovy b/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenServiceSpec.groovy new file mode 100644 index 0000000..dd3959f --- /dev/null +++ b/micronaut-aws-sdk/src/test/groovy/com/agorapulse/micronaut/aws/sts/SecurityTokenServiceSpec.groovy @@ -0,0 +1,42 @@ +package com.agorapulse.micronaut.aws.sts + +import com.amazonaws.services.securitytoken.AWSSecurityTokenService +import com.amazonaws.services.securitytoken.model.AssumeRoleRequest +import spock.lang.Specification + +import java.util.concurrent.atomic.AtomicReference + +/** + * Tests for SecurityTokenService. + */ +class SecurityTokenServiceSpec extends Specification { + + public static final String SESSION_NAME = 'session' + public static final String ROLE_ARN = 'arn:::my-role' + public static final int DURATION_SECONDS = 360 + public static final String EXTERNAL_ID = '123456789' + + AWSSecurityTokenService client = Mock() + + SecurityTokenService service = new DefaultSecurityTokenService(client) + + void 'assume role'() { + given: + AtomicReference requestReference = new AtomicReference<>() + when: + // tag::usage[] + service.assumeRole('session', 'arn:::my-role', 360) { + externalId = '123456789' + } + // end::usage[] + then: + requestReference.get() + requestReference.get().externalId == EXTERNAL_ID + requestReference.get().roleSessionName == SESSION_NAME + requestReference.get().roleArn == ROLE_ARN + requestReference.get().durationSeconds == DURATION_SECONDS + + 1 * client.assumeRole(_) >> { AssumeRoleRequest request -> requestReference.set(request) } + } + +}