Skip to content

Commit

Permalink
bugfix: more safety when parsing table IDs and table entity IDs, alig…
Browse files Browse the repository at this point in the history
…n ID string with AzureRM provider, and also accept table IDs in both legacy and newer formats
  • Loading branch information
manicminer committed Feb 27, 2024
1 parent 9c02373 commit ed004ee
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 32 deletions.
21 changes: 13 additions & 8 deletions storage/2020-08-04/table/entities/resource_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (b EntityId) String() string {

// ParseEntityID parses `input` into a Entity ID using a known `domainSuffix`
func ParseEntityID(input, domainSuffix string) (*EntityId, error) {
// example: https://foo.table.core.windows.net/Bar1(PartitionKey='partition1',RowKey='row1')
// example: https://foo.table.core.windows.net/bar(PartitionKey='partition1',RowKey='row1')
if input == "" {
return nil, fmt.Errorf("`input` was empty")
}
Expand All @@ -79,23 +79,28 @@ func ParseEntityID(input, domainSuffix string) (*EntityId, error) {

// Tables and Table Entities are similar with table being `table1` and entities
// being `table1(PartitionKey='samplepartition',RowKey='samplerow')` so we need to validate this is a table
key := strings.TrimPrefix(uri.Path, "/")
if !strings.Contains(key, "(") || !strings.HasSuffix(key, ")") {
return nil, fmt.Errorf("expected the path to be an entity name but got a table name %q", key)
slug := strings.TrimPrefix(uri.Path, "/")
if strings.HasPrefix(slug, "Tables('") && strings.HasSuffix(slug, "')") {
// Ensure we do not parse a Table ID in the format: https://foo.table.core.windows.net/Table('foo')
return nil, fmt.Errorf("expected the path to be an entity name but got a table name: %q", slug)
} else if !strings.Contains(slug, "(") || !strings.HasSuffix(slug, ")") {
// Ensure we do not try to parse a bare table name
return nil, fmt.Errorf("expected the path to be an entity name but got an invalid format, possibly a table name: %q", slug)
}

indexOfFirstBracket := strings.Index(key, "(")
tableName := key[0:indexOfFirstBracket]
componentString := key[indexOfFirstBracket:]
indexOfFirstBracket := strings.Index(slug, "(")
tableName := slug[0:indexOfFirstBracket]
componentString := slug[indexOfFirstBracket:]
componentString = strings.TrimPrefix(componentString, "(")
componentString = strings.TrimSuffix(componentString, ")")
components := strings.Split(componentString, ",")
if len(components) != 2 {
return nil, fmt.Errorf("expected the path to be an entity name but got %q", key)
return nil, fmt.Errorf("expected the path to be an entity name but got %q", slug)
}

partitionKey := parseValueFromKey(components[0], "PartitionKey")
rowKey := parseValueFromKey(components[1], "RowKey")

return &EntityId{
AccountId: *account,
TableName: tableName,
Expand Down
25 changes: 20 additions & 5 deletions storage/2020-08-04/table/tables/resource_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (b TableId) String() string {

// ParseTableID parses `input` into a Table ID using a known `domainSuffix`
func ParseTableID(input, domainSuffix string) (*TableId, error) {
// example: https://foo.table.core.windows.net/Bar
// example: https://foo.table.core.windows.net/Table('bar')
if input == "" {
return nil, fmt.Errorf("`input` was empty")
}
Expand All @@ -73,12 +73,27 @@ func ParseTableID(input, domainSuffix string) (*TableId, error) {
return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments))
}

// Tables and Table Entities are similar with table being `table1` and entities
// being `table1(PartitionKey='samplepartition',RowKey='samplerow')` so we need to validate this is a table
tableName := strings.TrimPrefix(uri.Path, "/")
if strings.Contains(tableName, "(") || strings.Contains(tableName, ")") {
// Tables and Table Entities are similar however Tables use a reserved namespace, for example:
// Table('tableName')
// whereas Entities begin with the actual table name, for example:
// tableName(PartitionKey='samplepartition',RowKey='samplerow')
// However, there was a period of time when Table IDs did not use the reserved namespace, so we attempt to parse
// both forms for maximum compatibility.
var tableName string
slug := strings.TrimPrefix(uri.Path, "/")
if strings.HasPrefix(slug, "Tables('") && strings.HasSuffix(slug, "')") {
// Ensure both prefix and suffix are present before trimming them out
tableName = strings.TrimSuffix(strings.TrimPrefix(slug, "Tables('"), "')")
} else if !strings.Contains(slug, "(") && !strings.HasSuffix(slug, ")") {
// Also accept a bare table name
tableName = slug
} else {
return nil, fmt.Errorf("expected the path to a table name and not an entity name but got %q", tableName)
}
if tableName == "" {
return nil, fmt.Errorf("expected the path to a table name but the path was empty")
}

return &TableId{
AccountId: *account,
TableName: tableName,
Expand Down
21 changes: 13 additions & 8 deletions storage/2023-11-03/table/entities/resource_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (b EntityId) String() string {

// ParseEntityID parses `input` into a Entity ID using a known `domainSuffix`
func ParseEntityID(input, domainSuffix string) (*EntityId, error) {
// example: https://foo.table.core.windows.net/Bar1(PartitionKey='partition1',RowKey='row1')
// example: https://foo.table.core.windows.net/bar(PartitionKey='partition1',RowKey='row1')
if input == "" {
return nil, fmt.Errorf("`input` was empty")
}
Expand All @@ -79,23 +79,28 @@ func ParseEntityID(input, domainSuffix string) (*EntityId, error) {

// Tables and Table Entities are similar with table being `table1` and entities
// being `table1(PartitionKey='samplepartition',RowKey='samplerow')` so we need to validate this is a table
key := strings.TrimPrefix(uri.Path, "/")
if !strings.Contains(key, "(") || !strings.HasSuffix(key, ")") {
return nil, fmt.Errorf("expected the path to be an entity name but got a table name %q", key)
slug := strings.TrimPrefix(uri.Path, "/")
if strings.HasPrefix(slug, "Tables('") && strings.HasSuffix(slug, "')") {
// Ensure we do not parse a Table ID in the format: https://foo.table.core.windows.net/Table('foo')
return nil, fmt.Errorf("expected the path to be an entity name but got a table name: %q", slug)
} else if !strings.Contains(slug, "(") || !strings.HasSuffix(slug, ")") {
// Ensure we do not try to parse a bare table name
return nil, fmt.Errorf("expected the path to be an entity name but got an invalid format, possibly a table name: %q", slug)
}

indexOfFirstBracket := strings.Index(key, "(")
tableName := key[0:indexOfFirstBracket]
componentString := key[indexOfFirstBracket:]
indexOfFirstBracket := strings.Index(slug, "(")
tableName := slug[0:indexOfFirstBracket]
componentString := slug[indexOfFirstBracket:]
componentString = strings.TrimPrefix(componentString, "(")
componentString = strings.TrimSuffix(componentString, ")")
components := strings.Split(componentString, ",")
if len(components) != 2 {
return nil, fmt.Errorf("expected the path to be an entity name but got %q", key)
return nil, fmt.Errorf("expected the path to be an entity name but got %q", slug)
}

partitionKey := parseValueFromKey(components[0], "PartitionKey")
rowKey := parseValueFromKey(components[1], "RowKey")

return &EntityId{
AccountId: *account,
TableName: tableName,
Expand Down
27 changes: 21 additions & 6 deletions storage/2023-11-03/table/tables/resource_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func NewTableID(accountId accounts.AccountId, tableName string) TableId {
}

func (b TableId) ID() string {
return fmt.Sprintf("%s/%s", b.AccountId.ID(), b.TableName)
return fmt.Sprintf("%s/Tables('%s')", b.AccountId.ID(), b.TableName)
}

func (b TableId) String() string {
Expand All @@ -48,7 +48,7 @@ func (b TableId) String() string {

// ParseTableID parses `input` into a Table ID using a known `domainSuffix`
func ParseTableID(input, domainSuffix string) (*TableId, error) {
// example: https://foo.table.core.windows.net/Bar
// example: https://foo.table.core.windows.net/Table('bar')
if input == "" {
return nil, fmt.Errorf("`input` was empty")
}
Expand All @@ -73,12 +73,27 @@ func ParseTableID(input, domainSuffix string) (*TableId, error) {
return nil, fmt.Errorf("expected the path to contain 1 segment but got %d", len(segments))
}

// Tables and Table Entities are similar with table being `table1` and entities
// being `table1(PartitionKey='samplepartition',RowKey='samplerow')` so we need to validate this is a table
tableName := strings.TrimPrefix(uri.Path, "/")
if strings.Contains(tableName, "(") || strings.Contains(tableName, ")") {
// Tables and Table Entities are similar however Tables use a reserved namespace, for example:
// Table('tableName')
// whereas Entities begin with the actual table name, for example:
// tableName(PartitionKey='samplepartition',RowKey='samplerow')
// However, there was a period of time when Table IDs did not use the reserved namespace, so we attempt to parse
// both forms for maximum compatibility.
var tableName string
slug := strings.TrimPrefix(uri.Path, "/")
if strings.HasPrefix(slug, "Tables('") && strings.HasSuffix(slug, "')") {
// Ensure both prefix and suffix are present before trimming them out
tableName = strings.TrimSuffix(strings.TrimPrefix(slug, "Tables('"), "')")
} else if !strings.Contains(slug, "(") && !strings.HasSuffix(slug, ")") {
// Also accept a bare table name
tableName = slug
} else {
return nil, fmt.Errorf("expected the path to a table name and not an entity name but got %q", tableName)
}
if tableName == "" {
return nil, fmt.Errorf("expected the path to a table name but the path was empty")
}

return &TableId{
AccountId: *account,
TableName: tableName,
Expand Down
38 changes: 33 additions & 5 deletions storage/2023-11-03/table/tables/resource_id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,34 @@ func TestGetResourceManagerResourceID(t *testing.T) {
}

func TestParseTableIDStandard(t *testing.T) {
input := "https://example1.table.core.windows.net/Tables('table1')"
expected := TableId{
AccountId: accounts.AccountId{
AccountName: "example1",
SubDomainType: accounts.TableSubDomainType,
DomainSuffix: "core.windows.net",
},
TableName: "table1",
}
actual, err := ParseTableID(input, "core.windows.net")
if err != nil {
t.Fatalf(err.Error())
}
if actual.AccountId.AccountName != expected.AccountId.AccountName {
t.Fatalf("expected AccountName to be %q but got %q", expected.AccountId.AccountName, actual.AccountId.AccountName)
}
if actual.AccountId.SubDomainType != expected.AccountId.SubDomainType {
t.Fatalf("expected SubDomainType to be %q but got %q", expected.AccountId.SubDomainType, actual.AccountId.SubDomainType)
}
if actual.AccountId.DomainSuffix != expected.AccountId.DomainSuffix {
t.Fatalf("expected DomainSuffix to be %q but got %q", expected.AccountId.DomainSuffix, actual.AccountId.DomainSuffix)
}
if actual.TableName != expected.TableName {
t.Fatalf("expected TableName to be %q but got %q", expected.TableName, actual.TableName)
}
}

func TestParseTableIDLegacy(t *testing.T) {
input := "https://example1.table.core.windows.net/table1"
expected := TableId{
AccountId: accounts.AccountId{
Expand Down Expand Up @@ -44,7 +72,7 @@ func TestParseTableIDStandard(t *testing.T) {
}

func TestParseTableIDInADNSZone(t *testing.T) {
input := "https://example1.zone1.table.storage.azure.net/table1"
input := "https://example1.zone1.table.storage.azure.net/Tables('table1')"
expected := TableId{
AccountId: accounts.AccountId{
AccountName: "example1",
Expand Down Expand Up @@ -76,7 +104,7 @@ func TestParseTableIDInADNSZone(t *testing.T) {
}

func TestParseTableIDInAnEdgeZone(t *testing.T) {
input := "https://example1.table.zone1.edgestorage.azure.net/table1"
input := "https://example1.table.zone1.edgestorage.azure.net/Tables('table1')"
expected := TableId{
AccountId: accounts.AccountId{
AccountName: "example1",
Expand Down Expand Up @@ -121,7 +149,7 @@ func TestFormatTableIDStandard(t *testing.T) {
},
TableName: "table1",
}.ID()
expected := "https://example1.table.core.windows.net/table1"
expected := "https://example1.table.core.windows.net/Tables('table1')"
if actual != expected {
t.Fatalf("expected %q but got %q", expected, actual)
}
Expand All @@ -138,7 +166,7 @@ func TestFormatTableIDInDNSZone(t *testing.T) {
},
TableName: "table1",
}.ID()
expected := "https://example1.zone2.table.storage.azure.net/table1"
expected := "https://example1.zone2.table.storage.azure.net/Tables('table1')"
if actual != expected {
t.Fatalf("expected %q but got %q", expected, actual)
}
Expand All @@ -155,7 +183,7 @@ func TestFormatTableIDInEdgeZone(t *testing.T) {
},
TableName: "table1",
}.ID()
expected := "https://example1.table.zone2.edgestorage.azure.net/table1"
expected := "https://example1.table.zone2.edgestorage.azure.net/Tables('table1')"
if actual != expected {
t.Fatalf("expected %q but got %q", expected, actual)
}
Expand Down

0 comments on commit ed004ee

Please sign in to comment.