2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-08-31 14:25:30 +00:00

Open list groups access (#809)

This commit is contained in:
Britney Wright
2019-08-21 14:26:51 -04:00
committed by GitHub
parent e7820e6005
commit d0d88dc0ea
24 changed files with 232 additions and 101 deletions

View File

@@ -9,6 +9,7 @@ from vinyldns_context import VinylDNSTestContext
class ListGroupsSearchContext(object):
def __init__(self):
self.client = VinylDNSClient(VinylDNSTestContext.vinyldns_url, access_key='listGroupAccessKey', secret_key='listGroupSecretKey')
self.support_user_client = VinylDNSClient(VinylDNSTestContext.vinyldns_url, 'supportUserAccessKey', 'supportUserSecretKey')
self.tear_down() # ensures that the environment is clean before starting
try:
@@ -29,6 +30,15 @@ class ListGroupsSearchContext(object):
pass
raise
def verify_ignore_access(self, results):
assert_that(results, has_length(3)) # 3 fields
assert_that(len(results['groups']), greater_than(50))
assert_that(results, is_not(has_key('groupNameFilter')))
assert_that(results, is_not(has_key('startFrom')))
assert_that(results, is_not(has_key('nextId')))
assert_that(results['maxItems'], is_(100))
def tear_down(self):
clear_zones(self.client)
clear_groups(self.client)
@@ -51,7 +61,7 @@ def test_list_my_groups_no_parameters(list_my_groups_context):
results = list_my_groups_context.client.list_my_groups(status=200)
assert_that(results, has_length(2)) # 2 fields
assert_that(results, has_length(3)) # 3 fields
assert_that(results['groups'], has_length(50))
assert_that(results, is_not(has_key('groupNameFilter')))
@@ -70,7 +80,7 @@ def test_get_my_groups_using_old_account_auth(list_my_groups_context):
Test passing in an account will return an empty set
"""
results = list_my_groups_context.client.list_my_groups(status=200)
assert_that(results, has_length(2))
assert_that(results, has_length(3))
assert_that(results, is_not(has_key('groupNameFilter')))
assert_that(results, is_not(has_key('startFrom')))
assert_that(results, is_not(has_key('nextId')))
@@ -83,7 +93,7 @@ def test_list_my_groups_max_items(list_my_groups_context):
"""
results = list_my_groups_context.client.list_my_groups(max_items=5, status=200)
assert_that(results, has_length(3)) # 3 fields
assert_that(results, has_length(4)) # 4 fields
assert_that(results, has_key('groups'))
assert_that(results, is_not(has_key('groupNameFilter')))
@@ -98,7 +108,7 @@ def test_list_my_groups_paging(list_my_groups_context):
"""
results=list_my_groups_context.client.list_my_groups(max_items=20, status=200)
assert_that(results, has_length(3)) # 3 fields
assert_that(results, has_length(4)) # 4 fields
assert_that(results, has_key('groups'))
assert_that(results, is_not(has_key('groupNameFilter')))
assert_that(results, is_not(has_key('startFrom')))
@@ -110,7 +120,7 @@ def test_list_my_groups_paging(list_my_groups_context):
results = list_my_groups_context.client.list_my_groups(max_items=20, start_from=results['nextId'], status=200)
if 'nextId' in results:
assert_that(results, has_length(4)) # 4 fields
assert_that(results, has_length(5)) # 5 fields
assert_that(results, has_key('groups'))
assert_that(results, is_not(has_key('groupNameFilter')))
assert_that(results['startFrom'], is_(prev['nextId']))
@@ -118,7 +128,7 @@ def test_list_my_groups_paging(list_my_groups_context):
assert_that(results['maxItems'], is_(20))
else:
assert_that(results, has_length(3)) # 3 fields
assert_that(results, has_length(4)) # 4 fields
assert_that(results, has_key('groups'))
assert_that(results, is_not(has_key('groupNameFilter')))
assert_that(results['startFrom'], is_(prev['nextId']))
@@ -132,7 +142,7 @@ def test_list_my_groups_filter_matches(list_my_groups_context):
"""
results = list_my_groups_context.client.list_my_groups(group_name_filter="test-list-my-groups-01", status=200)
assert_that(results, has_length(3)) # 3 fields
assert_that(results, has_length(4)) # 4 fields
assert_that(results['groups'], has_length(10))
assert_that(results['groupNameFilter'], is_('test-list-my-groups-01'))
@@ -163,3 +173,35 @@ def test_list_my_groups_no_deleted(list_my_groups_context):
for g in results['groups']:
assert_that(g['status'], is_not('Deleted'))
def test_list_my_groups_with_ignore_access_true(list_my_groups_context):
"""
Test that we can get all the groups whether a user is a member or not
"""
results = list_my_groups_context.client.list_my_groups(ignore_access=True, status=200)
list_my_groups_context.verify_ignore_access(results)
my_results = list_my_groups_context.client.list_my_groups(status=200)
my_results['groups'] = sorted(my_results['groups'], key=lambda x: x['name'])
for i in range(0, 50):
assert_that(my_results['groups'][i]['name'], is_("test-list-my-groups-{0:0>3}".format(i)))
def test_list_my_groups_as_support_user(list_my_groups_context):
"""
Test that we can get all the groups as a support user, even without ignore_access
"""
results = list_my_groups_context.support_user_client.list_my_groups(status=200)
list_my_groups_context.verify_ignore_access(results)
def test_list_my_groups_as_support_user_with_ignore_access_true(list_my_groups_context):
"""
Test that we can get all the groups as a support user
"""
results = list_my_groups_context.support_user_client.list_my_groups(ignore_access=True, status=200)
list_my_groups_context.verify_ignore_access(results)

View File

@@ -226,12 +226,13 @@ class VinylDNSClient(object):
return data
def list_my_groups(self, group_name_filter=None, start_from=None, max_items=None, **kwargs):
def list_my_groups(self, group_name_filter=None, start_from=None, max_items=None, ignore_access=False, **kwargs):
"""
Retrieves my groups
:param start_from: the start key of the page
:param max_items: the page limit
:param group_name_filter: only returns groups whose names contain filter string
:param ignore_access: determines if groups should be retrieved based on requester's membership
:return: the content of the response
"""
@@ -242,6 +243,8 @@ class VinylDNSClient(object):
args.append(u'startFrom={0}'.format(start_from))
if max_items is not None:
args.append(u'maxItems={0}'.format(max_items))
if ignore_access is not False:
args.append(u'ignoreAccess={0}'.format(ignore_access))
url = urljoin(self.index_url, u'/groups') + u'?' + u'&'.join(args)
response, data = self.make_request(url, u'GET', self.headers, **kwargs)

View File

@@ -140,7 +140,8 @@ final case class ListMyGroupsResponse(
groupNameFilter: Option[String] = None,
startFrom: Option[String] = None,
nextId: Option[String] = None,
maxItems: Int)
maxItems: Int,
ignoreAccess: Boolean)
final case class GroupNotFoundError(msg: String) extends Throwable(msg)

