mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-08-30 13:58:15 +00:00
Open list groups access (#809)
This commit is contained in:
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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(
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -67,6 +67,7 @@ object VinylDNS {
|
||||
lastName: Option[String],
|
||||
email: Option[String],
|
||||
isSuper: Boolean,
|
||||
isSupport: Boolean,
|
||||
id: String,
|
||||
lockStatus: LockStatus)
|
||||
object UserInfo {
|
||||
@@ -77,6 +78,7 @@ object VinylDNS {
|
||||
lastName = user.lastName,
|
||||
email = user.email,
|
||||
isSuper = user.isSuper,
|
||||
isSupport = user.isSupport,
|
||||
id = user.id,
|
||||
lockStatus = user.lockStatus
|
||||
)
|
||||
@@ -218,7 +220,7 @@ class VinylDNS @Inject()(
|
||||
})
|
||||
}
|
||||
|
||||
def getMyGroups(): Action[AnyContent] = userAction.async { implicit request =>
|
||||
def getGroups(): Action[AnyContent] = userAction.async { implicit request =>
|
||||
val queryParameters = new HashMap[String, java.util.List[String]]()
|
||||
for {
|
||||
(name, values) <- request.queryString
|
||||
|
@@ -22,7 +22,17 @@
|
||||
<notification ng-model="alert"></notification>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<!-- START VERTICAL TABS -->
|
||||
<div class="panel panel-default panel-tabs">
|
||||
<ul class="nav nav-tabs bar_tabs">
|
||||
<li class="active"><a data-toggle="tab" ng-click="myGroups()">My Groups</a></li>
|
||||
<li><a data-toggle="tab" ng-click="allGroups()">All Groups</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="panel-body tab-content">
|
||||
<div class="tab-pane active" id="groups">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<!-- SIMPLE DATATABLE -->
|
||||
@@ -63,7 +73,7 @@
|
||||
<td class="wrap-long-text">{{group.description}}</td>
|
||||
<td>
|
||||
<div class="table-form-group">
|
||||
<a class="btn btn-info btn-rounded" ng-href="/groups/{{group.id}}">
|
||||
<a ng-if="canSeeGroup(group)" class="btn btn-info btn-rounded" ng-href="/groups/{{group.id}}">
|
||||
View</a>
|
||||
<a ng-if="groupAdmin(group)" class="btn btn-warning btn-rounded" ng-click="editGroup(group);">
|
||||
Edit</a>
|
||||
@@ -80,6 +90,11 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END VERTICAL TABS -->
|
||||
|
||||
</div>
|
||||
<!-- END PAGE CONTENT WRAPPER -->
|
||||
|
||||
|
@@ -42,7 +42,7 @@ PUT /api/zones/:zid/recordsets/:rid @controllers.VinylDNS.updateRec
|
||||
|
||||
GET /api/zones/:id/recordsetchanges @controllers.VinylDNS.listRecordSetChanges(id: String)
|
||||
|
||||
GET /api/groups @controllers.VinylDNS.getMyGroups
|
||||
GET /api/groups @controllers.VinylDNS.getGroups
|
||||
GET /api/groups/:gid @controllers.VinylDNS.getGroup(gid: String)
|
||||
POST /api/groups @controllers.VinylDNS.newGroup
|
||||
PUT /api/groups/:gid @controllers.VinylDNS.updateGroup(gid: String)
|
||||
|
@@ -19,7 +19,7 @@
|
||||
|
||||
angular.module('batch-change')
|
||||
.controller('BatchChangeNewController', function($scope, $log, $location, $timeout, $q, batchChangeService, utilityService, groupsService){
|
||||
groupsService.getMyGroups()
|
||||
groupsService.getGroups()
|
||||
.then(function (results) {
|
||||
$scope.myGroups = results['data']['groups'];
|
||||
if ($scope.myGroups.length == 1) {
|
||||
@@ -27,7 +27,7 @@
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
handleError(error, 'groupsService::getMyGroups-failure');
|
||||
handleError(error, 'groupsService::getGroups-failure');
|
||||
});
|
||||
|
||||
$scope.batch = {};
|
||||
|
@@ -153,7 +153,7 @@ describe('BatchChange', function(){
|
||||
deferred = $q.defer();
|
||||
|
||||
spyOn(batchChangeService, 'createBatchChange').and.returnValue(deferred.promise);
|
||||
groupsService.getMyGroups = function() {
|
||||
groupsService.getGroups = function() {
|
||||
return $q.when({
|
||||
data: {
|
||||
groups: "all my groups"
|
||||
|
@@ -23,6 +23,7 @@ angular.module('controller.groups', []).controller('GroupsController', function
|
||||
$scope.groups = { items: [] };
|
||||
$scope.groupsLoaded = false;
|
||||
$scope.alerts = [];
|
||||
$scope.ignoreAccess = false;
|
||||
|
||||
function handleError(error, type) {
|
||||
var alert = utilityService.failure(error, type);
|
||||
@@ -101,19 +102,28 @@ angular.module('controller.groups', []).controller('GroupsController', function
|
||||
});
|
||||
};
|
||||
|
||||
$scope.allGroups = function() {
|
||||
$scope.ignoreAccess = true;
|
||||
$scope.refresh();
|
||||
}
|
||||
|
||||
$scope.myGroups = function() {
|
||||
$scope.ignoreAccess = false;
|
||||
$scope.refresh();
|
||||
}
|
||||
|
||||
$scope.refresh = function () {
|
||||
//get users groups
|
||||
function success(result) {
|
||||
$log.log('getMyGroups:refresh-success', result);
|
||||
$log.log('getGroups:refresh-success', result);
|
||||
//update groups
|
||||
$scope.groups.items = result.groups;
|
||||
$scope.groupsLoaded = true;
|
||||
return result;
|
||||
}
|
||||
return getMyGroups()
|
||||
getGroups($scope.ignoreAccess)
|
||||
.then(success)
|
||||
.catch(function (error) {
|
||||
handleError(error, 'getMyGroups::refresh-failure');
|
||||
handleError(error, 'getGroups::refresh-failure');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -131,16 +141,16 @@ angular.module('controller.groups', []).controller('GroupsController', function
|
||||
return true;
|
||||
};
|
||||
|
||||
function getMyGroups() {
|
||||
function getGroups() {
|
||||
function success(response) {
|
||||
$log.log('groupsService::getMyGroups-success');
|
||||
$log.log('groupsService::getGroups-success');
|
||||
return response.data;
|
||||
}
|
||||
return groupsService
|
||||
.getMyGroups()
|
||||
.getGroups($scope.ignoreAccess)
|
||||
.then(success)
|
||||
.catch(function (error){
|
||||
handleError(error, 'groupsService::getMyGroups-failure');
|
||||
handleError(error, 'groupsService::getGroups-failure');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -233,7 +243,14 @@ angular.module('controller.groups', []).controller('GroupsController', function
|
||||
return x.id === $scope.profile.id;
|
||||
});
|
||||
var isSuper = $scope.profile.isSuper;
|
||||
return (isAdmin || isSuper) ? true : false;
|
||||
return isAdmin || isSuper;
|
||||
}
|
||||
|
||||
$scope.canSeeGroup = function(group) {
|
||||
var isMember = group.members.some(x => x.id === $scope.profile.id);
|
||||
var isSupport = $scope.profile.isSupport;
|
||||
var isSuper = $scope.profile.isSuper;
|
||||
return isMember || isSupport || isSuper;
|
||||
}
|
||||
|
||||
//get user data on groups view load
|
||||
|
@@ -31,7 +31,7 @@ describe('Controller: GroupsController', function () {
|
||||
profileService.getAuthenticatedUserData = function() {
|
||||
return $q.when('data')
|
||||
};
|
||||
this.groupsService.getMyGroups = function() {
|
||||
this.groupsService.getGroups = function() {
|
||||
return $q.when({
|
||||
data: {
|
||||
group: 'mock group'
|
||||
@@ -57,14 +57,14 @@ describe('Controller: GroupsController', function () {
|
||||
groups: "all my groups"
|
||||
}
|
||||
};
|
||||
var getMyGroups = spyOn(this.groupsService, 'getMyGroups')
|
||||
var getGroups = spyOn(this.groupsService, 'getGroups')
|
||||
.and.stub()
|
||||
.and.returnValue(this.q.when(response));
|
||||
|
||||
this.scope.refresh();
|
||||
this.scope.$digest();
|
||||
|
||||
expect(getMyGroups.calls.count()).toBe(1);
|
||||
expect(getGroups.calls.count()).toBe(1);
|
||||
expect(this.scope.groups.items).toBe("all my groups");
|
||||
});
|
||||
|
||||
|
@@ -18,7 +18,7 @@ angular.module('controller.manageZones', [])
|
||||
.controller('ManageZonesController', function ($scope, $timeout, $log, recordsService, zonesService, groupsService,
|
||||
profileService, utilityService) {
|
||||
|
||||
groupsService.getMyGroupsStored()
|
||||
groupsService.getGroupsStored()
|
||||
.then(function (results) {
|
||||
$scope.myGroups = results.groups;
|
||||
})
|
||||
|
@@ -33,7 +33,7 @@ describe('Controller: ManageZonesController', function () {
|
||||
this.recordsService = recordsService;
|
||||
this.profileService = profileService;
|
||||
this.q = $q;
|
||||
this.groupsService.getMyGroups = function () {
|
||||
this.groupsService.getGroups = function () {
|
||||
return $q.when({
|
||||
data: {
|
||||
groups: "all my groups"
|
||||
|
@@ -350,7 +350,7 @@ angular.module('controller.records', [])
|
||||
|
||||
function getMembership(){
|
||||
groupsService
|
||||
.getMyGroupsStored()
|
||||
.getGroupsStored()
|
||||
.then(
|
||||
function (results) {
|
||||
$scope.myGroups = results.groups;
|
||||
@@ -358,7 +358,7 @@ angular.module('controller.records', [])
|
||||
determineAdmin()
|
||||
})
|
||||
.catch(function (error){
|
||||
handleError(error, 'groupsService::getMyGroupsStored-failure');
|
||||
handleError(error, 'groupsService::getGroupsStored-failure');
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -126,7 +126,7 @@ describe('Controller: RecordsController', function () {
|
||||
spyOn(this.recordsService, 'getZone')
|
||||
.and.stub()
|
||||
.and.returnValue(this.q.when({ data: {zone: mockZone}}));
|
||||
spyOn(this.groupsService, 'getMyGroups')
|
||||
spyOn(this.groupsService, 'getGroups')
|
||||
.and.stub()
|
||||
.and.returnValue(this.q.when(mockGroups));
|
||||
this.$httpBackend.when('GET', '/api/users/currentuser').respond({});
|
||||
@@ -166,7 +166,7 @@ describe('Controller: RecordsController', function () {
|
||||
spyOn(this.recordsService, 'getZone')
|
||||
.and.stub()
|
||||
.and.returnValue(this.q.when({ data: {zone: mockZone}}));
|
||||
spyOn(this.groupsService, 'getMyGroups')
|
||||
spyOn(this.groupsService, 'getGroups')
|
||||
.and.stub()
|
||||
.and.returnValue(this.q.when(mockGroups));
|
||||
this.$httpBackend.when('GET', '/api/users/currentuser').respond({});
|
||||
@@ -206,7 +206,7 @@ describe('Controller: RecordsController', function () {
|
||||
spyOn(this.recordsService, 'getZone')
|
||||
.and.stub()
|
||||
.and.returnValue(this.q.when({ data: {zone: mockZone}}));
|
||||
spyOn(this.groupsService, 'getMyGroups')
|
||||
spyOn(this.groupsService, 'getGroups')
|
||||
.and.stub()
|
||||
.and.returnValue(this.q.when(mockGroups));
|
||||
this.$httpBackend.when('GET', '/api/users/currentuser').respond({});
|
||||
@@ -246,7 +246,7 @@ describe('Controller: RecordsController', function () {
|
||||
spyOn(this.recordsService, 'getZone')
|
||||
.and.stub()
|
||||
.and.returnValue(this.q.when({ data: {zone: mockZone}}));
|
||||
spyOn(this.groupsService, 'getMyGroups')
|
||||
spyOn(this.groupsService, 'getGroups')
|
||||
.and.stub()
|
||||
.and.returnValue(this.q.when(mockGroups));
|
||||
this.$httpBackend.when('GET', '/api/users/currentuser').respond({});
|
||||
|
@@ -50,7 +50,7 @@ angular.module('controller.zones', [])
|
||||
$scope.currentZone.transferConnection = {};
|
||||
};
|
||||
|
||||
groupsService.getMyGroups().then(function (results) {
|
||||
groupsService.getGroups().then(function (results) {
|
||||
if (results.data) {
|
||||
$scope.myGroups = results.data.groups;
|
||||
$scope.myGroupIds = results.data.groups.map(function(grp) {return grp['id']});
|
||||
|
@@ -38,7 +38,7 @@ describe('Controller: ZonesController', function () {
|
||||
profileService.getAuthenticatedUserData = function() {
|
||||
return $q.when({data: {}});
|
||||
};
|
||||
groupsService.getMyGroups = function() {
|
||||
groupsService.getGroups = function() {
|
||||
return $q.when({
|
||||
data: {
|
||||
groups: [{id: "all my groups"}]
|
||||
|
@@ -70,9 +70,9 @@ angular.module('service.groups', [])
|
||||
return $http.delete(url, {headers: utilityService.getCsrfHeader()});
|
||||
};
|
||||
|
||||
this.getMyGroups = function () {
|
||||
this.getGroups = function (ignoreAccess) {
|
||||
var url = '/api/groups';
|
||||
url = this.urlBuilder(url, { maxItems: 1000});
|
||||
url = this.urlBuilder(url, { maxItems: 1000, ignoreAccess: ignoreAccess});
|
||||
return $http.get(url);
|
||||
};
|
||||
|
||||
@@ -82,9 +82,9 @@ angular.module('service.groups', [])
|
||||
return $http.get(url);
|
||||
};
|
||||
|
||||
this.getMyGroupsStored = function () {
|
||||
this.getGroupsStored = function () {
|
||||
if (_refreshMyGroups || _myGroupsPromise == undefined) {
|
||||
_myGroupsPromise = this.getMyGroups().then(
|
||||
_myGroupsPromise = this.getGroups().then(
|
||||
function(response) {
|
||||
_refreshMyGroups = false;
|
||||
return response.data;
|
||||
|
@@ -432,26 +432,26 @@ describe('Service: groupsService', function () {
|
||||
this.$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('getMyGroups method should return 200 with valid groups', function (done) {
|
||||
it('getGroups method should return 200 with valid groups', function (done) {
|
||||
var url = '/api/groups';
|
||||
this.$httpBackend.whenRoute('GET', url).respond(200, getJSONFixture('mockGroupList.json'));
|
||||
this.groupsService.getMyGroups()
|
||||
this.groupsService.getGroups()
|
||||
.then(function (response) {
|
||||
expect(response.status).toBe(200);
|
||||
done();
|
||||
}, function (error) {
|
||||
fail('getMyGroups expected 200, but got ' + response.status.toString());
|
||||
fail('getGroups expected 200, but got ' + response.status.toString());
|
||||
done();
|
||||
});
|
||||
this.$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('getMyGroups method should return 401 with invalid credentials', function (done) {
|
||||
it('getGroups method should return 401 with invalid credentials', function (done) {
|
||||
var url = '/api/groups';
|
||||
this.$httpBackend.whenRoute('GET', url).respond(401, "The resource requires authentication, which was not supplied with the request");
|
||||
this.groupsService.getMyGroups()
|
||||
this.groupsService.getGroups()
|
||||
.then(function (response) {
|
||||
fail("getMyGroups expected 401, but got " + response.status.toString());
|
||||
fail("getGroups expected 401, but got " + response.status.toString());
|
||||
done();
|
||||
}, function (error) {
|
||||
expect(error.status).toBe(401);
|
||||
@@ -460,14 +460,14 @@ describe('Service: groupsService', function () {
|
||||
this.$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('getMyGroupsStored should only call the api on its 1st call', function (done) {
|
||||
it('getGroupsStored should only call the api on its 1st call', function (done) {
|
||||
var url = '/api/groups';
|
||||
var groupsResult = getJSONFixture('mockGroupList.json');
|
||||
this.$httpBackend.whenRoute('GET', url).respond(200, groupsResult);
|
||||
var getMyGroupsCalls = spyOn(this.groupsService, 'getMyGroups').and.callThrough();
|
||||
var getMyGroupsStoredCalls = spyOn(this.groupsService, 'getMyGroupsStored').and.callThrough();
|
||||
var getGroupsCalls = spyOn(this.groupsService, 'getGroups').and.callThrough();
|
||||
var getGroupsStoredCalls = spyOn(this.groupsService, 'getGroupsStored').and.callThrough();
|
||||
|
||||
this.groupsService.getMyGroupsStored().then(function (response) {
|
||||
this.groupsService.getGroupsStored().then(function (response) {
|
||||
expect(response).toEqual(groupsResult);
|
||||
}, function (error) {
|
||||
fail('getGroup expected 202 with json, but got' + error.status.toString());
|
||||
@@ -475,42 +475,42 @@ describe('Service: groupsService', function () {
|
||||
|
||||
this.$httpBackend.flush();
|
||||
|
||||
this.groupsService.getMyGroupsStored().then(function (response) {
|
||||
this.groupsService.getGroupsStored().then(function (response) {
|
||||
expect(response).toEqual(groupsResult);
|
||||
}, function (error) {
|
||||
fail('getGroup expected 202 with json, but got' + error.status.toString());
|
||||
});
|
||||
|
||||
expect(getMyGroupsStoredCalls.calls.count()).toBe(2);
|
||||
expect(getMyGroupsCalls.calls.count()).toBe(1);
|
||||
expect(getGroupsStoredCalls.calls.count()).toBe(2);
|
||||
expect(getGroupsCalls.calls.count()).toBe(1);
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
it('getMyGroupsStored should call the api twice when there is an error', function (done) {
|
||||
it('getGroupsStored should call the api twice when there is an error', function (done) {
|
||||
var url = '/api/groups';
|
||||
this.$httpBackend.whenRoute('GET', url).respond(401, "The resource requires authentication, which was not supplied with the request");
|
||||
var getMyGroupsCalls = spyOn(this.groupsService, 'getMyGroups').and.callThrough();
|
||||
var getMyGroupsStoredCalls = spyOn(this.groupsService, 'getMyGroupsStored').and.callThrough();
|
||||
var getGroupsCalls = spyOn(this.groupsService, 'getGroups').and.callThrough();
|
||||
var getGroupsStoredCalls = spyOn(this.groupsService, 'getGroupsStored').and.callThrough();
|
||||
|
||||
this.groupsService.getMyGroupsStored().then(function (response) {
|
||||
fail("getMyGroups expected 401, but got " + response.status.toString());
|
||||
this.groupsService.getGroupsStored().then(function (response) {
|
||||
fail("getGroups expected 401, but got " + response.status.toString());
|
||||
}, function (error) {
|
||||
expect(error.status).toBe(401);
|
||||
});
|
||||
|
||||
this.$httpBackend.flush();
|
||||
|
||||
this.groupsService.getMyGroupsStored().then(function (response) {
|
||||
fail("getMyGroups expected 401, but got " + response.status.toString());
|
||||
this.groupsService.getGroupsStored().then(function (response) {
|
||||
fail("getGroups expected 401, but got " + response.status.toString());
|
||||
}, function (error) {
|
||||
expect(error.status).toBe(401);
|
||||
});
|
||||
|
||||
this.$httpBackend.flush();
|
||||
|
||||
expect(getMyGroupsStoredCalls.calls.count()).toBe(2);
|
||||
expect(getMyGroupsCalls.calls.count()).toBe(2);
|
||||
expect(getGroupsStoredCalls.calls.count()).toBe(2);
|
||||
expect(getGroupsCalls.calls.count()).toBe(2);
|
||||
done();
|
||||
|
||||
});
|
||||
|
@@ -1418,7 +1418,7 @@ class VinylDNSSpec extends Specification with Mockito with TestApplicationData w
|
||||
client,
|
||||
components,
|
||||
crypto)
|
||||
val result = underTest.getMyGroups()(FakeRequest(GET, s"/api/groups")
|
||||
val result = underTest.getGroups()(FakeRequest(GET, s"/api/groups")
|
||||
.withSession("username" -> frodoUser.userName, "accessKey" -> frodoUser.accessKey))
|
||||
|
||||
status(result) must beEqualTo(OK)
|
||||
@@ -1443,7 +1443,7 @@ class VinylDNSSpec extends Specification with Mockito with TestApplicationData w
|
||||
client,
|
||||
components,
|
||||
crypto)
|
||||
val result = underTest.getMyGroups()(FakeRequest(GET, s"/api/groups"))
|
||||
val result = underTest.getGroups()(FakeRequest(GET, s"/api/groups"))
|
||||
|
||||
status(result) mustEqual 401
|
||||
contentAsString(result) must beEqualTo(
|
||||
@@ -1468,7 +1468,7 @@ class VinylDNSSpec extends Specification with Mockito with TestApplicationData w
|
||||
client,
|
||||
components,
|
||||
crypto)
|
||||
val result = underTest.getMyGroups()(
|
||||
val result = underTest.getGroups()(
|
||||
FakeRequest(GET, s"/api/groups")
|
||||
.withSession(
|
||||
"username" -> lockedFrodoUser.userName,
|
||||
@@ -1500,7 +1500,7 @@ class VinylDNSSpec extends Specification with Mockito with TestApplicationData w
|
||||
client,
|
||||
components,
|
||||
crypto)
|
||||
val result = underTest.getMyGroups()(FakeRequest(GET, s"/api/groups")
|
||||
val result = underTest.getGroups()(FakeRequest(GET, s"/api/groups")
|
||||
.withSession("username" -> frodoUser.userName, "accessKey" -> frodoUser.accessKey))
|
||||
|
||||
status(result) must beEqualTo(UNAUTHORIZED)
|
||||
|
Reference in New Issue
Block a user