1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.services.s3.internal.resource; 17 18 import static software.amazon.awssdk.services.s3.internal.resource.S3ArnUtils.parseOutpostArn; 19 20 import java.util.regex.Matcher; 21 import java.util.regex.Pattern; 22 import software.amazon.awssdk.annotations.SdkInternalApi; 23 import software.amazon.awssdk.arns.Arn; 24 import software.amazon.awssdk.arns.ArnResource; 25 26 /** 27 * An implementation of {@link ArnConverter} that can be used to convert valid {@link Arn} representations of s3 28 * resources into {@link S3Resource} objects. To fetch an instance of this class, use the singleton getter method 29 * {@link #create()}. 30 */ 31 @SdkInternalApi 32 public final class S3ArnConverter implements ArnConverter<S3Resource> { 33 private static final S3ArnConverter INSTANCE = new S3ArnConverter(); 34 private static final Pattern OBJECT_AP_PATTERN = Pattern.compile("^([0-9a-zA-Z-]+)/object/(.*)$"); 35 private static final String OBJECT_LAMBDA_SERVICE = "s3-object-lambda"; 36 S3ArnConverter()37 private S3ArnConverter() { 38 } 39 40 /** 41 * Gets a static singleton instance of an {@link S3ArnConverter}. 42 * @return A static instance of an {@link S3ArnConverter}. 43 */ create()44 public static S3ArnConverter create() { 45 return INSTANCE; 46 } 47 48 /** 49 * Converts a valid ARN representation of an S3 resource into a {@link S3Resource} object. 50 * @param arn The ARN to convert. 51 * @return An {@link S3Resource} object as specified by the ARN. 52 * @throws IllegalArgumentException if the ARN is not a valid representation of an S3 resource supported by this 53 * SDK. 54 */ 55 @Override convertArn(Arn arn)56 public S3Resource convertArn(Arn arn) { 57 if (isV1Arn(arn)) { 58 return convertV1Arn(arn); 59 } 60 S3ResourceType s3ResourceType; 61 62 String resourceType = arn.resource().resourceType() 63 .orElseThrow(() -> new IllegalArgumentException("Unknown ARN type")); 64 65 try { 66 s3ResourceType = S3ResourceType.fromValue(resourceType); 67 } catch (IllegalArgumentException e) { 68 throw new IllegalArgumentException("Unknown ARN type '" + arn.resource().resourceType().get() + "'"); 69 } 70 71 // OBJECT is a sub-resource under ACCESS_POINT and BUCKET and will not be recognized as a primary ARN resource 72 // type 73 switch (s3ResourceType) { 74 case ACCESS_POINT: 75 return parseS3AccessPointArn(arn); 76 case BUCKET: 77 return parseS3BucketArn(arn); 78 case OUTPOST: 79 return parseS3OutpostAccessPointArn(arn); 80 default: 81 throw new IllegalArgumentException("Unknown ARN type '" + s3ResourceType + "'"); 82 } 83 } 84 convertV1Arn(Arn arn)85 private S3Resource convertV1Arn(Arn arn) { 86 String resource = arn.resourceAsString(); 87 String[] splitResource = resource.split("/", 2); 88 89 if (splitResource.length > 1) { 90 // Bucket/key 91 S3BucketResource parentBucket = S3BucketResource.builder() 92 .partition(arn.partition()) 93 .bucketName(splitResource[0]) 94 .build(); 95 96 return S3ObjectResource.builder() 97 .parentS3Resource(parentBucket) 98 .key(splitResource[1]) 99 .build(); 100 } else { 101 // Just bucket 102 return S3BucketResource.builder() 103 .partition(arn.partition()) 104 .bucketName(resource) 105 .build(); 106 } 107 } 108 parseS3BucketArn(Arn arn)109 private S3BucketResource parseS3BucketArn(Arn arn) { 110 return S3BucketResource.builder() 111 .partition(arn.partition()) 112 .region(arn.region().orElse(null)) 113 .accountId(arn.accountId().orElse(null)) 114 .bucketName(arn.resource().resource()) 115 .build(); 116 } 117 parseS3AccessPointArn(Arn arn)118 private S3Resource parseS3AccessPointArn(Arn arn) { 119 Matcher objectMatcher = OBJECT_AP_PATTERN.matcher(arn.resource().resource()); 120 121 if (objectMatcher.matches()) { 122 // ARN is actually an object addressed through an access-point 123 String accessPointName = objectMatcher.group(1); 124 String objectKey = objectMatcher.group(2); 125 S3AccessPointResource parentResource = 126 S3AccessPointResource.builder() 127 .partition(arn.partition()) 128 .region(arn.region().orElse(null)) 129 .accountId(arn.accountId().orElse(null)) 130 .accessPointName(accessPointName) 131 .build(); 132 133 return S3ObjectResource.builder() 134 .parentS3Resource(parentResource) 135 .key(objectKey) 136 .build(); 137 } 138 139 if (OBJECT_LAMBDA_SERVICE.equals(arn.service())) { 140 return parseS3ObjectLambdaAccessPointArn(arn); 141 } 142 143 return S3AccessPointResource.builder() 144 .partition(arn.partition()) 145 .region(arn.region().orElse(null)) 146 .accountId(arn.accountId().orElse(null)) 147 .accessPointName(arn.resource().resource()) 148 .build(); 149 } 150 parseS3OutpostAccessPointArn(Arn arn)151 private S3Resource parseS3OutpostAccessPointArn(Arn arn) { 152 IntermediateOutpostResource intermediateOutpostResource = parseOutpostArn(arn); 153 ArnResource outpostSubResource = intermediateOutpostResource.outpostSubresource(); 154 155 String resourceType = outpostSubResource.resourceType() 156 .orElseThrow(() -> new IllegalArgumentException("Unknown ARN type")); 157 158 if (!OutpostResourceType.OUTPOST_ACCESS_POINT.toString().equals(resourceType)) { 159 throw new IllegalArgumentException("Unknown outpost ARN type '" + outpostSubResource.resourceType() + "'"); 160 } 161 162 return S3AccessPointResource.builder() 163 .accessPointName(outpostSubResource.resource()) 164 .parentS3Resource(S3OutpostResource.builder() 165 .partition(arn.partition()) 166 .region(arn.region().orElse(null)) 167 .accountId(arn.accountId().orElse(null)) 168 .outpostId(intermediateOutpostResource.outpostId()) 169 .build()) 170 .build(); 171 } 172 parseS3ObjectLambdaAccessPointArn(Arn arn)173 private S3Resource parseS3ObjectLambdaAccessPointArn(Arn arn) { 174 if (arn.resource().qualifier().isPresent()) { 175 throw new IllegalArgumentException("S3 object lambda access point arn shouldn't contain any sub resources."); 176 } 177 178 S3ObjectLambdaResource objectLambdaResource = S3ObjectLambdaResource.builder() 179 .accountId(arn.accountId().orElse(null)) 180 .region(arn.region().orElse(null)) 181 .partition(arn.partition()) 182 .accessPointName(arn.resource().resource()) 183 .build(); 184 return S3AccessPointResource.builder() 185 .accessPointName(objectLambdaResource.accessPointName()) 186 .parentS3Resource(objectLambdaResource) 187 .build(); 188 } 189 190 isV1Arn(Arn arn)191 private boolean isV1Arn(Arn arn) { 192 return !arn.accountId().isPresent() && !arn.region().isPresent(); 193 } 194 } 195