mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-08-22 10:10:12 +00:00
Wildcard search (#636)
* Add wildcard support * Add wildcard search for zones * Update documentation * Fix for AWSAuthenticator to support asterisks
This commit is contained in:
parent
95fe56e070
commit
e03690271a
2
.gitignore
vendored
2
.gitignore
vendored
@ -25,3 +25,5 @@ release.version
|
|||||||
.ensime_cache
|
.ensime_cache
|
||||||
package-lock.json
|
package-lock.json
|
||||||
*trustStore.jks
|
*trustStore.jks
|
||||||
|
.bloop
|
||||||
|
.metals
|
||||||
|
@ -214,7 +214,7 @@ def test_list_recordsets_with_record_name_filter_all(rs_fixture):
|
|||||||
client = rs_fixture.client
|
client = rs_fixture.client
|
||||||
ok_zone = rs_fixture.test_context
|
ok_zone = rs_fixture.test_context
|
||||||
|
|
||||||
list_results = client.list_recordsets(ok_zone['id'], record_name_filter="list", status=200)
|
list_results = client.list_recordsets(ok_zone['id'], record_name_filter="*list*", status=200)
|
||||||
rs_fixture.check_recordsets_page_accuracy(list_results, size=10, offset=0)
|
rs_fixture.check_recordsets_page_accuracy(list_results, size=10, offset=0)
|
||||||
|
|
||||||
|
|
||||||
@ -227,7 +227,7 @@ def test_list_recordsets_with_record_name_filter_and_page_size(rs_fixture):
|
|||||||
ok_zone = rs_fixture.test_context
|
ok_zone = rs_fixture.test_context
|
||||||
|
|
||||||
#page of 4 items
|
#page of 4 items
|
||||||
list_results = client.list_recordsets(ok_zone['id'], max_items=4, record_name_filter="CNAME", status=200)
|
list_results = client.list_recordsets(ok_zone['id'], max_items=4, record_name_filter="*CNAME*", status=200)
|
||||||
assert_that(list_results['recordSets'], has_length(4))
|
assert_that(list_results['recordSets'], has_length(4))
|
||||||
|
|
||||||
list_results_records = list_results['recordSets'];
|
list_results_records = list_results['recordSets'];
|
||||||
@ -235,7 +235,7 @@ def test_list_recordsets_with_record_name_filter_and_page_size(rs_fixture):
|
|||||||
assert_that(list_results_records[i]['name'], contains_string('CNAME'))
|
assert_that(list_results_records[i]['name'], contains_string('CNAME'))
|
||||||
|
|
||||||
#page of 5 items but excess max items
|
#page of 5 items but excess max items
|
||||||
list_results = client.list_recordsets(ok_zone['id'], max_items=7, record_name_filter="CNAME", status=200)
|
list_results = client.list_recordsets(ok_zone['id'], max_items=7, record_name_filter="*CNAME*", status=200)
|
||||||
assert_that(list_results['recordSets'], has_length(5))
|
assert_that(list_results['recordSets'], has_length(5))
|
||||||
|
|
||||||
list_results_records = list_results['recordSets'];
|
list_results_records = list_results['recordSets'];
|
||||||
@ -252,12 +252,12 @@ def test_list_recordsets_with_record_name_filter_and_chaining_pages_with_nextId(
|
|||||||
ok_zone = rs_fixture.test_context
|
ok_zone = rs_fixture.test_context
|
||||||
|
|
||||||
#page of 2 items
|
#page of 2 items
|
||||||
list_results = client.list_recordsets(ok_zone['id'], max_items=2, record_name_filter="CNAME", status=200)
|
list_results = client.list_recordsets(ok_zone['id'], max_items=2, record_name_filter="*CNAME*", status=200)
|
||||||
assert_that(list_results['recordSets'], has_length(2))
|
assert_that(list_results['recordSets'], has_length(2))
|
||||||
start_key = list_results['nextId']
|
start_key = list_results['nextId']
|
||||||
|
|
||||||
#page of 2 items
|
#page of 2 items
|
||||||
list_results = client.list_recordsets(ok_zone['id'], start_from=start_key, max_items=2, record_name_filter="CNAME", status=200)
|
list_results = client.list_recordsets(ok_zone['id'], start_from=start_key, max_items=2, record_name_filter="*CNAME*", status=200)
|
||||||
assert_that(list_results['recordSets'], has_length(2))
|
assert_that(list_results['recordSets'], has_length(2))
|
||||||
|
|
||||||
list_results_records = list_results['recordSets'];
|
list_results_records = list_results['recordSets'];
|
||||||
@ -272,7 +272,7 @@ def test_list_recordsets_with_record_name_filter_one(rs_fixture):
|
|||||||
client = rs_fixture.client
|
client = rs_fixture.client
|
||||||
ok_zone = rs_fixture.test_context
|
ok_zone = rs_fixture.test_context
|
||||||
|
|
||||||
list_results = client.list_recordsets(ok_zone['id'], record_name_filter="8", status=200)
|
list_results = client.list_recordsets(ok_zone['id'], record_name_filter="*8*", status=200)
|
||||||
rs_fixture.check_recordsets_page_accuracy(list_results, size=1, offset=8)
|
rs_fixture.check_recordsets_page_accuracy(list_results, size=1, offset=8)
|
||||||
|
|
||||||
|
|
||||||
@ -283,7 +283,7 @@ def test_list_recordsets_with_record_name_filter_none(rs_fixture):
|
|||||||
client = rs_fixture.client
|
client = rs_fixture.client
|
||||||
ok_zone = rs_fixture.test_context
|
ok_zone = rs_fixture.test_context
|
||||||
|
|
||||||
list_results = client.list_recordsets(ok_zone['id'], record_name_filter="Dummy", status=200)
|
list_results = client.list_recordsets(ok_zone['id'], record_name_filter="*Dummy*", status=200)
|
||||||
rs_fixture.check_recordsets_page_accuracy(list_results, size=0, offset=0)
|
rs_fixture.check_recordsets_page_accuracy(list_results, size=0, offset=0)
|
||||||
|
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ def test_list_zones_with_search_first_page(list_zones_context):
|
|||||||
"""
|
"""
|
||||||
Test that the first page of listing zones returns correctly when a name filter is provided
|
Test that the first page of listing zones returns correctly when a name filter is provided
|
||||||
"""
|
"""
|
||||||
result = list_zones_context.client.list_zones(name_filter='searched', max_items=2, status=200)
|
result = list_zones_context.client.list_zones(name_filter='*searched*', max_items=2, status=200)
|
||||||
zones = result['zones']
|
zones = result['zones']
|
||||||
|
|
||||||
assert_that(zones, has_length(2))
|
assert_that(zones, has_length(2))
|
||||||
@ -208,7 +208,7 @@ def test_list_zones_with_search_first_page(list_zones_context):
|
|||||||
|
|
||||||
assert_that(result['nextId'], is_('list-zones-test-searched-2.'))
|
assert_that(result['nextId'], is_('list-zones-test-searched-2.'))
|
||||||
assert_that(result['maxItems'], is_(2))
|
assert_that(result['maxItems'], is_(2))
|
||||||
assert_that(result['nameFilter'], is_('searched'))
|
assert_that(result['nameFilter'], is_('*searched*'))
|
||||||
assert_that(result, is_not(has_key('startFrom')))
|
assert_that(result, is_not(has_key('startFrom')))
|
||||||
|
|
||||||
|
|
||||||
@ -231,7 +231,7 @@ def test_list_zones_with_search_last_page(list_zones_context):
|
|||||||
"""
|
"""
|
||||||
Test that the second page of listing zones returns correctly when a name filter is provided
|
Test that the second page of listing zones returns correctly when a name filter is provided
|
||||||
"""
|
"""
|
||||||
result = list_zones_context.client.list_zones(name_filter='searched', start_from="list-zones-test-searched-2.", max_items=2, status=200)
|
result = list_zones_context.client.list_zones(name_filter='*searched*', start_from="list-zones-test-searched-2.", max_items=2, status=200)
|
||||||
zones = result['zones']
|
zones = result['zones']
|
||||||
|
|
||||||
assert_that(zones, has_length(1))
|
assert_that(zones, has_length(1))
|
||||||
@ -239,5 +239,5 @@ def test_list_zones_with_search_last_page(list_zones_context):
|
|||||||
|
|
||||||
assert_that(result, is_not(has_key('nextId')))
|
assert_that(result, is_not(has_key('nextId')))
|
||||||
assert_that(result['maxItems'], is_(2))
|
assert_that(result['maxItems'], is_(2))
|
||||||
assert_that(result['nameFilter'], is_('searched'))
|
assert_that(result['nameFilter'], is_('*searched*'))
|
||||||
assert_that(result['startFrom'], is_('list-zones-test-searched-2.'))
|
assert_that(result['startFrom'], is_('list-zones-test-searched-2.'))
|
||||||
|
@ -239,6 +239,7 @@ class Aws4Authenticator {
|
|||||||
.
|
.
|
||||||
// and doesn't encode '~' at all
|
// and doesn't encode '~' at all
|
||||||
replaceAllLiterally("%7E", "~")
|
replaceAllLiterally("%7E", "~")
|
||||||
|
.replaceAllLiterally("*", "%2A") // aws encodes the asterisk specially
|
||||||
|
|
||||||
private def hexString(bs: Array[Byte]) =
|
private def hexString(bs: Array[Byte]) =
|
||||||
bs.foldLeft("")((out, b) => f"$out%s${b & 0x0ff}%02x")
|
bs.foldLeft("")((out, b) => f"$out%s${b & 0x0ff}%02x")
|
||||||
|
@ -16,7 +16,7 @@ Retrieves a list of RecordSets from the zone
|
|||||||
|
|
||||||
name | type | required? | description |
|
name | type | required? | description |
|
||||||
------------ | ------------- | ----------- | :---------- |
|
------------ | ------------- | ----------- | :---------- |
|
||||||
recordNameFilter | string | no | One or more characters contained in the name of the record set to search for. For example `vinyl`. This is a contains search only, no wildcards or regular expressions are supported |
|
recordNameFilter | string | no | Characters that are part of the record name to search for. The wildcard character `*` is supported, for example `www*`. Omit the wildcard when searching for an exact record name. |
|
||||||
startFrom | *any* | no | In order to advance through pages of results, the startFrom is set to the `nextId` that is returned on the previous response. It is up to the client to maintain previous pages if the client wishes to advance forward and backward. If not specified, will return the first page of results |
|
startFrom | *any* | no | In order to advance through pages of results, the startFrom is set to the `nextId` that is returned on the previous response. It is up to the client to maintain previous pages if the client wishes to advance forward and backward. If not specified, will return the first page of results |
|
||||||
maxItems | integer | no | The number of items to return in the page. Valid values are 1 to 100. Defaults to 100 if not provided. |
|
maxItems | integer | no | The number of items to return in the page. Valid values are 1 to 100. Defaults to 100 if not provided. |
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ Retrieves the list of zones a user has access to. The zone name is only sorted
|
|||||||
|
|
||||||
name | type | required? | description |
|
name | type | required? | description |
|
||||||
------------ | ------------- | ----------- | :---------- |
|
------------ | ------------- | ----------- | :---------- |
|
||||||
nameFilter | string | no | One or more characters contained in the name of the zone to search for. For example `www-`. This is a contains search only, no wildcards or regular expressions are supported |
|
nameFilter | string | no | Characters that are part of the zone name to search for. The wildcard character `*` is supported, for example `www*`. Omit the wildcard character when searching for an exact zone name. |
|
||||||
startFrom | *any* | no | In order to advance through pages of results, the startFrom is set to the `nextId` that is returned on the previous response. It is up to the client to maintain previous pages if the client wishes to advance forward and backward. If not specified, will return the first page of results |
|
startFrom | *any* | no | In order to advance through pages of results, the startFrom is set to the `nextId` that is returned on the previous response. It is up to the client to maintain previous pages if the client wishes to advance forward and backward. If not specified, will return the first page of results |
|
||||||
maxItems | int | no | The number of items to return in the page. Valid values are 1 - 100. Defaults to 100 if not provided. |
|
maxItems | int | no | The number of items to return in the page. Valid values are 1 - 100. Defaults to 100 if not provided. |
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ position: 7
|
|||||||
# Getting Help
|
# Getting Help
|
||||||
|
|
||||||
- Gitter community:
|
- Gitter community:
|
||||||
<https://gitter.im/vinyldns>
|
<https://gitter.im/vinyldns/vinyldns>
|
||||||
|
|
||||||
- Contact the VinylDNS Core Team:
|
- Contact the VinylDNS Core Team:
|
||||||
vinyldns-core@googlegroups.com
|
vinyldns-core@googlegroups.com
|
||||||
|
@ -341,10 +341,7 @@ class MySqlRecordSetRepositoryIntegrationSpec
|
|||||||
found.recordSets should contain theSameElementsInOrderAs existing.slice(2, 4)
|
found.recordSets should contain theSameElementsInOrderAs existing.slice(2, 4)
|
||||||
}
|
}
|
||||||
"return the record sets after startFrom respecting maxItems and filter" in {
|
"return the record sets after startFrom respecting maxItems and filter" in {
|
||||||
// load some deterministic names so we can filter and respect max items
|
|
||||||
val recordNames = List("aaa", "bbb", "ccc", "ddd", "eeez", "fffz", "ggg", "hhhz", "iii", "jjj")
|
val recordNames = List("aaa", "bbb", "ccc", "ddd", "eeez", "fffz", "ggg", "hhhz", "iii", "jjj")
|
||||||
|
|
||||||
// our search will be filtered by records with "z"
|
|
||||||
val expectedNames = recordNames.filter(_.contains("z"))
|
val expectedNames = recordNames.filter(_.contains("z"))
|
||||||
|
|
||||||
val newRecordSets =
|
val newRecordSets =
|
||||||
@ -359,9 +356,58 @@ class MySqlRecordSetRepositoryIntegrationSpec
|
|||||||
val changes = newRecordSets.map(makeTestAddChange(_, okZone))
|
val changes = newRecordSets.map(makeTestAddChange(_, okZone))
|
||||||
insert(changes)
|
insert(changes)
|
||||||
|
|
||||||
// start after the second, pulling 3 records that have "z"
|
|
||||||
val startFrom = Some(newRecordSets(1).name)
|
val startFrom = Some(newRecordSets(1).name)
|
||||||
val found = repo.listRecordSets(okZone.id, startFrom, Some(3), Some("z")).unsafeRunSync()
|
val found = repo.listRecordSets(okZone.id, startFrom, Some(3), Some("*z*")).unsafeRunSync()
|
||||||
|
found.recordSets.map(_.name) should contain theSameElementsInOrderAs expectedNames
|
||||||
|
}
|
||||||
|
"return record sets using starts with wildcard" in {
|
||||||
|
val recordNames = List("aaa", "aab", "ccc")
|
||||||
|
val expectedNames = recordNames.filter(_.startsWith("aa"))
|
||||||
|
|
||||||
|
val newRecordSets =
|
||||||
|
for {
|
||||||
|
n <- recordNames
|
||||||
|
} yield
|
||||||
|
aaaa.copy(
|
||||||
|
zoneId = okZone.id,
|
||||||
|
name = n,
|
||||||
|
id = UUID.randomUUID().toString)
|
||||||
|
|
||||||
|
val changes = newRecordSets.map(makeTestAddChange(_, okZone))
|
||||||
|
insert(changes)
|
||||||
|
|
||||||
|
val found = repo.listRecordSets(okZone.id, None, Some(3), Some("aa*")).unsafeRunSync()
|
||||||
|
found.recordSets.map(_.name) should contain theSameElementsInOrderAs expectedNames
|
||||||
|
}
|
||||||
|
"return record sets using ends with wildcard" in {
|
||||||
|
val recordNames = List("aaa", "aab", "ccb")
|
||||||
|
val expectedNames = recordNames.filter(_.endsWith("b"))
|
||||||
|
|
||||||
|
val newRecordSets =
|
||||||
|
for {
|
||||||
|
n <- recordNames
|
||||||
|
} yield aaaa.copy(zoneId = okZone.id, name = n, id = UUID.randomUUID().toString)
|
||||||
|
|
||||||
|
val changes = newRecordSets.map(makeTestAddChange(_, okZone))
|
||||||
|
insert(changes)
|
||||||
|
|
||||||
|
val found = repo.listRecordSets(okZone.id, None, Some(3), Some("*b")).unsafeRunSync()
|
||||||
|
found.recordSets.map(_.name) should contain theSameElementsInOrderAs expectedNames
|
||||||
|
}
|
||||||
|
"return record sets exact match with no wildcards" in {
|
||||||
|
// load some deterministic names so we can filter and respect max items
|
||||||
|
val recordNames = List("aaa", "aab", "ccb")
|
||||||
|
val expectedNames = List("aaa")
|
||||||
|
|
||||||
|
val newRecordSets =
|
||||||
|
for {
|
||||||
|
n <- recordNames
|
||||||
|
} yield aaaa.copy(zoneId = okZone.id, name = n, id = UUID.randomUUID().toString)
|
||||||
|
|
||||||
|
val changes = newRecordSets.map(makeTestAddChange(_, okZone))
|
||||||
|
insert(changes)
|
||||||
|
|
||||||
|
val found = repo.listRecordSets(okZone.id, None, Some(3), Some("aaa")).unsafeRunSync()
|
||||||
found.recordSets.map(_.name) should contain theSameElementsInOrderAs expectedNames
|
found.recordSets.map(_.name) should contain theSameElementsInOrderAs expectedNames
|
||||||
}
|
}
|
||||||
"pages through the list properly" in {
|
"pages through the list properly" in {
|
||||||
|
@ -384,7 +384,7 @@ class MySqlZoneRepositoryIntegrationSpec
|
|||||||
val f =
|
val f =
|
||||||
for {
|
for {
|
||||||
_ <- saveZones(testZones)
|
_ <- saveZones(testZones)
|
||||||
retrieved <- repo.listZones(superUserAuth, zoneNameFilter = Some("system"))
|
retrieved <- repo.listZones(superUserAuth, zoneNameFilter = Some("system*"))
|
||||||
} yield retrieved
|
} yield retrieved
|
||||||
|
|
||||||
f.unsafeRunSync().zones should contain theSameElementsAs expectedZones
|
f.unsafeRunSync().zones should contain theSameElementsAs expectedZones
|
||||||
@ -405,7 +405,69 @@ class MySqlZoneRepositoryIntegrationSpec
|
|||||||
val f =
|
val f =
|
||||||
for {
|
for {
|
||||||
_ <- saveZones(testZones)
|
_ <- saveZones(testZones)
|
||||||
retrieved <- repo.listZones(auth, zoneNameFilter = Some("system"))
|
retrieved <- repo.listZones(auth, zoneNameFilter = Some("system*"))
|
||||||
|
} yield retrieved
|
||||||
|
|
||||||
|
f.unsafeRunSync().zones should contain theSameElementsInOrderAs expectedZones
|
||||||
|
}
|
||||||
|
|
||||||
|
"support starts with wildcard" in {
|
||||||
|
|
||||||
|
val testZones = Seq(
|
||||||
|
testZone("system-test", adminGroupId = "foo"),
|
||||||
|
testZone("system-temp", adminGroupId = "foo"),
|
||||||
|
testZone("system-nomatch", adminGroupId = "bar")
|
||||||
|
)
|
||||||
|
|
||||||
|
val expectedZones = Seq(testZones(0), testZones(1)).sortBy(_.name)
|
||||||
|
|
||||||
|
val auth = AuthPrincipal(dummyUser, Seq("foo"))
|
||||||
|
|
||||||
|
val f =
|
||||||
|
for {
|
||||||
|
_ <- saveZones(testZones)
|
||||||
|
retrieved <- repo.listZones(auth, zoneNameFilter = Some("system*"))
|
||||||
|
} yield retrieved
|
||||||
|
|
||||||
|
f.unsafeRunSync().zones should contain theSameElementsInOrderAs expectedZones
|
||||||
|
}
|
||||||
|
|
||||||
|
"support ends with wildcard" in {
|
||||||
|
|
||||||
|
val testZones = Seq(
|
||||||
|
testZone("system-test", adminGroupId = "foo"),
|
||||||
|
testZone("system-temp", adminGroupId = "foo"),
|
||||||
|
testZone("system-nomatch", adminGroupId = "bar")
|
||||||
|
)
|
||||||
|
|
||||||
|
val expectedZones = Seq(testZones(0))
|
||||||
|
|
||||||
|
val auth = AuthPrincipal(dummyUser, Seq("foo"))
|
||||||
|
|
||||||
|
val f =
|
||||||
|
for {
|
||||||
|
_ <- saveZones(testZones)
|
||||||
|
retrieved <- repo.listZones(auth, zoneNameFilter = Some("*test"))
|
||||||
|
} yield retrieved
|
||||||
|
|
||||||
|
f.unsafeRunSync().zones should contain theSameElementsInOrderAs expectedZones
|
||||||
|
}
|
||||||
|
|
||||||
|
"support contains wildcard" in {
|
||||||
|
val testZones = Seq(
|
||||||
|
testZone("system-jokerswild", adminGroupId = "foo"),
|
||||||
|
testZone("system-wildcard", adminGroupId = "foo"),
|
||||||
|
testZone("system-nomatch", adminGroupId = "bar")
|
||||||
|
)
|
||||||
|
|
||||||
|
val expectedZones = Seq(testZones(0), testZones(1))
|
||||||
|
|
||||||
|
val auth = AuthPrincipal(dummyUser, Seq("foo"))
|
||||||
|
|
||||||
|
val f =
|
||||||
|
for {
|
||||||
|
_ <- saveZones(testZones)
|
||||||
|
retrieved <- repo.listZones(auth, zoneNameFilter = Some("*wild*"))
|
||||||
} yield retrieved
|
} yield retrieved
|
||||||
|
|
||||||
f.unsafeRunSync().zones should contain theSameElementsInOrderAs expectedZones
|
f.unsafeRunSync().zones should contain theSameElementsInOrderAs expectedZones
|
||||||
|
@ -191,7 +191,7 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored {
|
|||||||
|
|
||||||
val params = (Some('zoneId -> zoneId) ++
|
val params = (Some('zoneId -> zoneId) ++
|
||||||
startFrom.map(n => 'startFrom -> n) ++
|
startFrom.map(n => 'startFrom -> n) ++
|
||||||
recordNameFilter.map(f => 'nameFilter -> s"%$f%") ++
|
recordNameFilter.map(f => 'nameFilter -> f.replace('*', '%')) ++
|
||||||
maxItems.map(m => 'maxItems -> m)).toSeq
|
maxItems.map(m => 'maxItems -> m)).toSeq
|
||||||
|
|
||||||
val query = "SELECT data FROM recordset WHERE zone_id = {zoneId} " + opts
|
val query = "SELECT data FROM recordset WHERE zone_id = {zoneId} " + opts
|
||||||
|
@ -228,7 +228,7 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M
|
|||||||
sb.append(withAccessorCheck)
|
sb.append(withAccessorCheck)
|
||||||
|
|
||||||
val filters = List(
|
val filters = List(
|
||||||
zoneNameFilter.map(flt => s"z.name LIKE '%$flt%'"),
|
zoneNameFilter.map(flt => s"z.name LIKE '${flt.replace('*', '%')}'"),
|
||||||
startFrom.map(os => s"z.name > '$os'")
|
startFrom.map(os => s"z.name > '$os'")
|
||||||
).flatten
|
).flatten
|
||||||
|
|
||||||
|
2
modules/portal/.gitignore
vendored
2
modules/portal/.gitignore
vendored
@ -12,3 +12,5 @@ package-lock.json
|
|||||||
release.version
|
release.version
|
||||||
private
|
private
|
||||||
public/gentelella
|
public/gentelella
|
||||||
|
.bloop
|
||||||
|
.metals
|
||||||
|
Loading…
x
Reference in New Issue
Block a user