View File

@@ -135,16 +135,17 @@ class MembershipService(
groupNameFilter: Option[String],
startFrom: Option[String],
maxItems: Int,
authPrincipal: AuthPrincipal): Result[ListMyGroupsResponse] = {
authPrincipal: AuthPrincipal,
ignoreAccess: Boolean): Result[ListMyGroupsResponse] = {
val groupsCall =
if (authPrincipal.isSystemAdmin) {
if (authPrincipal.isSystemAdmin || ignoreAccess) {
groupRepo.getAllGroups()
} else {
groupRepo.getGroups(authPrincipal.memberGroupIds.toSet)
}
groupsCall.map { grp =>
pageListGroupsResponse(grp.toList, groupNameFilter, startFrom, maxItems)
pageListGroupsResponse(grp.toList, groupNameFilter, startFrom, maxItems, ignoreAccess)
}
}.toResult
@@ -152,7 +153,8 @@ class MembershipService(
allGroups: Seq[Group],
groupNameFilter: Option[String],
startFrom: Option[String],
maxItems: Int): ListMyGroupsResponse = {
maxItems: Int,
ignoreAccess: Boolean): ListMyGroupsResponse = {
val allMyGroups = allGroups
.filter(_.status == GroupStatus.Active)
.sortBy(_.id)
@@ -165,7 +167,7 @@ class MembershipService(
val nextId = if (filtered.length > maxItems) Some(filtered(maxItems - 1).id) else None
val groups = filtered.take(maxItems)
ListMyGroupsResponse(groups, groupNameFilter, startFrom, nextId, maxItems)
ListMyGroupsResponse(groups, groupNameFilter, startFrom, nextId, maxItems, ignoreAccess)
}
def getGroupActivity(

View File

@@ -42,7 +42,8 @@ trait MembershipServiceAlgebra {
groupNameFilter: Option[String],
startFrom: Option[String],
maxItems: Int,
authPrincipal: AuthPrincipal): Result[ListMyGroupsResponse]
authPrincipal: AuthPrincipal,
ignoreAccess: Boolean): Result[ListMyGroupsResponse]
def listMembers(
groupId: String,

View File

@@ -69,8 +69,16 @@ class MembershipRoute(
}
} ~
(get & monitor("Endpoint.listMyGroups")) {
parameters("startFrom".?, "maxItems".as[Int].?(DEFAULT_MAX_ITEMS), "groupNameFilter".?) {
(startFrom: Option[String], maxItems: Int, groupNameFilter: Option[String]) =>
parameters(
"startFrom".?,
"maxItems".as[Int].?(DEFAULT_MAX_ITEMS),
"groupNameFilter".?,
"ignoreAccess".as[Boolean].?(false)) {
(
startFrom: Option[String],
maxItems: Int,
groupNameFilter: Option[String],
ignoreAccess: Boolean) =>
{
handleRejections(invalidQueryHandler) {
validate(
@@ -81,8 +89,9 @@ class MembershipRoute(
""".stripMargin
) {
authenticateAndExecute(membershipService
.listMyGroups(groupNameFilter, startFrom, maxItems, _)) { groups =>
complete(StatusCodes.OK, groups)
.listMyGroups(groupNameFilter, startFrom, maxItems, _, ignoreAccess)) {
groups =>
complete(StatusCodes.OK, groups)
}
}
}

View File

@@ -529,14 +529,15 @@ class MembershipServiceSpec
.when(mockGroupRepo)
.getGroups(any[Set[String]])
val result: ListMyGroupsResponse =
rightResultOf(underTest.listMyGroups(None, None, 100, listOfDummyGroupsAuth).value)
rightResultOf(underTest.listMyGroups(None, None, 100, listOfDummyGroupsAuth, false).value)
verify(mockGroupRepo, never()).getAllGroups()
result shouldBe ListMyGroupsResponse(
groups = listOfDummyGroupInfo.take(100),
None,
None,
nextId = Some(listOfDummyGroups(99).id),
maxItems = 100)
maxItems = 100,
ignoreAccess = false)
}
"return only return groups whose name matches the filter" in {
doReturn(IO.pure(listOfDummyGroups.toSet))
@@ -548,14 +549,16 @@ class MembershipServiceSpec
groupNameFilter = Some("name-dummy01"),
startFrom = None,
maxItems = 100,
listOfDummyGroupsAuth)
listOfDummyGroupsAuth,
false)
.value)
result shouldBe ListMyGroupsResponse(
groups = listOfDummyGroupInfo.slice(10, 20),
groupNameFilter = Some("name-dummy01"),
startFrom = None,
nextId = None,
maxItems = 100)
maxItems = 100,
ignoreAccess = false)
}
"return only return groups after startFrom" in {
doReturn(IO.pure(listOfDummyGroups.toSet))
@@ -567,14 +570,16 @@ class MembershipServiceSpec
groupNameFilter = None,
startFrom = Some(listOfDummyGroups(99).id),
maxItems = 100,
listOfDummyGroupsAuth)
listOfDummyGroupsAuth,
ignoreAccess = false)
.value)
result shouldBe ListMyGroupsResponse(
groups = listOfDummyGroupInfo.slice(100, 200),
groupNameFilter = None,
startFrom = Some(listOfDummyGroups(99).id),
nextId = None,
maxItems = 100)
maxItems = 100,
ignoreAccess = false)
}
"return only return maxItems groups" in {
doReturn(IO.pure(listOfDummyGroups.toSet))
@@ -586,35 +591,65 @@ class MembershipServiceSpec
groupNameFilter = None,
startFrom = None,
maxItems = 10,
listOfDummyGroupsAuth)
listOfDummyGroupsAuth,
ignoreAccess = false)
.value)
result shouldBe ListMyGroupsResponse(
groups = listOfDummyGroupInfo.slice(0, 10),
groupNameFilter = None,
startFrom = None,
nextId = Some(listOfDummyGroups(9).id),
maxItems = 10)
maxItems = 10,
ignoreAccess = false)
}
"return an empty set if the user is not a member of any groups" in {
doReturn(IO.pure(Set())).when(mockGroupRepo).getGroups(any[Set[String]])
val result: ListMyGroupsResponse =
rightResultOf(underTest.listMyGroups(None, None, 100, notAuth).value)
result shouldBe ListMyGroupsResponse(Seq(), None, None, None, 100)
rightResultOf(underTest.listMyGroups(None, None, 100, notAuth, false).value)
result shouldBe ListMyGroupsResponse(Seq(), None, None, None, 100, false)
}
"return groups from the database for super users" in {
"return all groups from the database if ignoreAccess is true" in {
doReturn(IO.pure(Set(okGroup, dummyGroup))).when(mockGroupRepo).getAllGroups()
val result: ListMyGroupsResponse =
rightResultOf(underTest.listMyGroups(None, None, 100, superUserAuth).value)
rightResultOf(underTest.listMyGroups(None, None, 100, notAuth, true).value)
verify(mockGroupRepo).getAllGroups()
result.groups should contain theSameElementsAs Seq(
GroupInfo(dummyGroup),
GroupInfo(okGroup))
}
"return groups from the database for support users" in {
"return all groups from the database for super users even if ignoreAccess is false" in {
doReturn(IO.pure(Set(okGroup, dummyGroup))).when(mockGroupRepo).getAllGroups()
val result: ListMyGroupsResponse =
rightResultOf(underTest.listMyGroups(None, None, 100, superUserAuth, false).value)
verify(mockGroupRepo).getAllGroups()
result.groups should contain theSameElementsAs Seq(
GroupInfo(dummyGroup),
GroupInfo(okGroup))
}
"return all groups from the database for super users if ignoreAccess is true" in {
doReturn(IO.pure(Set(okGroup, dummyGroup))).when(mockGroupRepo).getAllGroups()
val result: ListMyGroupsResponse =
rightResultOf(underTest.listMyGroups(None, None, 100, superUserAuth, true).value)
verify(mockGroupRepo).getAllGroups()
result.groups should contain theSameElementsAs Seq(
GroupInfo(dummyGroup),
GroupInfo(okGroup))
}
"return all groups from the database for support users even if ignoreAccess is false" in {
val supportAuth = AuthPrincipal(okUser.copy(isSupport = true), Seq())
doReturn(IO.pure(Set(okGroup, dummyGroup))).when(mockGroupRepo).getAllGroups()
val result: ListMyGroupsResponse =
rightResultOf(underTest.listMyGroups(None, None, 100, supportAuth).value)
rightResultOf(underTest.listMyGroups(None, None, 100, supportAuth, false).value)
verify(mockGroupRepo).getAllGroups()
result.groups should contain theSameElementsAs Seq(
GroupInfo(dummyGroup),
GroupInfo(okGroup))
}
"return all groups from the database for support users if ignoreAccess is true" in {
val supportAuth = AuthPrincipal(okUser.copy(isSupport = true), Seq())
doReturn(IO.pure(Set(okGroup, dummyGroup))).when(mockGroupRepo).getAllGroups()
val result: ListMyGroupsResponse =
rightResultOf(underTest.listMyGroups(None, None, 100, supportAuth, true).value)
verify(mockGroupRepo).getAllGroups()
result.groups should contain theSameElementsAs Seq(
GroupInfo(dummyGroup),
@@ -626,8 +661,8 @@ class MembershipServiceSpec
.when(mockGroupRepo)
.getGroups(any[Set[String]])
val result: ListMyGroupsResponse =
rightResultOf(underTest.listMyGroups(None, None, 100, deletedGroupAuth).value)
result shouldBe ListMyGroupsResponse(Seq(), None, None, None, 100)
rightResultOf(underTest.listMyGroups(None, None, 100, deletedGroupAuth, false).value)
result shouldBe ListMyGroupsResponse(Seq(), None, None, None, 100, false)
}
}

View File

@@ -188,15 +188,16 @@ class MembershipRoutingSpec
"return a 200 response with the groups when no optional parameters are used" in {
val twoUserGroupInfo = GroupInfo(twoUserGroup)
doReturn(
result(ListMyGroupsResponse(Seq(okGroupInfo, twoUserGroupInfo), None, None, None, 100)))
result(
ListMyGroupsResponse(Seq(okGroupInfo, twoUserGroupInfo), None, None, None, 100, false)))
.when(membershipService)
.listMyGroups(None, None, 100, okAuth)
.listMyGroups(None, None, 100, okAuth, false)
Get("/groups") ~> Route.seal(membershipRoute) ~> check {
status shouldBe StatusCodes.OK
val result = responseAs[ListMyGroupsResponse]
val expected =
ListMyGroupsResponse(Seq(okGroupInfo, twoUserGroupInfo), None, None, None, 100)
ListMyGroupsResponse(Seq(okGroupInfo, twoUserGroupInfo), None, None, None, 100, false)
result shouldBe expected
}
@@ -209,13 +210,15 @@ class MembershipRoutingSpec
groupNameFilter = Some("ok"),
startFrom = Some("anyString"),
nextId = None,
maxItems = 100)))
maxItems = 100,
ignoreAccess = false)))
.when(membershipService)
.listMyGroups(
groupNameFilter = Some("ok"),
startFrom = Some("anyString"),
maxItems = 100,
okAuth)
okAuth,
ignoreAccess = false)
Get("/groups?startFrom=anyString&maxItems=100&groupNameFilter=ok") ~> Route.seal(
membershipRoute) ~> check {
status shouldBe StatusCodes.OK
@@ -226,7 +229,8 @@ class MembershipRoutingSpec
groupNameFilter = Some("ok"),
startFrom = Some("anyString"),
maxItems = 100,
nextId = None)
nextId = None,
ignoreAccess = false)
result shouldBe expected
}
@@ -244,7 +248,7 @@ class MembershipRoutingSpec
"return a 500 response when fails" in {
doReturn(result(new IllegalArgumentException("fail")))
.when(membershipService)
.listMyGroups(None, None, 100, okAuth)
.listMyGroups(None, None, 100, okAuth, false)
Get("/groups") ~> Route.seal(membershipRoute) ~> check {
status shouldBe StatusCodes.InternalServerError