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