Skip to content

Stricter implementation of ARN rules when parsing resource strings. R… #1890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.util.Objects;
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
Expand Down Expand Up @@ -78,38 +77,64 @@ public static Builder builder() {
/**
* Parses a string containing either a resource, resource type and resource or
* resource type, resource and qualifier into an {@link ArnResource}.
*
* <p>
* Supports fields separated by either ":" or "/".
*
* <p>
* For legacy AWS Arns not following the resourceType:resource:qualifier pattern,
* the qualifier field will contain everything after the first two sections separated
* by either ":" or "/".
* Matches:
* <p><pre>
* resource-id
* resource-type:resource-id
* resource-type/resource-id
* resource-type:resource-id:qualifier
* resource-type/resource-id:qualifier
* </pre><p>
* resource-id can be a resource name or a resource path.
*
* @param resource - The resource string to parse.
* @return {@link ArnResource}
*/
public static ArnResource fromString(String resource) {
Character splitter = StringUtils.findFirstOccurrence(resource, ':', '/');
Integer resourceTypeBoundary = null;
Integer resourceIdBoundary = null;

if (splitter == null) {
return ArnResource.builder().resource(resource).build();
for (int i = 0; i < resource.length(); ++i) {
char ch = resource.charAt(i);

if (ch == ':' || ch == '/') {
resourceTypeBoundary = i;
break;
}
}

int resourceTypeColonIndex = resource.indexOf(splitter);
if (resourceTypeBoundary != null) {
for (int i = resource.length() - 1; i > resourceTypeBoundary; --i) {
char ch = resource.charAt(i);

ArnResource.Builder builder = ArnResource.builder().resourceType(resource.substring(0, resourceTypeColonIndex));
int resourceColonIndex = resource.indexOf(splitter, resourceTypeColonIndex);
int qualifierColonIndex = resource.indexOf(splitter, resourceColonIndex + 1);
if (qualifierColonIndex < 0) {
builder.resource(resource.substring(resourceTypeColonIndex + 1));
} else {
builder.resource(resource.substring(resourceTypeColonIndex + 1, qualifierColonIndex));
builder.qualifier(resource.substring(qualifierColonIndex + 1));
if (ch == ':') {
resourceIdBoundary = i;
break;
}
}
}

return builder.build();
if (resourceTypeBoundary == null) {
// 'resource-id'
return ArnResource.builder().resource(resource).build();
} else if (resourceIdBoundary == null) {
// 'resource-type:resource-id'
String resourceType = resource.substring(0, resourceTypeBoundary);
String resourceId = resource.substring(resourceTypeBoundary + 1);
return ArnResource.builder().resourceType(resourceType).resource(resourceId).build();
} else {
// 'resource-type:resource-id:qualifier'
String resourceType = resource.substring(0, resourceTypeBoundary);
String resourceId = resource.substring(resourceTypeBoundary + 1, resourceIdBoundary);
String qualifier = resource.substring(resourceIdBoundary + 1);
return ArnResource.builder()
.resourceType(resourceType)
.resource(resourceId)
.qualifier(qualifier)
.build();

}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@

package software.amazon.awssdk.arns;


import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.Optional;

import org.junit.Test;

public class ArnResourceTest {
Expand Down Expand Up @@ -57,6 +57,120 @@ public void arnResourceFromBuilder_shouldParseCorrectly() {
assertThat(arnResource.resource()).isEqualTo("bucket:foobar:1");
}

@Test
public void fromString_slashForm_pathNoQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint/test/object/unit-01/finance/*");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test/object/unit-01/finance/*");
assertThat(arnResource.qualifier()).isEmpty();
}

@Test
public void fromString_slashForm_pathWithQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint/test/object/unit-01/finance/file1:123");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test/object/unit-01/finance/file1");
assertThat(arnResource.qualifier()).isEqualTo(Optional.of("123"));
}

@Test
public void fromString_slashForm_pathEmptyQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint/test/object/unit-01/finance/*:");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test/object/unit-01/finance/*");
assertThat(arnResource.qualifier()).isEqualTo(Optional.of(""));
}

@Test
public void fromString_colonForm_pathNoQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint:test/object/unit-01/finance/*");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test/object/unit-01/finance/*");
assertThat(arnResource.qualifier()).isEmpty();
}

@Test
public void fromString_colonForm_pathWithQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint:test/object/unit-01/finance/file1:123");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test/object/unit-01/finance/file1");
assertThat(arnResource.qualifier()).isEqualTo(Optional.of("123"));
}

@Test
public void fromString_colonForm_pathEmptyQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint:test/object/unit-01/finance/file1:");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test/object/unit-01/finance/file1");
assertThat(arnResource.qualifier()).isEqualTo(Optional.of(""));
}

@Test
public void fromString_slashForm_typeAndNameNoQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint/test");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test");
assertThat(arnResource.qualifier()).isEmpty();
}

@Test
public void fromString_slashForm_typeAndNameWithQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint/test:123");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test");
assertThat(arnResource.qualifier()).isEqualTo(Optional.of("123"));
}

@Test
public void fromString_slashForm_typeAndNameEmptyQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint/test:");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test");
assertThat(arnResource.qualifier()).isEqualTo(Optional.of(""));
}

@Test
public void fromString_colonForm_typeAndNameNoQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint:test");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test");
assertThat(arnResource.qualifier()).isEmpty();
}

@Test
public void fromString_colonForm_typeAndNameWithQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint:test:123");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test");
assertThat(arnResource.qualifier()).isEqualTo(Optional.of("123"));
}

@Test
public void fromString_colonForm_typeAndNameEmptyQualifier() {
ArnResource arnResource = ArnResource.fromString("accesspoint:test:");
assertThat(arnResource.resourceType()).isEqualTo(Optional.of("accesspoint"));
assertThat(arnResource.resource()).isEqualTo("test");
assertThat(arnResource.qualifier()).isEqualTo(Optional.of(""));
}

@Test
public void fromString_nameOnly() {
ArnResource arnResource = ArnResource.fromString("bob");
assertThat(arnResource.resourceType()).isEmpty();
assertThat(arnResource.resource()).isEqualTo("bob");
assertThat(arnResource.qualifier()).isEmpty();
}

@Test(expected = IllegalArgumentException.class)
public void fromString_colonForm_typeWithNoId() {
ArnResource.fromString("bob:");
}

@Test(expected = IllegalArgumentException.class)
public void fromString_slashForm_typeWithNoId() {
ArnResource.fromString("bob/");
}

@Test
public void hashCodeEquals_minimalProperties() {
ArnResource arnResource = ArnResource.builder().resource("resource").build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ public void arnWithResourceTypeAndResource_SlashSplitter_ParsesCorrectly() {

@Test
public void arnWithResourceTypeAndResourceAndQualifier_SlashSplitter_ParsesCorrectly() {
String arnString = "arn:aws:s3:us-east-1:12345678910:bucket/foobar/1";
String arnString = "arn:aws:s3:us-east-1:12345678910:bucket/foobar:1";
Arn arn = Arn.fromString(arnString);
assertThat(arn.partition()).isEqualTo("aws");
assertThat(arn.service()).isEqualTo("s3");
assertThat(arn.region()).isEqualTo(Optional.of("us-east-1"));
assertThat(arn.resourceAsString()).isEqualTo("bucket/foobar/1");
assertThat(arn.resourceAsString()).isEqualTo("bucket/foobar:1");
verifyArnResource(arn.resource());
assertThat(arn.resource().qualifier().get()).isEqualTo("1");
}
Expand Down