From 946ddeb9e52aa835156d2c1b06b6d14e2b2ba136 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Thu, 2 Jun 2022 17:39:45 -0400 Subject: [PATCH 001/521] Added filter ptr zones checkbox to zones view Signed-off-by: Nicholas Spadaccino --- .../portal/app/views/zones/zones.scala.html | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/modules/portal/app/views/zones/zones.scala.html b/modules/portal/app/views/zones/zones.scala.html index 52ab8b759..510705855 100644 --- a/modules/portal/app/views/zones/zones.scala.html +++ b/modules/portal/app/views/zones/zones.scala.html @@ -1,5 +1,15 @@ @(rootAccountName: String)(implicit request: play.api.mvc.Request[Any], customLinks: models.CustomLinks, meta: models.Meta) - + + + + + + + + + + + @content = {
@@ -50,6 +60,19 @@
+
+
+
+
+
+ +
+
+
+
+
From 4fd3bde261f5f27ffa9000f93061fd21f655b3f4 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Tue, 7 Jun 2022 16:34:12 -0400 Subject: [PATCH 002/521] Update api and sql repo to filter reverse zones --- .../api/domain/zone/ZoneProtocol.scala | 3 ++- .../api/domain/zone/ZoneService.scala | 9 +++++--- .../api/domain/zone/ZoneServiceAlgebra.scala | 3 ++- .../vinyldns/api/route/ZoneRouting.scala | 8 ++++--- .../core/domain/zone/ListZonesResults.scala | 3 ++- .../core/domain/zone/ZoneRepository.scala | 3 ++- .../MySqlZoneRepositoryIntegrationSpec.scala | 9 +++++--- .../repository/MySqlZoneRepository.scala | 23 +++++++++++++++++-- .../portal/app/views/zones/zones.scala.html | 2 +- .../lib/controllers/controller.zones.js | 3 ++- .../lib/services/zones/service.zones.js | 5 ++-- 11 files changed, 52 insertions(+), 19 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala index 97370a633..a388ba909 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala @@ -266,7 +266,8 @@ case class ListZonesResponse( startFrom: Option[String] = None, nextId: Option[String] = None, maxItems: Int = 100, - ignoreAccess: Boolean = false + ignoreAccess: Boolean = false, + includeReverse: Boolean = true ) // Errors diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala index 74d7b3c4a..615e45fba 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala @@ -147,7 +147,8 @@ class ZoneService( nameFilter: Option[String] = None, startFrom: Option[String] = None, maxItems: Int = 100, - ignoreAccess: Boolean = false + ignoreAccess: Boolean = false, + includeReverse: Boolean = true ): Result[ListZonesResponse] = { for { listZonesResult <- zoneRepository.listZones( @@ -155,7 +156,8 @@ class ZoneService( nameFilter, startFrom, maxItems, - ignoreAccess + ignoreAccess, + includeReverse ) zones = listZonesResult.zones groupIds = zones.map(_.adminGroupId).toSet @@ -167,7 +169,8 @@ class ZoneService( listZonesResult.startFrom, listZonesResult.nextId, listZonesResult.maxItems, - listZonesResult.ignoreAccess + listZonesResult.ignoreAccess, + listZonesResult.includeReverse ) }.toResult diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala index 01457a64b..2a7ec4cd1 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala @@ -42,7 +42,8 @@ trait ZoneServiceAlgebra { nameFilter: Option[String], startFrom: Option[String], maxItems: Int, - ignoreAccess: Boolean + ignoreAccess: Boolean, + includeReverse: Boolean ): Result[ListZonesResponse] def listZoneChanges( diff --git a/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala index fe9365839..ade6f565c 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala @@ -78,13 +78,15 @@ class ZoneRoute( "nameFilter".?, "startFrom".as[String].?, "maxItems".as[Int].?(DEFAULT_MAX_ITEMS), - "ignoreAccess".as[Boolean].?(false) + "ignoreAccess".as[Boolean].?(false), + "includeReverse".as[Boolean].?(true) ) { ( nameFilter: Option[String], startFrom: Option[String], maxItems: Int, - ignoreAccess: Boolean + ignoreAccess: Boolean, + includeReverse: Boolean ) => { handleRejections(invalidQueryHandler) { @@ -94,7 +96,7 @@ class ZoneRoute( ) { authenticateAndExecute( zoneService - .listZones(_, nameFilter, startFrom, maxItems, ignoreAccess) + .listZones(_, nameFilter, startFrom, maxItems, ignoreAccess, includeReverse) ) { result => complete(StatusCodes.OK, result) } diff --git a/modules/core/src/main/scala/vinyldns/core/domain/zone/ListZonesResults.scala b/modules/core/src/main/scala/vinyldns/core/domain/zone/ListZonesResults.scala index 8d03b0073..de7dd1b17 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/zone/ListZonesResults.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/zone/ListZonesResults.scala @@ -22,5 +22,6 @@ case class ListZonesResults( startFrom: Option[String] = None, maxItems: Int = 100, ignoreAccess: Boolean = false, - zonesFilter: Option[String] = None + zonesFilter: Option[String] = None, + includeReverse: Boolean = true ) diff --git a/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneRepository.scala index 86a2f289c..49d33dde5 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneRepository.scala @@ -40,7 +40,8 @@ trait ZoneRepository extends Repository { zoneNameFilter: Option[String] = None, startFrom: Option[String] = None, maxItems: Int = 100, - ignoreAccess: Boolean = false + ignoreAccess: Boolean = false, + includeReverse: Boolean = true ): IO[ListZonesResults] def getZonesByAdminGroupId(adminGroupId: String): IO[List[Zone]] diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala index 314209155..2fed8132c 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala @@ -201,6 +201,9 @@ class MySqlZoneRepositoryIntegrationSpec repo .getZonesByFilters(Set("67.345.12.in-addr.arpa.", "extraZone")) .unsafeRunSync() should contain theSameElementsAs expectedZones + + println(repo + .getZonesByFilters(Set("67.345.12.in-addr.arpa.", "extraZone")).unsafeRunSync()) } "get authorized zones" in { @@ -450,19 +453,19 @@ class MySqlZoneRepositoryIntegrationSpec "apply the zone filter as a normal user" in { val testZones = Seq( - testZone("system-test.", adminGroupId = "foo"), + testZone("system-test.ip6.arpa.", adminGroupId = "foo"), testZone("system-temp.", adminGroupId = "foo"), testZone("system-nomatch.", adminGroupId = "bar") ) - val expectedZones = Seq(testZones(0), testZones(1)).sortBy(_.name) + val expectedZones = Seq(testZones(1)).sortBy(_.name) val auth = AuthPrincipal(dummyUser, Seq("foo")) val f = for { _ <- saveZones(testZones) - retrieved <- repo.listZones(auth, zoneNameFilter = Some("system*")) + retrieved <- repo.listZones(auth, zoneNameFilter = Some("system*"), includeReverse = false) } yield retrieved (f.unsafeRunSync().zones should contain).theSameElementsInOrderAs(expectedZones) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala index e80402632..ecc1cb937 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala @@ -243,7 +243,8 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M zoneNameFilter: Option[String] = None, startFrom: Option[String] = None, maxItems: Int = 100, - ignoreAccess: Boolean = false + ignoreAccess: Boolean = false, + includeReverse: Boolean = true ): IO[ListZonesResults] = monitor("repo.ZoneJDBC.listZones") { IO { @@ -253,6 +254,11 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M val sb = new StringBuilder sb.append(withAccessorCheck) + val noReverseRegex = + if (!includeReverse) + """(in-addr\.arpa\.)|(ip6\.arpa\.)$""" + else None + val filters = List( zoneNameFilter.map(flt => s"z.name LIKE '${ensureTrailingDot(flt.replace('*', '%'))}'"), startFrom.map(os => s"z.name > '$os'") @@ -263,10 +269,16 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M sb.append(filters.mkString(" AND ")) } + if (!includeReverse) { + sb.append(" AND ") + sb.append(s"z.name NOT RLIKE '$noReverseRegex'") + } + sb.append(s" GROUP BY z.name ") sb.append(s" LIMIT ${maxItems + 1}") val query = sb.toString + println(query) val results: List[Zone] = SQL(query) .bind(accessors: _*) @@ -274,6 +286,12 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M .list() .apply() + for(element<-results) + { + println(element) + } + + val (newResults, nextId) = if (results.size > maxItems) (results.dropRight(1), results.dropRight(1).lastOption.map(_.name)) @@ -285,7 +303,8 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M startFrom = startFrom, maxItems = maxItems, zonesFilter = zoneNameFilter, - ignoreAccess = ignoreAccess + ignoreAccess = ignoreAccess, + includeReverse = includeReverse ) } } diff --git a/modules/portal/app/views/zones/zones.scala.html b/modules/portal/app/views/zones/zones.scala.html index 510705855..695c35388 100644 --- a/modules/portal/app/views/zones/zones.scala.html +++ b/modules/portal/app/views/zones/zones.scala.html @@ -66,7 +66,7 @@
diff --git a/modules/portal/public/lib/controllers/controller.zones.js b/modules/portal/public/lib/controllers/controller.zones.js index d4057fcfd..f70ffe177 100644 --- a/modules/portal/public/lib/controllers/controller.zones.js +++ b/modules/portal/public/lib/controllers/controller.zones.js @@ -25,6 +25,7 @@ angular.module('controller.zones', []) $scope.allGroups = []; $scope.query = ""; + $scope.includeReverse = true; $scope.keyAlgorithms = ['HMAC-MD5', 'HMAC-SHA1', 'HMAC-SHA224', 'HMAC-SHA256', 'HMAC-SHA384', 'HMAC-SHA512']; @@ -87,7 +88,7 @@ angular.module('controller.zones', []) allZonesPaging = pagingService.resetPaging(allZonesPaging); zonesService - .getZones(zonesPaging.maxItems, undefined, $scope.query) + .getZones(zonesPaging.maxItems, undefined, $scope.query, $scope.includeReverse) .then(function (response) { $log.log('zonesService::getZones-success (' + response.data.zones.length + ' zones)'); zonesPaging.next = response.data.nextId; diff --git a/modules/portal/public/lib/services/zones/service.zones.js b/modules/portal/public/lib/services/zones/service.zones.js index 4ac031860..b1484af99 100644 --- a/modules/portal/public/lib/services/zones/service.zones.js +++ b/modules/portal/public/lib/services/zones/service.zones.js @@ -19,7 +19,7 @@ angular.module('service.zones', []) .service('zonesService', function ($http, groupsService, $log, utilityService) { - this.getZones = function (limit, startFrom, query, ignoreAccess) { + this.getZones = function (limit, startFrom, query, ignoreAccess, includeReverse) { if (query == "") { query = null; } @@ -27,7 +27,8 @@ angular.module('service.zones', []) "maxItems": limit, "startFrom": startFrom, "nameFilter": query, - "ignoreAccess": ignoreAccess + "ignoreAccess": ignoreAccess, + "includeReverse": includeReverse }; var url = groupsService.urlBuilder("/api/zones", params); return $http.get(url); From cc597cf1a54617252f20e44d143d22c0ac294bc5 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Thu, 9 Jun 2022 17:08:37 -0400 Subject: [PATCH 003/521] Fix issue with list zones sql query, update zone controller and view --- .../mysql/repository/MySqlZoneRepository.scala | 10 ++++++++-- modules/portal/app/views/zones/zones.scala.html | 2 +- .../portal/public/lib/controllers/controller.zones.js | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala index ecc1cb937..77c3e677b 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala @@ -270,8 +270,14 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M } if (!includeReverse) { - sb.append(" AND ") - sb.append(s"z.name NOT RLIKE '$noReverseRegex'") + if (filters.nonEmpty) { + sb.append(" AND ") + sb.append(s"z.name NOT RLIKE '$noReverseRegex'") + } + else { + sb.append(" WHERE ") + sb.append(s"z.name NOT RLIKE '$noReverseRegex'") + } } sb.append(s" GROUP BY z.name ") diff --git a/modules/portal/app/views/zones/zones.scala.html b/modules/portal/app/views/zones/zones.scala.html index 695c35388..8b7aa1fb3 100644 --- a/modules/portal/app/views/zones/zones.scala.html +++ b/modules/portal/app/views/zones/zones.scala.html @@ -66,7 +66,7 @@
diff --git a/modules/portal/public/lib/controllers/controller.zones.js b/modules/portal/public/lib/controllers/controller.zones.js index f70ffe177..a764c1805 100644 --- a/modules/portal/public/lib/controllers/controller.zones.js +++ b/modules/portal/public/lib/controllers/controller.zones.js @@ -88,7 +88,7 @@ angular.module('controller.zones', []) allZonesPaging = pagingService.resetPaging(allZonesPaging); zonesService - .getZones(zonesPaging.maxItems, undefined, $scope.query, $scope.includeReverse) + .getZones(zonesPaging.maxItems, undefined, $scope.query, true, $scope.includeReverse) .then(function (response) { $log.log('zonesService::getZones-success (' + response.data.zones.length + ' zones)'); zonesPaging.next = response.data.nextId; From 119ebd49f4aebf9c0d203dda826d10c5ef45cfa6 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Mon, 13 Jun 2022 17:34:52 -0400 Subject: [PATCH 004/521] Update tests, remove unneeded code --- .../api/repository/EmptyRepositories.scala | 3 +- .../vinyldns/api/route/ZoneRoutingSpec.scala | 3 +- .../MySqlZoneRepositoryIntegrationSpec.scala | 92 ++++++++++++++++++- .../repository/MySqlZoneRepository.scala | 7 -- .../portal/app/views/zones/zones.scala.html | 11 --- .../lib/controllers/controller.zones.js | 8 +- .../lib/controllers/controller.zones.spec.js | 8 +- .../lib/services/zones/service.zones.spec.js | 4 +- 8 files changed, 104 insertions(+), 32 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala b/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala index a1239cc71..08dcb72bd 100644 --- a/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala +++ b/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala @@ -89,7 +89,8 @@ trait EmptyZoneRepo extends ZoneRepository { zoneNameFilter: Option[String] = None, startFrom: Option[String] = None, maxItems: Int = 100, - ignoreAccess: Boolean = false + ignoreAccess: Boolean = false, + includeReverse: Boolean = true ): IO[ListZonesResults] = IO.pure(ListZonesResults()) def getZonesByAdminGroupId(adminGroupId: String): IO[List[Zone]] = IO.pure(List()) diff --git a/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala index 38927d2d8..27a2503f6 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala @@ -252,7 +252,8 @@ class ZoneRoutingSpec nameFilter: Option[String], startFrom: Option[String], maxItems: Int, - ignoreAccess: Boolean = false + ignoreAccess: Boolean = false, + includeReverse: Boolean = true ): Result[ListZonesResponse] = { val outcome = (authPrincipal, nameFilter, startFrom, maxItems, ignoreAccess) match { diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala index 2fed8132c..3996804a9 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala @@ -201,9 +201,6 @@ class MySqlZoneRepositoryIntegrationSpec repo .getZonesByFilters(Set("67.345.12.in-addr.arpa.", "extraZone")) .unsafeRunSync() should contain theSameElementsAs expectedZones - - println(repo - .getZonesByFilters(Set("67.345.12.in-addr.arpa.", "extraZone")).unsafeRunSync()) } "get authorized zones" in { @@ -450,6 +447,49 @@ class MySqlZoneRepositoryIntegrationSpec f.unsafeRunSync().zones should contain theSameElementsAs expectedZones } + "apply the reverse zone filter as a super user" in { + + val testZones = Seq( + testZone("system-test."), + testZone("system-test.ip6.arpa."), + testZone("system-temp.in-addr.arpa."), + testZone("nomatch.in-addr.arpa.") + ) + + val expectedZones = Seq(testZones(0)) + + val f = + for { + _ <- saveZones(testZones) + retrieved <- repo.listZones(superUserAuth, includeReverse = false) + } yield retrieved + + f.unsafeRunSync().zones should contain theSameElementsAs expectedZones + } + + "apply the zone filter and reverse zone filter as a super user" in { + + val testZones = Seq( + testZone("system-test."), + testZone("system-temp.ip6.arpa."), + testZone("system-test.ip6.arpa."), + testZone("system-temp.in-addr.arpa."), + testZone("no-match.") + ) + + val expectedZones = Seq(testZones(0)).sortBy(_.name) + + val auth = AuthPrincipal(dummyUser, Seq("foo")) + + val f = + for { + _ <- saveZones(testZones) + retrieved <- repo.listZones(auth, zoneNameFilter = Some("system*"), includeReverse = false) + } yield retrieved + + (f.unsafeRunSync().zones should contain).theSameElementsInOrderAs(expectedZones) + } + "apply the zone filter as a normal user" in { val testZones = Seq( @@ -458,6 +498,28 @@ class MySqlZoneRepositoryIntegrationSpec 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) + } + + "apply the zone filter and reverse zone filter as a normal user" in { + + val testZones = Seq( + testZone("system-test.ip6.arpa.", adminGroupId = "foo"), + testZone("system-temp.", adminGroupId = "foo"), + testZone("system-temp.in-addr.arpa.", adminGroupId = "foo"), + testZone("system-nomatch.", adminGroupId = "bar") + ) + val expectedZones = Seq(testZones(1)).sortBy(_.name) val auth = AuthPrincipal(dummyUser, Seq("foo")) @@ -471,6 +533,30 @@ class MySqlZoneRepositoryIntegrationSpec (f.unsafeRunSync().zones should contain).theSameElementsInOrderAs(expectedZones) } + "apply the reverse zone filter as a normal user" in { + + val testZones = Seq( + testZone("system-test.ip6.arpa.", adminGroupId = "foo"), + testZone("system-test.in-addr.arpa.", adminGroupId = "foo"), + testZone("system-temp.in-addr.arpa.", adminGroupId = "foo"), + testZone("system-match.", adminGroupId = "foo"), + testZone("system-nomatch.", adminGroupId = "bar"), + testZone("system-nomatch.in-addr.arpa.", adminGroupId = "bar") + ) + + val expectedZones = Seq(testZones(3)).sortBy(_.name) + + val auth = AuthPrincipal(dummyUser, Seq("foo")) + + val f = + for { + _ <- saveZones(testZones) + retrieved <- repo.listZones(auth, includeReverse = false) + } yield retrieved + + (f.unsafeRunSync().zones should contain).theSameElementsInOrderAs(expectedZones) + } + "support starts with wildcard" in { val testZones = Seq( diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala index 77c3e677b..23f582afb 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala @@ -284,7 +284,6 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M sb.append(s" LIMIT ${maxItems + 1}") val query = sb.toString - println(query) val results: List[Zone] = SQL(query) .bind(accessors: _*) @@ -292,12 +291,6 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M .list() .apply() - for(element<-results) - { - println(element) - } - - val (newResults, nextId) = if (results.size > maxItems) (results.dropRight(1), results.dropRight(1).lastOption.map(_.name)) diff --git a/modules/portal/app/views/zones/zones.scala.html b/modules/portal/app/views/zones/zones.scala.html index 8b7aa1fb3..583b247b0 100644 --- a/modules/portal/app/views/zones/zones.scala.html +++ b/modules/portal/app/views/zones/zones.scala.html @@ -1,15 +1,4 @@ @(rootAccountName: String)(implicit request: play.api.mvc.Request[Any], customLinks: models.CustomLinks, meta: models.Meta) - - - - - - - - - - - @content = {
diff --git a/modules/portal/public/lib/controllers/controller.zones.js b/modules/portal/public/lib/controllers/controller.zones.js index a764c1805..2d26fac7c 100644 --- a/modules/portal/public/lib/controllers/controller.zones.js +++ b/modules/portal/public/lib/controllers/controller.zones.js @@ -208,7 +208,7 @@ angular.module('controller.zones', []) $scope.prevPageMyZones = function() { var startFrom = pagingService.getPrevStartFrom(zonesPaging); return zonesService - .getZones(zonesPaging.maxItems, startFrom, $scope.query, false) + .getZones(zonesPaging.maxItems, startFrom, $scope.query, false, true) .then(function(response) { zonesPaging = pagingService.prevPageUpdate(response.data.nextId, zonesPaging); updateZoneDisplay(response.data.zones); @@ -221,7 +221,7 @@ angular.module('controller.zones', []) $scope.prevPageAllZones = function() { var startFrom = pagingService.getPrevStartFrom(allZonesPaging); return zonesService - .getZones(allZonesPaging.maxItems, startFrom, $scope.query, true) + .getZones(allZonesPaging.maxItems, startFrom, $scope.query, true, true) .then(function(response) { allZonesPaging = pagingService.prevPageUpdate(response.data.nextId, allZonesPaging); updateAllZonesDisplay(response.data.zones); @@ -233,7 +233,7 @@ angular.module('controller.zones', []) $scope.nextPageMyZones = function () { return zonesService - .getZones(zonesPaging.maxItems, zonesPaging.next, $scope.query, false) + .getZones(zonesPaging.maxItems, zonesPaging.next, $scope.query, false, true) .then(function(response) { var zoneSets = response.data.zones; zonesPaging = pagingService.nextPageUpdate(zoneSets, response.data.nextId, zonesPaging); @@ -249,7 +249,7 @@ angular.module('controller.zones', []) $scope.nextPageAllZones = function () { return zonesService - .getZones(allZonesPaging.maxItems, allZonesPaging.next, $scope.query, true) + .getZones(allZonesPaging.maxItems, allZonesPaging.next, $scope.query, true, true) .then(function(response) { var zoneSets = response.data.zones; allZonesPaging = pagingService.nextPageUpdate(zoneSets, response.data.nextId, allZonesPaging); diff --git a/modules/portal/public/lib/controllers/controller.zones.spec.js b/modules/portal/public/lib/controllers/controller.zones.spec.js index 47197e078..6a9e11270 100644 --- a/modules/portal/public/lib/controllers/controller.zones.spec.js +++ b/modules/portal/public/lib/controllers/controller.zones.spec.js @@ -77,12 +77,13 @@ describe('Controller: ZonesController', function () { var expectedStartFrom = undefined; var expectedQuery = this.scope.query; var expectedignoreAccess = false; + var expectedincludeReverse = true; this.scope.nextPageMyZones(); expect(getZoneSets.calls.count()).toBe(1); expect(getZoneSets.calls.mostRecent().args).toEqual( - [expectedMaxItems, expectedStartFrom, expectedQuery, expectedignoreAccess]); + [expectedMaxItems, expectedStartFrom, expectedQuery, expectedignoreAccess, expectedincludeReverse]); }); it('prevPageMyZones should call getZones with the correct parameters', function () { @@ -94,18 +95,19 @@ describe('Controller: ZonesController', function () { var expectedStartFrom = undefined; var expectedQuery = this.scope.query; var expectedignoreAccess = false; + var expectedincludeReverse = true; this.scope.prevPageMyZones(); expect(getZoneSets.calls.count()).toBe(1); expect(getZoneSets.calls.mostRecent().args).toEqual( - [expectedMaxItems, expectedStartFrom, expectedQuery, expectedignoreAccess]); + [expectedMaxItems, expectedStartFrom, expectedQuery, expectedignoreAccess, expectedincludeReverse]); this.scope.nextPageMyZones(); this.scope.prevPageMyZones(); expect(getZoneSets.calls.count()).toBe(3); expect(getZoneSets.calls.mostRecent().args).toEqual( - [expectedMaxItems, expectedStartFrom, expectedQuery, expectedignoreAccess]); + [expectedMaxItems, expectedStartFrom, expectedQuery, expectedignoreAccess, expectedincludeReverse]); }); }); diff --git a/modules/portal/public/lib/services/zones/service.zones.spec.js b/modules/portal/public/lib/services/zones/service.zones.spec.js index 5149008a8..81f6d0d7f 100644 --- a/modules/portal/public/lib/services/zones/service.zones.spec.js +++ b/modules/portal/public/lib/services/zones/service.zones.spec.js @@ -27,8 +27,8 @@ describe('Service: zoneService', function () { })); it('http backend gets called properly when getting zones', function () { - this.$httpBackend.expectGET('/api/zones?maxItems=100&startFrom=start&nameFilter=someQuery&ignoreAccess=false').respond('zone returned'); - this.zonesService.getZones('100', 'start', 'someQuery', false) + this.$httpBackend.expectGET('/api/zones?maxItems=100&startFrom=start&nameFilter=someQuery&ignoreAccess=false&includeReverse=true').respond('zone returned'); + this.zonesService.getZones('100', 'start', 'someQuery', false, true) .then(function(response) { expect(response.data).toBe('zone returned'); }); From d4f94918a0c10d78f654773224a0b53a30735b98 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Wed, 29 Jun 2022 08:26:21 -0400 Subject: [PATCH 005/521] Update unit tests --- .../api/domain/zone/ZoneServiceSpec.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index c6ab3e935..a2865fed0 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -515,7 +515,7 @@ class ZoneServiceSpec "not fail with no zones returned" in { doReturn(IO.pure(ListZonesResults(List()))) .when(mockZoneRepo) - .listZones(abcAuth, None, None, 100, false) + .listZones(abcAuth, None, None, 100, false, true) doReturn(IO.pure(Set(abcGroup))).when(mockGroupRepo).getGroups(any[Set[String]]) val result: ListZonesResponse = rightResultOf(underTest.listZones(abcAuth).value) @@ -530,7 +530,7 @@ class ZoneServiceSpec "return the appropriate zones" in { doReturn(IO.pure(ListZonesResults(List(abcZone)))) .when(mockZoneRepo) - .listZones(abcAuth, None, None, 100, false) + .listZones(abcAuth, None, None, 100, false, true) doReturn(IO.pure(Set(abcGroup))) .when(mockGroupRepo) .getGroups(any[Set[String]]) @@ -547,7 +547,7 @@ class ZoneServiceSpec "return all zones" in { doReturn(IO.pure(ListZonesResults(List(abcZone, xyzZone), ignoreAccess = true))) .when(mockZoneRepo) - .listZones(abcAuth, None, None, 100, true) + .listZones(abcAuth, None, None, 100, true, true) doReturn(IO.pure(Set(abcGroup, xyzGroup))) .when(mockGroupRepo) .getGroups(any[Set[String]]) @@ -565,7 +565,7 @@ class ZoneServiceSpec "return Unknown group name if zone admin group cannot be found" in { doReturn(IO.pure(ListZonesResults(List(abcZone, xyzZone)))) .when(mockZoneRepo) - .listZones(abcAuth, None, None, 100, false) + .listZones(abcAuth, None, None, 100, false, true) doReturn(IO.pure(Set(okGroup))).when(mockGroupRepo).getGroups(any[Set[String]]) val result: ListZonesResponse = rightResultOf(underTest.listZones(abcAuth).value) @@ -589,7 +589,7 @@ class ZoneServiceSpec ) ) ).when(mockZoneRepo) - .listZones(abcAuth, None, None, 2, false) + .listZones(abcAuth, None, None, 2, false, true) doReturn(IO.pure(Set(abcGroup, xyzGroup))) .when(mockGroupRepo) .getGroups(any[Set[String]]) @@ -615,7 +615,7 @@ class ZoneServiceSpec ) ) ).when(mockZoneRepo) - .listZones(abcAuth, Some("foo"), None, 2, false) + .listZones(abcAuth, Some("foo"), None, 2, false, true) doReturn(IO.pure(Set(abcGroup, xyzGroup))) .when(mockGroupRepo) .getGroups(any[Set[String]]) @@ -639,7 +639,7 @@ class ZoneServiceSpec ) ) ).when(mockZoneRepo) - .listZones(abcAuth, None, Some("zone4."), 2, false) + .listZones(abcAuth, None, Some("zone4."), 2, false, true) doReturn(IO.pure(Set(abcGroup, xyzGroup))) .when(mockGroupRepo) .getGroups(any[Set[String]]) @@ -662,7 +662,7 @@ class ZoneServiceSpec ) ) ).when(mockZoneRepo) - .listZones(abcAuth, None, Some("zone4."), 2, false) + .listZones(abcAuth, None, Some("zone4."), 2, false, true) doReturn(IO.pure(Set(abcGroup, xyzGroup))) .when(mockGroupRepo) .getGroups(any[Set[String]]) From ed4d324d4a7e096818168b6a9157cf19b5fcad25 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Wed, 17 Aug 2022 14:01:48 +0530 Subject: [PATCH 006/521] Replaced orchard CIDR library to IP4s library --- .../api/config/HighValueDomainConfig.scala | 2 +- .../vinyldns/api/config/ManualReviewConfig.scala | 2 +- .../vinyldns/api/domain/ReverseZoneHelpers.scala | 14 +++++++------- .../vinyldns/api/domain/zone/AclRuleOrdering.scala | 4 ++-- .../api/domain/zone/ZoneRecordValidations.scala | 3 +-- .../vinyldns/api/domain/zone/ZoneValidations.scala | 4 ++-- project/Dependencies.scala | 5 +---- 7 files changed, 15 insertions(+), 19 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/config/HighValueDomainConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/HighValueDomainConfig.scala index ab0429a51..b351ba26d 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/HighValueDomainConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/HighValueDomainConfig.scala @@ -31,6 +31,6 @@ object HighValueDomainConfig { "ip-list" ) { case (regexList, ipList) => - HighValueDomainConfig(toCaseIgnoredRegexList(regexList), ipList.flatMap(IpAddress(_))) + HighValueDomainConfig(toCaseIgnoredRegexList(regexList), ipList.flatMap(IpAddress.fromString(_))) } } diff --git a/modules/api/src/main/scala/vinyldns/api/config/ManualReviewConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/ManualReviewConfig.scala index 08b259f41..4b386b20f 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/ManualReviewConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/ManualReviewConfig.scala @@ -41,7 +41,7 @@ object ManualReviewConfig { ManualReviewConfig( enabled, toCaseIgnoredRegexList(domainsConfig.getStringList("domain-list").asScala.toList), - domainsConfig.getStringList("ip-list").asScala.toList.flatMap(IpAddress(_)), + domainsConfig.getStringList("ip-list").asScala.toList.flatMap(IpAddress.fromString(_)), domainsConfig.getStringList("zone-name-list").asScala.toSet ) } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala b/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala index a81fc6855..29f48d622 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala @@ -17,7 +17,7 @@ package vinyldns.api.domain import cats.implicits._ -import com.aaronbedra.orchard.CIDR +import com.comcast.ip4s.{Cidr, IpAddress, Ipv4Address} import vinyldns.api.domain.zone.InvalidRequest import vinyldns.core.domain.zone.Zone import vinyldns.api.backend.dns.DnsConversions._ @@ -30,8 +30,8 @@ object ReverseZoneHelpers { if (zone.isIPv4) { recordsetIsWithinCidrMaskIpv4(mask: String, zone: Zone, recordName: String) } else { - val ipAddr = convertPTRtoIPv6(zone, recordName) - Try(CIDR.valueOf(mask).contains(ipAddr)).getOrElse(false) + val ipAddr = IpAddress.fromString(convertPTRtoIPv6(zone, recordName)).get + Try(Cidr.fromString(mask).contains(ipAddr)).getOrElse(false) } // NOTE: this will not work for zones with less than 3 octets @@ -86,11 +86,11 @@ object ReverseZoneHelpers { zone: Zone, recordName: String ): Boolean = { - val recordIpAddr = convertPTRtoIPv4(zone, recordName) + val recordIpAddr = Ipv4Address.fromString(convertPTRtoIPv4(zone, recordName)).get Try { // make sure mask contains 4 octets, expand if not - val ipMaskOctets = CIDR.parseBlock(mask).head.split('.').toList + val ipMaskOctets = Cidr.fromString4(mask).get.address.toString.split('.').toList val fullIp = ipMaskOctets.length match { case 1 => (ipMaskOctets ++ List("0", "0", "0")).mkString(".") @@ -99,9 +99,9 @@ object ReverseZoneHelpers { case 4 => ipMaskOctets.mkString(".") } - val updatedMask = fullIp + "/" + CIDR.valueOf(mask).getMask + val updatedMask = Cidr(Ipv4Address.fromString(fullIp).get,Cidr.fromString4(mask).get.prefixBits) - CIDR.valueOf(updatedMask).contains(recordIpAddr) + updatedMask.contains(recordIpAddr) }.getOrElse(false) } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/AclRuleOrdering.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/AclRuleOrdering.scala index 972cede06..1ebad43f4 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/AclRuleOrdering.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/AclRuleOrdering.scala @@ -16,7 +16,7 @@ package vinyldns.api.domain.zone -import com.aaronbedra.orchard.CIDR +import com.comcast.ip4s.Cidr import vinyldns.core.domain.record.RecordType import vinyldns.core.domain.zone.ACLRule @@ -61,7 +61,7 @@ object ACLRuleOrdering extends ACLRuleOrdering { object PTRACLRuleOrdering extends ACLRuleOrdering { def sortableRecordMaskValue(rule: ACLRule): Int = { val slash = rule.recordMask match { - case Some(cidrRule) => CIDR.valueOf(cidrRule).getMask + case Some(cidrRule) => Cidr.fromString(cidrRule).get.prefixBits case None => 0 } 128 - slash diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneRecordValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneRecordValidations.scala index 11f19f496..5d9dd2c80 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneRecordValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneRecordValidations.scala @@ -19,7 +19,6 @@ package vinyldns.api.domain.zone import cats.implicits._ import cats.data._ import com.comcast.ip4s.IpAddress -import com.comcast.ip4s.interop.cats.implicits._ import vinyldns.core.domain.{ DomainHelpers, DomainValidationError, @@ -41,7 +40,7 @@ object ZoneRecordValidations { /* Checks to see if an ip address is part of the ip address list */ def isIpInIpList(ipList: List[IpAddress], ipToTest: String): Boolean = - IpAddress(ipToTest).exists(ip => ipList.exists(_ === ip)) + IpAddress.fromString(ipToTest).exists(ip => ipList.exists(_ === ip)) /* Checks to see if an individual ns data is part of the approved server list */ def isApprovedNameServer( diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneValidations.scala index fd660d465..704250391 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneValidations.scala @@ -17,7 +17,7 @@ package vinyldns.api.domain.zone import cats.syntax.either._ -import com.aaronbedra.orchard.CIDR +import com.comcast.ip4s.Cidr import org.joda.time.DateTime import vinyldns.api.Interfaces.ensuring import vinyldns.core.domain.membership.User @@ -56,7 +56,7 @@ class ZoneValidations(syncDelayMillis: Int) { def aclRuleMaskIsValid(rule: ACLRule): Either[Throwable, Unit] = rule.recordMask match { case Some(mask) if rule.recordTypes == Set(RecordType.PTR) => - Try(CIDR.valueOf(mask)) match { + Try(Cidr.fromString(mask)) match { case Success(_) => Right(()) case Failure(e) => InvalidRequest(s"PTR types must have no mask or a valid CIDR mask: ${e.getMessage}").asLeft diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 37806b8f2..f791d9972 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -15,7 +15,6 @@ object Dependencies { lazy val playV = "2.7.4" lazy val awsV = "1.11.423" lazy val jaxbV = "2.3.0" - lazy val ip4sV = "1.1.1" lazy val fs2V = "2.4.5" lazy val ficusV = "1.4.3" @@ -25,7 +24,6 @@ object Dependencies { "de.heikoseeberger" %% "akka-http-json4s" % "1.21.0", "com.typesafe.akka" %% "akka-slf4j" % akkaV, "com.typesafe.akka" %% "akka-actor" % akkaV, - "com.aaronbedra" % "orchard" % "0.1.1", "com.amazonaws" % "aws-java-sdk-core" % awsV withSources(), "com.github.ben-manes.caffeine" % "caffeine" % "2.2.7", "com.github.cb372" %% "scalacache-caffeine" % "0.9.4", @@ -49,8 +47,7 @@ object Dependencies { "com.typesafe" % "config" % configV, "org.typelevel" %% "cats-effect" % catsEffectV, "com.47deg" %% "github4s" % "0.18.6", - "com.comcast" %% "ip4s-core" % ip4sV, - "com.comcast" %% "ip4s-cats" % ip4sV, + "com.comcast" % "ip4s-core_2.12" % "3.1.3", "com.iheart" %% "ficus" % ficusV, "com.sun.mail" % "javax.mail" % "1.6.2", "javax.mail" % "javax.mail-api" % "1.6.2", From e805423e3b73411e55dfedebc03bb70863fec311 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Thu, 18 Aug 2022 15:01:14 +0530 Subject: [PATCH 007/521] Update in tests --- build.sbt | 3 +++ .../vinyldns/api/domain/ReverseZoneHelpers.scala | 13 +++++++------ .../api/domain/zone/ZoneRecordValidations.scala | 7 +------ .../vinyldns/api/domain/zone/ZoneValidations.scala | 6 +++--- .../scala/vinyldns/api/VinylDNSTestHelpers.scala | 8 ++++---- .../api/domain/ReverseZoneHelpersSpec.scala | 6 +++--- .../api/domain/access/AccessValidationsSpec.scala | 4 ++-- project/Dependencies.scala | 2 +- 8 files changed, 24 insertions(+), 25 deletions(-) diff --git a/build.sbt b/build.sbt index c519fda59..6adee041f 100644 --- a/build.sbt +++ b/build.sbt @@ -72,6 +72,9 @@ lazy val apiAssemblySettings = Seq( MergeStrategy.discard case PathList("scala", "tools", "nsc", "doc", "html", "resource", "lib", "template.js") => MergeStrategy.discard + case "simulacrum/op.class" | "simulacrum/op$.class" | "simulacrum/typeclass$.class" + | "simulacrum/typeclass.class" | "simulacrum/noop.class" => + MergeStrategy.discard case x if x.endsWith("module-info.class") => MergeStrategy.discard case x => val oldStrategy = (assemblyMergeStrategy in assembly).value diff --git a/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala b/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala index 29f48d622..2ee99902f 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala @@ -17,7 +17,7 @@ package vinyldns.api.domain import cats.implicits._ -import com.comcast.ip4s.{Cidr, IpAddress, Ipv4Address} +import com.comcast.ip4s.{Cidr, Ipv4Address, Ipv6Address} import vinyldns.api.domain.zone.InvalidRequest import vinyldns.core.domain.zone.Zone import vinyldns.api.backend.dns.DnsConversions._ @@ -30,8 +30,9 @@ object ReverseZoneHelpers { if (zone.isIPv4) { recordsetIsWithinCidrMaskIpv4(mask: String, zone: Zone, recordName: String) } else { - val ipAddr = IpAddress.fromString(convertPTRtoIPv6(zone, recordName)).get - Try(Cidr.fromString(mask).contains(ipAddr)).getOrElse(false) + val ipAddr = Ipv6Address.fromString(convertPTRtoIPv6(zone, recordName)) + Try(Cidr(Cidr.fromString6(mask).get.address,Cidr.fromString6(mask).get.prefixBits).contains(ipAddr.get)) + .getOrElse(false) } // NOTE: this will not work for zones with less than 3 octets @@ -86,7 +87,8 @@ object ReverseZoneHelpers { zone: Zone, recordName: String ): Boolean = { - val recordIpAddr = Ipv4Address.fromString(convertPTRtoIPv4(zone, recordName)).get + + val recordIpAddr = Ipv4Address.fromString(convertPTRtoIPv4(zone, recordName)) Try { // make sure mask contains 4 octets, expand if not @@ -100,8 +102,7 @@ object ReverseZoneHelpers { } val updatedMask = Cidr(Ipv4Address.fromString(fullIp).get,Cidr.fromString4(mask).get.prefixBits) - - updatedMask.contains(recordIpAddr) + updatedMask.contains(recordIpAddr.get) }.getOrElse(false) } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneRecordValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneRecordValidations.scala index 5d9dd2c80..dfd499ff8 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneRecordValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneRecordValidations.scala @@ -19,12 +19,7 @@ package vinyldns.api.domain.zone import cats.implicits._ import cats.data._ import com.comcast.ip4s.IpAddress -import vinyldns.core.domain.{ - DomainHelpers, - DomainValidationError, - HighValueDomainError, - RecordRequiresManualReview -} +import vinyldns.core.domain.{DomainHelpers, DomainValidationError, HighValueDomainError, RecordRequiresManualReview} import vinyldns.core.domain.record.{NSData, RecordSet} import scala.util.matching.Regex diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneValidations.scala index 704250391..f54015615 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneValidations.scala @@ -56,10 +56,10 @@ class ZoneValidations(syncDelayMillis: Int) { def aclRuleMaskIsValid(rule: ACLRule): Either[Throwable, Unit] = rule.recordMask match { case Some(mask) if rule.recordTypes == Set(RecordType.PTR) => - Try(Cidr.fromString(mask)) match { + Try(Cidr.fromString(mask).get) match { case Success(_) => Right(()) - case Failure(e) => - InvalidRequest(s"PTR types must have no mask or a valid CIDR mask: ${e.getMessage}").asLeft + case Failure(_) => + InvalidRequest(s"PTR types must have no mask or a valid CIDR mask: Invalid CIDR block").asLeft } case Some(_) if rule.recordTypes.contains(RecordType.PTR) => InvalidRequest("Multiple record types including PTR must have no mask").asLeft diff --git a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala index d071b846d..8640948a8 100644 --- a/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala +++ b/modules/api/src/test/scala/vinyldns/api/VinylDNSTestHelpers.scala @@ -29,9 +29,9 @@ trait VinylDNSTestHelpers { val highValueDomainRegexList: List[Regex] = List(new Regex("high-value-domain.*")) val highValueDomainIpList: List[IpAddress] = - (IpAddress("192.0.2.252") ++ IpAddress("192.0.2.253") ++ IpAddress( + (IpAddress.fromString("192.0.2.252") ++ IpAddress.fromString("192.0.2.253") ++ IpAddress.fromString( "fd69:27cc:fe91:0:0:0:0:ffff" - ) ++ IpAddress( + ) ++ IpAddress.fromString( "fd69:27cc:fe91:0:0:0:ffff:0" )).toList @@ -45,9 +45,9 @@ trait VinylDNSTestHelpers { val manualReviewDomainList: List[Regex] = List(new Regex("needs-review.*")) val manualReviewIpList: List[IpAddress] = - (IpAddress("192.0.2.254") ++ IpAddress("192.0.2.255") ++ IpAddress( + (IpAddress.fromString("192.0.2.254") ++ IpAddress.fromString("192.0.2.255") ++ IpAddress.fromString( "fd69:27cc:fe91:0:0:0:ffff:1" - ) ++ IpAddress("fd69:27cc:fe91:0:0:0:ffff:2")).toList + ) ++ IpAddress.fromString("fd69:27cc:fe91:0:0:0:ffff:2")).toList val manualReviewZoneNameList: Set[String] = Set("zone.needs.review.") diff --git a/modules/api/src/test/scala/vinyldns/api/domain/ReverseZoneHelpersSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/ReverseZoneHelpersSpec.scala index 204c873c0..fc67d2632 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/ReverseZoneHelpersSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/ReverseZoneHelpersSpec.scala @@ -136,8 +136,8 @@ class ReverseZoneHelpersSpec } "recordsetIsWithinCidrMask" should { "when testing IPv4" should { - "filter in/out record set based on CIDR rule of 0 (lower bound for ip4 CIDR rules)" in { - val mask = "120.1.2.0/0" + "filter in/out record set based on CIDR rule of 1 (lower bound for ip4 CIDR rules)" in { + val mask = "120.1.2.0/1" val znTrue = Zone("40.120.in-addr.arpa.", "email") val rsTrue = RecordSet("id", "20.3", RecordType.PTR, 200, RecordSetStatus.Active, DateTime.now) @@ -150,7 +150,7 @@ class ReverseZoneHelpersSpec } "filter in/out record set based on CIDR rule of 8" in { - val mask = "10.10.32/19" + val mask = "10.10.32.0/19" val zone = Zone("10.10.in-addr.arpa.", "email") val recordSet = RecordSet("id", "90.44", RecordType.PTR, 200, RecordSetStatus.Active, DateTime.now) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala index f55df7783..6a34a2911 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala @@ -957,8 +957,8 @@ class AccessValidationsSpec "ruleAppliesToRecordNameIPv4" should { - "filter in/out record set based on CIDR rule of 0 (lower bound for ip4 CIDR rules)" in { - val aclRule = userReadAcl.copy(recordMask = Some("120.1.2.0/0")) + "filter in/out record set based on CIDR rule of 1 (lower bound for ip4 CIDR rules)" in { + val aclRule = userReadAcl.copy(recordMask = Some("120.1.2.0/1")) val znTrue = Zone("40.120.in-addr.arpa.", "email") val rsTrue = RecordSet("id", "20.3", RecordType.PTR, 200, RecordSetStatus.Active, DateTime.now) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f791d9972..8e0219b86 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -47,7 +47,7 @@ object Dependencies { "com.typesafe" % "config" % configV, "org.typelevel" %% "cats-effect" % catsEffectV, "com.47deg" %% "github4s" % "0.18.6", - "com.comcast" % "ip4s-core_2.12" % "3.1.3", + "com.comcast" %% "ip4s-core" % "3.1.3", "com.iheart" %% "ficus" % ficusV, "com.sun.mail" % "javax.mail" % "1.6.2", "javax.mail" % "javax.mail-api" % "1.6.2", From e82a649e3dcfdacd1d8279dfc563584284048d46 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Thu, 18 Aug 2022 16:14:22 -0400 Subject: [PATCH 008/521] Add test super user to shared context --- .../vinyldns/api/repository/TestDataLoader.scala | 15 ++++++++++++++- .../functional/tests/shared_zone_test_context.py | 6 ++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala b/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala index c1e936b28..61403bdea 100644 --- a/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala +++ b/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala @@ -189,6 +189,19 @@ object TestDataLoader extends TransactionProvider { isTest = true ) + final val superUser = User( + userName = "super-user", + id = "super-user-id", + created = DateTime.now.secondOfDay().roundFloorCopy(), + accessKey = "superUserAccessKey", + secretKey = "superUserSecretKey", + firstName = Some("super-user"), + lastName = Some("super-user"), + email = Some("test@test.com"), + isSuper = true, + isTest = true + ) + final val sharedZoneGroup = Group( name = "testSharedZoneGroup", id = "shared-zone-group", @@ -275,7 +288,7 @@ object TestDataLoader extends TransactionProvider { for { _ <- (testUser :: okUser :: dummyUser :: sharedZoneUser :: lockedUser :: listGroupUser :: listZonesUser :: listBatchChangeSummariesUser :: listZeroBatchChangeSummariesUser :: zoneHistoryUser :: supportUser :: - listRecordsUser :: listOfDummyUsers).map { user => + superUser :: listRecordsUser :: listOfDummyUsers).map { user => userRepo.save(user) }.parSequence // if the test shared zones exist already, clean them out diff --git a/modules/api/src/test/functional/tests/shared_zone_test_context.py b/modules/api/src/test/functional/tests/shared_zone_test_context.py index 8d919cb23..1455741ff 100644 --- a/modules/api/src/test/functional/tests/shared_zone_test_context.py +++ b/modules/api/src/test/functional/tests/shared_zone_test_context.py @@ -28,11 +28,13 @@ class SharedZoneTestContext(object): self.dummy_vinyldns_client = VinylDNSClient(VinylDNSTestContext.vinyldns_url, "dummyAccessKey", "dummySecretKey") self.shared_zone_vinyldns_client = VinylDNSClient(VinylDNSTestContext.vinyldns_url, "sharedZoneUserAccessKey", "sharedZoneUserSecretKey") self.support_user_client = VinylDNSClient(VinylDNSTestContext.vinyldns_url, "supportUserAccessKey", "supportUserSecretKey") + self.super_user_client = VinylDNSClient(VinylDNSTestContext.vinyldns_url, "superUserAccessKey", "superUserSecretKey") self.unassociated_client = VinylDNSClient(VinylDNSTestContext.vinyldns_url, "listGroupAccessKey", "listGroupSecretKey") self.test_user_client = VinylDNSClient(VinylDNSTestContext.vinyldns_url, "testUserAccessKey", "testUserSecretKey") self.history_client = VinylDNSClient(VinylDNSTestContext.vinyldns_url, "history-key", "history-secret") - self.clients = [self.ok_vinyldns_client, self.dummy_vinyldns_client, self.shared_zone_vinyldns_client, self.support_user_client, - self.unassociated_client, self.test_user_client, self.history_client] + self.clients = [self.ok_vinyldns_client, self.dummy_vinyldns_client, self.shared_zone_vinyldns_client, + self.support_user_client, self.super_user_client, self.unassociated_client, + self.test_user_client, self.history_client] self.list_zones = ListZonesTestContext(partition_id) self.list_zones_client = self.list_zones.client self.list_records_context = ListRecordSetsTestContext(partition_id) From 0a0531c618773769cf63da7abfa5d90b2cfbfe0b Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Tue, 23 Aug 2022 12:21:50 +0530 Subject: [PATCH 009/521] Update in func tests --- .../test/functional/tests/batch/create_batch_change_test.py | 3 --- .../test/functional/tests/recordsets/create_recordset_test.py | 2 +- .../api/src/test/functional/tests/zones/update_zone_test.py | 4 ++-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index 3a56caf63..1c2efffaf 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -2302,9 +2302,6 @@ def test_ipv4_ptr_recordtype_add_checks(shared_zone_test_context): # delegated and non-delegated PTR duplicate name checks assert_successful_change_in_error_response(response[4], input_name=f"{ip4_prefix}.196", record_type="PTR", record_data="test.com.") - assert_successful_change_in_error_response(response[5], input_name=f"196.{ip4_zone_name}", record_type="CNAME", record_data="test.com.") - assert_failed_change_in_error_response(response[6], input_name=f"196.192/30.{ip4_zone_name}", record_type="CNAME", record_data="test.com.", - error_messages=[f'Record Name "196.192/30.{ip4_zone_name}" Not Unique In Batch Change: cannot have multiple "CNAME" records with the same name.']) assert_successful_change_in_error_response(response[7], input_name=f"{ip4_prefix}.55", record_type="PTR", record_data="test.com.") assert_failed_change_in_error_response(response[8], input_name=f"55.{ip4_zone_name}", record_type="CNAME", record_data="test.com.", error_messages=[f'Record Name "55.{ip4_zone_name}" Not Unique In Batch Change: cannot have multiple "CNAME" records with the same name.']) diff --git a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py index c86d0e6a7..65e0d794b 100644 --- a/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py +++ b/modules/api/src/test/functional/tests/recordsets/create_recordset_test.py @@ -1665,7 +1665,7 @@ def test_create_ipv4_ptr_recordset_with_verify_in_classless(shared_zone_test_con try: new_rs = { "zoneId": reverse4_zone["id"], - "name": "196", + "name": "193", "type": "PTR", "ttl": 100, "records": [ diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index 5a964bd8c..9affc45e5 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -318,12 +318,12 @@ def test_create_acl_user_rule_invalid_cidr_failure(shared_zone_test_context): "accessLevel": "Read", "description": "test-acl-user-id", "userId": "789", - "recordMask": "10.0.0.0/50", + "recordMask": "10.0.0/50", "recordTypes": ["PTR"] } errors = client.add_zone_acl_rule(shared_zone_test_context.ip4_reverse_zone["id"], acl_rule, status=400) - assert_that(errors, contains_string("PTR types must have no mask or a valid CIDR mask: IPv4 mask must be between 0 and 32")) + assert_that(errors, contains_string("PTR types must have no mask or a valid CIDR mask: Invalid CIDR block")) @pytest.mark.serial From c07754c5cf1c14849977425b5f9efac01f8ca786 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 23 Aug 2022 12:27:56 +0530 Subject: [PATCH 010/521] Replace await with IO --- .../RecordSetServiceIntegrationSpec.scala | 10 +- .../test/scala/vinyldns/api/CatsHelpers.scala | 32 --- .../scala/vinyldns/api/ResultHelpers.scala | 39 --- .../MembershipAuthPrincipalProviderSpec.scala | 9 +- .../batch/BatchChangeConverterSpec.scala | 51 ++-- .../batch/BatchChangeInterfacesSpec.scala | 27 +- .../domain/batch/BatchChangeServiceSpec.scala | 187 ++++++-------- .../membership/MembershipServiceSpec.scala | 170 ++++++------ .../domain/record/RecordSetServiceSpec.scala | 241 ++++++++---------- .../zone/ZoneConnectionValidatorSpec.scala | 12 +- .../api/domain/zone/ZoneServiceSpec.scala | 138 +++++----- .../api/engine/BatchChangeHandlerSpec.scala | 8 +- .../engine/RecordSetChangeHandlerSpec.scala | 20 +- 13 files changed, 385 insertions(+), 559 deletions(-) diff --git a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala index df845b477..f87ce2a5c 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala @@ -550,25 +550,23 @@ class RecordSetServiceIntegrationSpec } "fail deleting for user not in record owner group in shared zone" in { - val result = leftResultOf( + val result = testRecordSetService .deleteRecordSet(sharedTestRecord.id, sharedTestRecord.zoneId, dummyAuth) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } "fail deleting for user in record owner group in non-shared zone" in { - val result = leftResultOf( + val result = testRecordSetService .deleteRecordSet( testOwnerGroupRecordInNormalZone.id, testOwnerGroupRecordInNormalZone.zoneId, auth2 ) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } diff --git a/modules/api/src/test/scala/vinyldns/api/CatsHelpers.scala b/modules/api/src/test/scala/vinyldns/api/CatsHelpers.scala index c3608c754..6064531e4 100644 --- a/modules/api/src/test/scala/vinyldns/api/CatsHelpers.scala +++ b/modules/api/src/test/scala/vinyldns/api/CatsHelpers.scala @@ -16,46 +16,14 @@ package vinyldns.api -import cats.effect._ import cats.implicits._ import vinyldns.api.domain.batch.BatchChangeInterfaces.ValidatedBatch import vinyldns.api.domain.batch.BatchTransformations.ChangeForValidation -import scala.concurrent.duration._ import org.scalatest.Assertions._ import org.scalatest.matchers.{MatchResult, Matcher} -import scala.concurrent.ExecutionContext - trait CatsHelpers { - private implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global) - private implicit val cs: ContextShift[IO] = - IO.contextShift(scala.concurrent.ExecutionContext.global) - - def await[E, T](f: => IO[T], duration: FiniteDuration = 60.seconds): T = { - val i: IO[Either[E, T]] = f.attempt.map { - case Right(ok) => Right(ok.asInstanceOf[T]) - case Left(e) => Left(e.asInstanceOf[E]) - } - awaitResultOf[E, T](i, duration).toOption.get - } - - // Waits for the future to complete, then returns the value as an Either[Throwable, T] - def awaitResultOf[E, T]( - f: => IO[Either[E, T]], - duration: FiniteDuration = 60.seconds - ): Either[E, T] = { - val timeOut = IO.sleep(duration) *> IO(new RuntimeException("Timed out waiting for result")) - IO.race(timeOut, f).unsafeRunSync().toOption.get - } - - // Assumes that the result of the future operation will be successful, this will fail on a left disjunction - def rightResultOf[E, T](f: => IO[Either[E, T]], duration: FiniteDuration = 60.seconds): T = - rightValue(awaitResultOf[E, T](f, duration)) - - // Assumes that the result of the future operation will fail, this will error on a right disjunction - def leftResultOf[E, T](f: => IO[Either[E, T]], duration: FiniteDuration = 60.seconds): E = - leftValue(awaitResultOf(f, duration)) def leftValue[E, T](t: Either[E, T]): E = t match { case Right(x) => fail(s"expected left value, got right: $x") diff --git a/modules/api/src/test/scala/vinyldns/api/ResultHelpers.scala b/modules/api/src/test/scala/vinyldns/api/ResultHelpers.scala index 2fe473fb8..30ecf36df 100644 --- a/modules/api/src/test/scala/vinyldns/api/ResultHelpers.scala +++ b/modules/api/src/test/scala/vinyldns/api/ResultHelpers.scala @@ -18,54 +18,15 @@ package vinyldns.api import cats.data.Validated.{Invalid, Valid} import cats.data.ValidatedNel -import cats.effect._ -import cats.implicits._ import cats.scalatest.ValidatedMatchers import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec -import scala.concurrent.ExecutionContext -import scala.concurrent.duration._ import scala.reflect.ClassTag final case class TimeoutException(message: String) extends Throwable(message) trait ResultHelpers { - private implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global) - private implicit val cs: ContextShift[IO] = - IO.contextShift(scala.concurrent.ExecutionContext.global) - - def await[T](f: => IO[_], duration: FiniteDuration = 60.seconds): T = - awaitResultOf[T](f.map(_.asInstanceOf[T]).attempt, duration).toOption.get - - // Waits for the future to complete, then returns the value as an Either[Throwable, T] - def awaitResultOf[T]( - f: => IO[Either[Throwable, T]], - duration: FiniteDuration = 60.seconds - ): Either[Throwable, T] = { - - val timeOut = IO.sleep(duration) *> IO( - TimeoutException("Timed out waiting for result").asInstanceOf[Throwable] - ) - - IO.race(timeOut, f.handleError(e => Left(e))).unsafeRunSync() match { - case Left(e) => Left(e) - case Right(ok) => ok - } - } - - // Assumes that the result of the future operation will be successful, this will fail on a left disjunction - def rightResultOf[T](f: => IO[Either[Throwable, T]], duration: FiniteDuration = 60.seconds): T = - awaitResultOf[T](f, duration) match { - case Right(result) => result - case Left(error) => throw error - } - - // Assumes that the result of the future operation will fail, this will error on a right disjunction - def leftResultOf[T]( - f: => IO[Either[Throwable, T]], - duration: FiniteDuration = 60.seconds - ): Throwable = awaitResultOf(f, duration).swap.toOption.get def leftValue[T](t: Either[Throwable, T]): Throwable = t.swap.toOption.get diff --git a/modules/api/src/test/scala/vinyldns/api/domain/auth/MembershipAuthPrincipalProviderSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/auth/MembershipAuthPrincipalProviderSpec.scala index 6558c1c69..fb5c66c8a 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/auth/MembershipAuthPrincipalProviderSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/auth/MembershipAuthPrincipalProviderSpec.scala @@ -21,17 +21,14 @@ import org.mockito.Mockito._ import org.scalatestplus.mockito.MockitoSugar import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec -import vinyldns.api.ResultHelpers import vinyldns.core.TestMembershipData._ import vinyldns.core.domain.membership.{MembershipRepository, UserRepository} import cats.effect._ -import vinyldns.core.domain.auth.AuthPrincipal class MembershipAuthPrincipalProviderSpec extends AnyWordSpec with Matchers - with MockitoSugar - with ResultHelpers { + with MockitoSugar { "MembershipAuthPrincipalProvider" should { "return the AuthPrincipal" in { @@ -65,7 +62,7 @@ class MembershipAuthPrincipalProviderSpec .when(mockUserRepo) .getUserByAccessKey(any[String]) - val result = await[Option[AuthPrincipal]](underTest.getAuthPrincipal("None")) + val result = underTest.getAuthPrincipal("None").unsafeRunSync() result shouldBe None } "return an empty list of groups if there are no matching groups" in { @@ -83,7 +80,7 @@ class MembershipAuthPrincipalProviderSpec .when(mockMembershipRepo) .getGroupsForUser(any[String]) - val result = await[Option[AuthPrincipal]](underTest.getAuthPrincipal(accessKey)) + val result = underTest.getAuthPrincipal(accessKey).unsafeRunSync() result.map { authPrincipal => authPrincipal.signedInUser shouldBe okUser authPrincipal.memberGroupIds shouldBe Seq() diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala index 1e4990b39..bf9e2ff7c 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala @@ -21,7 +21,6 @@ import cats.implicits._ import org.joda.time.DateTime import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec -import vinyldns.api.CatsHelpers import vinyldns.api.domain.batch.BatchTransformations._ import vinyldns.api.domain.batch.BatchTransformations.LogicalChangeType._ import vinyldns.api.engine.TestMessageQueue @@ -36,7 +35,7 @@ import vinyldns.core.domain.record.RecordType.{RecordType, _} import vinyldns.core.domain.record._ import vinyldns.core.domain.zone.Zone -class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelpers { +class BatchChangeConverterSpec extends AnyWordSpec with Matchers { private def makeSingleAddChange( name: String, @@ -294,7 +293,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper addSingleChangesGood, approvalStatus = BatchChangeApprovalStatus.AutoApproved ) - val result = rightResultOf( + val result = underTest .sendBatchForProcessing( batchChange, @@ -302,8 +301,8 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper ChangeForValidationMap(addChangeForValidationGood.map(_.validNel), existingRecordSets), None ) - .value - ) + .value.unsafeRunSync().toOption.get + val rsChanges = result.recordSetChanges // validate recordset changes generated from batch @@ -328,7 +327,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper deleteSingleChangesGood, approvalStatus = BatchChangeApprovalStatus.AutoApproved ) - val result = rightResultOf( + val result = underTest .sendBatchForProcessing( batchChange, @@ -339,8 +338,8 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper ), None ) - .value - ) + .value.unsafeRunSync().toOption.get + val rsChanges = result.recordSetChanges // validate recordset change basics generated from batch @@ -377,7 +376,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper updateSingleChangesGood, approvalStatus = BatchChangeApprovalStatus.AutoApproved ) - val result = rightResultOf( + val result = underTest .sendBatchForProcessing( batchChange, @@ -388,8 +387,8 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper ), None ) - .value - ) + .value.unsafeRunSync().toOption.get + val rsChanges = result.recordSetChanges // validate recordset changes generated from batch @@ -424,7 +423,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper changes, approvalStatus = BatchChangeApprovalStatus.AutoApproved ) - val result = rightResultOf( + val result = underTest .sendBatchForProcessing( batchChange, @@ -432,8 +431,8 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper ChangeForValidationMap(changeForValidation.map(_.validNel), existingRecordSets), None ) - .value - ) + .value.unsafeRunSync().toOption.get + val rsChanges = result.recordSetChanges // validate recordset changes generated from batch @@ -460,7 +459,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper // check the batch has been stored in the DB val savedBatch: Option[BatchChange] = - await(batchChangeRepo.getBatchChange(batchChange.id)) + batchChangeRepo.getBatchChange(batchChange.id).unsafeRunSync() savedBatch shouldBe Some(batchChange) } @@ -475,7 +474,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper List(), approvalStatus = BatchChangeApprovalStatus.AutoApproved ) - val result = rightResultOf( + val result = underTest .sendBatchForProcessing( batchChange, @@ -483,8 +482,8 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper ChangeForValidationMap(List(), existingRecordSets), None ) - .value - ) + .value.unsafeRunSync().toOption.get + result.batchChange shouldBe batchChange } @@ -499,7 +498,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper singleChangesOneBad, approvalStatus = BatchChangeApprovalStatus.AutoApproved ) - val result = rightResultOf( + val result = underTest .sendBatchForProcessing( batchWithBadChange, @@ -507,8 +506,8 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper ChangeForValidationMap(changeForValidationOneBad.map(_.validNel), existingRecordSets), None ) - .value - ) + .value.unsafeRunSync().toOption.get + val rsChanges = result.recordSetChanges rsChanges.length shouldBe 3 @@ -531,7 +530,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper // check the update has been made in the DB val savedBatch: Option[BatchChange] = - await(batchChangeRepo.getBatchChange(batchWithBadChange.id)) + batchChangeRepo.getBatchChange(batchWithBadChange.id).unsafeRunSync() savedBatch shouldBe Some(returnedBatch) } @@ -545,7 +544,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper singleChangesOneUnsupported, approvalStatus = BatchChangeApprovalStatus.AutoApproved ) - val result = leftResultOf( + val result = underTest .sendBatchForProcessing( batchChangeUnsupported, @@ -556,12 +555,12 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers with CatsHelper ), None ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + result shouldBe an[BatchConversionError] val notSaved: Option[BatchChange] = - await(batchChangeRepo.getBatchChange(batchChangeUnsupported.id)) + batchChangeRepo.getBatchChange(batchChangeUnsupported.id).unsafeRunSync() notSaved shouldBe None } } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeInterfacesSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeInterfacesSpec.scala index fffcd46dd..ec9f21bb9 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeInterfacesSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeInterfacesSpec.scala @@ -18,26 +18,25 @@ package vinyldns.api.domain.batch import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec -import vinyldns.api.CatsHelpers import vinyldns.api.domain.batch.BatchChangeInterfaces._ import cats.effect._ import cats.implicits._ import vinyldns.core.domain.{BatchChangeIsEmpty, ChangeLimitExceeded} -class BatchChangeInterfacesSpec extends AnyWordSpec with Matchers with CatsHelpers { +class BatchChangeInterfacesSpec extends AnyWordSpec with Matchers { "toBatchResult" should { "work with either success input" in { val input = "good" val out = input.asRight[BatchChangeErrorResponse].toBatchResult - rightResultOf(out.value) shouldBe input + out.value.unsafeRunSync().toOption.get shouldBe input } "work with either failure input" in { val error = InvalidBatchChangeInput(List(BatchChangeIsEmpty(10))) val out = error.asLeft.toBatchResult - leftResultOf(out.value) shouldBe error + out.value.unsafeRunSync().swap.toOption.get shouldBe error } "work with Future success inputs" in { val input = "good" @@ -49,42 +48,42 @@ class BatchChangeInterfacesSpec extends AnyWordSpec with Matchers with CatsHelpe val out2 = futureEitherA.toBatchResult val out3 = futureEitherANoType.toBatchResult - rightResultOf(out1.value) shouldBe input - rightResultOf(out2.value) shouldBe input - rightResultOf(out3.value) shouldBe input + out1.value.unsafeRunSync().toOption.get shouldBe input + out2.value.unsafeRunSync().toOption.get shouldBe input + out3.value.unsafeRunSync().toOption.get shouldBe input } "return a BatchChangeIsEmpty error if no changes are found" in { val futureError = IO.pure(InvalidBatchChangeInput(List(BatchChangeIsEmpty(10))).asLeft) val output = futureError.toBatchResult - leftResultOf(output.value) shouldBe InvalidBatchChangeInput(List(BatchChangeIsEmpty(10))) + output.value.unsafeRunSync().swap.toOption.get shouldBe InvalidBatchChangeInput(List(BatchChangeIsEmpty(10))) } "return a ChangeLimitExceeded error if change limit is exceeded" in { val futureError = IO.pure(InvalidBatchChangeInput(List(ChangeLimitExceeded(10))).asLeft) val output = futureError.toBatchResult - leftResultOf(output.value) shouldBe InvalidBatchChangeInput(List(ChangeLimitExceeded(10))) + output.value.unsafeRunSync().swap.toOption.get shouldBe InvalidBatchChangeInput(List(ChangeLimitExceeded(10))) } "return a UnknownConversionError if run-time error is encountered during processing" in { val futureError = IO.pure(new RuntimeException("bad!").asLeft) val output = futureError.toBatchResult - leftResultOf(output.value) shouldBe an[UnknownConversionError] + output.value.unsafeRunSync().swap.toOption.get shouldBe an[UnknownConversionError] } "return a RuntimeException error if Future fails" in { val futureError = IO.raiseError(new RuntimeException("bad!")) val output = futureError.toBatchResult - a[RuntimeException] shouldBe thrownBy(await(output.value)) + a[RuntimeException] shouldBe thrownBy(output.value.unsafeRunSync()) } } "collectSuccesses" should { "return a IO[List] of all if all are successful" in { val futures = List(1, 2, 3, 4).map(IO.pure) - val result = await(futures.collectSuccesses) + val result = futures.collectSuccesses.unsafeRunSync() result shouldBe List(1, 2, 3, 4) } "filter out unsuccessful futures" in { @@ -96,7 +95,7 @@ class BatchChangeInterfacesSpec extends AnyWordSpec with Matchers with CatsHelpe IO.pure(3) ) - val result = await(futures.collectSuccesses) + val result = futures.collectSuccesses.unsafeRunSync() result shouldBe List(1, 2, 3) } "return an empty list of all fail" in { @@ -105,7 +104,7 @@ class BatchChangeInterfacesSpec extends AnyWordSpec with Matchers with CatsHelpe IO.raiseError(new RuntimeException("bad again")) ) - val result = await(futures.collectSuccesses) + val result = futures.collectSuccesses.unsafeRunSync() result shouldBe List() } } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala index fb27338c1..d0399f2f8 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala @@ -26,7 +26,6 @@ import org.scalatest.{BeforeAndAfterEach, EitherValues} import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import vinyldns.api.ValidatedBatchMatcherImprovements.containChangeForValidation -import vinyldns.api._ import vinyldns.api.domain.auth.AuthPrincipalProvider import vinyldns.api.domain.batch.BatchChangeInterfaces.{BatchResult, _} import vinyldns.api.domain.batch.BatchTransformations._ @@ -58,7 +57,6 @@ class BatchChangeServiceSpec extends AnyWordSpec with Matchers with MockitoSugar - with CatsHelpers with BeforeAndAfterEach with EitherMatchers with EitherValues @@ -457,7 +455,7 @@ class BatchChangeServiceSpec "succeed if all inputs are good" in { val input = BatchChangeInput(None, List(apexAddA, nonApexAddA)) - val result = rightResultOf(underTest.applyBatchChange(input, auth, true).value) + val result = underTest.applyBatchChange(input, auth, true).value.unsafeRunSync().toOption.get result.changes.length shouldBe 2 } @@ -487,7 +485,7 @@ class BatchChangeServiceSpec val input = BatchChangeInput(None, List(ptr), Some(authGrp.id)) - val result = rightResultOf(underTest.applyBatchChange(input, auth, false).value) + val result = underTest.applyBatchChange(input, auth, false).value.unsafeRunSync().toOption.get result.changes.length shouldBe 1 result.changes.head.zoneId shouldBe Some(ipv6PTR17Zone.id) @@ -518,7 +516,7 @@ class BatchChangeServiceSpec val input = BatchChangeInput(None, List(ptr), Some(authGrp.id)) - val result = rightResultOf(underTest.applyBatchChange(input, auth, false).value) + val result = underTest.applyBatchChange(input, auth, false).value.unsafeRunSync().toOption.get result.changes.length shouldBe 1 result.changes.head.zoneId shouldBe Some(ipv6PTR16Zone.id) @@ -526,7 +524,7 @@ class BatchChangeServiceSpec "fail if conversion cannot process" in { val input = BatchChangeInput(Some("conversionError"), List(apexAddA, nonApexAddA)) - val result = leftResultOf(underTest.applyBatchChange(input, auth, true).value) + val result = underTest.applyBatchChange(input, auth, true).value.unsafeRunSync().swap.toOption.get result shouldBe an[BatchConversionError] } @@ -534,7 +532,7 @@ class BatchChangeServiceSpec "fail with GroupDoesNotExist if owner group ID is provided for a non-existent group" in { val ownerGroupId = "non-existent-group-id" val input = BatchChangeInput(None, List(apexAddA), Some(ownerGroupId)) - val result = leftResultOf(underTest.applyBatchChange(input, auth, true).value) + val result = underTest.applyBatchChange(input, auth, true).value.unsafeRunSync().swap.toOption.get result shouldBe InvalidBatchChangeInput(List(GroupDoesNotExist(ownerGroupId))) } @@ -542,7 +540,7 @@ class BatchChangeServiceSpec "fail with UserDoesNotBelongToOwnerGroup if normal user does not belong to group specified by owner group ID" in { val ownerGroupId = "user-is-not-member" val input = BatchChangeInput(None, List(apexAddA), Some(ownerGroupId)) - val result = leftResultOf(underTest.applyBatchChange(input, notAuth, true).value) + val result = underTest.applyBatchChange(input, notAuth, true).value.unsafeRunSync().swap.toOption.get result shouldBe InvalidBatchChangeInput( @@ -552,7 +550,7 @@ class BatchChangeServiceSpec "succeed if owner group ID is provided and user is a member of the group" in { val input = BatchChangeInput(None, List(apexAddA), Some(okGroup.id)) - val result = rightResultOf(underTest.applyBatchChange(input, okAuth, true).value) + val result = underTest.applyBatchChange(input, okAuth, true).value.unsafeRunSync().toOption.get result.changes.length shouldBe 1 } @@ -561,11 +559,9 @@ class BatchChangeServiceSpec val ownerGroupId = Some("user-is-not-member") val input = BatchChangeInput(None, List(apexAddA), ownerGroupId) val result = - rightResultOf( underTest .applyBatchChange(input, AuthPrincipal(superUser, Seq(baseZone.adminGroupId)), true) - .value - ) + .value.unsafeRunSync().toOption.get result.changes.length shouldBe 1 } @@ -579,7 +575,7 @@ class BatchChangeServiceSpec AddChangeInput("non-apex.test.com.", RecordType.TXT, None, TXTData("hello")) val input = BatchChangeInput(None, List(noTtl, withTtl, noTtlDel, noTtlUpdate)) - val result = rightResultOf(underTest.applyBatchChange(input, auth, true).value) + val result = underTest.applyBatchChange(input, auth, true).value.unsafeRunSync().toOption.get result.changes.length shouldBe 4 result @@ -607,15 +603,13 @@ class BatchChangeServiceSpec doReturn(IO.unit).when(mockNotifier).notify(any[Notification[_]]) val result = - rightResultOf( underTest .rejectBatchChange( batchChange.id, supportUserAuth, RejectBatchChangeInput(Some("review comment")) ) - .value - ) + .value.unsafeRunSync().toOption.get result.status shouldBe BatchChangeStatus.Rejected result.approvalStatus shouldBe BatchChangeApprovalStatus.ManuallyRejected @@ -641,11 +635,9 @@ class BatchChangeServiceSpec val rejectAuth = AuthPrincipal(supportUser.copy(isTest = true), List()) val result = - rightResultOf( underTestManualEnabled .rejectBatchChange(batchChange.id, rejectAuth, RejectBatchChangeInput(Some("bad"))) - .value - ) + .value.unsafeRunSync().toOption.get result.status shouldBe BatchChangeStatus.Rejected } @@ -663,11 +655,9 @@ class BatchChangeServiceSpec val rejectAuth = AuthPrincipal(supportUser.copy(isTest = true), List()) val result = - leftResultOf( underTestManualEnabled .rejectBatchChange(batchChange.id, rejectAuth, RejectBatchChangeInput(Some("bad"))) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe UserNotAuthorizedError(batchChange.id) } @@ -684,11 +674,9 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - leftResultOf( underTest .rejectBatchChange(batchChange.id, supportUserAuth, RejectBatchChangeInput()) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe BatchChangeNotPendingReview(batchChange.id) } @@ -706,9 +694,7 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - leftResultOf( - underTest.rejectBatchChange(batchChange.id, auth, RejectBatchChangeInput()).value - ) + underTest.rejectBatchChange(batchChange.id, auth, RejectBatchChangeInput()).value.unsafeRunSync().swap.toOption.get result shouldBe UserNotAuthorizedError(batchChange.id) } @@ -726,9 +712,7 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - leftResultOf( - underTest.rejectBatchChange(batchChange.id, auth, RejectBatchChangeInput()).value - ) + underTest.rejectBatchChange(batchChange.id, auth, RejectBatchChangeInput()).value.unsafeRunSync().swap.toOption.get result shouldBe UserNotAuthorizedError(batchChange.id) } @@ -748,15 +732,13 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChangeNeedsApproval) val result = - rightResultOf( underTestManualEnabled .approveBatchChange( batchChangeNeedsApproval.id, supportUserAuth, ApproveBatchChangeInput(Some("reviewed!")) ) - .value - ) + .value.unsafeRunSync().toOption.get result.userId shouldBe batchChangeNeedsApproval.userId result.userName shouldBe batchChangeNeedsApproval.userName @@ -776,15 +758,13 @@ class BatchChangeServiceSpec val auth = AuthPrincipal(supportUser.copy(isTest = true), List()) val result = - leftResultOf( underTestManualEnabled .approveBatchChange( batchChangeNeedsApproval.id, auth, ApproveBatchChangeInput(Some("reviewed!")) ) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe UserNotAuthorizedError(batchChangeNeedsApproval.id) } @@ -794,11 +774,9 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - leftResultOf( underTest .approveBatchChange(batchChange.id, supportUserAuth, ApproveBatchChangeInput()) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe BatchChangeNotPendingReview(batchChange.id) } @@ -807,11 +785,9 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChangeNeedsApproval) val result = - leftResultOf( underTest .approveBatchChange(batchChangeNeedsApproval.id, auth, ApproveBatchChangeInput()) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe UserNotAuthorizedError(batchChangeNeedsApproval.id) } @@ -822,9 +798,7 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - leftResultOf( - underTest.approveBatchChange(batchChange.id, auth, ApproveBatchChangeInput()).value - ) + underTest.approveBatchChange(batchChange.id, auth, ApproveBatchChangeInput()).value.unsafeRunSync().swap.toOption.get result shouldBe UserNotAuthorizedError(batchChange.id) } @@ -842,11 +816,9 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - leftResultOf( underTest .approveBatchChange(batchChange.id, superUserAuth, ApproveBatchChangeInput()) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe BatchRequesterNotFound("someOtherUserId", "someUn") } @@ -866,11 +838,9 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - rightResultOf( underTest .cancelBatchChange(batchChange.id, auth) - .value - ) + .value.unsafeRunSync().toOption.get result.status shouldBe BatchChangeStatus.Cancelled result.approvalStatus shouldBe BatchChangeApprovalStatus.Cancelled @@ -891,7 +861,7 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - leftResultOf(underTest.cancelBatchChange(batchChange.id, supportUserAuth).value) + underTest.cancelBatchChange(batchChange.id, supportUserAuth).value.unsafeRunSync().swap.toOption.get result shouldBe UserNotAuthorizedError(batchChange.id) } @@ -909,11 +879,9 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - leftResultOf( underTest .cancelBatchChange(batchChange.id, auth) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe BatchChangeNotPendingReview(batchChange.id) } @@ -931,11 +899,9 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChange) val result = - leftResultOf( underTest .cancelBatchChange(batchChange.id, supportUserAuth) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe BatchChangeNotPendingReview(batchChange.id) } @@ -954,13 +920,13 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChange) - val result = rightResultOf(underTest.getBatchChange(batchChange.id, auth).value) + val result = underTest.getBatchChange(batchChange.id, auth).value.unsafeRunSync().toOption.get result shouldBe BatchChangeInfo(batchChange) } "Fail if batchChange id does not exist" in { - val result = leftResultOf(underTest.getBatchChange("badId", auth).value) + val result = underTest.getBatchChange("badId", auth).value.unsafeRunSync().swap.toOption.get result shouldBe BatchChangeNotFound("badId") } @@ -976,7 +942,7 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChange) - val result = leftResultOf(underTest.getBatchChange(batchChange.id, notAuth).value) + val result = underTest.getBatchChange(batchChange.id, notAuth).value.unsafeRunSync().swap.toOption.get result shouldBe UserNotAuthorizedError(batchChange.id) } @@ -994,7 +960,7 @@ class BatchChangeServiceSpec val authSuper = notAuth.copy(signedInUser = notAuth.signedInUser.copy(isSuper = true)) - val result = rightResultOf(underTest.getBatchChange(batchChange.id, authSuper).value) + val result = underTest.getBatchChange(batchChange.id, authSuper).value.unsafeRunSync().toOption.get result shouldBe BatchChangeInfo(batchChange) } @@ -1012,7 +978,7 @@ class BatchChangeServiceSpec val authSuper = notAuth.copy(signedInUser = notAuth.signedInUser.copy(isSupport = true)) - val result = rightResultOf(underTest.getBatchChange(batchChange.id, authSuper).value) + val result = underTest.getBatchChange(batchChange.id, authSuper).value.unsafeRunSync().toOption.get result shouldBe BatchChangeInfo(batchChange) } @@ -1030,7 +996,7 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChange) - val result = rightResultOf(underTest.getBatchChange(batchChange.id, auth).value) + val result = underTest.getBatchChange(batchChange.id, auth).value.unsafeRunSync().toOption.get result shouldBe BatchChangeInfo(batchChange, Some(okGroup.name)) } @@ -1047,7 +1013,7 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChange) - val result = rightResultOf(underTest.getBatchChange(batchChange.id, auth).value) + val result = underTest.getBatchChange(batchChange.id, auth).value.unsafeRunSync().toOption.get result shouldBe BatchChangeInfo(batchChange) } @@ -1067,7 +1033,7 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChange) - val result = rightResultOf(underTest.getBatchChange(batchChange.id, auth).value) + val result = underTest.getBatchChange(batchChange.id, auth).value.unsafeRunSync().toOption.get result shouldBe BatchChangeInfo(batchChange, Some(okGroup.name), Some(superUser.userName)) } } @@ -1086,7 +1052,7 @@ class BatchChangeServiceSpec error ) val zoneMap = ExistingZones(Set(apexZone, baseZone, ptrZone, delegatedPTRZone, ipv6PTRZone)) - val result = await(underTest.getExistingRecordSets(in, zoneMap)) + val result = underTest.getExistingRecordSets(in, zoneMap).unsafeRunSync() val expected = List(existingApex, existingNonApex, existingPtr, existingPtrDelegated, existingPtrV6) @@ -1103,7 +1069,7 @@ class BatchChangeServiceSpec error ) val zoneMap = ExistingZones(Set(apexZone, baseZone, ptrZone, ipv6PTRZone)) - val result = await(underTest.getExistingRecordSets(in, zoneMap)) + val result = underTest.getExistingRecordSets(in, zoneMap).unsafeRunSync() val expected = List(existingApex, existingNonApex, existingPtr, existingPtrV6) @@ -1113,7 +1079,7 @@ class BatchChangeServiceSpec "not fail if gets all lefts" in { val errors = List(error) val zoneMap = ExistingZones(Set(apexZone, baseZone, ptrZone, delegatedPTRZone, ipv6PTRZone)) - val result = await(underTest.getExistingRecordSets(errors, zoneMap)) + val result = underTest.getExistingRecordSets(errors, zoneMap).unsafeRunSync() result.recordSets.length shouldBe 0 } @@ -1122,42 +1088,42 @@ class BatchChangeServiceSpec "getZonesForRequest" should { "return names for the apex and base zones if they both exist" in { val underTestBaseApexZoneList: ExistingZones = - await(underTest.getZonesForRequest(List(apexAddA.validNel))) + underTest.getZonesForRequest(List(apexAddA.validNel)).unsafeRunSync() (underTestBaseApexZoneList.zones should contain).allOf(apexZone, baseZone) } "return only the apex zone if only the apex zone exists or A or AAAA records" in { val underTestOnlyApexZoneList: ExistingZones = - await(underTest.getZonesForRequest(List(onlyApexAddA.validNel))) + underTest.getZonesForRequest(List(onlyApexAddA.validNel)).unsafeRunSync() (underTestOnlyApexZoneList.zones should contain).only(onlyApexZone) } "return only the base zone if only the base zone exists" in { val underTestOnlyBaseZoneList: ExistingZones = - await(underTest.getZonesForRequest(List(onlyBaseAddAAAA.validNel))) + underTest.getZonesForRequest(List(onlyBaseAddAAAA.validNel)).unsafeRunSync() (underTestOnlyBaseZoneList.zones should contain).only(onlyBaseZone) } "return no zones if neither the apex nor base zone exist" in { val underTestOnlyNoZonesList: ExistingZones = - await(underTest.getZonesForRequest(List(noZoneAddA.validNel))) + underTest.getZonesForRequest(List(noZoneAddA.validNel)).unsafeRunSync() underTestOnlyNoZonesList.zones shouldBe Set() } "return all possible zones for a dotted host" in { val underTestZonesList: ExistingZones = - await(underTest.getZonesForRequest(List(dottedAddA.validNel))) + underTest.getZonesForRequest(List(dottedAddA.validNel)).unsafeRunSync() (underTestZonesList.zones should contain).allOf(apexZone, baseZone) } "return all possible zones given an IPv4 PTR" in { val underTestPTRZonesList: ExistingZones = - await(underTest.getZonesForRequest(List(ptrAdd.validNel))) + underTest.getZonesForRequest(List(ptrAdd.validNel)).unsafeRunSync() (underTestPTRZonesList.zones should contain).allOf(ptrZone, delegatedPTRZone) } @@ -1197,7 +1163,7 @@ class BatchChangeServiceSpec ) val ptr = AddChangeInput(ip, RecordType.PTR, ttl, PTRData(Fqdn("ptr."))).validNel - val underTestPTRZonesList: ExistingZones = await(underTest.getZonesForRequest(List(ptr))) + val underTestPTRZonesList: ExistingZones = underTest.getZonesForRequest(List(ptr)).unsafeRunSync() val zoneNames = underTestPTRZonesList.zones.map(_.name) zoneNames should contain theSameElementsAs possibleZones @@ -1223,7 +1189,7 @@ class BatchChangeServiceSpec val ip = "2001:0db8:0000:0000:0000:ff00:0042:8329" val ptr = AddChangeInput(ip, RecordType.PTR, ttl, PTRData(Fqdn("ptr."))).validNel - val underTestPTRZonesList: ExistingZones = await(underTest.getZonesForRequest(List(ptr))) + val underTestPTRZonesList: ExistingZones = underTest.getZonesForRequest(List(ptr)).unsafeRunSync() val zoneNames = underTestPTRZonesList.zones.map(_.name) zoneNames shouldBe Set("0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.") @@ -1272,7 +1238,7 @@ class BatchChangeServiceSpec AddChangeInput(v6Name, RecordType.PTR, ttl, PTRData(Fqdn("ptr."))).validNel } - val underTestPTRZonesList: ExistingZones = await(underTest.getZonesForRequest(ptrs)) + val underTestPTRZonesList: ExistingZones = underTest.getZonesForRequest(ptrs).unsafeRunSync() val zoneNames = underTestPTRZonesList.zones.map(_.name) zoneNames should contain theSameElementsAs (possibleZones1 ++ possibleZones2) @@ -1280,7 +1246,7 @@ class BatchChangeServiceSpec "return a set of distinct zones, given duplicates" in { val underTestDistinctZonesList: ExistingZones = - await(underTest.getZonesForRequest(List(cnameReverseAdd.validNel, ptrAdd.validNel))) + underTest.getZonesForRequest(List(cnameReverseAdd.validNel, ptrAdd.validNel)).unsafeRunSync() underTestDistinctZonesList.zones.count(_.id == "nonDelegatedPTR") shouldBe 1 } @@ -2031,7 +1997,7 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChange) - val result = rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 100).value) + val result = underTest.listBatchChangeSummaries(auth, maxItems = 100).value.unsafeRunSync().toOption.get result.maxItems shouldBe 100 result.nextId shouldBe None @@ -2070,7 +2036,7 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChangeTwo) - val result = rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 100).value) + val result = underTest.listBatchChangeSummaries(auth, maxItems = 100).value.unsafeRunSync().toOption.get result.maxItems shouldBe 100 result.nextId shouldBe None @@ -2104,7 +2070,7 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChangeTwo) - val result = rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 1).value) + val result = underTest.listBatchChangeSummaries(auth, maxItems = 1).value.unsafeRunSync().toOption.get result.maxItems shouldBe 1 result.nextId shouldBe Some(1) @@ -2137,14 +2103,13 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChangeTwo) - val result = rightResultOf( + val result = underTest .listBatchChangeSummaries( auth, approvalStatus = Some(BatchChangeApprovalStatus.PendingReview) ) - .value - ) + .value.unsafeRunSync().toOption.get result.maxItems shouldBe 100 result.nextId shouldBe None @@ -2179,7 +2144,7 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChangeTwo) val result = - rightResultOf(underTest.listBatchChangeSummaries(auth, startFrom = Some(1)).value) + underTest.listBatchChangeSummaries(auth, startFrom = Some(1)).value.unsafeRunSync().toOption.get result.maxItems shouldBe 100 result.nextId shouldBe None @@ -2213,7 +2178,7 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChangeUserTwo) val result = - rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 100).value).batchChanges + underTest.listBatchChangeSummaries(auth, maxItems = 100).value.unsafeRunSync().toOption.get.batchChanges result.length shouldBe 1 result(0).createdTimestamp shouldBe batchChangeUserOne.createdTimestamp @@ -2242,7 +2207,7 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChangeUserTwo) val result = - rightResultOf(underTest.listBatchChangeSummaries(auth, ignoreAccess = true).value).batchChanges + underTest.listBatchChangeSummaries(auth, ignoreAccess = true).value.unsafeRunSync().toOption.get.batchChanges result.length shouldBe 1 result(0).createdTimestamp shouldBe batchChangeUserOne.createdTimestamp @@ -2271,7 +2236,7 @@ class BatchChangeServiceSpec batchChangeRepo.save(batchChangeUserTwo) val result = - rightResultOf(underTest.listBatchChangeSummaries(superUserAuth, ignoreAccess = true).value) + underTest.listBatchChangeSummaries(superUserAuth, ignoreAccess = true).value.unsafeRunSync().toOption.get result.maxItems shouldBe 100 result.nextId shouldBe None @@ -2285,7 +2250,7 @@ class BatchChangeServiceSpec "return an empty list of batchChangeSummaries if none exist" in { val result = - rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 100).value).batchChanges + underTest.listBatchChangeSummaries(auth, maxItems = 100).value.unsafeRunSync().toOption.get.batchChanges result.length shouldBe 0 } @@ -2303,7 +2268,7 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChange) - val result = rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 100).value) + val result = underTest.listBatchChangeSummaries(auth, maxItems = 100).value.unsafeRunSync().toOption.get result.maxItems shouldBe 100 result.nextId shouldBe None @@ -2328,7 +2293,7 @@ class BatchChangeServiceSpec ) batchChangeRepo.save(batchChange) - val result = rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 100).value) + val result = underTest.listBatchChangeSummaries(auth, maxItems = 100).value.unsafeRunSync().toOption.get result.maxItems shouldBe 100 result.nextId shouldBe None @@ -2343,29 +2308,29 @@ class BatchChangeServiceSpec "getOwnerGroup" should { "return None if owner group ID is None" in { - rightResultOf(underTest.getOwnerGroup(None).value) shouldBe None + underTest.getOwnerGroup(None).value.unsafeRunSync().toOption.get shouldBe None } "return None if group does not exist for owner group ID" in { - rightResultOf(underTest.getOwnerGroup(Some("non-existent-group-id")).value) shouldBe None + underTest.getOwnerGroup(Some("non-existent-group-id")).value.unsafeRunSync().toOption.get shouldBe None } "return the group if the group exists for the owner group ID" in { - rightResultOf(underTest.getOwnerGroup(Some(okGroup.id)).value) shouldBe Some(okGroup) + underTest.getOwnerGroup(Some(okGroup.id)).value.unsafeRunSync().toOption.get shouldBe Some(okGroup) } } "getReviewer" should { "return None if reviewer ID is None" in { - rightResultOf(underTest.getReviewer(None).value) shouldBe None + underTest.getReviewer(None).value.unsafeRunSync().toOption.get shouldBe None } "return None if reviewer does not exist for the given reviewer ID" in { - rightResultOf(underTest.getReviewer(Some("non-existent-user-id")).value) shouldBe None + underTest.getReviewer(Some("non-existent-user-id")).value.unsafeRunSync().toOption.get shouldBe None } "return the reviewer if the reviewer exists for the given reviewer ID" in { - rightResultOf(underTest.getReviewer(Some(superUser.id)).value) shouldBe Some(superUser) + underTest.getReviewer(Some(superUser.id)).value.unsafeRunSync().toOption.get shouldBe Some(superUser) } } @@ -2381,7 +2346,7 @@ class BatchChangeServiceSpec approvalStatus = BatchChangeApprovalStatus.AutoApproved ) - val result = rightResultOf( + val result = underTestManualEnabled .convertOrSave( batchChange, @@ -2389,8 +2354,8 @@ class BatchChangeServiceSpec ChangeForValidationMap(List(), ExistingRecordSets(List())), None ) - .value - ) + .value.unsafeRunSync().toOption.get + result.reviewComment shouldBe Some("batchSentToConverter") } "not send to the converter, save the change if PendingReview and MA enabled" in { @@ -2404,7 +2369,7 @@ class BatchChangeServiceSpec approvalStatus = BatchChangeApprovalStatus.PendingReview ) - val result = rightResultOf( + val result = underTestManualEnabled .convertOrSave( batchChange, @@ -2412,8 +2377,7 @@ class BatchChangeServiceSpec ChangeForValidationMap(List(), ExistingRecordSets(List())), None ) - .value - ) + .value.unsafeRunSync().toOption.get // not sent to converter result.reviewComment shouldBe None @@ -2431,7 +2395,7 @@ class BatchChangeServiceSpec approvalStatus = BatchChangeApprovalStatus.PendingReview ) - val result = leftResultOf( + val result = underTest .convertOrSave( batchChange, @@ -2439,8 +2403,7 @@ class BatchChangeServiceSpec ChangeForValidationMap(List(), ExistingRecordSets(List())), None ) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe an[UnknownConversionError] } @@ -2455,7 +2418,7 @@ class BatchChangeServiceSpec approvalStatus = BatchChangeApprovalStatus.ManuallyApproved ) - val result = leftResultOf( + val result = underTest .convertOrSave( batchChange, @@ -2463,8 +2426,8 @@ class BatchChangeServiceSpec ChangeForValidationMap(List(), ExistingRecordSets(List())), None ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + result shouldBe an[UnknownConversionError] } } @@ -2528,7 +2491,7 @@ class BatchChangeServiceSpec "combine gets for each valid record" in { val in = List(apexAddForVal.validNel, error) - val result = rightResultOf(underTest.getGroupIdsFromUnauthorizedErrors(in).value) + val result = underTest.getGroupIdsFromUnauthorizedErrors(in).value.unsafeRunSync().toOption.get result shouldBe Set(okGroup) } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index 9276f12b1..f549d5556 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -25,7 +25,6 @@ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import org.scalatest.BeforeAndAfterEach import vinyldns.api.Interfaces._ -import vinyldns.api.ResultHelpers import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.zone.ZoneRepository import cats.effect._ @@ -41,7 +40,6 @@ class MembershipServiceSpec with Matchers with MockitoSugar with BeforeAndAfterEach - with ResultHelpers with EitherMatchers { private val mockGroupRepo = mock[GroupRepository] @@ -122,7 +120,7 @@ class MembershipServiceSpec .saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean) doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange]) - val result: Group = rightResultOf(underTest.createGroup(groupInfo, okAuth).value) + val result: Group = underTest.createGroup(groupInfo, okAuth).value.unsafeRunSync().toOption.get result shouldBe groupInfo val groupCaptor = ArgumentCaptor.forClass(classOf[Group]) @@ -149,7 +147,7 @@ class MembershipServiceSpec .saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean) doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange]) - val result: Group = rightResultOf(underTest.createGroup(groupInfo, okAuth).value) + val result: Group = underTest.createGroup(groupInfo, okAuth).value.unsafeRunSync().toOption.get result shouldBe groupInfo val groupChangeCaptor = ArgumentCaptor.forClass(classOf[GroupChange]) @@ -178,7 +176,7 @@ class MembershipServiceSpec ).thenReturn(IO.pure(expectedMembersAdded)) doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange]) - val result: Group = rightResultOf(underTest.createGroup(info, okAuth).value) + val result: Group = underTest.createGroup(info, okAuth).value.unsafeRunSync().toOption.get result shouldBe info val memberIdCaptor = ArgumentCaptor.forClass(classOf[Set[String]]) @@ -204,7 +202,7 @@ class MembershipServiceSpec .saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean) doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange]) - val result: Group = rightResultOf(underTest.createGroup(info, okAuth).value) + val result: Group = underTest.createGroup(info, okAuth).value.unsafeRunSync().toOption.get result.memberIds should contain(okAuth.userId) } @@ -214,7 +212,7 @@ class MembershipServiceSpec .when(underTest) .groupWithSameNameDoesNotExist(groupInfo.name) - val error = leftResultOf(underTest.createGroup(groupInfo, okAuth).value) + val error = underTest.createGroup(groupInfo, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[GroupAlreadyExistsError] verify(mockGroupRepo, never()).save(any[DB], any[Group]) @@ -229,7 +227,7 @@ class MembershipServiceSpec .when(underTest) .usersExist(groupInfo.memberIds) - val error = leftResultOf(underTest.createGroup(groupInfo, okAuth).value) + val error = underTest.createGroup(groupInfo, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[UserNotFoundError] verify(mockGroupRepo, never()).save(any[DB], any[Group]) @@ -244,7 +242,7 @@ class MembershipServiceSpec doReturn(IO.raiseError(new RuntimeException("fail"))).when(mockGroupRepo).save(any[DB], any[Group]) doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange]) - val error = leftResultOf(underTest.createGroup(groupInfo, okAuth).value) + val error = underTest.createGroup(groupInfo, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[RuntimeException] verify(mockMembershipRepo, never()) @@ -261,7 +259,7 @@ class MembershipServiceSpec .saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean) doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange]) - val error = leftResultOf(underTest.createGroup(groupInfo, okAuth).value) + val error = underTest.createGroup(groupInfo, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[RuntimeException] } } @@ -284,7 +282,6 @@ class MembershipServiceSpec .when(mockGroupChangeRepo) .save(any[DB], any[GroupChange]) - awaitResultOf( underTest .updateGroup( updatedInfo.id, @@ -295,8 +292,7 @@ class MembershipServiceSpec updatedInfo.adminUserIds, okAuth ) - .value - ) + .value.unsafeRunSync() val groupCaptor = ArgumentCaptor.forClass(classOf[Group]) val addedMemberCaptor = ArgumentCaptor.forClass(classOf[Set[String]]) @@ -346,7 +342,7 @@ class MembershipServiceSpec "return an error if the user is not an admin" in { doReturn(IO.pure(Some(okGroup))).when(mockGroupRepo).getGroup(anyString) - val error = leftResultOf( + val error = underTest .updateGroup( updatedInfo.id, @@ -357,8 +353,7 @@ class MembershipServiceSpec updatedInfo.adminUserIds, dummyAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -372,7 +367,7 @@ class MembershipServiceSpec .when(underTest) .differentGroupWithSameNameDoesNotExist(updatedInfo.name, existingGroup.id) - val error = leftResultOf( + val error = underTest .updateGroup( updatedInfo.id, @@ -383,15 +378,15 @@ class MembershipServiceSpec updatedInfo.adminUserIds, okAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + error shouldBe a[GroupAlreadyExistsError] } "return an error if the group is not found" in { doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(existingGroup.id) - val error = leftResultOf( + val error = underTest .updateGroup( updatedInfo.id, @@ -402,8 +397,8 @@ class MembershipServiceSpec updatedInfo.adminUserIds, okAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + error shouldBe a[GroupNotFoundError] } @@ -418,7 +413,7 @@ class MembershipServiceSpec .when(underTest) .usersExist(any[Set[String]]) - val error = leftResultOf( + val error = underTest .updateGroup( updatedInfo.id, @@ -429,8 +424,8 @@ class MembershipServiceSpec updatedInfo.adminUserIds, okAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + error shouldBe a[UserNotFoundError] } @@ -439,7 +434,7 @@ class MembershipServiceSpec .when(mockGroupRepo) .getGroup(existingGroup.id) - val error = leftResultOf( + val error = underTest .updateGroup( updatedInfo.id, @@ -450,8 +445,8 @@ class MembershipServiceSpec Set(), okAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + error shouldBe an[InvalidGroupError] } } @@ -474,7 +469,7 @@ class MembershipServiceSpec .when(mockZoneRepo) .getFirstOwnedZoneAclGroupId(anyString()) - val result: Group = rightResultOf(underTest.deleteGroup("ok", okAuth).value) + val result: Group = underTest.deleteGroup("ok", okAuth).value.unsafeRunSync().toOption.get result shouldBe okGroup.copy(status = GroupStatus.Deleted) val groupCaptor = ArgumentCaptor.forClass(classOf[Group]) @@ -495,7 +490,7 @@ class MembershipServiceSpec "return an error if the user is not an admin" in { doReturn(IO.pure(Some(okGroup))).when(mockGroupRepo).getGroup(anyString) - val error = leftResultOf(underTest.deleteGroup("ok", dummyAuth).value) + val error = underTest.deleteGroup("ok", dummyAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -504,7 +499,7 @@ class MembershipServiceSpec doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(anyString) doReturn(IO.pure(List())).when(mockZoneRepo).getZonesByAdminGroupId(anyString) - val error = leftResultOf(underTest.deleteGroup("ok", okAuth).value) + val error = underTest.deleteGroup("ok", okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[GroupNotFoundError] } @@ -515,7 +510,7 @@ class MembershipServiceSpec .when(mockZoneRepo) .getZonesByAdminGroupId(anyString) - val error = leftResultOf(underTest.deleteGroup("ok", okAuth).value) + val error = underTest.deleteGroup("ok", okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidGroupRequestError] } @@ -525,7 +520,7 @@ class MembershipServiceSpec doReturn(IO.pure(Some("somerecordsetid"))) .when(mockRecordSetRepo) .getFirstOwnedRecordByGroup(anyString()) - val error = leftResultOf(underTest.deleteGroup("ok", okAuth).value) + val error = underTest.deleteGroup("ok", okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidGroupRequestError] } @@ -535,7 +530,7 @@ class MembershipServiceSpec doReturn(IO.pure(Some("someId"))) .when(mockZoneRepo) .getFirstOwnedZoneAclGroupId(anyString()) - val error = leftResultOf(underTest.deleteGroup("ok", okAuth).value) + val error = underTest.deleteGroup("ok", okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidGroupRequestError] } @@ -545,13 +540,13 @@ class MembershipServiceSpec "get a group" should { "return the group" in { doReturn(IO.pure(Some(okGroup))).when(mockGroupRepo).getGroup(anyString) - val result: Group = rightResultOf(underTest.getGroup(okGroup.id, okAuth).value) + val result: Group = underTest.getGroup(okGroup.id, okAuth).value.unsafeRunSync().toOption.get result shouldBe okGroup } "return an error if the group is not found" in { doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(anyString) - val error = leftResultOf(underTest.getGroup("notfound", okAuth).value) + val error = underTest.getGroup("notfound", okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[GroupNotFoundError] } } @@ -562,7 +557,7 @@ class MembershipServiceSpec .when(mockGroupRepo) .getGroups(any[Set[String]]) val result: ListMyGroupsResponse = - rightResultOf(underTest.listMyGroups(None, None, 100, listOfDummyGroupsAuth, false).value) + underTest.listMyGroups(None, None, 100, listOfDummyGroupsAuth, false).value.unsafeRunSync().toOption.get verify(mockGroupRepo, never()).getAllGroups() result shouldBe ListMyGroupsResponse( groups = listOfDummyGroupInfo.take(100), @@ -577,7 +572,7 @@ class MembershipServiceSpec doReturn(IO.pure(listOfDummyGroups.toSet)) .when(mockGroupRepo) .getGroups(any[Set[String]]) - val result: ListMyGroupsResponse = rightResultOf( + val result: ListMyGroupsResponse = underTest .listMyGroups( groupNameFilter = Some("name-dummy01"), @@ -586,8 +581,8 @@ class MembershipServiceSpec listOfDummyGroupsAuth, false ) - .value - ) + .value.unsafeRunSync().toOption.get + result shouldBe ListMyGroupsResponse( groups = listOfDummyGroupInfo.slice(10, 20), groupNameFilter = Some("name-dummy01"), @@ -601,7 +596,7 @@ class MembershipServiceSpec doReturn(IO.pure(listOfDummyGroups.toSet)) .when(mockGroupRepo) .getGroups(any[Set[String]]) - val result: ListMyGroupsResponse = rightResultOf( + val result: ListMyGroupsResponse = underTest .listMyGroups( groupNameFilter = None, @@ -610,8 +605,8 @@ class MembershipServiceSpec listOfDummyGroupsAuth, ignoreAccess = false ) - .value - ) + .value.unsafeRunSync().toOption.get + result shouldBe ListMyGroupsResponse( groups = listOfDummyGroupInfo.slice(100, 200), groupNameFilter = None, @@ -625,7 +620,7 @@ class MembershipServiceSpec doReturn(IO.pure(listOfDummyGroups.toSet)) .when(mockGroupRepo) .getGroups(any[Set[String]]) - val result: ListMyGroupsResponse = rightResultOf( + val result: ListMyGroupsResponse = underTest .listMyGroups( groupNameFilter = None, @@ -634,8 +629,8 @@ class MembershipServiceSpec listOfDummyGroupsAuth, ignoreAccess = false ) - .value - ) + .value.unsafeRunSync().toOption.get + result shouldBe ListMyGroupsResponse( groups = listOfDummyGroupInfo.slice(0, 10), groupNameFilter = None, @@ -648,13 +643,13 @@ class MembershipServiceSpec "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, false).value) + underTest.listMyGroups(None, None, 100, notAuth, false).value.unsafeRunSync().toOption.get result shouldBe ListMyGroupsResponse(Seq(), None, None, None, 100, false) } "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, notAuth, true).value) + underTest.listMyGroups(None, None, 100, notAuth, true).value.unsafeRunSync().toOption.get verify(mockGroupRepo).getAllGroups() result.groups should contain theSameElementsAs Seq( GroupInfo(dummyGroup), @@ -664,7 +659,7 @@ class MembershipServiceSpec "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) + underTest.listMyGroups(None, None, 100, superUserAuth, false).value.unsafeRunSync().toOption.get verify(mockGroupRepo).getAllGroups() result.groups should contain theSameElementsAs Seq( GroupInfo(dummyGroup), @@ -674,7 +669,7 @@ class MembershipServiceSpec "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) + underTest.listMyGroups(None, None, 100, superUserAuth, true).value.unsafeRunSync().toOption.get verify(mockGroupRepo).getAllGroups() result.groups should contain theSameElementsAs Seq( GroupInfo(dummyGroup), @@ -685,7 +680,7 @@ class MembershipServiceSpec 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, false).value) + underTest.listMyGroups(None, None, 100, supportAuth, false).value.unsafeRunSync().toOption.get verify(mockGroupRepo).getAllGroups() result.groups should contain theSameElementsAs Seq( GroupInfo(dummyGroup), @@ -696,7 +691,7 @@ class MembershipServiceSpec 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) + underTest.listMyGroups(None, None, 100, supportAuth, true).value.unsafeRunSync().toOption.get verify(mockGroupRepo).getAllGroups() result.groups should contain theSameElementsAs Seq( GroupInfo(dummyGroup), @@ -709,7 +704,7 @@ class MembershipServiceSpec .when(mockGroupRepo) .getGroups(any[Set[String]]) val result: ListMyGroupsResponse = - rightResultOf(underTest.listMyGroups(None, None, 100, deletedGroupAuth, false).value) + underTest.listMyGroups(None, None, 100, deletedGroupAuth, false).value.unsafeRunSync().toOption.get result shouldBe ListMyGroupsResponse(Seq(), None, None, None, 100, false) } } @@ -728,7 +723,7 @@ class MembershipServiceSpec listOfDummyGroupChanges.map(GroupChangeInfo.apply).take(100) val result: ListGroupChangesResponse = - rightResultOf(underTest.getGroupActivity(dummyGroup.id, None, 100, dummyAuth).value) + underTest.getGroupActivity(dummyGroup.id, None, 100, dummyAuth).value.unsafeRunSync().toOption.get result.changes should contain theSameElementsAs expected result.maxItems shouldBe 100 result.nextId shouldBe Some(listOfDummyGroupChanges(100).id) @@ -748,7 +743,7 @@ class MembershipServiceSpec listOfDummyGroupChanges.map(GroupChangeInfo.apply).take(100) val result: ListGroupChangesResponse = - rightResultOf(underTest.getGroupActivity(dummyGroup.id, None, 100, okAuth).value) + underTest.getGroupActivity(dummyGroup.id, None, 100, okAuth).value.unsafeRunSync().toOption.get result.changes should contain theSameElementsAs expected result.maxItems shouldBe 100 result.nextId shouldBe Some(listOfDummyGroupChanges(100).id) @@ -769,7 +764,7 @@ class MembershipServiceSpec .getUsers(testGroup.adminUserIds, None, None) val result: ListAdminsResponse = - rightResultOf(underTest.listAdmins(testGroup.id, okAuth).value) + underTest.listAdmins(testGroup.id, okAuth).value.unsafeRunSync().toOption.get result.admins should contain theSameElementsAs expectedAdmins } @@ -785,7 +780,7 @@ class MembershipServiceSpec .getUsers(testGroup.adminUserIds, None, None) val result: ListAdminsResponse = - rightResultOf(underTest.listAdmins(testGroup.id, dummyAuth).value) + underTest.listAdmins(testGroup.id, dummyAuth).value.unsafeRunSync().toOption.get result.admins should contain theSameElementsAs expectedAdmins } } @@ -806,7 +801,7 @@ class MembershipServiceSpec .getUsers(testGroup.memberIds, None, Some(100)) val result: ListMembersResponse = - rightResultOf(underTest.listMembers(testGroup.id, None, 100, testAuth).value) + underTest.listMembers(testGroup.id, None, 100, testAuth).value.unsafeRunSync().toOption.get result.members should contain theSameElementsAs expectedMembers result.nextId shouldBe testListUsersResult.lastEvaluatedId @@ -831,7 +826,7 @@ class MembershipServiceSpec .getUsers(testGroup.memberIds, None, Some(100)) val result: ListMembersResponse = - rightResultOf(underTest.listMembers(testGroup.id, None, 100, supportAuth).value) + underTest.listMembers(testGroup.id, None, 100, supportAuth).value.unsafeRunSync().toOption.get result.members should contain theSameElementsAs expectedMembers result.nextId shouldBe testListUsersResult.lastEvaluatedId @@ -852,7 +847,7 @@ class MembershipServiceSpec .getUsers(testGroup.memberIds, None, Some(100)) val result: ListMembersResponse = - rightResultOf(underTest.listMembers(testGroup.id, None, 100, dummyAuth).value) + underTest.listMembers(testGroup.id, None, 100, dummyAuth).value.unsafeRunSync().toOption.get result.members should contain theSameElementsAs expectedMembers result.nextId shouldBe testListUsersResult.lastEvaluatedId @@ -865,21 +860,21 @@ class MembershipServiceSpec "return true when a group with the same name does not exist" in { doReturn(IO.pure(None)).when(mockGroupRepo).getGroupByName("foo") - val result = awaitResultOf(underTest.groupWithSameNameDoesNotExist("foo").value) + val result = underTest.groupWithSameNameDoesNotExist("foo").value.unsafeRunSync() result should be(right) } "return a GroupAlreadyExistsError if a group with the same name already exists" in { doReturn(IO.pure(Some(okGroup))).when(mockGroupRepo).getGroupByName("foo") - val result = leftResultOf(underTest.groupWithSameNameDoesNotExist("foo").value) + val result = underTest.groupWithSameNameDoesNotExist("foo").value.unsafeRunSync().swap.toOption.get result shouldBe a[GroupAlreadyExistsError] } "return true if a group with the same name exists but is deleted" in { doReturn(IO.pure(Some(deletedGroup))).when(mockGroupRepo).getGroupByName("foo") - val result = awaitResultOf(underTest.groupWithSameNameDoesNotExist("foo").value) + val result = underTest.groupWithSameNameDoesNotExist("foo").value.unsafeRunSync() result should be(right) } } @@ -890,7 +885,7 @@ class MembershipServiceSpec .when(mockUserRepo) .getUsers(okGroup.memberIds, None, None) - val result = awaitResultOf(underTest.usersExist(okGroup.memberIds).value) + val result = underTest.usersExist(okGroup.memberIds).value.unsafeRunSync() result should be(right) } @@ -899,7 +894,7 @@ class MembershipServiceSpec .when(mockUserRepo) .getUsers(Set(okUser.id, dummyUser.id), None, None) - val result = leftResultOf(underTest.usersExist(Set(okUser.id, dummyUser.id)).value) + val result = underTest.usersExist(Set(okUser.id, dummyUser.id)).value.unsafeRunSync().swap.toOption.get result shouldBe a[UserNotFoundError] } } @@ -911,7 +906,7 @@ class MembershipServiceSpec doReturn(IO.pure(Some(existingGroup))).when(mockGroupRepo).getGroupByName("foo") val error = - leftResultOf(underTest.differentGroupWithSameNameDoesNotExist("foo", "bar").value) + underTest.differentGroupWithSameNameDoesNotExist("foo", "bar").value.unsafeRunSync().swap.toOption.get error shouldBe a[GroupAlreadyExistsError] } @@ -919,9 +914,8 @@ class MembershipServiceSpec doReturn(IO.pure(Some(okGroup))).when(mockGroupRepo).getGroupByName(okGroup.name) - val result = awaitResultOf( - underTest.differentGroupWithSameNameDoesNotExist(okGroup.name, okGroup.id).value - ) + val result = + underTest.differentGroupWithSameNameDoesNotExist(okGroup.name, okGroup.id).value.unsafeRunSync() result should be(right) } @@ -932,9 +926,8 @@ class MembershipServiceSpec .when(mockGroupRepo) .getGroupByName(okGroup.name) - val result = awaitResultOf( - underTest.differentGroupWithSameNameDoesNotExist(okGroup.name, okGroup.id).value - ) + val result = + underTest.differentGroupWithSameNameDoesNotExist(okGroup.name, okGroup.id).value.unsafeRunSync() result should be(right) } } @@ -943,7 +936,7 @@ class MembershipServiceSpec "return true when a group for deletion is not the admin of a zone" in { doReturn(IO.pure(List())).when(mockZoneRepo).getZonesByAdminGroupId(okGroup.id) - val result = awaitResultOf(underTest.isNotZoneAdmin(okGroup).value) + val result = underTest.isNotZoneAdmin(okGroup).value.unsafeRunSync() result should be(right) } @@ -952,7 +945,7 @@ class MembershipServiceSpec .when(mockZoneRepo) .getZonesByAdminGroupId(okGroup.id) - val error = leftResultOf(underTest.isNotZoneAdmin(okGroup).value) + val error = underTest.isNotZoneAdmin(okGroup).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidGroupRequestError] } } @@ -961,7 +954,7 @@ class MembershipServiceSpec "return true when a group for deletion is not the admin of a zone" in { doReturn(IO.pure(None)).when(mockRecordSetRepo).getFirstOwnedRecordByGroup(okGroup.id) - val result = awaitResultOf(underTest.isNotRecordOwnerGroup(okGroup).value) + val result = underTest.isNotRecordOwnerGroup(okGroup).value.unsafeRunSync() result should be(right) } @@ -970,7 +963,7 @@ class MembershipServiceSpec .when(mockRecordSetRepo) .getFirstOwnedRecordByGroup(okGroup.id) - val error = leftResultOf(underTest.isNotRecordOwnerGroup(okGroup).value) + val error = underTest.isNotRecordOwnerGroup(okGroup).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidGroupRequestError] } } @@ -979,7 +972,7 @@ class MembershipServiceSpec "return successfully when a groupId is not in any zone ACL" in { doReturn(IO.pure(None)).when(mockZoneRepo).getFirstOwnedZoneAclGroupId(okGroup.id) - val result = awaitResultOf(underTest.isNotInZoneAclRule(okGroup).value) + val result = underTest.isNotInZoneAclRule(okGroup).value.unsafeRunSync() result should be(right) } @@ -988,7 +981,7 @@ class MembershipServiceSpec .when(mockZoneRepo) .getFirstOwnedZoneAclGroupId(okGroup.id) - val error = leftResultOf(underTest.isNotInZoneAclRule(okGroup).value) + val error = underTest.isNotInZoneAclRule(okGroup).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidGroupRequestError] } } @@ -1031,11 +1024,10 @@ class MembershipServiceSpec } "return an error if the signed in user is not a super user" in { - val error = leftResultOf( + val error = underTest .updateUserLockStatus(okUser.id, LockStatus.Locked, dummyAuth) - .value - ) + .value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -1045,22 +1037,22 @@ class MembershipServiceSpec signedInUser = dummyAuth.signedInUser.copy(isSupport = true), memberGroupIds = Seq.empty ) - val error = leftResultOf( + val error = underTest .updateUserLockStatus(okUser.id, LockStatus.Locked, supportAuth) - .value - ) + .value.unsafeRunSync().swap.toOption.get + error shouldBe a[NotAuthorizedError] } "return an error if the requested user is not found" in { doReturn(IO.pure(None)).when(mockUserRepo).getUser(okUser.id) - val error = leftResultOf( + val error = underTest .updateUserLockStatus(okUser.id, LockStatus.Locked, superUserAuth) - .value - ) + .value.unsafeRunSync().swap.toOption.get + error shouldBe a[UserNotFoundError] } } @@ -1068,13 +1060,13 @@ class MembershipServiceSpec "get user" should { "return the user" in { doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUserByIdOrName(anyString) - val result: User = rightResultOf(underTest.getUser(okUser.id, okAuth).value) + val result: User = underTest.getUser(okUser.id, okAuth).value.unsafeRunSync().toOption.get result shouldBe okUser } "return an error if the user is not found" in { doReturn(IO.pure(None)).when(mockUserRepo).getUserByIdOrName(anyString) - val error = leftResultOf(underTest.getUser("notfound", okAuth).value) + val error = underTest.getUser("notfound", okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[UserNotFoundError] } } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 3d629e43b..236c7a5cf 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -24,7 +24,7 @@ import org.scalatestplus.mockito.MockitoSugar import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import org.scalatest.BeforeAndAfterEach -import vinyldns.api.{ResultHelpers, VinylDNSTestHelpers} +import vinyldns.api.VinylDNSTestHelpers import vinyldns.api.domain.access.AccessValidations import vinyldns.api.domain.record.RecordSetHelpers._ import vinyldns.api.domain.zone._ @@ -45,7 +45,6 @@ class RecordSetServiceSpec with EitherMatchers with Matchers with MockitoSugar - with ResultHelpers with BeforeAndAfterEach { private val mockZoneRepo = mock[ZoneRepository] @@ -117,9 +116,7 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) val result: RecordSetChange = - rightResultOf( - underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get matches(result.recordSet, record, okZone.name) shouldBe true result.changeType shouldBe RecordSetChangeType.Create @@ -129,7 +126,7 @@ class RecordSetServiceSpec val mockZone = okZone.copy(id = "fakeZone") doReturn(IO.pure(None)).when(mockZoneRepo).getZone(mockZone.id) - val result = leftResultOf(underTest.getRecordSetByZone(aaaa.id, mockZone.id, okAuth).value) + val result = underTest.getRecordSetByZone(aaaa.id, mockZone.id, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[ZoneNotFoundError] } @@ -138,7 +135,7 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSet(aaaa.id) val result = - leftResultOf(underTest.getRecordSetByZone(aaaa.id, zoneNotAuthorized.id, okAuth).value) + underTest.getRecordSetByZone(aaaa.id, zoneNotAuthorized.id, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } "fail if the record already exists" in { @@ -152,7 +149,7 @@ class RecordSetServiceSpec .when(mockBackend) .resolve(record.name, okZone.name, record.typ) - val result = leftResultOf(underTest.addRecordSet(aaaa, okAuth).value) + val result = underTest.addRecordSet(aaaa, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[RecordSetAlreadyExists] } "fail if the record is dotted" in { @@ -166,7 +163,7 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - val result = leftResultOf(underTest.addRecordSet(record, okAuth).value) + val result = underTest.addRecordSet(record, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "fail if the record is relative with trailing dot" in { @@ -181,14 +178,14 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) val result = - leftResultOf(underTestWithDnsBackendValidations.addRecordSet(record, okAuth).value) + underTestWithDnsBackendValidations.addRecordSet(record, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "fail if the record is a high value domain" in { val record = aaaa.copy(name = "high-value-domain", zoneId = okZone.id, status = RecordSetStatus.Active) - val result = leftResultOf(underTest.addRecordSet(record, okAuth).value) + val result = underTest.addRecordSet(record, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe InvalidRequest( HighValueDomainError(s"high-value-domain.${okZone.name}").message ) @@ -205,9 +202,8 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - val result: RecordSetChange = rightResultOf( - underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.name shouldBe okZone.name } @@ -223,9 +219,8 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - val result: RecordSetChange = rightResultOf( - underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.name shouldBe okZone.name } @@ -241,9 +236,8 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, record.name) - val result: RecordSetChange = rightResultOf( - underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.name shouldBe okZone.name } @@ -260,9 +254,8 @@ class RecordSetServiceSpec .when(mockGroupRepo) .getGroup(okGroup.id) - val result: RecordSetChange = rightResultOf( - underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.ownerGroupId shouldBe Some(okGroup.id) } @@ -279,7 +272,7 @@ class RecordSetServiceSpec .when(mockGroupRepo) .getGroup(dummyGroup.id) - val result = leftResultOf(underTest.addRecordSet(record, okAuth).value) + val result = underTest.addRecordSet(record, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } @@ -296,7 +289,7 @@ class RecordSetServiceSpec .when(mockGroupRepo) .getGroup(dummyGroup.id) - val result = leftResultOf(underTest.addRecordSet(record, okAuth).value) + val result = underTest.addRecordSet(record, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidGroupError] } @@ -314,12 +307,10 @@ class RecordSetServiceSpec .getRecordSetsByName(okZone.id, record.name) val result: RecordSetChange = - rightResultOf( underTestWithDnsBackendValidations .addRecordSet(record, okAuth) .map(_.asInstanceOf[RecordSetChange]) - .value - ) + .value.unsafeRunSync().toOption.get matches(result.recordSet, record, okZone.name) shouldBe true result.changeType shouldBe RecordSetChangeType.Create @@ -342,9 +333,8 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - val result: RecordSetChange = rightResultOf( - underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get matches(result.recordSet, newRecord, okZone.name) shouldBe true matches(result.updates.get, oldRecord, okZone.name) shouldBe true @@ -358,9 +348,9 @@ class RecordSetServiceSpec doReturn(IO.pure(Some(aaaa.copy(zoneId = zoneNotAuthorized.id)))) .when(mockRecordRepo) .getRecordSet(aaaa.id) - val result = leftResultOf( - underTest.updateRecordSet(aaaa.copy(zoneId = zoneNotAuthorized.id), okAuth).value - ) + val result = + underTest.updateRecordSet(aaaa.copy(zoneId = zoneNotAuthorized.id), okAuth).value.unsafeRunSync().swap.toOption.get + result shouldBe a[NotAuthorizedError] } "succeed if the dotted record name is unchanged" in { @@ -378,9 +368,8 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - val result: RecordSetChange = rightResultOf( - underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.name shouldBe oldRecord.name result.recordSet.ttl shouldBe oldRecord.ttl + 1000 @@ -399,7 +388,7 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - val result = leftResultOf(underTest.updateRecordSet(newRecord, okAuth).value) + val result = underTest.updateRecordSet(newRecord, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "succeed if record is apex with dot" in { @@ -417,9 +406,8 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - val result: RecordSetChange = rightResultOf( - underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.name shouldBe okZone.name result.recordSet.ttl shouldBe oldRecord.ttl + 1000 @@ -439,9 +427,8 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - val result: RecordSetChange = rightResultOf( - underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.name shouldBe okZone.name result.recordSet.ttl shouldBe oldRecord.ttl + 1000 @@ -461,9 +448,8 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - val result: RecordSetChange = rightResultOf( - underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.name shouldBe okZone.name result.recordSet.ttl shouldBe oldRecord.ttl + 1000 @@ -484,7 +470,7 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - val result = leftResultOf(underTest.updateRecordSet(newRecord, okAuth).value) + val result = underTest.updateRecordSet(newRecord, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe InvalidRequest( HighValueDomainError(s"high-value-domain.${okZone.name}").message ) @@ -510,7 +496,7 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(okZone.id, newRecord.name) - val result = leftResultOf(underTest.updateRecordSet(newRecord, auth).value) + val result = underTest.updateRecordSet(newRecord, auth).value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } "fail if new owner group does not exist" in { @@ -539,7 +525,7 @@ class RecordSetServiceSpec .when(mockGroupRepo) .getGroup("doesnt-exist") - val result = leftResultOf(underTest.updateRecordSet(newRecord, auth).value) + val result = underTest.updateRecordSet(newRecord, auth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidGroupError] } "fail if user not in new owner group" in { @@ -568,7 +554,7 @@ class RecordSetServiceSpec .when(mockGroupRepo) .getGroup(okGroup.id) - val result = leftResultOf(underTest.updateRecordSet(newRecord, auth).value) + val result = underTest.updateRecordSet(newRecord, auth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "succeed if user is in owner group and zone is shared" in { @@ -596,9 +582,8 @@ class RecordSetServiceSpec .when(mockGroupRepo) .getGroup(oneUserDummyGroup.id) - val result = rightResultOf( - underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result = + underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.ttl shouldBe newRecord.ttl result.recordSet.ownerGroupId shouldBe Some(oneUserDummyGroup.id) @@ -625,9 +610,8 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSetsByName(zone.id, newRecord.name) - val result = rightResultOf( - underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result = + underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.ttl shouldBe newRecord.ttl result.recordSet.ownerGroupId shouldBe None @@ -645,7 +629,7 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSet(newRecord.id) - val result = leftResultOf(underTest.updateRecordSet(newRecord, auth).value) + val result = underTest.updateRecordSet(newRecord, auth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "succeed if new record exists in database but not in DNS backend" in { @@ -665,12 +649,11 @@ class RecordSetServiceSpec .when(mockBackend) .resolve(newRecord.name, okZone.name, newRecord.typ) - val result: RecordSetChange = rightResultOf( + val result: RecordSetChange = underTestWithDnsBackendValidations .updateRecordSet(newRecord, okAuth) .map(_.asInstanceOf[RecordSetChange]) - .value - ) + .value.unsafeRunSync().toOption.get matches(result.recordSet, newRecord, okZone.name) shouldBe true matches(result.updates.get, oldRecord, okZone.name) shouldBe true @@ -691,7 +674,7 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSet(newRecord.id) - val result = leftResultOf(underTest.updateRecordSet(newRecord, auth).value) + val result = underTest.updateRecordSet(newRecord, auth).value.unsafeRunSync().swap.toOption.get result shouldBe a[InvalidRequest] } } @@ -703,12 +686,11 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSet(record.id) - val result: RecordSetChange = rightResultOf( + val result: RecordSetChange = underTest .deleteRecordSet(record.id, okZone.id, okAuth) .map(_.asInstanceOf[RecordSetChange]) - .value - ) + .value.unsafeRunSync().toOption.get matches(result.recordSet, record, okZone.name) shouldBe true result.changeType shouldBe RecordSetChangeType.Delete @@ -719,7 +701,7 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSet(aaaa.id) val result = - leftResultOf(underTest.deleteRecordSet(aaaa.id, zoneNotAuthorized.id, okAuth).value) + underTest.deleteRecordSet(aaaa.id, zoneNotAuthorized.id, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } "fail if the record is a high value domain" in { @@ -731,16 +713,14 @@ class RecordSetServiceSpec .getRecordSet(record.id) val result = - leftResultOf(underTest.deleteRecordSet(record.id, okZone.id, okAuth).value) + underTest.deleteRecordSet(record.id, okZone.id, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe InvalidRequest( HighValueDomainError(s"high-value-domain.${okZone.name}").message ) } "fail for user who is not in record owner group in shared zone" in { val result = - leftResultOf( - underTest.deleteRecordSet(sharedZoneRecord.id, sharedZoneRecord.zoneId, dummyAuth).value - ) + underTest.deleteRecordSet(sharedZoneRecord.id, sharedZoneRecord.zoneId, dummyAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } @@ -750,9 +730,7 @@ class RecordSetServiceSpec .getZone(sharedZone.id) val result = - leftResultOf( - underTest.deleteRecordSet(sharedZoneRecord.id, sharedZoneRecord.zoneId, okAuth).value - ) + underTest.deleteRecordSet(sharedZoneRecord.id, sharedZoneRecord.zoneId, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } @@ -801,7 +779,7 @@ class RecordSetServiceSpec doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(any[String]) val result: RecordSetInfo = - rightResultOf(underTest.getRecordSet(aaaa.id, okAuth).value) + underTest.getRecordSet(aaaa.id, okAuth).value.unsafeRunSync().toOption.get result shouldBe expectedRecordSetInfo } @@ -812,7 +790,7 @@ class RecordSetServiceSpec .when(mockRecordRepo) .getRecordSet(mockRecord.id) - val result = leftResultOf(underTest.getRecordSet(mockRecord.id, okAuth).value) + val result = underTest.getRecordSet(mockRecord.id, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[RecordSetNotFoundError] } @@ -830,7 +808,7 @@ class RecordSetServiceSpec doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(any[String]) val result: RecordSetInfo = - rightResultOf(underTest.getRecordSetByZone(aaaa.id, okZone.id, okAuth).value) + underTest.getRecordSetByZone(aaaa.id, okZone.id, okAuth).value.unsafeRunSync().toOption.get result shouldBe expectedRecordSetInfo } @@ -842,7 +820,7 @@ class RecordSetServiceSpec .getRecordSet(mockRecord.id) val result = - leftResultOf(underTest.getRecordSetByZone(mockRecord.id, okZone.id, okAuth).value) + underTest.getRecordSetByZone(mockRecord.id, okZone.id, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[RecordSetNotFoundError] } @@ -856,9 +834,8 @@ class RecordSetServiceSpec val expectedRecordSetInfo = RecordSetInfo(sharedZoneRecord, Some(okGroup.name)) val result: RecordSetInfo = - rightResultOf( - underTest.getRecordSetByZone(sharedZoneRecord.id, sharedZone.id, okAuth).value - ) + underTest.getRecordSetByZone(sharedZoneRecord.id, sharedZone.id, okAuth).value.unsafeRunSync().toOption.get + result shouldBe expectedRecordSetInfo } @@ -872,9 +849,8 @@ class RecordSetServiceSpec val expectedRecordSetInfo = RecordSetInfo(sharedZoneRecord, None) val result: RecordSetInfo = - rightResultOf( - underTest.getRecordSetByZone(sharedZoneRecord.id, sharedZone.id, sharedAuth).value - ) + underTest.getRecordSetByZone(sharedZoneRecord.id, sharedZone.id, sharedAuth).value.unsafeRunSync().toOption.get + result shouldBe expectedRecordSetInfo } @@ -886,7 +862,7 @@ class RecordSetServiceSpec doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(any[String]) val result = - leftResultOf(underTest.getRecordSetByZone(aaaa.id, zoneNotAuthorized.id, okAuth).value) + underTest.getRecordSetByZone(aaaa.id, zoneNotAuthorized.id, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } @@ -900,11 +876,10 @@ class RecordSetServiceSpec val expectedRecordSetInfo = RecordSetInfo(sharedZoneRecordNoOwnerGroup, None) val result: RecordSetInfo = - rightResultOf( underTest .getRecordSetByZone(sharedZoneRecordNoOwnerGroup.id, sharedZone.id, sharedAuth) - .value - ) + .value.unsafeRunSync().toOption.get + result shouldBe expectedRecordSetInfo } @@ -916,11 +891,9 @@ class RecordSetServiceSpec doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(any[String]) val result = - leftResultOf( underTest .getRecordSetByZone(sharedZoneRecordNotApprovedRecordType.id, sharedZone.id, okAuth) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } @@ -946,11 +919,10 @@ class RecordSetServiceSpec doReturn(IO.pure(Some(okGroup))).when(mockGroupRepo).getGroup(any[String]) - val result = leftResultOf( + val result = underTest .getRecordSetByZone(notSharedZoneRecordWithOwnerGroup.id, zoneNotAuthorized.id, okAuth) - .value - ) + .value.unsafeRunSync().swap.toOption.get result shouldBe a[NotAuthorizedError] } } @@ -959,12 +931,12 @@ class RecordSetServiceSpec "return the group name if a record owner group ID is present" in { doReturn(IO.pure(Some(okGroup))).when(mockGroupRepo).getGroup(any[String]) - val result = rightResultOf(underTest.getGroupName(Some(okGroup.id)).value) + val result = underTest.getGroupName(Some(okGroup.id)).value.unsafeRunSync().toOption.get result shouldBe Some("ok") } "return None if a record owner group ID is not present" in { - val result = rightResultOf(underTest.getGroupName(None).value) + val result = underTest.getGroupName(None).value.unsafeRunSync().toOption.get result shouldBe None } } @@ -999,7 +971,7 @@ class RecordSetServiceSpec nameSort = any[NameSort.NameSort] ) - val result: ListGlobalRecordSetsResponse = rightResultOf( + val result: ListGlobalRecordSetsResponse = underTest .listRecordSets( startFrom = None, @@ -1010,8 +982,8 @@ class RecordSetServiceSpec nameSort = NameSort.ASC, authPrincipal = sharedAuth ) - .value - ) + .value.unsafeRunSync().toOption.get + result.recordSets shouldBe List( RecordSetGlobalInfo( @@ -1024,7 +996,7 @@ class RecordSetServiceSpec } "fail if recordNameFilter is fewer than two characters" in { - val result = leftResultOf( + val result = underTest .listRecordSets( startFrom = None, @@ -1035,8 +1007,8 @@ class RecordSetServiceSpec nameSort = NameSort.ASC, authPrincipal = okAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + result shouldBe an[InvalidRequest] } } @@ -1071,7 +1043,7 @@ class RecordSetServiceSpec nameSort = any[NameSort.NameSort] ) - val result = rightResultOf( + val result = underTest .searchRecordSets( startFrom = None, @@ -1082,8 +1054,8 @@ class RecordSetServiceSpec nameSort = NameSort.ASC, authPrincipal = sharedAuth ) - .value - ) + .value.unsafeRunSync().toOption.get + result.recordSets shouldBe List( RecordSetGlobalInfo( @@ -1096,7 +1068,7 @@ class RecordSetServiceSpec } "fail recordSetData if recordNameFilter is fewer than two characters" in { - val result = leftResultOf( + val result = underTest .searchRecordSets( startFrom = None, @@ -1107,8 +1079,8 @@ class RecordSetServiceSpec nameSort = NameSort.ASC, authPrincipal = okAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + result shouldBe an[InvalidRequest] } } @@ -1138,7 +1110,7 @@ class RecordSetServiceSpec nameSort = NameSort.ASC ) - val result: ListRecordSetsByZoneResponse = rightResultOf( + val result: ListRecordSetsByZoneResponse = underTest .listRecordSetsByZone( sharedZone.id, @@ -1150,8 +1122,8 @@ class RecordSetServiceSpec recordOwnerGroupFilter = None, nameSort = NameSort.ASC ) - .value - ) + .value.unsafeRunSync().toOption.get + result.recordSets shouldBe List( RecordSetListInfo( @@ -1181,7 +1153,7 @@ class RecordSetServiceSpec nameSort = NameSort.ASC ) - val result: ListRecordSetsByZoneResponse = rightResultOf( + val result: ListRecordSetsByZoneResponse = underTest .listRecordSetsByZone( okZone.id, @@ -1193,14 +1165,14 @@ class RecordSetServiceSpec nameSort = NameSort.ASC, authPrincipal = AuthPrincipal(okAuth.signedInUser.copy(isSupport = true), Seq.empty) ) - .value - ) + .value.unsafeRunSync().toOption.get + result.recordSets shouldBe List( RecordSetListInfo(RecordSetInfo(aaaa, None), AccessLevel.Read) ) } "fails when the account is not authorized" in { - val result = leftResultOf( + val result = underTest .listRecordSetsByZone( zoneNotAuthorized.id, @@ -1212,8 +1184,8 @@ class RecordSetServiceSpec nameSort = NameSort.ASC, authPrincipal = okAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + result shouldBe a[NotAuthorizedError] } } @@ -1231,7 +1203,7 @@ class RecordSetServiceSpec .getUsers(any[Set[String]], any[Option[String]], any[Option[Int]]) val result: ListRecordSetChangesResponse = - rightResultOf(underTest.listRecordSetChanges(okZone.id, authPrincipal = okAuth).value) + underTest.listRecordSetChanges(okZone.id, authPrincipal = okAuth).value.unsafeRunSync().toOption.get val changesWithName = completeRecordSetChanges.map(change => RecordSetChangeInfo(change, Some("ok"))) val expectedResults = ListRecordSetChangesResponse( @@ -1250,7 +1222,7 @@ class RecordSetServiceSpec .listRecordSetChanges(zoneId = okZone.id, startFrom = None, maxItems = 100) val result: ListRecordSetChangesResponse = - rightResultOf(underTest.listRecordSetChanges(okZone.id, authPrincipal = okAuth).value) + underTest.listRecordSetChanges(okZone.id, authPrincipal = okAuth).value.unsafeRunSync().toOption.get val expectedResults = ListRecordSetChangesResponse( zoneId = okZone.id, recordSetChanges = List(), @@ -1262,9 +1234,9 @@ class RecordSetServiceSpec } "return a NotAuthorizedError" in { - val error = leftResultOf( - underTest.listRecordSetChanges(zoneNotAuthorized.id, authPrincipal = okAuth).value - ) + val error = + underTest.listRecordSetChanges(zoneNotAuthorized.id, authPrincipal = okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[NotAuthorizedError] } @@ -1277,7 +1249,7 @@ class RecordSetServiceSpec .listRecordSetChanges(zoneId = okZone.id, startFrom = None, maxItems = 100) val result: ListRecordSetChangesResponse = - rightResultOf(underTest.listRecordSetChanges(okZone.id, authPrincipal = okAuth).value) + underTest.listRecordSetChanges(okZone.id, authPrincipal = okAuth).value.unsafeRunSync().toOption.get val changesWithName = List(RecordSetChangeInfo(rsChange2, Some("ok")), RecordSetChangeInfo(rsChange1, Some("ok"))) val expectedResults = ListRecordSetChangesResponse( @@ -1298,7 +1270,7 @@ class RecordSetServiceSpec .getRecordSetChange(okZone.id, pendingCreateAAAA.id) val actual: RecordSetChange = - rightResultOf(underTest.getRecordSetChange(okZone.id, pendingCreateAAAA.id, okAuth).value) + underTest.getRecordSetChange(okZone.id, pendingCreateAAAA.id, okAuth).value.unsafeRunSync().toOption.get actual shouldBe pendingCreateAAAA } @@ -1308,9 +1280,8 @@ class RecordSetServiceSpec .getRecordSetChange(sharedZone.id, pendingCreateSharedRecord.id) val actual: RecordSetChange = - rightResultOf( - underTest.getRecordSetChange(sharedZone.id, pendingCreateSharedRecord.id, okAuth).value - ) + underTest.getRecordSetChange(sharedZone.id, pendingCreateSharedRecord.id, okAuth).value.unsafeRunSync().toOption.get + actual shouldBe pendingCreateSharedRecord } @@ -1319,7 +1290,7 @@ class RecordSetServiceSpec .when(mockRecordChangeRepo) .getRecordSetChange(okZone.id, pendingCreateAAAA.id) val error = - leftResultOf(underTest.getRecordSetChange(okZone.id, pendingCreateAAAA.id, okAuth).value) + underTest.getRecordSetChange(okZone.id, pendingCreateAAAA.id, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[RecordSetChangeNotFoundError] } @@ -1329,9 +1300,8 @@ class RecordSetServiceSpec .when(mockRecordChangeRepo) .getRecordSetChange(zoneActive.id, pendingCreateAAAA.id) - val error = leftResultOf( - underTest.getRecordSetChange(zoneActive.id, pendingCreateAAAA.id, dummyAuth).value - ) + val error = + underTest.getRecordSetChange(zoneActive.id, pendingCreateAAAA.id, dummyAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -1342,15 +1312,14 @@ class RecordSetServiceSpec .when(mockRecordChangeRepo) .getRecordSetChange(zoneNotAuthorized.id, pendingCreateSharedRecordNotSharedZone.id) - val error = leftResultOf( + val error = underTest .getRecordSetChange( zoneNotAuthorized.id, pendingCreateSharedRecordNotSharedZone.id, okAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -1358,17 +1327,17 @@ class RecordSetServiceSpec "formatRecordNameFilter" should { "return an FQDN from an IPv4 address" in { - rightResultOf(underTest.formatRecordNameFilter("10.10.0.25").value) shouldBe + underTest.formatRecordNameFilter("10.10.0.25").value.unsafeRunSync().toOption.get shouldBe "25.0.10.10.in-addr.arpa." } "return an FQDN from an IPv6 address" in { - rightResultOf(underTest.formatRecordNameFilter("10.10.0.25").value) shouldBe + underTest.formatRecordNameFilter("10.10.0.25").value.unsafeRunSync().toOption.get shouldBe "25.0.10.10.in-addr.arpa." } "return a string with a trailing dot" in { - rightResultOf(underTest.formatRecordNameFilter("thing.com").value) shouldBe + underTest.formatRecordNameFilter("thing.com").value.unsafeRunSync().toOption.get shouldBe "thing.com." } } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneConnectionValidatorSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneConnectionValidatorSpec.scala index 10e3e53ac..6a88033a1 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneConnectionValidatorSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneConnectionValidatorSpec.scala @@ -24,7 +24,6 @@ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import org.scalatest.BeforeAndAfterEach import vinyldns.core.domain.record._ -import vinyldns.api.ResultHelpers import cats.effect._ import org.mockito.Matchers.any import vinyldns.core.domain.Fqdn @@ -39,7 +38,6 @@ class ZoneConnectionValidatorSpec with Matchers with MockitoSugar with BeforeAndAfterEach - with ResultHelpers with EitherMatchers with EitherValues { @@ -152,7 +150,7 @@ class ZoneConnectionValidatorSpec doReturn(IO.pure(true)).when(mockBackend).zoneExists(any[Zone]) doReturn(mockBackend).when(mockBackendResolver).resolve(any[Zone]) - val result = awaitResultOf(underTest.validateZoneConnections(testZone).value) + val result = underTest.validateZoneConnections(testZone).value.unsafeRunSync() result should be(right) } @@ -164,7 +162,7 @@ class ZoneConnectionValidatorSpec doReturn(IO.pure(true)).when(mockBackend).zoneExists(any[Zone]) doReturn(mockBackend).when(mockBackendResolver).resolve(any[Zone]) - val result = leftResultOf(underTest.validateZoneConnections(testZone).value) + val result = underTest.validateZoneConnections(testZone).value.unsafeRunSync().swap.toOption.get result shouldBe ZoneValidationFailed( testZone, List(s"Name Server not.approved. is not an approved name server."), @@ -181,7 +179,7 @@ class ZoneConnectionValidatorSpec doReturn(IO.pure(true)).when(mockBackend).zoneExists(any[Zone]) doReturn(mockBackend).when(mockBackendResolver).resolve(any[Zone]) - val result = leftResultOf(underTest.validateZoneConnections(testZone).value) + val result = underTest.validateZoneConnections(testZone).value.unsafeRunSync().swap.toOption.get result shouldBe a[ZoneValidationFailed] result shouldBe ZoneValidationFailed( testZone, @@ -202,7 +200,7 @@ class ZoneConnectionValidatorSpec doReturn(IO.pure(true)).when(mockBackend).zoneExists(any[Zone]) doReturn(mockBackend).when(mockBackendResolver).resolve(any[Zone]) - val result = leftResultOf(underTest.validateZoneConnections(badZone).value) + val result = underTest.validateZoneConnections(badZone).value.unsafeRunSync().swap.toOption.get result shouldBe a[ConnectionFailed] } @@ -219,7 +217,7 @@ class ZoneConnectionValidatorSpec doReturn(IO.pure(true)).when(mockBackend).zoneExists(any[Zone]) doReturn(mockBackend).when(mockBackendResolver).resolve(any[Zone]) - val result = leftResultOf(underTest.validateZoneConnections(badZone).value) + val result = underTest.validateZoneConnections(badZone).value.unsafeRunSync().swap.toOption.get result shouldBe a[ConnectionFailed] result.getMessage should include("transfer connection failure!") } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index c6ab3e935..0e321a884 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -23,7 +23,6 @@ import org.scalatest.wordspec.AnyWordSpec import org.scalatestplus.mockito.MockitoSugar import cats.implicits._ import vinyldns.api.Interfaces._ -import vinyldns.api.ResultHelpers import cats.effect._ import org.scalatest.{BeforeAndAfterEach, EitherValues} import vinyldns.api.domain.access.AccessValidations @@ -37,13 +36,10 @@ import vinyldns.core.TestZoneData._ import vinyldns.core.crypto.NoOpCrypto import vinyldns.core.domain.backend.BackendResolver -import scala.concurrent.duration._ - class ZoneServiceSpec extends AnyWordSpec with Matchers with MockitoSugar - with ResultHelpers with BeforeAndAfterEach with EitherValues { @@ -109,9 +105,8 @@ class ZoneServiceSpec "return an appropriate zone change response" in { doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) - val resultChange: ZoneChange = rightResultOf( - underTest.connectToZone(createZoneAuthorized, okAuth).map(_.asInstanceOf[ZoneChange]).value - ) + val resultChange: ZoneChange = + underTest.connectToZone(createZoneAuthorized, okAuth).map(_.asInstanceOf[ZoneChange]).value.unsafeRunSync().toOption.get resultChange.changeType shouldBe ZoneChangeType.Create Option(resultChange.created) shouldBe defined @@ -131,12 +126,11 @@ class ZoneServiceSpec doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) val nonTestUser = okAuth.copy(signedInUser = okAuth.signedInUser.copy(isTest = false)) - val resultChange: ZoneChange = rightResultOf( + val resultChange: ZoneChange = underTest .connectToZone(createZoneAuthorized, nonTestUser) .map(_.asInstanceOf[ZoneChange]) - .value - ) + .value.unsafeRunSync().toOption.get resultChange.zone.isTest shouldBe false } @@ -146,12 +140,11 @@ class ZoneServiceSpec val testUser = okAuth.copy(signedInUser = okAuth.signedInUser.copy(isTest = true)) testUser.isTestUser shouldBe true - val resultChange: ZoneChange = rightResultOf( + val resultChange: ZoneChange = underTest .connectToZone(createZoneAuthorized, testUser) .map(_.asInstanceOf[ZoneChange]) - .value - ) + .value.unsafeRunSync().toOption.get resultChange.zone.isTest shouldBe true } @@ -159,7 +152,7 @@ class ZoneServiceSpec "return a ZoneAlreadyExists error if the zone exists" in { doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) - val error = leftResultOf(underTest.connectToZone(createZoneAuthorized, okAuth).value) + val error = underTest.connectToZone(createZoneAuthorized, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[ZoneAlreadyExistsError] } @@ -168,7 +161,7 @@ class ZoneServiceSpec doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) doReturn(IO.pure(None)).when(mockGroupRepo).getGroup(anyString) - val error = leftResultOf(underTest.connectToZone(createZoneAuthorized, okAuth).value) + val error = underTest.connectToZone(createZoneAuthorized, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidGroupError] } @@ -176,9 +169,9 @@ class ZoneServiceSpec "allow the zone to be created if it exists and the zone is deleted" in { doReturn(IO.pure(Some(zoneDeleted))).when(mockZoneRepo).getZoneByName(anyString) - val resultChange: ZoneChange = rightResultOf( - underTest.connectToZone(createZoneAuthorized, okAuth).map(_.asInstanceOf[ZoneChange]).value - ) + val resultChange: ZoneChange = + underTest.connectToZone(createZoneAuthorized, okAuth).map(_.asInstanceOf[ZoneChange]).value.unsafeRunSync().toOption.get + resultChange.changeType shouldBe ZoneChangeType.Create } @@ -186,7 +179,7 @@ class ZoneServiceSpec val badAcl = ACLRule(baseAclRuleInfo.copy(recordMask = Some("x{5,-3}"))) val newZone = createZoneAuthorized.copy(acl = ZoneACL(Set(badAcl))) - val error = leftResultOf(underTest.connectToZone(newZone, okAuth).value) + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidRequest] } @@ -194,9 +187,8 @@ class ZoneServiceSpec val newZone = createZoneAuthorized.copy(shared = true) doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) - val resultZone = rightResultOf( - underTest.connectToZone(newZone, superUserAuth).map(_.asInstanceOf[ZoneChange]).value - ).zone + val resultZone = + underTest.connectToZone(newZone, superUserAuth).map(_.asInstanceOf[ZoneChange]).value.unsafeRunSync().toOption.get.zone Option(resultZone.id) should not be None resultZone.email shouldBe okZone.email @@ -210,12 +202,11 @@ class ZoneServiceSpec val newZone = createZoneAuthorized.copy(shared = true) doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) - val resultZone = rightResultOf( + val resultZone = underTest .connectToZone(newZone, supportUserAuth) .map(_.asInstanceOf[ZoneChange]) - .value - ).zone + .value.unsafeRunSync().toOption.get.zone Option(resultZone.id) should not be None resultZone.email shouldBe okZone.email @@ -229,14 +220,14 @@ class ZoneServiceSpec val newZone = createZoneAuthorized.copy(shared = true) doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) - val error = leftResultOf(underTest.connectToZone(newZone, okAuth).value) + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } "return an InvalidRequest if zone has a specified backend ID that is invalid" in { val newZone = createZoneAuthorized.copy(backendId = Some("badId")) - val error = leftResultOf(underTest.connectToZone(newZone, okAuth).value) + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidRequest] } } @@ -248,13 +239,11 @@ class ZoneServiceSpec val doubleAuth = AuthPrincipal(TestDataLoader.okUser, Seq(twoUserGroup.id, okGroup.id)) val updateZoneInput = updateZoneAuthorized.copy(adminGroupId = twoUserGroup.id) - val resultChange: ZoneChange = rightResultOf( + val resultChange: ZoneChange = underTest .updateZone(updateZoneInput, doubleAuth) .map(_.asInstanceOf[ZoneChange]) - .value, - duration = 2.seconds - ) + .value.unsafeRunSync().toOption.get resultChange.zone.id shouldBe okZone.id resultChange.changeType shouldBe ZoneChangeType.Update @@ -272,12 +261,11 @@ class ZoneServiceSpec val doubleAuth = AuthPrincipal(TestDataLoader.okUser, Seq(okGroup.id, okGroup.id)) val resultChange: ZoneChange = - rightResultOf( underTest .updateZone(newZone, doubleAuth) .map(_.asInstanceOf[ZoneChange]) - .value - ) + .value.unsafeRunSync().toOption.get + resultChange.zone.id shouldBe oldZone.id resultChange.zone.connection shouldBe oldZone.connection } @@ -288,7 +276,7 @@ class ZoneServiceSpec val newZone = updateZoneAuthorized.copy(connection = Some(badConnection), adminGroupId = okGroup.id) - val error = leftResultOf(underTest.updateZone(newZone, okAuth).value) + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[ConnectionFailed] } @@ -297,7 +285,7 @@ class ZoneServiceSpec val noAuth = AuthPrincipal(TestDataLoader.okUser, Seq()) - val error = leftResultOf(underTest.updateZone(updateZoneAuthorized, noAuth).value) + val error = underTest.updateZone(updateZoneAuthorized, noAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -307,7 +295,7 @@ class ZoneServiceSpec val badAcl = ACLRule(baseAclRuleInfo.copy(recordMask = Some("x{5,-3}"))) val newZone = updateZoneAuthorized.copy(acl = ZoneACL(Set(badAcl))) - val error = leftResultOf(underTest.updateZone(newZone, okAuth).value) + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidRequest] } @@ -317,11 +305,11 @@ class ZoneServiceSpec .when(mockZoneRepo) .getZone(newZone.id) - val result = rightResultOf( + val result = underTest .updateZone(newZone, AuthPrincipal(superUser, List.empty)) - .value - ) + .value.unsafeRunSync().toOption.get + result shouldBe a[ZoneChange] } @@ -331,11 +319,11 @@ class ZoneServiceSpec .when(mockZoneRepo) .getZone(newZone.id) - val result = rightResultOf( + val result = underTest .updateZone(newZone, supportUserAuth) - .value - ) + .value.unsafeRunSync().toOption.get + result shouldBe a[ZoneChange] } @@ -346,7 +334,7 @@ class ZoneServiceSpec .when(mockZoneRepo) .getZone(newZone.id) - val error = leftResultOf(underTest.updateZone(newZone, okAuth).value) + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -356,13 +344,13 @@ class ZoneServiceSpec .when(mockZoneRepo) .getZone(newZone.id) - val result = rightResultOf(underTest.updateZone(newZone, okAuth).value) + val result = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().toOption.get result shouldBe a[ZoneChange] } "return an InvalidRequest if zone has a specified backend ID that is invalid" in { val newZone = updateZoneAuthorized.copy(backendId = Some("badId")) - val error = leftResultOf(underTest.updateZone(newZone, okAuth).value) + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidRequest] } } @@ -372,7 +360,7 @@ class ZoneServiceSpec doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZone(anyString) val resultChange: ZoneChange = - rightResultOf(underTest.deleteZone(okZone.id, okAuth).map(_.asInstanceOf[ZoneChange]).value) + underTest.deleteZone(okZone.id, okAuth).map(_.asInstanceOf[ZoneChange]).value.unsafeRunSync().toOption.get resultChange.zone.id shouldBe okZone.id resultChange.changeType shouldBe ZoneChangeType.Delete @@ -383,7 +371,7 @@ class ZoneServiceSpec val noAuth = AuthPrincipal(TestDataLoader.okUser, Seq()) - val error = leftResultOf(underTest.deleteZone(okZone.id, noAuth).value) + val error = underTest.deleteZone(okZone.id, noAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } } @@ -393,7 +381,7 @@ class ZoneServiceSpec doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZone(anyString) val resultChange: ZoneChange = - rightResultOf(underTest.syncZone(okZone.id, okAuth).map(_.asInstanceOf[ZoneChange]).value) + underTest.syncZone(okZone.id, okAuth).map(_.asInstanceOf[ZoneChange]).value.unsafeRunSync().toOption.get resultChange.zone.id shouldBe okZone.id resultChange.changeType shouldBe ZoneChangeType.Sync @@ -405,7 +393,7 @@ class ZoneServiceSpec val noAuth = AuthPrincipal(TestDataLoader.okUser, Seq()) - val error = leftResultOf(underTest.syncZone(okZone.id, noAuth).value) + val error = underTest.syncZone(okZone.id, noAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } } @@ -414,7 +402,7 @@ class ZoneServiceSpec "fail with no zone returned" in { doReturn(IO.pure(None)).when(mockZoneRepo).getZone("notAZoneId") - val error = leftResultOf(underTest.getZone("notAZoneId", okAuth).value) + val error = underTest.getZone("notAZoneId", okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[ZoneNotFoundError] } @@ -423,7 +411,7 @@ class ZoneServiceSpec val noAuth = AuthPrincipal(TestDataLoader.okUser, Seq()) - val error = leftResultOf(underTest.getZone(okZone.id, noAuth).value) + val error = underTest.getZone(okZone.id, noAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -471,7 +459,7 @@ class ZoneServiceSpec goodGroup.name, AccessLevel.Delete ) - val result: ZoneInfo = rightResultOf(underTest.getZone(zoneWithRules.id, abcAuth).value) + val result: ZoneInfo = underTest.getZone(zoneWithRules.id, abcAuth).value.unsafeRunSync().toOption.get result shouldBe expectedZoneInfo } @@ -485,14 +473,14 @@ class ZoneServiceSpec val expectedZoneInfo = ZoneInfo(abcZone, ZoneACLInfo(Set()), "Unknown group name", AccessLevel.Delete) - val result: ZoneInfo = rightResultOf(underTest.getZone(abcZone.id, abcAuth).value) + val result: ZoneInfo = underTest.getZone(abcZone.id, abcAuth).value.unsafeRunSync().toOption.get result shouldBe expectedZoneInfo } "return a zone by name with failure when no zone is found" in { doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName("someZoneName.") - val error = leftResultOf(underTest.getZoneByName("someZoneName", okAuth).value) + val error = underTest.getZoneByName("someZoneName", okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[ZoneNotFoundError] } @@ -518,7 +506,7 @@ class ZoneServiceSpec .listZones(abcAuth, None, None, 100, false) doReturn(IO.pure(Set(abcGroup))).when(mockGroupRepo).getGroups(any[Set[String]]) - val result: ListZonesResponse = rightResultOf(underTest.listZones(abcAuth).value) + val result: ListZonesResponse = underTest.listZones(abcAuth).value.unsafeRunSync().toOption.get result.zones shouldBe List() result.maxItems shouldBe 100 result.startFrom shouldBe None @@ -535,7 +523,7 @@ class ZoneServiceSpec .when(mockGroupRepo) .getGroups(any[Set[String]]) - val result: ListZonesResponse = rightResultOf(underTest.listZones(abcAuth).value) + val result: ListZonesResponse = underTest.listZones(abcAuth).value.unsafeRunSync().toOption.get result.zones shouldBe List(abcZoneSummary) result.maxItems shouldBe 100 result.startFrom shouldBe None @@ -553,7 +541,7 @@ class ZoneServiceSpec .getGroups(any[Set[String]]) val result: ListZonesResponse = - rightResultOf(underTest.listZones(abcAuth, ignoreAccess = true).value) + underTest.listZones(abcAuth, ignoreAccess = true).value.unsafeRunSync().toOption.get result.zones shouldBe List(abcZoneSummary, xyzZoneSummary) result.maxItems shouldBe 100 result.startFrom shouldBe None @@ -568,7 +556,7 @@ class ZoneServiceSpec .listZones(abcAuth, None, None, 100, false) doReturn(IO.pure(Set(okGroup))).when(mockGroupRepo).getGroups(any[Set[String]]) - val result: ListZonesResponse = rightResultOf(underTest.listZones(abcAuth).value) + val result: ListZonesResponse = underTest.listZones(abcAuth).value.unsafeRunSync().toOption.get val expectedZones = List(abcZoneSummary, xyzZoneSummary).map(_.copy(adminGroupName = "Unknown group name")) result.zones shouldBe expectedZones @@ -595,7 +583,7 @@ class ZoneServiceSpec .getGroups(any[Set[String]]) val result: ListZonesResponse = - rightResultOf(underTest.listZones(abcAuth, maxItems = 2).value) + underTest.listZones(abcAuth, maxItems = 2).value.unsafeRunSync().toOption.get result.zones shouldBe List(abcZoneSummary, xyzZoneSummary) result.maxItems shouldBe 2 result.startFrom shouldBe None @@ -621,7 +609,7 @@ class ZoneServiceSpec .getGroups(any[Set[String]]) val result: ListZonesResponse = - rightResultOf(underTest.listZones(abcAuth, nameFilter = Some("foo"), maxItems = 2).value) + underTest.listZones(abcAuth, nameFilter = Some("foo"), maxItems = 2).value.unsafeRunSync().toOption.get result.zones shouldBe List(abcZoneSummary, xyzZoneSummary) result.nameFilter shouldBe Some("foo") result.nextId shouldBe Some("zone2.") @@ -645,7 +633,7 @@ class ZoneServiceSpec .getGroups(any[Set[String]]) val result: ListZonesResponse = - rightResultOf(underTest.listZones(abcAuth, startFrom = Some("zone4."), maxItems = 2).value) + underTest.listZones(abcAuth, startFrom = Some("zone4."), maxItems = 2).value.unsafeRunSync().toOption.get result.zones shouldBe List(abcZoneSummary, xyzZoneSummary) result.startFrom shouldBe Some("zone4.") } @@ -668,7 +656,7 @@ class ZoneServiceSpec .getGroups(any[Set[String]]) val result: ListZonesResponse = - rightResultOf(underTest.listZones(abcAuth, startFrom = Some("zone4."), maxItems = 2).value) + underTest.listZones(abcAuth, startFrom = Some("zone4."), maxItems = 2).value.unsafeRunSync().toOption.get result.zones shouldBe List(abcZoneSummary, xyzZoneSummary) result.nextId shouldBe Some("zone6.") } @@ -684,7 +672,7 @@ class ZoneServiceSpec .listZoneChanges(okZone.id, startFrom = None, maxItems = 100) val result: ListZoneChangesResponse = - rightResultOf(underTest.listZoneChanges(okZone.id, okAuth).value) + underTest.listZoneChanges(okZone.id, okAuth).value.unsafeRunSync().toOption.get result.zoneChanges shouldBe List(zoneUpdate, zoneCreate) result.zoneId shouldBe okZone.id @@ -699,7 +687,7 @@ class ZoneServiceSpec .listZoneChanges(okZone.id, startFrom = None, maxItems = 100) val result: ListZoneChangesResponse = - rightResultOf(underTest.listZoneChanges(okZone.id, okAuth).value) + underTest.listZoneChanges(okZone.id, okAuth).value.unsafeRunSync().toOption.get result.zoneChanges shouldBe empty result.zoneId shouldBe okZone.id @@ -710,7 +698,7 @@ class ZoneServiceSpec .when(mockZoneRepo) .getZone(zoneNotAuthorized.id) - val error = leftResultOf(underTest.listZoneChanges(zoneNotAuthorized.id, okAuth).value) + val error = underTest.listZoneChanges(zoneNotAuthorized.id, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -725,7 +713,7 @@ class ZoneServiceSpec .listZoneChanges(zoneId = okZone.id, startFrom = None, maxItems = 100) val result: ListZoneChangesResponse = - rightResultOf(underTest.listZoneChanges(okZone.id, okAuth).value) + underTest.listZoneChanges(okZone.id, okAuth).value.unsafeRunSync().toOption.get result.zoneChanges.head shouldBe zoneUpdate result.zoneChanges(1) shouldBe zoneCreate @@ -737,19 +725,18 @@ class ZoneServiceSpec doReturn(IO.pure(Some(zoneNotAuthorized))).when(mockZoneRepo).getZone(anyString) val error = - leftResultOf(underTest.addACLRule(zoneNotAuthorized.id, baseAclRuleInfo, okAuth).value) + underTest.addACLRule(zoneNotAuthorized.id, baseAclRuleInfo, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } "generate a zone update if the request is valid" in { doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZone(anyString) - val result: ZoneChange = rightResultOf( + val result: ZoneChange = underTest .addACLRule(okZone.id, userAclRuleInfo, okAuth) .map(_.asInstanceOf[ZoneChange]) - .value - ) + .value.unsafeRunSync().toOption.get result.changeType shouldBe ZoneChangeType.Update result.zone.acl.rules.size shouldBe 1 @@ -761,7 +748,7 @@ class ZoneServiceSpec val invalidRegexMaskRuleInfo = baseAclRuleInfo.copy(recordMask = Some("x{5,-3}")) val error = - leftResultOf(underTest.addACLRule(okZone.id, invalidRegexMaskRuleInfo, okAuth).value) + underTest.addACLRule(okZone.id, invalidRegexMaskRuleInfo, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidRequest] } } @@ -771,7 +758,7 @@ class ZoneServiceSpec doReturn(IO.pure(Some(zoneNotAuthorized))).when(mockZoneRepo).getZone(anyString) val error = - leftResultOf(underTest.deleteACLRule(zoneNotAuthorized.id, baseAclRuleInfo, okAuth).value) + underTest.deleteACLRule(zoneNotAuthorized.id, baseAclRuleInfo, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[NotAuthorizedError] } @@ -780,12 +767,11 @@ class ZoneServiceSpec val zone = okZone.copy(acl = acl) doReturn(IO.pure(Some(zone))).when(mockZoneRepo).getZone(anyString) - val result: ZoneChange = rightResultOf( + val result: ZoneChange = underTest .deleteACLRule(zone.id, userAclRuleInfo, okAuth) .map(_.asInstanceOf[ZoneChange]) - .value - ) + .value.unsafeRunSync().toOption.get result.changeType shouldBe ZoneChangeType.Update result.zone.acl.rules.size shouldBe 0 diff --git a/modules/api/src/test/scala/vinyldns/api/engine/BatchChangeHandlerSpec.scala b/modules/api/src/test/scala/vinyldns/api/engine/BatchChangeHandlerSpec.scala index 5708fc88a..a365d62e3 100644 --- a/modules/api/src/test/scala/vinyldns/api/engine/BatchChangeHandlerSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/engine/BatchChangeHandlerSpec.scala @@ -23,7 +23,6 @@ import org.mockito.Mockito.{doReturn, verify} import org.scalatest.BeforeAndAfterEach import org.scalatest.wordspec.AnyWordSpec import org.scalatestplus.mockito.MockitoSugar -import vinyldns.api.CatsHelpers import vinyldns.api.repository.InMemoryBatchChangeRepository import vinyldns.core.domain.batch._ import vinyldns.core.domain.record._ @@ -34,8 +33,7 @@ import scala.concurrent.ExecutionContext class BatchChangeHandlerSpec extends AnyWordSpec with MockitoSugar - with BeforeAndAfterEach - with CatsHelpers { + with BeforeAndAfterEach { implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global implicit val contextShift: ContextShift[IO] = IO.contextShift(ec) @@ -76,7 +74,7 @@ class BatchChangeHandlerSpec "notify on batch change complete" in { doReturn(IO.unit).when(mockNotifier).notify(any[Notification[_]]) - await(batchRepo.save(completedBatchChange)) + batchRepo.save(completedBatchChange).unsafeRunSync() BatchChangeHandler .process(batchRepo, notifiers, BatchChangeCommand(completedBatchChange.id)) @@ -91,7 +89,7 @@ class BatchChangeHandlerSpec val partiallyFailedBatchChange = completedBatchChange.copy(changes = List(addChange.copy(status = SingleChangeStatus.Failed))) - await(batchRepo.save(partiallyFailedBatchChange)) + batchRepo.save(partiallyFailedBatchChange).unsafeRunSync() BatchChangeHandler .process(batchRepo, notifiers, BatchChangeCommand(partiallyFailedBatchChange.id)) diff --git a/modules/api/src/test/scala/vinyldns/api/engine/RecordSetChangeHandlerSpec.scala b/modules/api/src/test/scala/vinyldns/api/engine/RecordSetChangeHandlerSpec.scala index d31ddafd2..4f3d86665 100644 --- a/modules/api/src/test/scala/vinyldns/api/engine/RecordSetChangeHandlerSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/engine/RecordSetChangeHandlerSpec.scala @@ -28,7 +28,6 @@ import org.scalatest.{BeforeAndAfterEach, EitherValues} import vinyldns.api.backend.dns.DnsProtocol.{NotAuthorized, TryAgain} import vinyldns.api.engine.RecordSetChangeHandler.{AlreadyApplied, ReadyToApply, Requeue} import vinyldns.api.repository.InMemoryBatchChangeRepository -import vinyldns.api.CatsHelpers import vinyldns.core.domain.batch.{BatchChange, BatchChangeApprovalStatus, SingleAddChange, SingleChangeStatus} import vinyldns.core.domain.record.RecordType.RecordType import vinyldns.core.domain.record.{ChangeSet, RecordChangeRepository, RecordSetRepository, _} @@ -45,7 +44,6 @@ class RecordSetChangeHandlerSpec with Matchers with MockitoSugar with BeforeAndAfterEach - with CatsHelpers with EitherValues with TransactionProvider { @@ -115,7 +113,7 @@ class RecordSetChangeHandlerSpec batchRepo.clear() // seed the linked batch change in the DB - await(batchRepo.save(batchChange)) + batchRepo.save(batchChange).unsafeRunSync() doReturn(IO.pure(Nil)) .when(mockRsRepo) @@ -150,7 +148,7 @@ class RecordSetChangeHandlerSpec savedCs.status shouldBe ChangeSetStatus.Complete savedCs.changes.head.status shouldBe RecordSetChangeStatus.Complete - val batchChangeUpdates = await(batchRepo.getBatchChange(batchChange.id)) + val batchChangeUpdates = batchRepo.getBatchChange(batchChange.id).unsafeRunSync() val updatedSingleChanges = completeCreateAAAASingleChanges.map { ch => ch.copy( status = SingleChangeStatus.Complete, @@ -195,7 +193,7 @@ class RecordSetChangeHandlerSpec verify(mockBackend).applyChange(rsChange) verify(mockBackend, times(2)).resolve(rs.name, rsChange.zone.name, rs.typ) - val batchChangeUpdates = await(batchRepo.getBatchChange(batchChange.id)) + val batchChangeUpdates = batchRepo.getBatchChange(batchChange.id).unsafeRunSync() val updatedSingleChanges = completeCreateAAAASingleChanges.map { ch => ch.copy( status = SingleChangeStatus.Complete, @@ -245,7 +243,7 @@ class RecordSetChangeHandlerSpec // make sure we only called resolve once when validating, ensures that verify was not called verify(mockBackend, times(1)).resolve(rs.name, rsChange.zone.name, rs.typ) - val batchChangeUpdates = await(batchRepo.getBatchChange(batchChange.id)) + val batchChangeUpdates = batchRepo.getBatchChange(batchChange.id).unsafeRunSync() val updatedSingleChanges = completeCreateAAAASingleChanges.map { ch => ch.copy( status = SingleChangeStatus.Failed, @@ -291,7 +289,7 @@ class RecordSetChangeHandlerSpec // we will retry the verify 3 times based on the mock setup verify(mockBackend, times(2)).resolve(rs.name, rsChange.zone.name, rs.typ) - val batchChangeUpdates = await(batchRepo.getBatchChange(batchChange.id)) + val batchChangeUpdates = batchRepo.getBatchChange(batchChange.id).unsafeRunSync() val updatedSingleChanges = completeCreateAAAASingleChanges.map { ch => ch.copy( status = SingleChangeStatus.Failed, @@ -347,7 +345,7 @@ class RecordSetChangeHandlerSpec verify(mockBackend, never()).applyChange(rsChange) verify(mockBackend, times(1)).resolve(rs.name, rsChange.zone.name, rs.typ) - val batchChangeUpdates = await(batchRepo.getBatchChange(batchChange.id)) + val batchChangeUpdates = batchRepo.getBatchChange(batchChange.id).unsafeRunSync() val updatedSingleChanges = completeCreateAAAASingleChanges.map { ch => ch.copy( status = SingleChangeStatus.Failed, @@ -390,7 +388,7 @@ class RecordSetChangeHandlerSpec verify(mockBackend, times(1)).applyChange(rsChange) verify(mockBackend, times(1)).resolve(rs.name, rsChange.zone.name, rs.typ) - val batchChangeUpdates = await(batchRepo.getBatchChange(batchChange.id)) + val batchChangeUpdates = batchRepo.getBatchChange(batchChange.id).unsafeRunSync() val updatedSingleChanges = completeCreateAAAASingleChanges.map { ch => ch.copy( status = SingleChangeStatus.Failed, @@ -445,7 +443,7 @@ class RecordSetChangeHandlerSpec // make sure we never called resolve, as we skip validate step and verify verify(mockBackend, never).resolve(rs.name, rsChange.zone.name, rs.typ) - val batchChangeUpdates = await(batchRepo.getBatchChange(batchChange.id)) + val batchChangeUpdates = batchRepo.getBatchChange(batchChange.id).unsafeRunSync() val updatedSingleChanges = completeCreateAAAASingleChanges.map { ch => ch.copy( status = SingleChangeStatus.Complete, @@ -599,7 +597,7 @@ class RecordSetChangeHandlerSpec savedCs.status shouldBe ChangeSetStatus.Complete savedCs.changes.head.status shouldBe RecordSetChangeStatus.Complete - val batchChangeUpdates = await(batchRepo.getBatchChange(batchChange.id)) + val batchChangeUpdates = batchRepo.getBatchChange(batchChange.id).unsafeRunSync() val updatedSingleChanges = completeCreateAAAASingleChanges.map { ch => ch.copy( status = SingleChangeStatus.Complete, From 229efc744b50bab7bb76618b664bf3f074659e3d Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Tue, 23 Aug 2022 13:26:58 +0530 Subject: [PATCH 011/521] Update in func tests --- .../test/functional/tests/batch/create_batch_change_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index 1c2efffaf..df92b5d5f 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -2302,6 +2302,9 @@ def test_ipv4_ptr_recordtype_add_checks(shared_zone_test_context): # delegated and non-delegated PTR duplicate name checks assert_successful_change_in_error_response(response[4], input_name=f"{ip4_prefix}.196", record_type="PTR", record_data="test.com.") + assert_failed_change_in_error_response(response[5], input_name=f"196.{ip4_zone_name}", record_type="CNAME", record_data="test.com.", + error_messages=[f'Record Name "196.{ip4_zone_name}" Not Unique In Batch Change: cannot have multiple "CNAME" records with the same name.']) + assert_successful_change_in_error_response(response[6], input_name=f"196.192/30.{ip4_zone_name}", record_type="CNAME", record_data="test.com.") assert_successful_change_in_error_response(response[7], input_name=f"{ip4_prefix}.55", record_type="PTR", record_data="test.com.") assert_failed_change_in_error_response(response[8], input_name=f"55.{ip4_zone_name}", record_type="CNAME", record_data="test.com.", error_messages=[f'Record Name "55.{ip4_zone_name}" Not Unique In Batch Change: cannot have multiple "CNAME" records with the same name.']) From fcfd84125ebe7ba42c53ec3a5b7bfb1acf57b62f Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Wed, 24 Aug 2022 14:57:46 +0530 Subject: [PATCH 012/521] Update --- .../main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala b/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala index 2ee99902f..eb88c0fbf 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/ReverseZoneHelpers.scala @@ -101,8 +101,8 @@ object ReverseZoneHelpers { case 4 => ipMaskOctets.mkString(".") } - val updatedMask = Cidr(Ipv4Address.fromString(fullIp).get,Cidr.fromString4(mask).get.prefixBits) - updatedMask.contains(recordIpAddr.get) + val updatedMask = Cidr(recordIpAddr.get,Cidr.fromString4(mask).get.prefixBits) + updatedMask.contains(Ipv4Address.fromString(fullIp).get) }.getOrElse(false) } From 807dde2ff0cfa01b31d3e8b609e8f8f91cf2baa5 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Mon, 29 Aug 2022 17:49:34 -0400 Subject: [PATCH 013/521] Add new condition to canUpdateRecordSet --- .../api/domain/access/AccessValidations.scala | 7 ++-- .../access/AccessValidationsAlgebra.scala | 1 + .../domain/batch/BatchChangeValidations.scala | 1 + .../api/domain/record/RecordSetService.scala | 3 +- .../domain/record/RecordSetValidations.scala | 34 +++++++++++++++++++ .../core/domain/membership/User.scala | 2 +- 6 files changed, 44 insertions(+), 4 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidations.scala index 4aef5a679..62e7c848c 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidations.scala @@ -73,6 +73,7 @@ class AccessValidations( recordType: RecordType, zone: Zone, recordOwnerGroupId: Option[String], + superUserCanUpdateOwnerGroup: Boolean, newRecordData: List[RecordData] = List.empty ): Either[Throwable, Unit] = { val accessLevel = @@ -82,7 +83,7 @@ class AccessValidations( s"User ${auth.signedInUser.userName} does not have access to update " + s"$recordName.${zone.name}" ) - )(accessLevel == AccessLevel.Delete || accessLevel == AccessLevel.Write) + )(accessLevel == AccessLevel.Delete || accessLevel == AccessLevel.Write || superUserCanUpdateOwnerGroup) } def canDeleteRecordSet( @@ -222,7 +223,9 @@ class AccessValidations( AccessLevel.Delete case support if support.isSystemAdmin => val aclAccess = getAccessFromAcl(auth, recordName, recordType, zone) - if (aclAccess == AccessLevel.NoAccess) AccessLevel.Read else aclAccess + if (aclAccess == AccessLevel.NoAccess) + AccessLevel.Read + else aclAccess case globalAclUser if globalAcls.isAuthorized(globalAclUser, recordName, recordType, zone, recordData) => AccessLevel.Delete diff --git a/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidationsAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidationsAlgebra.scala index 59d6c300a..eb3c6feda 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidationsAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidationsAlgebra.scala @@ -47,6 +47,7 @@ trait AccessValidationsAlgebra { recordType: RecordType, zone: Zone, recordOwnerGroupId: Option[String], + superUserCanUpdateOwnerGroup: Boolean = false, newRecordData: List[RecordData] = List.empty ): Either[Throwable, Unit] diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index efa72e443..fa0cd3814 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -552,6 +552,7 @@ class BatchChangeValidations( input.inputChange.typ, input.zone, ownerGroupId, + false, addRecords ) result diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index cc4f18331..632d211fa 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -127,8 +127,9 @@ class RecordSetService( change <- RecordSetChangeGenerator.forUpdate(existing, recordSet, zone, Some(auth)).toResult // because changes happen to the RS in forUpdate itself, converting 1st and validating on that rsForValidations = change.recordSet + superUserCanUpdateOwnerGroup = canSuperUserUpdateOwnerGroup(existing, recordSet, zone, auth) _ <- isNotHighValueDomain(recordSet, zone, highValueDomainConfig).toResult - _ <- canUpdateRecordSet(auth, existing.name, existing.typ, zone, existing.ownerGroupId).toResult + _ <- canUpdateRecordSet(auth, existing.name, existing.typ, zone, existing.ownerGroupId, superUserCanUpdateOwnerGroup).toResult ownerGroup <- getGroupIfProvided(rsForValidations.ownerGroupId) _ <- canUseOwnerGroup(rsForValidations.ownerGroupId, ownerGroup, auth).toResult _ <- notPending(existing).toResult diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 0bff5984a..19978bd2a 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -323,6 +323,40 @@ object RecordSetValidations { InvalidRequest("Cannot update RecordSet's zone ID.") ) +// def superUserCanUpdateOwnerGroup( +// existing: RecordSet, +// updates: RecordSet, +// zone: Zone, +// auth: AuthPrincipal +// ): Either[Throwable, Unit] = +// Either.cond( +// updates.ownerGroupId != existing.ownerGroupId +// && updates.zoneId == existing.zoneId +// && updates.name == existing.name +// && updates.typ == existing.typ +// && updates.ttl == existing.ttl +// && updates.records == existing.records +// && zone.shared +// && auth.isSuper, +// (), +// InvalidRequest("Cannot update RecordSet.") +// ) + + def canSuperUserUpdateOwnerGroup( + existing: RecordSet, + updates: RecordSet, + zone: Zone, + auth: AuthPrincipal + ): Boolean = + (updates.ownerGroupId != existing.ownerGroupId + && updates.zoneId == existing.zoneId + && updates.name == existing.name + && updates.typ == existing.typ + && updates.ttl == existing.ttl + && updates.records == existing.records + && zone.shared + && auth.isSuper) + def validRecordNameFilterLength(recordNameFilter: String): Either[Throwable, Unit] = ensuring(onError = InvalidRequest(RecordNameFilterError)) { val searchRegex = "[a-zA-Z0-9].*[a-zA-Z0-9]+".r diff --git a/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala b/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala index 9172dc9a3..42af71b4a 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala @@ -37,7 +37,7 @@ final case class User( email: Option[String] = None, created: DateTime = DateTime.now, id: String = UUID.randomUUID().toString, - isSuper: Boolean = false, + isSuper: Boolean = true, lockStatus: LockStatus = LockStatus.Unlocked, isSupport: Boolean = false, isTest: Boolean = false From 30514caa052f0e79cf1e517a2eebe439d7636e5d Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Wed, 31 Aug 2022 17:23:02 -0400 Subject: [PATCH 014/521] Remove commented code, add and update tests --- .../RecordSetServiceIntegrationSpec.scala | 2 +- .../api/domain/access/AccessValidations.scala | 2 +- .../domain/record/RecordSetValidations.scala | 23 +++--------- .../domain/access/AccessValidationsSpec.scala | 11 +++++- .../domain/record/RecordSetServiceSpec.scala | 35 ++++++++++++++++++- .../record/RecordSetValidationsSpec.scala | 31 ++++++++++++++++ .../core/domain/membership/User.scala | 2 +- .../vinyldns/core/TestMembershipData.scala | 2 +- 8 files changed, 83 insertions(+), 25 deletions(-) diff --git a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala index df845b477..044ceecc7 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala @@ -314,7 +314,7 @@ class RecordSetServiceIntegrationSpec rightValue(originalRecord).name shouldBe "live-zone-test" } } - + //todo "RecordSetService" should { "create apex record without trailing dot and save record name with trailing dot" in { val newRecord = RecordSet( diff --git a/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidations.scala index 62e7c848c..d2c1b1c45 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/access/AccessValidations.scala @@ -73,7 +73,7 @@ class AccessValidations( recordType: RecordType, zone: Zone, recordOwnerGroupId: Option[String], - superUserCanUpdateOwnerGroup: Boolean, + superUserCanUpdateOwnerGroup: Boolean = false, newRecordData: List[RecordData] = List.empty ): Either[Throwable, Unit] = { val accessLevel = diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala index 19978bd2a..e5d41ec54 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetValidations.scala @@ -323,25 +323,10 @@ object RecordSetValidations { InvalidRequest("Cannot update RecordSet's zone ID.") ) -// def superUserCanUpdateOwnerGroup( -// existing: RecordSet, -// updates: RecordSet, -// zone: Zone, -// auth: AuthPrincipal -// ): Either[Throwable, Unit] = -// Either.cond( -// updates.ownerGroupId != existing.ownerGroupId -// && updates.zoneId == existing.zoneId -// && updates.name == existing.name -// && updates.typ == existing.typ -// && updates.ttl == existing.ttl -// && updates.records == existing.records -// && zone.shared -// && auth.isSuper, -// (), -// InvalidRequest("Cannot update RecordSet.") -// ) - + /** + * Checks of the user is a superuser, the zone is shared, and the only record attribute being changed + * is the record owner group. + */ def canSuperUserUpdateOwnerGroup( existing: RecordSet, updates: RecordSet, diff --git a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala index f55df7783..a95af3376 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala @@ -255,6 +255,8 @@ class AccessValidationsSpec ) should be(right) } } + + //TODO "canUpdateRecordSet" should { "return a NotAuthorizedError if the user has AccessLevel.NoAccess" in { val error = leftValue( @@ -292,6 +294,13 @@ class AccessValidationsSpec ) } + "return true if the user has AccessLevel.Read or AccessLevel.NoAccess and superUserCanUpdateOwnerGroup is true" in { + accessValidationTest.canUpdateRecordSet(userAuthRead, "test", RecordType.A, zoneInRead, + None, true) should be(right) + accessValidationTest.canUpdateRecordSet(userAuthNone, "test", RecordType.A, zoneInNone, + None, true) should be(right) + } + "return true if the user is in the owner group and the zone is shared" in { val zone = okZone.copy(shared = true) val record = aaaa.copy(zoneId = zone.id, ownerGroupId = Some(oneUserDummyGroup.id)) @@ -365,7 +374,7 @@ class AccessValidationsSpec RecordType.PTR, zoneIp4, None, - List(PTRData(Fqdn("test.foo.comcast.net"))) + newRecordData = List(PTRData(Fqdn("test.foo.comcast.net"))) ) should be(right) } } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 3d629e43b..e405a013f 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -326,7 +326,7 @@ class RecordSetServiceSpec result.status shouldBe RecordSetChangeStatus.Pending } } - + //TODO "updateRecordSet" should { "return the recordSet change as the result" in { val oldRecord = aaaa.copy(zoneId = okZone.id, status = RecordSetStatus.Active) @@ -603,6 +603,39 @@ class RecordSetServiceSpec result.recordSet.ttl shouldBe newRecord.ttl result.recordSet.ownerGroupId shouldBe Some(oneUserDummyGroup.id) } + + "succeed if user is a superuser and zone is shared and the only record attribute being changed is the record owner group." in { + val zone = okZone.copy(shared = true, id = "test-owner-group") +// val auth = AuthPrincipal(listOfDummyUsers.head, Seq(oneUserDummyGroup.id)) + val auth = superUserAuth + val oldRecord = aaaa.copy( + name = "test-owner-group-success", + zoneId = zone.id, + status = RecordSetStatus.Active, + ownerGroupId = Some(oneUserDummyGroup.id) + ) + + val newRecord = oldRecord.copy(ownerGroupId = Some(okGroup.id)) + + doReturn(IO.pure(Some(zone))) + .when(mockZoneRepo) + .getZone(zone.id) + doReturn(IO.pure(Some(oldRecord))) + .when(mockRecordRepo) + .getRecordSet(newRecord.id) + doReturn(IO.pure(List(oldRecord))) + .when(mockRecordRepo) + .getRecordSetsByName(zone.id, newRecord.name) + doReturn(IO.pure(Some(okGroup))) + .when(mockGroupRepo) + .getGroup(okGroup.id) + + val result = rightResultOf( + underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value + ) + + result.recordSet.ownerGroupId shouldBe Some(okGroup.id) + } "succeed if user is in owner group and zone is shared and new owner group is none" in { val zone = okZone.copy(shared = true, id = "test-owner-group") val auth = AuthPrincipal(listOfDummyUsers.head, Seq(oneUserDummyGroup.id)) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala index 304f65ed4..a83d7038c 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala @@ -605,5 +605,36 @@ class RecordSetValidationsSpec error.getMessage() shouldBe RecordNameFilterError } } + + //TODO + "canSuperUserUpdateOwnerGroup" should { + "return true when record owner group is the only field changed in the updated record, the zone is shared, " + + "and user is a superuser" in { + val zone = sharedZone + val existing = sharedZoneRecord.copy(ownerGroupId = Some(okGroup.id)) + val rs = sharedZoneRecord.copy(ownerGroupId = Some(dummyGroup.id)) + canSuperUserUpdateOwnerGroup(existing, rs, zone, superUserAuth) should be(true) + } + "return false when record owner group is the only field changed in the updated record, the zone is shared, " + + "and user is NOT a superuser" in { + val zone = sharedZone + val existing = sharedZoneRecord.copy(ownerGroupId = Some(okGroup.id)) + val rs = sharedZoneRecord.copy(ownerGroupId = Some(dummyGroup.id)) + canSuperUserUpdateOwnerGroup(existing, rs, zone, okAuth) should be(false) + } + "return false when record owner group is the only field changed in the updated record, the zone is NOT shared, " + + "and user is a superuser" in { + val zone = okZone + val existing = sharedZoneRecord.copy(ownerGroupId = Some(okGroup.id)) + val rs = sharedZoneRecord.copy(ownerGroupId = Some(dummyGroup.id)) + canSuperUserUpdateOwnerGroup(existing, rs, zone, superUserAuth) should be(false) + } + "return false when record owner group is NOT the only field changed in the updated record" in { + val zone = sharedZone + val existing = sharedZoneRecord.copy(ownerGroupId = Some(okGroup.id), records = List(AData("10.1.1.1"))) + val rs = sharedZoneRecord.copy(ownerGroupId = Some(dummyGroup.id), records = List(AData("10.1.1.2"))) + canSuperUserUpdateOwnerGroup(existing, rs, zone, superUserAuth) should be(false) + } + } } } diff --git a/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala b/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala index 42af71b4a..9172dc9a3 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala @@ -37,7 +37,7 @@ final case class User( email: Option[String] = None, created: DateTime = DateTime.now, id: String = UUID.randomUUID().toString, - isSuper: Boolean = true, + isSuper: Boolean = false, lockStatus: LockStatus = LockStatus.Unlocked, isSupport: Boolean = false, isTest: Boolean = false diff --git a/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala b/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala index 36ba54ec0..ad745ca3c 100644 --- a/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala +++ b/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala @@ -123,7 +123,7 @@ object TestMembershipData { val supportUserAuth: AuthPrincipal = AuthPrincipal(supportUser, Seq(okGroup.id)) - val superUserAuth = AuthPrincipal(superUser, Seq.empty) + val superUserAuth: AuthPrincipal = AuthPrincipal(superUser, Seq.empty) /* GROUP CHANGES */ val okGroupChange: GroupChange = GroupChange( From 1e4d63be2005a06e3058ca53bba83f969c853845 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Thu, 1 Sep 2022 17:44:36 -0400 Subject: [PATCH 015/521] Added additional tests --- .../tests/recordsets/update_recordset_test.py | 57 +++++++++++++++++++ .../domain/access/AccessValidationsSpec.scala | 4 +- .../domain/record/RecordSetServiceSpec.scala | 30 +++++++++- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/modules/api/src/test/functional/tests/recordsets/update_recordset_test.py b/modules/api/src/test/functional/tests/recordsets/update_recordset_test.py index 62d653bdf..a9be4f65e 100644 --- a/modules/api/src/test/functional/tests/recordsets/update_recordset_test.py +++ b/modules/api/src/test/functional/tests/recordsets/update_recordset_test.py @@ -1789,6 +1789,63 @@ def test_update_from_unassociated_user_in_shared_zone_fails(shared_zone_test_con delete_result = shared_client.delete_recordset(zone["id"], create_rs["id"], status=202) shared_client.wait_until_recordset_change_status(delete_result, "Complete") +def test_update_from_super_user_in_shared_zone_passes_when_owner_group_is_only_update(shared_zone_test_context): + """ + Test that updating with a user in the record owner group passes when the zone is set to shared + """ + ok_client = shared_zone_test_context.ok_vinyldns_client + super_user_client = shared_zone_test_context.super_user_client + shared_record_group = shared_zone_test_context.shared_record_group + dummy_group = shared_zone_test_context.dummy_group + shared_client = shared_zone_test_context.shared_zone_vinyldns_client + shared_zone = shared_zone_test_context.shared_zone + update_rs = None + + try: + record_json = create_recordset(shared_zone, "test_shared_success", "A", [{"address": "1.1.1.1"}]) + record_json["ownerGroupId"] = shared_record_group["id"] + create_response = shared_client.create_recordset(record_json, status=202) + update = shared_client.wait_until_recordset_change_status(create_response, "Complete")["recordSet"] + assert_that(update["ownerGroupId"], is_(shared_record_group["id"])) + + update["ownerGroupId"] = dummy_group["id"] + update_response = super_user_client.update_recordset(update, status=202) + update_rs = shared_client.wait_until_recordset_change_status(update_response, "Complete")["recordSet"] + assert_that(update_rs["ownerGroupId"], is_(dummy_group["id"])) + finally: + if update_rs: + delete_result = shared_client.delete_recordset(shared_zone["id"], update_rs["id"], status=202) + shared_client.wait_until_recordset_change_status(delete_result, "Complete") + +def test_update_from_unassociated_user_in_shared_zone_fails_when_owner_group_is_only_update(shared_zone_test_context): + """ + Test that updating with a user in the record owner group passes when the zone is set to shared + """ + ok_client = shared_zone_test_context.ok_vinyldns_client + super_user_client = shared_zone_test_context.super_user_client + shared_record_group = shared_zone_test_context.shared_record_group + dummy_group = shared_zone_test_context.dummy_group + shared_client = shared_zone_test_context.shared_zone_vinyldns_client + shared_zone = shared_zone_test_context.shared_zone + create_rs = None + + try: + record_json = create_recordset(shared_zone, "test_shared_success", "A", [{"address": "1.1.1.1"}]) + record_json["ownerGroupId"] = shared_record_group["id"] + create_response = shared_client.create_recordset(record_json, status=202) + create_rs = shared_client.wait_until_recordset_change_status(create_response, "Complete")["recordSet"] + assert_that(create_rs["ownerGroupId"], is_(shared_record_group["id"])) + + update = create_rs + update["ownerGroupId"] = dummy_group["id"] +# error = ok_client.update_recordset(update, status=403) +# assert_that(error, is_(f'User ok does not have access to update test_shared_success.{zone["name"]}')) + error = ok_client.update_recordset(update, status=422) + assert_that(error, is_(f"User not in record owner group with id \"{dummy_group['id']}\"")) + finally: + if create_rs: + delete_result = shared_client.delete_recordset(shared_zone["id"], create_rs["id"], status=202) + shared_client.wait_until_recordset_change_status(delete_result, "Complete") @pytest.mark.serial def test_update_from_acl_for_shared_zone_passes(shared_zone_test_context): diff --git a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala index a95af3376..0fccc1bb2 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala @@ -296,9 +296,9 @@ class AccessValidationsSpec "return true if the user has AccessLevel.Read or AccessLevel.NoAccess and superUserCanUpdateOwnerGroup is true" in { accessValidationTest.canUpdateRecordSet(userAuthRead, "test", RecordType.A, zoneInRead, - None, true) should be(right) + None, superUserCanUpdateOwnerGroup = true) should be(right) accessValidationTest.canUpdateRecordSet(userAuthNone, "test", RecordType.A, zoneInNone, - None, true) should be(right) + None, superUserCanUpdateOwnerGroup = true) should be(right) } "return true if the user is in the owner group and the zone is shared" in { diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index e405a013f..19c64ebdf 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -603,10 +603,8 @@ class RecordSetServiceSpec result.recordSet.ttl shouldBe newRecord.ttl result.recordSet.ownerGroupId shouldBe Some(oneUserDummyGroup.id) } - "succeed if user is a superuser and zone is shared and the only record attribute being changed is the record owner group." in { val zone = okZone.copy(shared = true, id = "test-owner-group") -// val auth = AuthPrincipal(listOfDummyUsers.head, Seq(oneUserDummyGroup.id)) val auth = superUserAuth val oldRecord = aaaa.copy( name = "test-owner-group-success", @@ -636,6 +634,34 @@ class RecordSetServiceSpec result.recordSet.ownerGroupId shouldBe Some(okGroup.id) } + "fail if user is a superuser and zone is shared and attributes other than record owner group are changed." in { + val zone = okZone.copy(shared = true, id = "test-owner-group") + val auth = superUserAuth + val oldRecord = aaaa.copy( + name = "test-owner-group-success", + zoneId = zone.id, + status = RecordSetStatus.Active, + ownerGroupId = Some(oneUserDummyGroup.id) + ) + + val newRecord = oldRecord.copy(ttl = oldRecord.ttl + 1000, ownerGroupId = Some(okGroup.id)) + + doReturn(IO.pure(Some(zone))) + .when(mockZoneRepo) + .getZone(zone.id) + doReturn(IO.pure(Some(oldRecord))) + .when(mockRecordRepo) + .getRecordSet(newRecord.id) + doReturn(IO.pure(List(oldRecord))) + .when(mockRecordRepo) + .getRecordSetsByName(zone.id, newRecord.name) + doReturn(IO.pure(Some(oneUserDummyGroup))) + .when(mockGroupRepo) + .getGroup(oneUserDummyGroup.id) + + val result = leftResultOf(underTest.updateRecordSet(newRecord, auth).value) + result shouldBe an[NotAuthorizedError] + } "succeed if user is in owner group and zone is shared and new owner group is none" in { val zone = okZone.copy(shared = true, id = "test-owner-group") val auth = AuthPrincipal(listOfDummyUsers.head, Seq(oneUserDummyGroup.id)) From f6b492448be34712003da9ab2378abc72cfcffc5 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Fri, 2 Sep 2022 14:20:15 -0400 Subject: [PATCH 016/521] Update functional tests --- .../tests/recordsets/update_recordset_test.py | 68 ++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/modules/api/src/test/functional/tests/recordsets/update_recordset_test.py b/modules/api/src/test/functional/tests/recordsets/update_recordset_test.py index a9be4f65e..d9ef7194e 100644 --- a/modules/api/src/test/functional/tests/recordsets/update_recordset_test.py +++ b/modules/api/src/test/functional/tests/recordsets/update_recordset_test.py @@ -1791,9 +1791,8 @@ def test_update_from_unassociated_user_in_shared_zone_fails(shared_zone_test_con def test_update_from_super_user_in_shared_zone_passes_when_owner_group_is_only_update(shared_zone_test_context): """ - Test that updating with a user in the record owner group passes when the zone is set to shared + Test that updating with a superuser passes when the zone is set to shared and the owner group is the only change """ - ok_client = shared_zone_test_context.ok_vinyldns_client super_user_client = shared_zone_test_context.super_user_client shared_record_group = shared_zone_test_context.shared_record_group dummy_group = shared_zone_test_context.dummy_group @@ -1819,9 +1818,63 @@ def test_update_from_super_user_in_shared_zone_passes_when_owner_group_is_only_u def test_update_from_unassociated_user_in_shared_zone_fails_when_owner_group_is_only_update(shared_zone_test_context): """ - Test that updating with a user in the record owner group passes when the zone is set to shared + Test that updating with a user not in the record owner group fails when the zone is set to shared and + the owner group is the only change """ ok_client = shared_zone_test_context.ok_vinyldns_client + shared_record_group = shared_zone_test_context.shared_record_group + dummy_group = shared_zone_test_context.dummy_group + shared_client = shared_zone_test_context.shared_zone_vinyldns_client + shared_zone = shared_zone_test_context.shared_zone + create_rs = None + + try: + record_json = create_recordset(shared_zone, "test_shared_fail", "A", [{"address": "1.1.1.1"}]) + record_json["ownerGroupId"] = shared_record_group["id"] + create_response = shared_client.create_recordset(record_json, status=202) + create_rs = shared_client.wait_until_recordset_change_status(create_response, "Complete")["recordSet"] + assert_that(create_rs["ownerGroupId"], is_(shared_record_group["id"])) + + update = create_rs + update["ownerGroupId"] = dummy_group["id"] + error = ok_client.update_recordset(update, status=422) + assert_that(error, is_(f"User not in record owner group with id \"{dummy_group['id']}\"")) + finally: + if create_rs: + delete_result = shared_client.delete_recordset(shared_zone["id"], create_rs["id"], status=202) + shared_client.wait_until_recordset_change_status(delete_result, "Complete") + +def test_update_from_super_user_in_private_zone_fails_when_owner_group_is_only_update(shared_zone_test_context): + """ + Test that updating with a superuser fails when the zone is set to private and the owner group is the only change + """ + ok_client = shared_zone_test_context.ok_vinyldns_client + super_user_client = shared_zone_test_context.super_user_client + ok_record_group = shared_zone_test_context.ok_group + dummy_group = shared_zone_test_context.dummy_group + ok_zone = shared_zone_test_context.ok_zone + create_rs = None + + try: + record_json = create_recordset(ok_zone, "test_private_fail", "A", [{"address": "1.1.1.1"}]) + record_json["ownerGroupId"] = ok_record_group["id"] + create_response = ok_client.create_recordset(record_json, status=202) + create_rs = ok_client.wait_until_recordset_change_status(create_response, "Complete")["recordSet"] + assert_that(create_rs["ownerGroupId"], is_(ok_record_group["id"])) + + update = create_rs + update["ownerGroupId"] = dummy_group["id"] + error = super_user_client.update_recordset(update, status=403) + assert_that(error, is_(f'User super-user does not have access to update test-private-fail.{ok_zone["name"]}')) + finally: + if create_rs: + delete_result = ok_client.delete_recordset(ok_zone["id"], create_rs["id"], status=202) + ok_client.wait_until_recordset_change_status(delete_result, "Complete") + +def test_update_from_super_user_in_shared_zone_fails_when_owner_group_is_not_the_only_update(shared_zone_test_context): + """ + Test that updating with a superuser fails when the zone is set to shared and the owner group is not the only change + """ super_user_client = shared_zone_test_context.super_user_client shared_record_group = shared_zone_test_context.shared_record_group dummy_group = shared_zone_test_context.dummy_group @@ -1830,7 +1883,7 @@ def test_update_from_unassociated_user_in_shared_zone_fails_when_owner_group_is_ create_rs = None try: - record_json = create_recordset(shared_zone, "test_shared_success", "A", [{"address": "1.1.1.1"}]) + record_json = create_recordset(shared_zone, "test_shared_fail", "A", [{"address": "1.1.1.1"}]) record_json["ownerGroupId"] = shared_record_group["id"] create_response = shared_client.create_recordset(record_json, status=202) create_rs = shared_client.wait_until_recordset_change_status(create_response, "Complete")["recordSet"] @@ -1838,10 +1891,9 @@ def test_update_from_unassociated_user_in_shared_zone_fails_when_owner_group_is_ update = create_rs update["ownerGroupId"] = dummy_group["id"] -# error = ok_client.update_recordset(update, status=403) -# assert_that(error, is_(f'User ok does not have access to update test_shared_success.{zone["name"]}')) - error = ok_client.update_recordset(update, status=422) - assert_that(error, is_(f"User not in record owner group with id \"{dummy_group['id']}\"")) + update["ttl"] = update["ttl"] + 100 + error = super_user_client.update_recordset(update, status=403) + assert_that(error, is_(f'User super-user does not have access to update test-shared-fail.{shared_zone["name"]}')) finally: if create_rs: delete_result = shared_client.delete_recordset(shared_zone["id"], create_rs["id"], status=202) From f3bcb1c708c1cb7ca2ce8532e4eb9bfb11be151b Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Tue, 6 Sep 2022 16:27:22 -0400 Subject: [PATCH 017/521] Remove todo comments --- .../api/domain/record/RecordSetServiceIntegrationSpec.scala | 2 +- .../vinyldns/api/domain/access/AccessValidationsSpec.scala | 1 - .../vinyldns/api/domain/record/RecordSetServiceSpec.scala | 2 +- .../vinyldns/api/domain/record/RecordSetValidationsSpec.scala | 3 +-- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala index 044ceecc7..df845b477 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala @@ -314,7 +314,7 @@ class RecordSetServiceIntegrationSpec rightValue(originalRecord).name shouldBe "live-zone-test" } } - //todo + "RecordSetService" should { "create apex record without trailing dot and save record name with trailing dot" in { val newRecord = RecordSet( diff --git a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala index 0fccc1bb2..6f19901e5 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala @@ -256,7 +256,6 @@ class AccessValidationsSpec } } - //TODO "canUpdateRecordSet" should { "return a NotAuthorizedError if the user has AccessLevel.NoAccess" in { val error = leftValue( diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 19c64ebdf..bd0682236 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -326,7 +326,7 @@ class RecordSetServiceSpec result.status shouldBe RecordSetChangeStatus.Pending } } - //TODO + "updateRecordSet" should { "return the recordSet change as the result" in { val oldRecord = aaaa.copy(zoneId = okZone.id, status = RecordSetStatus.Active) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala index a83d7038c..437354bd4 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetValidationsSpec.scala @@ -605,8 +605,7 @@ class RecordSetValidationsSpec error.getMessage() shouldBe RecordNameFilterError } } - - //TODO + "canSuperUserUpdateOwnerGroup" should { "return true when record owner group is the only field changed in the updated record, the zone is shared, " + "and user is a superuser" in { From 7deaa78df7d176f9d75c741913ce259fe909095c Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 8 Sep 2022 13:35:30 +0530 Subject: [PATCH 018/521] disable logs in production --- modules/portal/public/app.js | 2 +- .../lib/controllers/controller.groups.js | 16 +++++++-------- .../lib/controllers/controller.manageZones.js | 4 ++-- .../lib/controllers/controller.membership.js | 20 +++++++++---------- .../lib/controllers/controller.records.js | 12 +++++------ .../lib/controllers/controller.zones.js | 12 +++++------ .../lib/services/utility/service.utility.js | 4 ++-- .../lib/services/zones/service.zones.js | 4 ++-- 8 files changed, 37 insertions(+), 37 deletions(-) diff --git a/modules/portal/public/app.js b/modules/portal/public/app.js index 98cd98de0..8879d4af0 100644 --- a/modules/portal/public/app.js +++ b/modules/portal/public/app.js @@ -13,7 +13,7 @@ angular.module('vinyldns', [ }); $animateProvider .classNameFilter(/toshow/); - //turning off $log + // turning off $log. Change to true for local development and testing $logProvider.debugEnabled(false); }) .controller('AppController', function ($scope, $timeout, profileService, utilityService) { diff --git a/modules/portal/public/lib/controllers/controller.groups.js b/modules/portal/public/lib/controllers/controller.groups.js index 998ce1b60..e3add74f7 100644 --- a/modules/portal/public/lib/controllers/controller.groups.js +++ b/modules/portal/public/lib/controllers/controller.groups.js @@ -68,10 +68,10 @@ angular.module('controller.groups', []).controller('GroupsController', function //prevent user executing service call multiple times //if true prevent, if false allow for execution of rest of code //ng-href='/groups' - $log.log('createGroup::called', $scope.data); + $log.debug('createGroup::called', $scope.data); if ($scope.processing) { - $log.log('createGroup::processing is true; exiting'); + $log.debug('createGroup::processing is true; exiting'); return; } //flag to prevent multiple clicks until previous promise has resolved. @@ -116,7 +116,7 @@ angular.module('controller.groups', []).controller('GroupsController', function $scope.refresh = function () { function success(result) { - $log.log('getGroups:refresh-success', result); + $log.debug('getGroups:refresh-success', result); //update groups $scope.groups.items = result.groups; $scope.groupsLoaded = true; @@ -149,7 +149,7 @@ angular.module('controller.groups', []).controller('GroupsController', function function getGroups() { function success(response) { - $log.log('groupsService::getGroups-success'); + $log.debug('groupsService::getGroups-success'); return response.data; } @@ -163,7 +163,7 @@ angular.module('controller.groups', []).controller('GroupsController', function function getGroupsAbridged() { function success(response) { - $log.log('groupsService::getGroups-success'); + $log.debug('groupsService::getGroups-success'); return response.data; } @@ -202,10 +202,10 @@ angular.module('controller.groups', []).controller('GroupsController', function //prevent user executing service call multiple times //if true prevent, if false allow for execution of rest of code //ng-href='/groups' - $log.log('updateGroup::called', $scope.data); + $log.debug('updateGroup::called', $scope.data); if ($scope.processing) { - $log.log('updateGroup::processing is true; exiting'); + $log.debug('updateGroup::processing is true; exiting'); return; } //flag to prevent multiple clicks until previous promise has resolved. @@ -268,7 +268,7 @@ angular.module('controller.groups', []).controller('GroupsController', function //update user profile data //make user profile available to page $scope.profile = results.data; - $log.log($scope.profile); + $log.debug($scope.profile); //load data in grid $scope.refresh(); } diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 4ec47c526..8411dc75b 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -195,7 +195,7 @@ angular.module('controller.manageZones', []) if ($scope.currentAclRule.priority == 'User') { profileService.getUserDataByUsername($scope.currentAclRule.userName) .then(function (profile) { - $log.log('profileService::getUserDataByUsername-success'); + $log.debug('profileService::getUserDataByUsername-success'); $scope.currentAclRule.userId = profile.data.id; $scope.postUserLookup(type); }) @@ -269,7 +269,7 @@ angular.module('controller.manageZones', []) $scope.refreshZone = function() { function success(response) { - $log.log('recordsService::getZone-success'); + $log.debug('recordsService::getZone-success'); $scope.zoneInfo = response.data.zone; $scope.updateZoneInfo = angular.copy($scope.zoneInfo); $scope.updateZoneInfo.hiddenKey = ''; diff --git a/modules/portal/public/lib/controllers/controller.membership.js b/modules/portal/public/lib/controllers/controller.membership.js index e827a037e..ed25b07cf 100644 --- a/modules/portal/public/lib/controllers/controller.membership.js +++ b/modules/portal/public/lib/controllers/controller.membership.js @@ -29,7 +29,7 @@ angular.module('controller.membership', []).controller('MembershipController', f $scope.getGroupMemberList = function(groupId) { function success(response) { - $log.log('groupsService::getGroupMemberList-success'); + $log.debug('groupsService::getGroupMemberList-success'); return response.data; } return groupsService @@ -42,7 +42,7 @@ angular.module('controller.membership', []).controller('MembershipController', f $scope.getGroup = function(groupId) { function success(response) { - $log.log('groupsService::getGroup-success'); + $log.debug('groupsService::getGroup-success'); return response.data; } return groupsService @@ -71,7 +71,7 @@ angular.module('controller.membership', []).controller('MembershipController', f }; $scope.addMember = function() { - $log.log('addGroupMember::newMemberData', $scope.newMemberData); + $log.debug('addGroupMember::newMemberData', $scope.newMemberData); function lookupAccountSuccess(response) { if (response.data) { $scope.membership.group.members.push({ id: response.data.id }); @@ -110,7 +110,7 @@ angular.module('controller.membership', []).controller('MembershipController', f return user.id != memberId; }; - $log.log('removing group member ' + memberId + ' from group ' + $scope.membership.group.id); + $log.debug('removing group member ' + memberId + ' from group ' + $scope.membership.group.id); $scope.membership.group.admins = $scope.membership.group.admins.filter(keepUser); $scope.membership.group.members = $scope.membership.group.members.filter(keepUser); @@ -138,13 +138,13 @@ angular.module('controller.membership', []).controller('MembershipController', f return user.id != member.id; }; - $log.log('toggleAdmin::toggled for member', member); + $log.debug('toggleAdmin::toggled for member', member); if(member.isAdmin) { - $log.log('toggleAdmin::toggled making an admin'); + $log.debug('toggleAdmin::toggled making an admin'); $scope.membership.group.admins.push({ id: member.id }); } else { - $log.log('toggleAdmin::toggled removing as admin'); + $log.debug('toggleAdmin::toggled removing as admin'); $scope.membership.group.admins = $scope.membership.group.admins.filter(keepUser); } @@ -170,14 +170,14 @@ angular.module('controller.membership', []).controller('MembershipController', f $scope.getGroupInfo = function (id) { //store group membership function getGroupSuccess(result) { - $log.log('refresh::getGroupSuccess-success', result); + $log.debug('refresh::getGroupSuccess-success', result); //update groups $scope.membership.group = result; determineAdmin(); function getGroupMemberListSuccess(result) { - $log.log('refresh::getGroupMemberList-success', result); + $log.debug('refresh::getGroupMemberList-success', result); //update groups $scope.membership.members = result.members; $scope.membershipLoaded = true; @@ -201,7 +201,7 @@ angular.module('controller.membership', []).controller('MembershipController', f $scope.refresh = function () { var id = $location.absUrl().toString(); id = id.substring(id.lastIndexOf('/') + 1); - $log.log('loading group with id ', id); + $log.debug('loading group with id ', id); $scope.isGroupAdmin = false; diff --git a/modules/portal/public/lib/controllers/controller.records.js b/modules/portal/public/lib/controllers/controller.records.js index d9867a39d..be82f5752 100644 --- a/modules/portal/public/lib/controllers/controller.records.js +++ b/modules/portal/public/lib/controllers/controller.records.js @@ -401,7 +401,7 @@ angular.module('controller.records', []) $scope.refreshZone = function() { function success(response) { - $log.log('recordsService::getZone-success'); + $log.debug('recordsService::getZone-success'); $scope.zoneInfo = response.data.zone; // Get current user's groups and determine if they're an admin of this zone getMembership() @@ -416,7 +416,7 @@ angular.module('controller.records', []) $scope.syncZone = function() { function success(response) { - $log.log('recordsService::syncZone-success'); + $log.debug('recordsService::syncZone-success'); location.reload(); } return recordsService @@ -429,7 +429,7 @@ angular.module('controller.records', []) $scope.refreshRecordChangesPreview = function() { function success(response) { - $log.log('recordsService::getRecordSetChanges-success'); + $log.debug('recordsService::getRecordSetChanges-success'); var newChanges = []; angular.forEach(response.data.recordSetChanges, function(change) { newChanges.push(change); @@ -447,7 +447,7 @@ angular.module('controller.records', []) $scope.refreshRecordChanges = function() { changePaging = pagingService.resetPaging(changePaging); function success(response) { - $log.log('recordsService::getRecordSetChanges-success'); + $log.debug('recordsService::getRecordSetChanges-success'); changePaging.next = response.data.nextId; updateChangeDisplay(response.data.recordSetChanges) } @@ -470,7 +470,7 @@ angular.module('controller.records', []) $scope.refreshRecords = function() { recordsPaging = pagingService.resetPaging(recordsPaging); function success(response) { - $log.log('recordsService::listRecordSetsByZone-success ('+ response.data.recordSets.length +' records)'); + $log.debug('recordsService::listRecordSetsByZone-success ('+ response.data.recordSets.length +' records)'); recordsPaging.next = response.data.nextId; updateRecordDisplay(response.data.recordSets); } @@ -608,7 +608,7 @@ angular.module('controller.records', []) function profileSuccess(results) { if (results.data) { $scope.profile = results.data; - $log.log('profileService::getAuthenticatedUserData-success'); + $log.debug('profileService::getAuthenticatedUserData-success'); } } diff --git a/modules/portal/public/lib/controllers/controller.zones.js b/modules/portal/public/lib/controllers/controller.zones.js index d4057fcfd..21aec2991 100644 --- a/modules/portal/public/lib/controllers/controller.zones.js +++ b/modules/portal/public/lib/controllers/controller.zones.js @@ -70,7 +70,7 @@ angular.module('controller.zones', []) }); $scope.canAccessGroup = function(groupId) { - return $scope.myGroupIds !== "undefined" && $scope.myGroupIds.indexOf(groupId) > -1; + return $scope.myGroupIds !== undefined && $scope.myGroupIds.indexOf(groupId) > -1; }; $scope.canAccessZone = function(accessLevel) { @@ -89,7 +89,7 @@ angular.module('controller.zones', []) zonesService .getZones(zonesPaging.maxItems, undefined, $scope.query) .then(function (response) { - $log.log('zonesService::getZones-success (' + response.data.zones.length + ' zones)'); + $log.debug('zonesService::getZones-success (' + response.data.zones.length + ' zones)'); zonesPaging.next = response.data.nextId; updateZoneDisplay(response.data.zones); if (!$scope.query.length) { @@ -103,7 +103,7 @@ angular.module('controller.zones', []) zonesService .getZones(zonesPaging.maxItems, undefined, $scope.query, true) .then(function (response) { - $log.log('zonesService::getZones-success (' + response.data.zones.length + ' zones)'); + $log.debug('zonesService::getZones-success (' + response.data.zones.length + ' zones)'); allZonesPaging.next = response.data.nextId; updateAllZonesDisplay(response.data.zones); }) @@ -116,7 +116,7 @@ angular.module('controller.zones', []) $scope.zones = zones; $scope.myZoneIds = zones.map(function(zone) {return zone['id']}); $scope.zonesLoaded = true; - $log.log("Displaying my zones: ", $scope.zones); + $log.debug("Displaying my zones: ", $scope.zones); if($scope.zones.length > 0) { $("td.dataTables_empty").hide(); } else { @@ -127,7 +127,7 @@ angular.module('controller.zones', []) function updateAllZonesDisplay (zones) { $scope.allZones = zones; $scope.allZonesLoaded = true; - $log.log("Displaying all zones: ", $scope.allZones); + $log.debug("Displaying all zones: ", $scope.allZones); if($scope.allZones.length > 0) { $("td.dataTables_empty").hide(); } else { @@ -139,7 +139,7 @@ angular.module('controller.zones', []) $scope.addZoneConnection = function () { if ($scope.processing) { - $log.log('zoneConnection::processing is true; exiting'); + $log.debug('zoneConnection::processing is true; exiting'); return; } diff --git a/modules/portal/public/lib/services/utility/service.utility.js b/modules/portal/public/lib/services/utility/service.utility.js index de656c3b4..5fb97ebaa 100644 --- a/modules/portal/public/lib/services/utility/service.utility.js +++ b/modules/portal/public/lib/services/utility/service.utility.js @@ -39,7 +39,7 @@ angular.module('service.utility', []) msg += stripQuotes(error.data); } - $log.log(type, error); + $log.debug(type, error); return { type: "danger", content: msg }; @@ -47,7 +47,7 @@ angular.module('service.utility', []) this.success = function(message, response, type) { var msg = "HTTP " + response.status + " (" + response.statusText + "): " + message; - $log.log(type, response); + $log.debug(type, response); return { type: "success", content: msg }; diff --git a/modules/portal/public/lib/services/zones/service.zones.js b/modules/portal/public/lib/services/zones/service.zones.js index 4ac031860..9303cff6c 100644 --- a/modules/portal/public/lib/services/zones/service.zones.js +++ b/modules/portal/public/lib/services/zones/service.zones.js @@ -40,7 +40,7 @@ angular.module('service.zones', []) this.sendZone = function (payload) { var sanitizedPayload = this.sanitizeConnections(payload); - $log.info("service.zones: sending zone", sanitizedPayload); + $log.debug("service.zones: sending zone", sanitizedPayload); return $http.post("/api/zones", sanitizedPayload, {headers: utilityService.getCsrfHeader()}); }; @@ -50,7 +50,7 @@ angular.module('service.zones', []) this.updateZone = function (id, payload) { var sanitizedPayload = this.sanitizeConnections(payload); - $log.info("service.zones: updating zone", sanitizedPayload); + $log.debug("service.zones: updating zone", sanitizedPayload); return $http.put("/api/zones/"+id, sanitizedPayload, {headers: utilityService.getCsrfHeader()}); }; From 3c3f2a0c00c75ff45357d56af6132129ea4c70eb Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Fri, 9 Sep 2022 12:57:34 +0530 Subject: [PATCH 019/521] Added Cname validation without IPaddress --- .../scala/vinyldns/api/domain/DomainValidations.scala | 8 ++++++++ .../api/domain/batch/BatchChangeValidations.scala | 2 +- .../vinyldns/core/domain/DomainValidationErrors.scala | 5 +++++ .../scala/vinyldns/core/domain/SingleChangeError.scala | 3 ++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala index 459285267..9fc771d37 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala @@ -27,6 +27,7 @@ import scala.util.matching.Regex Object to house common domain validations */ object DomainValidations { + val validFQDNRegex: Regex = """^(?:([0-9a-zA-Z_]{1,63}|[0-9a-zA-Z_]{1}[0-9a-zA-Z\-\/_]{0,61}[0-9a-zA-Z_]{1}|[*.]{2}[0-9a-zA-Z\-\/_]{0,60}[0-9a-zA-Z_]{1})\.)*$""".r val validIpv4Regex: Regex = @@ -57,6 +58,13 @@ object DomainValidations { val MX_PREFERENCE_MIN_VALUE: Int = 0 val MX_PREFERENCE_MAX_VALUE: Int = 65535 + // Cname check - Cname should not be IP address + def validateCName(name: Fqdn): ValidatedNel[DomainValidationError, Fqdn] = + validateIpv4Address(name.fqdn.dropRight(1)).isValid match { + case true => InvalidCName(name.toString).invalidNel + case false => validateHostName(name.fqdn).map(_ => name) + } + def validateHostName(name: Fqdn): ValidatedNel[DomainValidationError, Fqdn] = validateHostName(name.fqdn).map(_ => name) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index efa72e443..a9d1e9b54 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -234,7 +234,7 @@ class BatchChangeValidations( record match { case a: AData => validateIpv4Address(a.address).asUnit case aaaa: AAAAData => validateIpv6Address(aaaa.address).asUnit - case cname: CNAMEData => validateHostName(cname.cname).asUnit + case cname: CNAMEData => validateCName(cname.cname).asUnit case ptr: PTRData => validateHostName(ptr.ptrdname).asUnit case txt: TXTData => validateTxtTextLength(txt.text).asUnit case mx: MXData => diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index 91486ce79..0590c4225 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -52,6 +52,11 @@ final case class InvalidDomainName(param: String) extends DomainValidationError "joined by dots, and terminated with a dot." } +final case class InvalidCName(param: String) extends DomainValidationError { + def message: String = + s"""Invalid Cname: "$param", valid cname should not be IP address""" +} + final case class InvalidLength(param: String, minLengthInclusive: Int, maxLengthInclusive: Int) extends DomainValidationError { def message: String = diff --git a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala index a7ed47b4e..99731d7db 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala @@ -36,7 +36,7 @@ object DomainValidationErrorType extends Enumeration { CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch, RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError, NewMultiRecordError, CnameAtZoneApexError, RecordRequiresManualReview, UnsupportedOperation, - DeleteRecordDataDoesNotExist = Value + DeleteRecordDataDoesNotExist, InvalidCName = Value // $COVERAGE-OFF$ def from(error: DomainValidationError): DomainValidationErrorType = @@ -72,6 +72,7 @@ object DomainValidationErrorType extends Enumeration { case _: RecordRequiresManualReview => RecordRequiresManualReview case _: UnsupportedOperation => UnsupportedOperation case _: DeleteRecordDataDoesNotExist => DeleteRecordDataDoesNotExist + case _: InvalidCName => InvalidCName } // $COVERAGE-ON$ } From 2180bae712d362d756a4e74255b25ccf180f9e19 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Fri, 9 Sep 2022 15:07:29 +0530 Subject: [PATCH 020/521] Added tests --- .../api/domain/DomainValidationsSpec.scala | 51 ++++++++++++++++++- .../batch/BatchChangeValidationsSpec.scala | 15 ++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala index b547dec43..64ce6b3a8 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala @@ -23,7 +23,7 @@ import org.scalatest._ import org.scalatest.propspec.AnyPropSpec import org.scalatest.matchers.should.Matchers import vinyldns.api.ValidationTestImprovements._ -import vinyldns.core.domain.{InvalidDomainName, InvalidLength} +import vinyldns.core.domain.{Fqdn, InvalidCName, InvalidDomainName, InvalidLength} class DomainValidationsSpec extends AnyPropSpec @@ -78,6 +78,55 @@ class DomainValidationsSpec validateHostName("asterisk.domain*.name.") shouldBe invalid } + property("Shortests fqdn name should be valid") { + val fqdn = Fqdn("a.") + validateCName(fqdn) shouldBe valid + } + + property("Ip address in cname should be invalid") { + val fqdn = Fqdn("1.2.3.4") + println(validateCName(fqdn)) + validateCName(fqdn) shouldBe invalid + } + + property("Longest fqdn name should be valid") { + val fqdn = Fqdn(("a" * 50 + ".") * 5) + validateCName(fqdn) shouldBe valid + } + + property("fqdn name should pass property-based testing") { + forAll(domainGenerator) { domain: String => + val domains= Fqdn(domain) + whenever(validateCName(domains).isValid) { + domains.fqdn.length should be > 0 + domains.fqdn.length should be < 256 + (domains.fqdn should fullyMatch).regex(validFQDNRegex) + domains.fqdn should endWith(".") + } + } + } + + property("fqdn names beginning with invalid characters should fail with InvalidDomainName") { + validateCName(Fqdn("/slash.domain.name.")).failWith[InvalidCName] + validateCName(Fqdn("-hyphen.domain.name.")).failWith[InvalidCName] + } + + property("fqdn names with underscores should pass property-based testing") { + validateCName(Fqdn("_underscore.domain.name.")).isValid + validateCName(Fqdn("under_score.domain.name.")).isValid + validateCName(Fqdn("underscore._domain.name.")).isValid + } + + // For wildcard records. '*' can only be in the beginning followed by '.' and domain name + property("fqdn names beginning with asterisk should pass property-based testing") { + validateCName(Fqdn("*.domain.name.")) shouldBe valid + validateCName(Fqdn("aste*risk.domain.name.")) shouldBe invalid + validateCName(Fqdn("*asterisk.domain.name.")) shouldBe invalid + validateCName(Fqdn("asterisk*.domain.name.")) shouldBe invalid + validateCName(Fqdn("asterisk.*domain.name."))shouldBe invalid + validateCName(Fqdn("asterisk.domain*.name.")) shouldBe invalid + } + property("Valid Ipv4 addresses should pass property-based testing") { forAll(validIpv4Gen) { validIp: String => val res = validateIpv4Address(validIp) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index f322b2523..8ce5bf987 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -715,6 +715,21 @@ class BatchChangeValidationsSpec result should haveInvalid[DomainValidationError](InvalidDomainName(s"$invalidCNAMERecordData.")) } + property("""validateAddChangeInput: should fail with Invalid CNAME + |if validateRecordData fails for IPv4 Address in CNAME record data""".stripMargin) { + val invalidCNAMERecordData = "1.2.3.4" + val change = + AddChangeInput( + "test.comcast.com.", + RecordType.CNAME, + ttl, + CNAMEData(Fqdn(invalidCNAMERecordData)) + ) + val result = validateAddChangeInput(change, false) + + result should haveInvalid[DomainValidationError](InvalidCName(s"Fqdn($invalidCNAMERecordData.)")) + } + property("""validateAddChangeInput: should fail with InvalidLength |if validateRecordData fails for invalid CNAME record data""".stripMargin) { val invalidCNAMERecordData = "s" * 256 From 813138b5971723d6610c0c0c40adc9dbe4621b45 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Fri, 9 Sep 2022 15:48:43 +0530 Subject: [PATCH 021/521] update --- .../scala/vinyldns/api/domain/DomainValidationsSpec.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala index 64ce6b3a8..a7ad20263 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala @@ -23,7 +23,7 @@ import org.scalatest._ import org.scalatest.propspec.AnyPropSpec import org.scalatest.matchers.should.Matchers import vinyldns.api.ValidationTestImprovements._ -import vinyldns.core.domain.{Fqdn, InvalidCName, InvalidDomainName, InvalidLength} +import vinyldns.core.domain.{Fqdn, InvalidDomainName, InvalidLength} class DomainValidationsSpec extends AnyPropSpec @@ -107,8 +107,8 @@ class DomainValidationsSpec } property("fqdn names beginning with invalid characters should fail with InvalidDomainName") { - validateCName(Fqdn("/slash.domain.name.")).failWith[InvalidCName] - validateCName(Fqdn("-hyphen.domain.name.")).failWith[InvalidCName] + validateCName(Fqdn("/slash.domain.name.")).failWith[InvalidDomainName] + validateCName(Fqdn("-hyphen.domain.name.")).failWith[InvalidDomainName] } property("fqdn names with underscores should pass property-based testing") { From f29ae4af8b761dba5e878d34377560a915b6b9ec Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Fri, 9 Sep 2022 17:13:04 +0530 Subject: [PATCH 022/521] Added func tests --- .../test/functional/tests/batch/create_batch_change_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index 3a56caf63..c8d8b5b3a 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -1945,7 +1945,8 @@ def test_cname_recordtype_add_checks(shared_zone_test_context): get_change_CNAME_json(existing_forward_fqdn), get_change_CNAME_json(existing_cname_fqdn), get_change_CNAME_json(f"0.{ip4_zone_name}", cname="duplicate.in.db."), - get_change_CNAME_json(f"user-add-unauthorized.{dummy_zone_name}") + get_change_CNAME_json(f"user-add-unauthorized.{dummy_zone_name}"), + get_change_CNAME_json(f"invalid-ipv4-{parent_zone_name}", cname="1.2.3.4") ] } @@ -2021,6 +2022,9 @@ def test_cname_recordtype_add_checks(shared_zone_test_context): assert_failed_change_in_error_response(response[16], input_name=f"user-add-unauthorized.{dummy_zone_name}", record_type="CNAME", record_data="test.com.", error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + assert_failed_change_in_error_response(response[17], input_name=f"invalid-ipv4-{parent_zone_name}", record_type="CNAME", record_data="1.2.3.4.", + error_messages=[f'Invalid Cname: "Fqdn(1.2.3.4.)", valid cname should not be IP address']) + finally: clear_recordset_list(to_delete, client) From 6a07f533cb528345edb72b97a77dc03e8f172bae Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Mon, 12 Sep 2022 18:17:42 +0530 Subject: [PATCH 023/521] Added record type sort in API --- .../domain/record/RecordSetCacheService.scala | 4 +-- .../api/domain/record/RecordSetService.scala | 22 ++++++++++----- .../record/RecordSetServiceAlgebra.scala | 10 ++++--- .../api/domain/zone/ZoneViewLoader.scala | 5 ++-- .../vinyldns/api/route/RecordSetRouting.scala | 27 ++++++++++++------- .../domain/record/ListRecordSetResults.scala | 13 +++++++++ .../domain/record/RecordSetRepository.scala | 4 ++- .../repository/MySqlRecordSetRepository.scala | 20 ++++++++++---- 8 files changed, 76 insertions(+), 29 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetCacheService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetCacheService.scala index 6c90aaa1a..a0d6bd06f 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetCacheService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetCacheService.scala @@ -19,7 +19,7 @@ package vinyldns.api.domain.record import cats.effect.IO import org.slf4j.LoggerFactory import scalikejdbc.DB -import vinyldns.core.domain.record.{NameSort, ListRecordSetResults, RecordSetCacheRepository, RecordSetRepository} +import vinyldns.core.domain.record.{ListRecordSetResults, NameSort, RecordSetCacheRepository, RecordSetRepository, RecordTypeSort} import vinyldns.mysql.TransactionProvider @@ -30,7 +30,7 @@ class RecordSetCacheService(recordSetRepository: RecordSetRepository, final def populateRecordSetCache(nextId: Option[String] = None): IO[ListRecordSetResults] = { logger.info(s"Populating recordset data. Starting at $nextId") for { - result <- recordSetRepository.listRecordSets(None, nextId, Some(1000), None, None, None, NameSort.ASC) + result <- recordSetRepository.listRecordSets(None, nextId, Some(1000), None, None, None, NameSort.ASC, RecordTypeSort.ASC) _ <- executeWithinTransaction { db: DB => IO { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index cc4f18331..24cbc4db7 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -35,6 +35,7 @@ import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordType.RecordType import vinyldns.core.domain.DomainHelpers.ensureTrailingDot import vinyldns.core.domain.backend.{Backend, BackendResolver} +import vinyldns.core.domain.record.RecordTypeSort.RecordTypeSort import scala.util.matching.Regex @@ -203,7 +204,8 @@ class RecordSetService( recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupFilter: Option[String], nameSort: NameSort, - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + recordTypeSort: RecordTypeSort ): Result[ListGlobalRecordSetsResponse] = for { _ <- validRecordNameFilterLength(recordNameFilter).toResult @@ -216,7 +218,8 @@ class RecordSetService( Some(formattedRecordNameFilter), recordTypeFilter, recordOwnerGroupFilter, - nameSort + nameSort, + recordTypeSort ) .toResult[ListRecordSetResults] rsOwnerGroupIds = recordSetResults.recordSets.flatMap(_.ownerGroupId).toSet @@ -254,7 +257,8 @@ class RecordSetService( recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupFilter: Option[String], nameSort: NameSort, - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + recordTypeSort: RecordTypeSort ): Result[ListGlobalRecordSetsResponse] = { for { _ <- validRecordNameFilterLength(recordNameFilter).toResult @@ -279,7 +283,8 @@ class RecordSetService( Some(formattedRecordNameFilter), recordTypeFilter, recordOwnerGroupFilter, - nameSort + nameSort, + recordTypeSort ).toResult[ListRecordSetResults] } rsOwnerGroupIds = recordSetResults.recordSets.flatMap(_.ownerGroupId).toSet @@ -307,7 +312,8 @@ class RecordSetService( recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupFilter: Option[String], nameSort: NameSort, - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + recordTypeSort: RecordTypeSort ): Result[ListRecordSetsByZoneResponse] = for { zone <- getZone(zoneId) @@ -320,7 +326,8 @@ class RecordSetService( recordNameFilter, recordTypeFilter, recordOwnerGroupFilter, - nameSort + nameSort, + recordTypeSort ) .toResult[ListRecordSetResults] rsOwnerGroupIds = recordSetResults.recordSets.flatMap(_.ownerGroupId).toSet @@ -335,7 +342,8 @@ class RecordSetService( recordSetResults.recordNameFilter, recordSetResults.recordTypeFilter, recordSetResults.recordOwnerGroupFilter, - recordSetResults.nameSort + recordSetResults.nameSort, + recordTypeSort ) def getRecordSetChange( diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala index 5d7089196..b24aa1f33 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala @@ -23,6 +23,7 @@ import vinyldns.core.domain.zone.ZoneCommandResult import vinyldns.api.route.{ListGlobalRecordSetsResponse, ListRecordSetsByZoneResponse} import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordType.RecordType +import vinyldns.core.domain.record.RecordTypeSort.RecordTypeSort import vinyldns.core.domain.record.{RecordSet, RecordSetChange} trait RecordSetServiceAlgebra { @@ -54,7 +55,8 @@ trait RecordSetServiceAlgebra { recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupId: Option[String], nameSort: NameSort, - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + recordTypeSort: RecordTypeSort ): Result[ListGlobalRecordSetsResponse] /** @@ -76,7 +78,8 @@ trait RecordSetServiceAlgebra { recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupId: Option[String], nameSort: NameSort, - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + recordTypeSort: RecordTypeSort ): Result[ListGlobalRecordSetsResponse] def listRecordSetsByZone( @@ -87,7 +90,8 @@ trait RecordSetServiceAlgebra { recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupId: Option[String], nameSort: NameSort, - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + recordTypeSort: RecordTypeSort ): Result[ListRecordSetsByZoneResponse] def getRecordSetChange( diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneViewLoader.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneViewLoader.scala index f841d48fc..e1cf02d0d 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneViewLoader.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneViewLoader.scala @@ -20,7 +20,7 @@ import cats.effect._ import org.slf4j.LoggerFactory import vinyldns.api.backend.dns.DnsConversions import vinyldns.core.domain.backend.Backend -import vinyldns.core.domain.record.{NameSort, RecordSetCacheRepository, RecordSetRepository} +import vinyldns.core.domain.record.{NameSort, RecordSetCacheRepository, RecordSetRepository, RecordTypeSort} import vinyldns.core.domain.zone.Zone import vinyldns.core.route.Monitored @@ -69,7 +69,8 @@ case class VinylDNSZoneViewLoader( recordNameFilter = None, recordTypeFilter = None, recordOwnerGroupFilter = None, - nameSort = NameSort.ASC + nameSort = NameSort.ASC, + recordTypeSort = RecordTypeSort.ASC ) .map { result => VinylDNSZoneViewLoader.logger.info( diff --git a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala index 72cd04596..4974c4af2 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala @@ -26,7 +26,8 @@ import vinyldns.api.config.LimitsConfig import vinyldns.api.domain.zone._ import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordType.RecordType -import vinyldns.core.domain.record.{NameSort, RecordSet, RecordType} +import vinyldns.core.domain.record.RecordTypeSort.RecordTypeSort +import vinyldns.core.domain.record.{NameSort, RecordSet, RecordType, RecordTypeSort} import vinyldns.core.domain.zone.ZoneCommandResult import scala.concurrent.duration._ @@ -52,7 +53,8 @@ case class ListRecordSetsByZoneResponse( recordNameFilter: Option[String] = None, recordTypeFilter: Option[Set[RecordType]] = None, recordOwnerGroupFilter: Option[String] = None, - nameSort: NameSort + nameSort: NameSort, + recordTypeSort: RecordTypeSort ) class RecordSetRoute( @@ -100,7 +102,8 @@ class RecordSetRoute( "recordNameFilter".?, "recordTypeFilter".?, "recordOwnerGroupFilter".?, - "nameSort".as[String].?("ASC") + "nameSort".as[String].?("ASC"), + "recordTypeSort".as[String].?("None") ) { ( startFrom: Option[String], @@ -108,7 +111,8 @@ class RecordSetRoute( recordNameFilter: Option[String], recordTypeFilter: Option[String], recordOwnerGroupFilter: Option[String], - nameSort: String + nameSort: String, + recordTypeSort: String ) => val convertedRecordTypeFilter = convertRecordTypeFilter(recordTypeFilter) handleRejections(invalidQueryHandler) { @@ -126,8 +130,10 @@ class RecordSetRoute( convertedRecordTypeFilter, recordOwnerGroupFilter, NameSort.find(nameSort), - _ - ) + _, + RecordTypeSort.find(recordTypeSort), + + ) ) { rsResponse => complete(StatusCodes.OK, rsResponse) } @@ -144,7 +150,8 @@ class RecordSetRoute( "recordNameFilter".as[String], "recordTypeFilter".?, "recordOwnerGroupFilter".?, - "nameSort".as[String].?("ASC") + "nameSort".as[String].?("ASC"), + "recordTypeSort".as[String].?("ASC") ) { ( startFrom: Option[String], @@ -152,7 +159,8 @@ class RecordSetRoute( recordNameFilter: String, recordTypeFilter: Option[String], recordOwnerGroupFilter: Option[String], - nameSort: String + nameSort: String, + recordTypeSort: String ) => val convertedRecordTypeFilter = convertRecordTypeFilter(recordTypeFilter) handleRejections(invalidQueryHandler) { @@ -169,7 +177,8 @@ class RecordSetRoute( convertedRecordTypeFilter, recordOwnerGroupFilter, NameSort.find(nameSort), - _ + _, + RecordTypeSort.find(recordTypeSort) ) ) { rsResponse => complete(StatusCodes.OK, rsResponse) diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala index f076ab457..7739b7049 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala @@ -26,6 +26,19 @@ object NameSort extends Enumeration { def find(value: String): Value = value.toUpperCase match { case "DESC" => NameSort.DESC case _ => NameSort.ASC + + } +} + +object RecordTypeSort extends Enumeration { + type RecordTypeSort = Value + val ASC, DESC, NONE = Value + + def find(value: String): Value = value.toUpperCase match { + case "DESC" => RecordTypeSort.DESC + case "ASC" => RecordTypeSort.ASC + case _ => NONE + } } diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordSetRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordSetRepository.scala index bafa7d8ec..1699855f0 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordSetRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordSetRepository.scala @@ -20,6 +20,7 @@ import cats.effect._ import scalikejdbc.DB import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordType.RecordType +import vinyldns.core.domain.record.RecordTypeSort.RecordTypeSort import vinyldns.core.repository.Repository trait RecordSetRepository extends Repository { @@ -33,7 +34,8 @@ trait RecordSetRepository extends Repository { recordNameFilter: Option[String], recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupFilter: Option[String], - nameSort: NameSort + nameSort: NameSort, + recordTypeSort: RecordTypeSort ): IO[ListRecordSetResults] def getRecordSets(zoneId: String, name: String, typ: RecordType): IO[List[RecordSet]] diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala index adf811525..652a118d3 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala @@ -20,9 +20,10 @@ import cats.effect._ import cats.implicits._ import org.slf4j.LoggerFactory import scalikejdbc._ -import vinyldns.core.domain.record.NameSort.{ASC, NameSort} -import vinyldns.core.domain.record.RecordType.RecordType +import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record._ +import vinyldns.core.domain.record.RecordType.RecordType +import vinyldns.core.domain.record.RecordTypeSort.RecordTypeSort import vinyldns.core.protobuf.ProtobufConversions import vinyldns.core.route.Monitored import vinyldns.proto.VinylDNSProto @@ -176,7 +177,8 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored { recordNameFilter: Option[String], recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupFilter: Option[String], - nameSort: NameSort + nameSort: NameSort, + recordTypeSort: RecordTypeSort, ): IO[ListRecordSetResults] = monitor("repo.RecordSet.listRecordSets") { IO { @@ -226,19 +228,27 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored { val opts = (zoneAndNameFilters ++ sortBy ++ typeFilter ++ ownerGroupFilter).toList - val qualifiers = if (nameSort == ASC) { + val nameSortQualifiers = if (nameSort == NameSort.ASC) { sqls"ORDER BY fqdn ASC, type ASC " } else { sqls"ORDER BY fqdn DESC, type ASC " } + val recordTypeSortQualifiers = if (recordTypeSort == RecordTypeSort.ASC) { + sqls"ORDER BY type ASC" + } + else { + sqls"ORDER BY type DESC" + } + val recordLimit = maxPlusOne match { case Some(limit) => sqls"LIMIT $limit" case None => sqls"" } - val finalQualifiers = qualifiers.append(recordLimit) + val finalQualifiers = if (recordTypeSort == RecordTypeSort.NONE) nameSortQualifiers.append(recordLimit) + else recordTypeSortQualifiers.append(recordLimit) // construct query val initialQuery = sqls"SELECT data, fqdn FROM recordset " From f9cc06f2618ca64cfe7ebd1673c51089205f2055 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Tue, 13 Sep 2022 11:43:27 +0530 Subject: [PATCH 024/521] Added record type sort in portal --- .../repository/MySqlRecordSetRepository.scala | 13 ++++++------ .../zones/zoneTabs/manageRecords.scala.html | 6 +++++- .../lib/controllers/controller.records.js | 20 ++++++++++++++++--- .../lib/services/records/service.records.js | 8 ++++++-- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala index 652a118d3..a7fa96e5e 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala @@ -235,11 +235,11 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored { sqls"ORDER BY fqdn DESC, type ASC " } - val recordTypeSortQualifiers = if (recordTypeSort == RecordTypeSort.ASC) { - sqls"ORDER BY type ASC" - } - else { - sqls"ORDER BY type DESC" + val recordTypeSortQualifiers = recordTypeSort match { + case RecordTypeSort.ASC => sqls"ORDER BY type ASC" + case RecordTypeSort.DESC => sqls"ORDER BY type DESC" + case RecordTypeSort.NONE => nameSortQualifiers + } val recordLimit = maxPlusOne match { @@ -247,8 +247,7 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored { case None => sqls"" } - val finalQualifiers = if (recordTypeSort == RecordTypeSort.NONE) nameSortQualifiers.append(recordLimit) - else recordTypeSortQualifiers.append(recordLimit) + val finalQualifiers = recordTypeSortQualifiers.append(recordLimit) // construct query val initialQuery = sqls"SELECT data, fqdn FROM recordset " diff --git a/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html b/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html index 850bd8a81..5af6cc12e 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html @@ -109,7 +109,11 @@ - Type + + Type + + + TTL Record Data @if(meta.sharedDisplayEnabled) { diff --git a/modules/portal/public/lib/controllers/controller.records.js b/modules/portal/public/lib/controllers/controller.records.js index d9867a39d..d00a85a74 100644 --- a/modules/portal/public/lib/controllers/controller.records.js +++ b/modules/portal/public/lib/controllers/controller.records.js @@ -23,7 +23,9 @@ angular.module('controller.records', []) $scope.query = ""; $scope.nameSort = "asc"; + $scope.recordTypeSort = "none"; $scope.nameSortSymbol = "fa-chevron-up"; + $scope.recordTypeSortSymbol = "fa-chevron-up"; $scope.alerts = []; $scope.recordTypes = ['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'PTR', 'SRV', 'NAPTR', 'SSHFP', 'TXT']; @@ -475,7 +477,7 @@ angular.module('controller.records', []) updateRecordDisplay(response.data.recordSets); } return recordsService - .listRecordSetsByZone($scope.zoneId, recordsPaging.maxItems, undefined, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort) + .listRecordSetsByZone($scope.zoneId, recordsPaging.maxItems, undefined, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort, $scope.recordTypeSort) .then(success) .catch(function (error){ handleError(error, 'recordsService::listRecordSetsByZone-failure'); @@ -516,7 +518,7 @@ angular.module('controller.records', []) $scope.prevPage = function() { var startFrom = pagingService.getPrevStartFrom(recordsPaging); return recordsService - .listRecordSetsByZone($scope.zoneId, recordsPaging.maxItems, startFrom, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort) + .listRecordSetsByZone($scope.zoneId, recordsPaging.maxItems, startFrom, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort, $scope.recordTypeSort) .then(function(response) { recordsPaging = pagingService.prevPageUpdate(response.data.nextId, recordsPaging); updateRecordDisplay(response.data.recordSets); @@ -528,7 +530,7 @@ angular.module('controller.records', []) $scope.nextPage = function() { return recordsService - .listRecordSetsByZone($scope.zoneId, recordsPaging.maxItems, recordsPaging.next, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort) + .listRecordSetsByZone($scope.zoneId, recordsPaging.maxItems, recordsPaging.next, $scope.query, $scope.selectedRecordTypes.toString(), $scope.nameSort, $scope.recordTypeSort) .then(function(response) { var recordSets = response.data.recordSets; recordsPaging = pagingService.nextPageUpdate(recordSets, response.data.nextId, recordsPaging); @@ -543,6 +545,7 @@ angular.module('controller.records', []) }; $scope.toggleNameSort = function() { + $scope.recordTypeSort = "NONE" if ($scope.nameSort == "asc") { $scope.nameSort = "desc"; $scope.nameSortSymbol = "fa-chevron-down"; @@ -553,6 +556,17 @@ angular.module('controller.records', []) return $scope.refreshRecords(); }; + $scope.toggleRecordtypeSort = function() { + if ($scope.recordTypeSort == "asc") { + $scope.recordTypeSort = "desc"; + $scope.recordTypeSortSymbol = "fa-chevron-down"; + } else { + $scope.recordTypeSort = "asc"; + $scope.recordTypeSortSymbol = "fa-chevron-up"; + } + return $scope.refreshRecords(); + }; + $scope.toggleCheckedRecordType = function(recordType) { if($scope.selectedRecordTypes.includes(recordType)) { $scope.selectedRecordTypes.splice($scope.selectedRecordTypes.indexOf(recordType), 1); diff --git a/modules/portal/public/lib/services/records/service.records.js b/modules/portal/public/lib/services/records/service.records.js index 2bcf3cb0b..c4fff2fe9 100644 --- a/modules/portal/public/lib/services/records/service.records.js +++ b/modules/portal/public/lib/services/records/service.records.js @@ -55,7 +55,7 @@ angular.module('service.records', []) return promis }; - this.listRecordSetsByZone = function (id, limit, startFrom, nameFilter, typeFilter, nameSort) { + this.listRecordSetsByZone = function (id, limit, startFrom, nameFilter, typeFilter, nameSort, recordTypeSort) { if (nameFilter == "") { nameFilter = null; } @@ -65,12 +65,16 @@ angular.module('service.records', []) if (nameSort == "") { nameSort = null; } + if (recordTypeSort == "") { + recordTypeSort = null; + } var params = { "maxItems": limit, "startFrom": startFrom, "recordNameFilter": nameFilter, "recordTypeFilter": typeFilter, - "nameSort": nameSort + "nameSort": nameSort, + "recordTypeSort": recordTypeSort }; var url = utilityService.urlBuilder("/api/zones/"+id+"/recordsets", params); return $http.get(url); From f2cafd5d89aa80b0be9ffd96144a752feafd7cff Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Tue, 13 Sep 2022 16:40:38 +0530 Subject: [PATCH 025/521] Added tests --- .../route53/Route53ApiIntegrationSpec.scala | 4 +- .../vinyldns/api/route/RecordSetRouting.scala | 4 +- .../domain/record/RecordSetServiceSpec.scala | 41 +++++++++----- .../api/domain/zone/ZoneViewLoaderSpec.scala | 7 ++- .../api/engine/ZoneSyncHandlerSpec.scala | 6 +- .../api/repository/EmptyRepositories.scala | 10 ++-- .../api/route/RecordSetRoutingSpec.scala | 56 +++++++++++++++++-- .../domain/record/ListRecordSetResults.scala | 4 +- ...qlRecordSetRepositoryIntegrationSpec.scala | 38 +++++++------ .../MySqlRecordSetCacheRepository.scala | 3 +- .../repository/MySqlRecordSetRepository.scala | 3 +- .../zones/zoneTabs/manageRecords.scala.html | 2 +- .../lib/controllers/controller.records.js | 4 +- .../controllers/controller.records.spec.js | 30 +++++++++- 14 files changed, 155 insertions(+), 57 deletions(-) diff --git a/modules/api/src/it/scala/vinyldns/api/route53/Route53ApiIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/route53/Route53ApiIntegrationSpec.scala index 29d1bf320..bbf5a37c0 100644 --- a/modules/api/src/it/scala/vinyldns/api/route53/Route53ApiIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/route53/Route53ApiIntegrationSpec.scala @@ -30,7 +30,7 @@ import vinyldns.api.engine.ZoneSyncHandler import vinyldns.api.{MySqlApiIntegrationSpec, ResultHelpers} import vinyldns.core.TestRecordSetData._ import vinyldns.core.domain.backend.{Backend, BackendResolver} -import vinyldns.core.domain.record.{NameSort, RecordType} +import vinyldns.core.domain.record.{NameSort, RecordType, RecordTypeSort} import vinyldns.core.domain.zone.{Zone, ZoneChange, ZoneChangeType} import vinyldns.core.health.HealthCheck.HealthCheck import vinyldns.route53.backend.{Route53Backend, Route53BackendConfig} @@ -120,7 +120,7 @@ class Route53ApiIntegrationSpec // We should have both the record we created above as well as at least one NS record val results = recordSetRepository - .listRecordSets(Some(testZone.id), None, None, None, None, None, NameSort.ASC) + .listRecordSets(Some(testZone.id), None, None, None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() results.recordSets.map(_.typ).distinct should contain theSameElementsAs List( rsOk.typ, diff --git a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala index 4974c4af2..80cef2ba5 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala @@ -40,7 +40,7 @@ case class ListGlobalRecordSetsResponse( nextId: Option[String] = None, maxItems: Option[Int] = None, recordNameFilter: String, - recordTypeFilter: Option[Set[RecordType]] = None, + recordTypeFilter: Option[Set[RecordType]] = None, recordOwnerGroupFilter: Option[String] = None, nameSort: NameSort ) @@ -151,7 +151,7 @@ class RecordSetRoute( "recordTypeFilter".?, "recordOwnerGroupFilter".?, "nameSort".as[String].?("ASC"), - "recordTypeSort".as[String].?("ASC") + "recordTypeSort".as[String].?("NONE") ) { ( startFrom: Option[String], diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 3d629e43b..7d8f4f24a 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -985,7 +985,8 @@ class RecordSetServiceSpec List(sharedZoneRecord), recordNameFilter = Some("aaaa*"), nameSort = NameSort.ASC, - recordOwnerGroupFilter = Some("owner group id") + recordOwnerGroupFilter = Some("owner group id") , + recordTypeSort = RecordTypeSort.NONE ) ) ).when(mockRecordRepo) @@ -996,7 +997,8 @@ class RecordSetServiceSpec recordNameFilter = any[Option[String]], recordTypeFilter = any[Option[Set[RecordType.RecordType]]], recordOwnerGroupFilter = any[Option[String]], - nameSort = any[NameSort.NameSort] + nameSort = any[NameSort.NameSort], + recordTypeSort = any[RecordTypeSort.RecordTypeSort] ) val result: ListGlobalRecordSetsResponse = rightResultOf( @@ -1008,7 +1010,8 @@ class RecordSetServiceSpec recordTypeFilter = None, recordOwnerGroupFilter = Some("owner group id"), nameSort = NameSort.ASC, - authPrincipal = sharedAuth + authPrincipal = sharedAuth, + recordTypeSort = RecordTypeSort.ASC ) .value ) @@ -1033,7 +1036,8 @@ class RecordSetServiceSpec recordTypeFilter = None, recordOwnerGroupFilter = Some("owner group id"), nameSort = NameSort.ASC, - authPrincipal = okAuth + authPrincipal = okAuth, + recordTypeSort = RecordTypeSort.ASC ) .value ) @@ -1057,7 +1061,8 @@ class RecordSetServiceSpec List(sharedZoneRecord), recordNameFilter = Some("aaaa*"), nameSort = NameSort.ASC, - recordOwnerGroupFilter = Some("owner group id") + recordOwnerGroupFilter = Some("owner group id"), + recordTypeSort = RecordTypeSort.NONE ) ) ).when(mockRecordDataRepo) @@ -1080,7 +1085,8 @@ class RecordSetServiceSpec recordTypeFilter = None, recordOwnerGroupFilter = Some("owner group id"), nameSort = NameSort.ASC, - authPrincipal = sharedAuth + authPrincipal = sharedAuth, + recordTypeSort = RecordTypeSort.ASC ) .value ) @@ -1105,7 +1111,8 @@ class RecordSetServiceSpec recordTypeFilter = None, recordOwnerGroupFilter = Some("owner group id"), nameSort = NameSort.ASC, - authPrincipal = okAuth + authPrincipal = okAuth, + recordTypeSort = RecordTypeSort.ASC ) .value ) @@ -1124,7 +1131,8 @@ class RecordSetServiceSpec IO.pure( ListRecordSetResults( List(sharedZoneRecord, sharedZoneRecordNotFoundOwnerGroup), - nameSort = NameSort.ASC + nameSort = NameSort.ASC, + recordTypeSort = RecordTypeSort.ASC ) ) ).when(mockRecordRepo) @@ -1135,7 +1143,8 @@ class RecordSetServiceSpec recordNameFilter = None, recordTypeFilter = None, recordOwnerGroupFilter = None, - nameSort = NameSort.ASC + nameSort = NameSort.ASC, + recordTypeSort = RecordTypeSort.ASC ) val result: ListRecordSetsByZoneResponse = rightResultOf( @@ -1148,7 +1157,8 @@ class RecordSetServiceSpec authPrincipal = sharedAuth, recordTypeFilter = None, recordOwnerGroupFilter = None, - nameSort = NameSort.ASC + nameSort = NameSort.ASC, + recordTypeSort = RecordTypeSort.ASC ) .value ) @@ -1169,7 +1179,7 @@ class RecordSetServiceSpec .when(mockGroupRepo) .getGroups(Set()) - doReturn(IO.pure(ListRecordSetResults(List(aaaa), nameSort = NameSort.ASC))) + doReturn(IO.pure(ListRecordSetResults(List(aaaa), nameSort = NameSort.ASC, recordTypeSort = RecordTypeSort.NONE))) .when(mockRecordRepo) .listRecordSets( zoneId = Some(okZone.id), @@ -1178,7 +1188,8 @@ class RecordSetServiceSpec recordNameFilter = None, recordTypeFilter = None, recordOwnerGroupFilter = None, - nameSort = NameSort.ASC + nameSort = NameSort.ASC, + recordTypeSort = RecordTypeSort.ASC ) val result: ListRecordSetsByZoneResponse = rightResultOf( @@ -1191,7 +1202,8 @@ class RecordSetServiceSpec recordTypeFilter = None, recordOwnerGroupFilter = None, nameSort = NameSort.ASC, - authPrincipal = AuthPrincipal(okAuth.signedInUser.copy(isSupport = true), Seq.empty) + authPrincipal = AuthPrincipal(okAuth.signedInUser.copy(isSupport = true), Seq.empty), + recordTypeSort = RecordTypeSort.ASC ) .value ) @@ -1210,7 +1222,8 @@ class RecordSetServiceSpec recordTypeFilter = None, recordOwnerGroupFilter = None, nameSort = NameSort.ASC, - authPrincipal = okAuth + authPrincipal = okAuth, + recordTypeSort = RecordTypeSort.ASC ) .value ) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneViewLoaderSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneViewLoaderSpec.scala index 813814c3c..67e7ac05f 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneViewLoaderSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneViewLoaderSpec.scala @@ -17,7 +17,6 @@ package vinyldns.api.domain.zone import java.net.InetAddress - import org.joda.time.DateTime import org.mockito.Matchers._ import org.mockito.Mockito._ @@ -36,6 +35,7 @@ import vinyldns.core.domain.Fqdn import vinyldns.core.domain.backend.{Backend, BackendResolver} import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordType.RecordType +import vinyldns.core.domain.record.RecordTypeSort.RecordTypeSort import vinyldns.core.domain.zone.{Zone, ZoneConnection, ZoneStatus} class ZoneViewLoaderSpec extends AnyWordSpec with Matchers with MockitoSugar with DnsConversions { @@ -95,7 +95,7 @@ class ZoneViewLoaderSpec extends AnyWordSpec with Matchers with MockitoSugar wit val mockRecordSetDataRepo = mock[RecordSetCacheRepository] - doReturn(IO(ListRecordSetResults(records, None, None, None, None, None, None, NameSort.ASC))) + doReturn(IO(ListRecordSetResults(records, None, None, None, None, None, None, NameSort.ASC, RecordTypeSort.NONE))) .when(mockRecordSetRepo) .listRecordSets( any[Option[String]], @@ -104,7 +104,8 @@ class ZoneViewLoaderSpec extends AnyWordSpec with Matchers with MockitoSugar wit any[Option[String]], any[Option[Set[RecordType]]], any[Option[String]], - any[NameSort] + any[NameSort], + any[RecordTypeSort] ) val underTest = VinylDNSZoneViewLoader(testZone, mockRecordSetRepo, mockRecordSetDataRepo) diff --git a/modules/api/src/test/scala/vinyldns/api/engine/ZoneSyncHandlerSpec.scala b/modules/api/src/test/scala/vinyldns/api/engine/ZoneSyncHandlerSpec.scala index 7972995ab..2ad236b7d 100644 --- a/modules/api/src/test/scala/vinyldns/api/engine/ZoneSyncHandlerSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/engine/ZoneSyncHandlerSpec.scala @@ -39,6 +39,7 @@ import vinyldns.core.domain.zone._ import cats.syntax.all._ import org.slf4j.{Logger, LoggerFactory} import vinyldns.api.engine.ZoneSyncHandler.{monitor, time} +import vinyldns.core.domain.record.RecordTypeSort.RecordTypeSort import vinyldns.mysql.TransactionProvider class ZoneSyncHandlerSpec @@ -310,7 +311,7 @@ class ZoneSyncHandlerSpec ) doReturn( - IO(ListRecordSetResults(List(testRecord1), None, None, None, None, None, None, NameSort.ASC)) + IO(ListRecordSetResults(List(testRecord1), None, None, None, None, None, None, NameSort.ASC, recordTypeSort = RecordTypeSort.NONE)) ).when(recordSetRepo) .listRecordSets( any[Option[String]], @@ -319,7 +320,8 @@ class ZoneSyncHandlerSpec any[Option[String]], any[Option[Set[RecordType]]], any[Option[String]], - any[NameSort] + any[NameSort], + any[RecordTypeSort], ) doReturn(IO(testChangeSet)).when(recordSetRepo).apply(any[DB], any[ChangeSet]) diff --git a/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala b/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala index a1239cc71..baa1d47a4 100644 --- a/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala +++ b/modules/api/src/test/scala/vinyldns/api/repository/EmptyRepositories.scala @@ -19,11 +19,12 @@ package vinyldns.api.repository import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.record.RecordType.RecordType import vinyldns.core.domain.record._ -import vinyldns.core.domain.zone.{Zone, ZoneRepository, ListZonesResults} +import vinyldns.core.domain.zone.{ListZonesResults, Zone, ZoneRepository} import cats.effect._ import scalikejdbc._ import vinyldns.core.domain.membership._ import vinyldns.core.domain.record.NameSort.NameSort +import vinyldns.core.domain.record.RecordTypeSort.RecordTypeSort import vinyldns.core.domain.zone.ZoneRepository.DuplicateZoneError // Empty implementations let our other test classes just edit with the methods they need @@ -42,9 +43,10 @@ trait EmptyRecordSetRepo extends RecordSetRepository { recordNameFilter: Option[String], recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupFilter: Option[String], - nameSort: NameSort + nameSort: NameSort, + recordTypeSort: RecordTypeSort ): IO[ListRecordSetResults] = - IO.pure(ListRecordSetResults(nameSort = nameSort)) + IO.pure(ListRecordSetResults(nameSort = nameSort,recordTypeSort=recordTypeSort)) def getRecordSets(zoneId: String, name: String, typ: RecordType): IO[List[RecordSet]] = @@ -73,7 +75,7 @@ trait EmptyRecordSetCacheRepo extends RecordSetCacheRepository { recordOwnerGroupFilter: Option[String], nameSort: NameSort ): IO[ListRecordSetResults] = - IO.pure(ListRecordSetResults(nameSort = nameSort)) + IO.pure(ListRecordSetResults(nameSort = nameSort, recordTypeSort = RecordTypeSort.NONE)) } trait EmptyZoneRepo extends ZoneRepository { diff --git a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala index 93f7d6d38..66ca6ab5c 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala @@ -35,6 +35,7 @@ import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordSetChangeType.RecordSetChangeType import vinyldns.core.domain.record.RecordType._ +import vinyldns.core.domain.record.RecordTypeSort.{NONE, RecordTypeSort} import vinyldns.core.domain.record._ import vinyldns.core.domain.zone._ @@ -521,7 +522,8 @@ class RecordSetRoutingSpec recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupFilter: Option[String], nameSort: NameSort, - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + recordTypeSort: RecordTypeSort ): Result[ListGlobalRecordSetsResponse] = { if (recordTypeFilter.contains(Set(CNAME))) { Right( @@ -570,7 +572,8 @@ class RecordSetRoutingSpec recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupFilter: Option[String], nameSort: NameSort, - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + recordTypeSort: RecordTypeSort ): Result[ListGlobalRecordSetsResponse] = { if (recordTypeFilter.contains(Set(CNAME))) { Right( @@ -620,10 +623,29 @@ class RecordSetRoutingSpec recordTypeFilter: Option[Set[RecordType]], recordOwnerGroupFilter: Option[String], nameSort: NameSort, - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + recordTypeSort: RecordTypeSort ): Result[ListRecordSetsByZoneResponse] = { zoneId match { case zoneNotFound.id => Left(ZoneNotFoundError(s"$zoneId")) + case okZone.id if recordTypeSort!=NONE => + Right( + ListRecordSetsByZoneResponse( + List( + RecordSetListInfo(RecordSetInfo(cname, None), AccessLevel.Read), + RecordSetListInfo(RecordSetInfo(soa, None), AccessLevel.Read), + RecordSetListInfo(RecordSetInfo(aaaa, None), AccessLevel.Read) + ), + startFrom, + None, + maxItems, + recordNameFilter, + recordTypeFilter, + None, + nameSort, + recordTypeSort = RecordTypeSort.ASC + ) + ) case okZone.id if recordTypeFilter.contains(Set(CNAME)) => Right( ListRecordSetsByZoneResponse( @@ -636,7 +658,8 @@ class RecordSetRoutingSpec recordNameFilter, recordTypeFilter, recordOwnerGroupFilter, - nameSort + nameSort, + recordTypeSort = RecordTypeSort.ASC ) ) case okZone.id if recordTypeFilter.isEmpty => @@ -653,7 +676,8 @@ class RecordSetRoutingSpec recordNameFilter, recordTypeFilter, None, - nameSort + nameSort, + recordTypeSort = RecordTypeSort.ASC ) ) } @@ -1052,6 +1076,28 @@ class RecordSetRoutingSpec } } + "return all recordsets types in descending order" in { + + Get(s"/zones/${okZone.id}/recordsets?recordTypeSort=desc") ~> recordSetRoute ~> check { + status shouldBe StatusCodes.OK + + val resultRs = responseAs[ListRecordSetsByZoneResponse] + (resultRs.recordSets.map(_.id) should contain) + .only(soa.id, cname.id, aaaa.id) + } + } + + "return all recordsets types in ascending order" in { + + Get(s"/zones/${okZone.id}/recordsets?recordTypeSort=asc") ~> recordSetRoute ~> check { + status shouldBe StatusCodes.OK + + val resultRs = responseAs[ListRecordSetsByZoneResponse] + (resultRs.recordSets.map(_.id) should contain) + .only(aaaa.id, cname.id, soa.id) + } + } + "return recordsets of a specific type" in { Get(s"/zones/${okZone.id}/recordsets?recordTypeFilter=cname") ~> recordSetRoute ~> check { status shouldBe StatusCodes.OK diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala index 7739b7049..d023d567e 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala @@ -18,6 +18,7 @@ package vinyldns.core.domain.record import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordType.RecordType +import vinyldns.core.domain.record.RecordTypeSort.RecordTypeSort object NameSort extends Enumeration { type NameSort = Value @@ -50,5 +51,6 @@ case class ListRecordSetResults( recordNameFilter: Option[String] = None, recordTypeFilter: Option[Set[RecordType]] = None, recordOwnerGroupFilter: Option[String] = None, - nameSort: NameSort + nameSort: NameSort, + recordTypeSort: RecordTypeSort ) diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordSetRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordSetRepositoryIntegrationSpec.scala index 0efdfe387..682bf74ec 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordSetRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordSetRepositoryIntegrationSpec.scala @@ -418,7 +418,7 @@ class MySqlRecordSetRepositoryIntegrationSpec "return all record sets in a zone when optional params are not set" in { val existing = insert(okZone, 10).map(_.recordSet) val found = repo - .listRecordSets(Some(okZone.id), None, None, None, None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), None, None, None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() found.recordSets should contain theSameElementsAs existing.map( r => recordSetWithFQDN(r, okZone) @@ -429,7 +429,7 @@ class MySqlRecordSetRepositoryIntegrationSpec val existing = insert(okZone, 5).map(_.recordSet).sortBy(_.name) val startFrom = Some(PagingKey.toNextId(existing(2), true)) val found = repo - .listRecordSets(Some(okZone.id), startFrom, None, None, None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), startFrom, None, None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (found.recordSets should contain).theSameElementsInOrderAs( @@ -443,7 +443,7 @@ class MySqlRecordSetRepositoryIntegrationSpec val existing = insert(okZone, 5).map(_.recordSet).sortBy(_.name) val startFrom = Some(PagingKey.toNextId(existing(1), true)) val found = repo - .listRecordSets(Some(okZone.id), startFrom, Some(2), None, None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), startFrom, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (found.recordSets should contain).theSameElementsInOrderAs( @@ -474,7 +474,8 @@ class MySqlRecordSetRepositoryIntegrationSpec Some("*z*"), None, None, - NameSort.ASC + NameSort.ASC, + RecordTypeSort.ASC ) .unsafeRunSync() (found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames) @@ -492,7 +493,7 @@ class MySqlRecordSetRepositoryIntegrationSpec insert(changes) val found = repo - .listRecordSets(Some(okZone.id), None, Some(3), Some("aa*"), None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), None, Some(3), Some("aa*"), None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames) } @@ -509,7 +510,7 @@ class MySqlRecordSetRepositoryIntegrationSpec insert(changes) val found = repo - .listRecordSets(Some(okZone.id), None, Some(3), Some("*b"), None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), None, Some(3), Some("*b"), None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames) } @@ -527,14 +528,14 @@ class MySqlRecordSetRepositoryIntegrationSpec insert(changes) val found = repo - .listRecordSets(Some(okZone.id), None, Some(3), Some("aaa"), None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), None, Some(3), Some("aaa"), None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames) } "return select types of recordsets in a zone" in { insert(okZone, 10).map(_.recordSet) val found = repo - .listRecordSets(Some(okZone.id), None, None, None, Some(Set(CNAME)), None, NameSort.ASC) + .listRecordSets(Some(okZone.id), None, None, None, Some(Set(CNAME)), None, NameSort.ASC,RecordTypeSort.ASC) .unsafeRunSync() found.recordSets shouldBe List() found.recordTypeFilter shouldBe Some(Set(CNAME)) @@ -542,18 +543,19 @@ class MySqlRecordSetRepositoryIntegrationSpec "return all recordsets in a zone in descending order" in { val existing = insert(okZone, 10).map(_.recordSet) val found = repo - .listRecordSets(Some(okZone.id), None, None, None, None, None, NameSort.DESC) + .listRecordSets(Some(okZone.id), None, None, None, None, None, NameSort.DESC, RecordTypeSort.DESC) .unsafeRunSync() found.recordSets should contain theSameElementsAs existing.map( r => recordSetWithFQDN(r, okZone) ) found.nameSort shouldBe NameSort.DESC } + "pages through the list properly" in { // load 5 records, pages of 2, last page should have 1 result and no next id val existing = insert(okZone, 5).map(_.recordSet).sortBy(_.name) val page1 = repo - .listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (page1.recordSets should contain).theSameElementsInOrderAs( existing @@ -563,7 +565,7 @@ class MySqlRecordSetRepositoryIntegrationSpec page1.nextId shouldBe Some(PagingKey.toNextId(page1.recordSets(1), true)) val page2 = repo - .listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (page2.recordSets should contain).theSameElementsInOrderAs( existing @@ -573,7 +575,7 @@ class MySqlRecordSetRepositoryIntegrationSpec page2.nextId shouldBe Some(PagingKey.toNextId(page2.recordSets(1), true)) val page3 = repo - .listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (page3.recordSets should contain).theSameElementsInOrderAs( existing @@ -596,7 +598,7 @@ class MySqlRecordSetRepositoryIntegrationSpec val existing = editedChanges.map(_.recordSet) val page1 = repo - .listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (page1.recordSets should contain).theSameElementsInOrderAs( List(recordSetWithFQDN(existing.head, okZone), recordSetWithFQDN(existing(1), okZone)) @@ -604,7 +606,7 @@ class MySqlRecordSetRepositoryIntegrationSpec page1.nextId shouldBe Some(PagingKey.toNextId(page1.recordSets.last, true)) val page2 = repo - .listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (page2.recordSets should contain).theSameElementsInOrderAs( List(recordSetWithFQDN(existing(2), okZone), recordSetWithFQDN(existing(3), okZone)) @@ -612,7 +614,7 @@ class MySqlRecordSetRepositoryIntegrationSpec page2.nextId shouldBe Some(PagingKey.toNextId(page2.recordSets.last, true)) val page3 = repo - .listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, None, NameSort.ASC) + .listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() (page3.recordSets should contain) .theSameElementsInOrderAs(List(recordSetWithFQDN(existing(4), okZone))) @@ -621,7 +623,7 @@ class MySqlRecordSetRepositoryIntegrationSpec "return applicable recordsets in ascending order when recordNameFilter is given" in { val existing = insert(okZone, 10).map(_.recordSet) val found = repo - .listRecordSets(None, None, None, Some("*.ok*"), None, None, NameSort.ASC) + .listRecordSets(None, None, None, Some("*.ok*"), None, None, NameSort.ASC, RecordTypeSort.ASC) .unsafeRunSync() found.recordSets should contain theSameElementsAs existing.map( r => recordSetWithFQDN(r, okZone) @@ -630,7 +632,7 @@ class MySqlRecordSetRepositoryIntegrationSpec "return applicable recordsets in descending order when recordNameFilter is given and name sort is descending" in { val existing = insert(okZone, 10).map(_.recordSet) val found = repo - .listRecordSets(None, None, None, Some("*.ok*"), None, None, NameSort.DESC) + .listRecordSets(None, None, None, Some("*.ok*"), None, None, NameSort.DESC, RecordTypeSort.ASC) .unsafeRunSync() found.recordSets should contain theSameElementsAs existing .map(r => recordSetWithFQDN(r, okZone)) @@ -638,7 +640,7 @@ class MySqlRecordSetRepositoryIntegrationSpec } "return no recordsets when no zoneId or recordNameFilter are given" in { val found = - repo.listRecordSets(None, None, None, None, None, None, NameSort.ASC).unsafeRunSync() + repo.listRecordSets(None, None, None, None, None, None, NameSort.ASC, RecordTypeSort.ASC).unsafeRunSync() found.recordSets shouldBe empty } } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala index 1a8e30ece..28b80fa41 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala @@ -394,7 +394,8 @@ class MySqlRecordSetCacheRepository maxItems = maxItems, recordNameFilter = recordNameFilter, recordTypeFilter = recordTypeFilter, - nameSort = nameSort) + nameSort = nameSort, + recordTypeSort = RecordTypeSort.NONE) } } } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala index a7fa96e5e..cbc2846a3 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala @@ -287,7 +287,8 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored { maxItems = maxItems, recordNameFilter = recordNameFilter, recordTypeFilter = recordTypeFilter, - nameSort = nameSort + nameSort = nameSort, + recordTypeSort = recordTypeSort ) } } diff --git a/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html b/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html index 5af6cc12e..25cdedd86 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html @@ -110,7 +110,7 @@ - Type + Type diff --git a/modules/portal/public/lib/controllers/controller.records.js b/modules/portal/public/lib/controllers/controller.records.js index d00a85a74..4f4713d74 100644 --- a/modules/portal/public/lib/controllers/controller.records.js +++ b/modules/portal/public/lib/controllers/controller.records.js @@ -545,7 +545,7 @@ angular.module('controller.records', []) }; $scope.toggleNameSort = function() { - $scope.recordTypeSort = "NONE" + $scope.recordTypeSort = "none" if ($scope.nameSort == "asc") { $scope.nameSort = "desc"; $scope.nameSortSymbol = "fa-chevron-down"; @@ -556,7 +556,7 @@ angular.module('controller.records', []) return $scope.refreshRecords(); }; - $scope.toggleRecordtypeSort = function() { + $scope.toggleRecordTypeSort = function() { if ($scope.recordTypeSort == "asc") { $scope.recordTypeSort = "desc"; $scope.recordTypeSortSymbol = "fa-chevron-down"; diff --git a/modules/portal/public/lib/controllers/controller.records.spec.js b/modules/portal/public/lib/controllers/controller.records.spec.js index ec0d99cab..0ef275977 100644 --- a/modules/portal/public/lib/controllers/controller.records.spec.js +++ b/modules/portal/public/lib/controllers/controller.records.spec.js @@ -338,7 +338,7 @@ describe('Controller: RecordsController', function () { [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, '', expectedSort]); }); - it('toggle sort should call listRecordSetsByZone with the correct parameters', function () { + it('toggle name sort should call listRecordSetsByZone with the correct parameters', function () { var mockRecords = {data: { recordSets: [ { name: "dummy", records: [{address: "1.1.1.1"}], @@ -366,6 +366,34 @@ describe('Controller: RecordsController', function () { [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, '', expectedSort]); }); + it('toggle record type sort should call listRecordSetsByZone with the correct parameters', function () { + var mockRecords = {data: { recordSets: [ + { name: "dummy", + records: [{address: "1.1.1.1"}], + status: "Active", + ttl: 38400, + type: "A"} + ], + maxItems: 100, + recordTypeSort: "desc"}}; + + var listRecordSetsByZone = spyOn(this.recordsService, 'listRecordSetsByZone') + .and.stub() + .and.returnValue(this.q.when(mockRecords)); + + var expectedZoneId = this.scope.zoneId; + var expectedMaxItems = 100; + var expectedStartFrom = undefined; + var expectedQuery = this.scope.query; + var expectedSort = "desc"; + + this.scope.toggleRecordTypeSort(); + + expect(listRecordSetsByZone.calls.count()).toBe(1); + expect(listRecordSetsByZone.calls.mostRecent().args).toEqual( + [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, '', expectedSort]); + }); + it('filter by record type should call listRecordSetsByZone with the correct parameters', function () { var mockRecords = {data: { recordSets: [ { name: "dummy", From 67e22eb30e6487398fae4fd1e737dc397e1cc646 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Tue, 13 Sep 2022 17:00:31 +0530 Subject: [PATCH 026/521] update tests --- ...qlRecordSetRepositoryIntegrationSpec.scala | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordSetRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordSetRepositoryIntegrationSpec.scala index 682bf74ec..b46390c71 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordSetRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordSetRepositoryIntegrationSpec.scala @@ -429,7 +429,7 @@ class MySqlRecordSetRepositoryIntegrationSpec val existing = insert(okZone, 5).map(_.recordSet).sortBy(_.name) val startFrom = Some(PagingKey.toNextId(existing(2), true)) val found = repo - .listRecordSets(Some(okZone.id), startFrom, None, None, None, None, NameSort.ASC, RecordTypeSort.ASC) + .listRecordSets(Some(okZone.id), startFrom, None, None, None, None, NameSort.ASC, RecordTypeSort.NONE) .unsafeRunSync() (found.recordSets should contain).theSameElementsInOrderAs( @@ -443,7 +443,7 @@ class MySqlRecordSetRepositoryIntegrationSpec val existing = insert(okZone, 5).map(_.recordSet).sortBy(_.name) val startFrom = Some(PagingKey.toNextId(existing(1), true)) val found = repo - .listRecordSets(Some(okZone.id), startFrom, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) + .listRecordSets(Some(okZone.id), startFrom, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.NONE) .unsafeRunSync() (found.recordSets should contain).theSameElementsInOrderAs( @@ -475,7 +475,7 @@ class MySqlRecordSetRepositoryIntegrationSpec None, None, NameSort.ASC, - RecordTypeSort.ASC + RecordTypeSort.NONE ) .unsafeRunSync() (found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames) @@ -493,7 +493,7 @@ class MySqlRecordSetRepositoryIntegrationSpec insert(changes) val found = repo - .listRecordSets(Some(okZone.id), None, Some(3), Some("aa*"), None, None, NameSort.ASC, RecordTypeSort.ASC) + .listRecordSets(Some(okZone.id), None, Some(3), Some("aa*"), None, None, NameSort.ASC, RecordTypeSort.NONE) .unsafeRunSync() (found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames) } @@ -510,7 +510,7 @@ class MySqlRecordSetRepositoryIntegrationSpec insert(changes) val found = repo - .listRecordSets(Some(okZone.id), None, Some(3), Some("*b"), None, None, NameSort.ASC, RecordTypeSort.ASC) + .listRecordSets(Some(okZone.id), None, Some(3), Some("*b"), None, None, NameSort.ASC, RecordTypeSort.NONE) .unsafeRunSync() (found.recordSets.map(_.name) should contain).theSameElementsInOrderAs(expectedNames) } @@ -543,7 +543,7 @@ class MySqlRecordSetRepositoryIntegrationSpec "return all recordsets in a zone in descending order" in { val existing = insert(okZone, 10).map(_.recordSet) val found = repo - .listRecordSets(Some(okZone.id), None, None, None, None, None, NameSort.DESC, RecordTypeSort.DESC) + .listRecordSets(Some(okZone.id), None, None, None, None, None, NameSort.DESC, RecordTypeSort.NONE) .unsafeRunSync() found.recordSets should contain theSameElementsAs existing.map( r => recordSetWithFQDN(r, okZone) @@ -551,11 +551,22 @@ class MySqlRecordSetRepositoryIntegrationSpec found.nameSort shouldBe NameSort.DESC } + "return all recordsets record type in a zone in descending order" in { + val existing = insert(okZone, 10).map(_.recordSet) + val found = repo + .listRecordSets(Some(okZone.id), None, None, None, None, None, NameSort.ASC, RecordTypeSort.DESC) + .unsafeRunSync() + found.recordSets should contain theSameElementsAs existing.map( + r => recordSetWithFQDN(r, okZone) + ) + found.recordTypeSort shouldBe RecordTypeSort.DESC + } + "pages through the list properly" in { // load 5 records, pages of 2, last page should have 1 result and no next id val existing = insert(okZone, 5).map(_.recordSet).sortBy(_.name) val page1 = repo - .listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) + .listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.NONE) .unsafeRunSync() (page1.recordSets should contain).theSameElementsInOrderAs( existing @@ -565,7 +576,7 @@ class MySqlRecordSetRepositoryIntegrationSpec page1.nextId shouldBe Some(PagingKey.toNextId(page1.recordSets(1), true)) val page2 = repo - .listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) + .listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.NONE) .unsafeRunSync() (page2.recordSets should contain).theSameElementsInOrderAs( existing @@ -598,7 +609,7 @@ class MySqlRecordSetRepositoryIntegrationSpec val existing = editedChanges.map(_.recordSet) val page1 = repo - .listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) + .listRecordSets(Some(okZone.id), None, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.NONE) .unsafeRunSync() (page1.recordSets should contain).theSameElementsInOrderAs( List(recordSetWithFQDN(existing.head, okZone), recordSetWithFQDN(existing(1), okZone)) @@ -606,7 +617,7 @@ class MySqlRecordSetRepositoryIntegrationSpec page1.nextId shouldBe Some(PagingKey.toNextId(page1.recordSets.last, true)) val page2 = repo - .listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) + .listRecordSets(Some(okZone.id), page1.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.NONE) .unsafeRunSync() (page2.recordSets should contain).theSameElementsInOrderAs( List(recordSetWithFQDN(existing(2), okZone), recordSetWithFQDN(existing(3), okZone)) @@ -614,7 +625,7 @@ class MySqlRecordSetRepositoryIntegrationSpec page2.nextId shouldBe Some(PagingKey.toNextId(page2.recordSets.last, true)) val page3 = repo - .listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.ASC) + .listRecordSets(Some(okZone.id), page2.nextId, Some(2), None, None, None, NameSort.ASC, RecordTypeSort.NONE) .unsafeRunSync() (page3.recordSets should contain) .theSameElementsInOrderAs(List(recordSetWithFQDN(existing(4), okZone))) From 3c5fc57d0148c57df018472ebb0be447212bce84 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Tue, 13 Sep 2022 17:56:33 +0530 Subject: [PATCH 027/521] update tests --- .../lib/controllers/controller.records.js | 1 + .../controllers/controller.records.spec.js | 36 ++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/modules/portal/public/lib/controllers/controller.records.js b/modules/portal/public/lib/controllers/controller.records.js index 4f4713d74..c5536b6bf 100644 --- a/modules/portal/public/lib/controllers/controller.records.js +++ b/modules/portal/public/lib/controllers/controller.records.js @@ -557,6 +557,7 @@ angular.module('controller.records', []) }; $scope.toggleRecordTypeSort = function() { + $scope.nameSort = "" if ($scope.recordTypeSort == "asc") { $scope.recordTypeSort = "desc"; $scope.recordTypeSortSymbol = "fa-chevron-down"; diff --git a/modules/portal/public/lib/controllers/controller.records.spec.js b/modules/portal/public/lib/controllers/controller.records.spec.js index 0ef275977..525ac43ca 100644 --- a/modules/portal/public/lib/controllers/controller.records.spec.js +++ b/modules/portal/public/lib/controllers/controller.records.spec.js @@ -275,13 +275,15 @@ describe('Controller: RecordsController', function () { var expectedMaxItems = 100; var expectedStartFrom = undefined; var expectedQuery = this.scope.query; - var expectedSort = "asc"; + var expectedNameSort = "asc"; + var expectedRecordTypeSort = "none"; + this.scope.refreshRecords(); expect(listRecordSetsByZone.calls.count()).toBe(1); expect(listRecordSetsByZone.calls.mostRecent().args).toEqual( - [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, "", expectedSort]); + [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, "", expectedNameSort, expectedRecordTypeSort]); }); it('next page should call listRecordSetsByZone with the correct parameters', function () { @@ -302,13 +304,14 @@ describe('Controller: RecordsController', function () { var expectedMaxItems = 100; var expectedStartFrom = undefined; var expectedQuery = this.scope.query; - var expectedSort = "asc"; + var expectedNameSort = "asc"; + var expectedRecordTypeSort = "none"; this.scope.nextPage(); expect(listRecordSetsByZone.calls.count()).toBe(1); expect(listRecordSetsByZone.calls.mostRecent().args).toEqual( - [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, "", expectedSort]); + [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, "", expectedNameSort, expectedRecordTypeSort]); }); it('prev page should call listRecordSetsByZone with the correct parameters', function () { @@ -329,13 +332,14 @@ describe('Controller: RecordsController', function () { var expectedMaxItems = 100; var expectedStartFrom = undefined; var expectedQuery = this.scope.query; - var expectedSort = "asc"; + var expectedNameSort = "asc"; + var expectedRecordTypeSort = "none"; this.scope.prevPage(); expect(listRecordSetsByZone.calls.count()).toBe(1); expect(listRecordSetsByZone.calls.mostRecent().args).toEqual( - [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, '', expectedSort]); + [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, '', expectedNameSort, expectedRecordTypeSort]); }); it('toggle name sort should call listRecordSetsByZone with the correct parameters', function () { @@ -357,13 +361,14 @@ describe('Controller: RecordsController', function () { var expectedMaxItems = 100; var expectedStartFrom = undefined; var expectedQuery = this.scope.query; - var expectedSort = "desc"; + var expectedNameSort = "desc"; + var expectedRecordTypeSort = "none"; this.scope.toggleNameSort(); expect(listRecordSetsByZone.calls.count()).toBe(1); expect(listRecordSetsByZone.calls.mostRecent().args).toEqual( - [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, '', expectedSort]); + [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, '', expectedNameSort, expectedRecordTypeSort]); }); it('toggle record type sort should call listRecordSetsByZone with the correct parameters', function () { @@ -375,7 +380,8 @@ describe('Controller: RecordsController', function () { type: "A"} ], maxItems: 100, - recordTypeSort: "desc"}}; + nameSort: "", + recordTypeSort: "asc"}}; var listRecordSetsByZone = spyOn(this.recordsService, 'listRecordSetsByZone') .and.stub() @@ -385,13 +391,15 @@ describe('Controller: RecordsController', function () { var expectedMaxItems = 100; var expectedStartFrom = undefined; var expectedQuery = this.scope.query; - var expectedSort = "desc"; + var expectedNameSort = ""; + var expectedRecordTypeSort = "asc"; this.scope.toggleRecordTypeSort(); expect(listRecordSetsByZone.calls.count()).toBe(1); expect(listRecordSetsByZone.calls.mostRecent().args).toEqual( - [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, '', expectedSort]); + [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, '', expectedNameSort, expectedRecordTypeSort]); + }); it('filter by record type should call listRecordSetsByZone with the correct parameters', function () { @@ -414,13 +422,15 @@ describe('Controller: RecordsController', function () { var expectedStartFrom = undefined; var expectedQuery = this.scope.query; var expectedRecordTypeFilter = "A"; - var expectedSort = "asc"; + var expectedNameSort = "asc"; + var expectedRecordTypeSort = "none"; + this.scope.toggleCheckedRecordType("A"); this.scope.refreshRecords(); expect(listRecordSetsByZone.calls.count()).toBe(1); expect(listRecordSetsByZone.calls.mostRecent().args).toEqual( - [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, expectedRecordTypeFilter, expectedSort]); + [expectedZoneId, expectedMaxItems, expectedStartFrom, expectedQuery, expectedRecordTypeFilter, expectedNameSort, expectedRecordTypeSort]); }); }); From 150cbd42ab02479ac66d3e5f1580890425dcd007 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Wed, 14 Sep 2022 13:53:18 +0530 Subject: [PATCH 028/521] update tests --- .../api/domain/record/RecordSetService.scala | 2 +- .../api/route/RecordSetRoutingSpec.scala | 74 +++++++++++++++---- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 24cbc4db7..d25984d0f 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -343,7 +343,7 @@ class RecordSetService( recordSetResults.recordTypeFilter, recordSetResults.recordOwnerGroupFilter, recordSetResults.nameSort, - recordTypeSort + recordSetResults.recordTypeSort ) def getRecordSetChange( diff --git a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala index 66ca6ab5c..cbbaa8f85 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala @@ -35,7 +35,7 @@ import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordSetChangeType.RecordSetChangeType import vinyldns.core.domain.record.RecordType._ -import vinyldns.core.domain.record.RecordTypeSort.{NONE, RecordTypeSort} +import vinyldns.core.domain.record.RecordTypeSort.{ASC, DESC, RecordTypeSort} import vinyldns.core.domain.record._ import vinyldns.core.domain.zone._ @@ -628,12 +628,13 @@ class RecordSetRoutingSpec ): Result[ListRecordSetsByZoneResponse] = { zoneId match { case zoneNotFound.id => Left(ZoneNotFoundError(s"$zoneId")) - case okZone.id if recordTypeSort!=NONE => + // NameSort will be in ASC by default + case okZone.id if recordTypeSort==DESC && nameSort == NameSort.ASC=> Right( ListRecordSetsByZoneResponse( List( - RecordSetListInfo(RecordSetInfo(cname, None), AccessLevel.Read), RecordSetListInfo(RecordSetInfo(soa, None), AccessLevel.Read), + RecordSetListInfo(RecordSetInfo(cname, None), AccessLevel.Read), RecordSetListInfo(RecordSetInfo(aaaa, None), AccessLevel.Read) ), startFrom, @@ -643,7 +644,44 @@ class RecordSetRoutingSpec recordTypeFilter, None, nameSort, - recordTypeSort = RecordTypeSort.ASC + recordTypeSort + ) + ) + // NameSort will be in ASC by default + case okZone.id if recordTypeSort==ASC && nameSort == NameSort.ASC => + Right( + ListRecordSetsByZoneResponse( + List( + RecordSetListInfo(RecordSetInfo(aaaa, None), AccessLevel.Read), + RecordSetListInfo(RecordSetInfo(cname, None), AccessLevel.Read), + RecordSetListInfo(RecordSetInfo(soa, None), AccessLevel.Read) + ), + startFrom, + None, + maxItems, + recordNameFilter, + recordTypeFilter, + None, + nameSort, + recordTypeSort + ) + ) + case okZone.id if recordTypeFilter.isEmpty && nameSort == NameSort.DESC && recordTypeSort==ASC => + Right( + ListRecordSetsByZoneResponse( + List( + RecordSetListInfo(RecordSetInfo(soa, None), AccessLevel.Read), + RecordSetListInfo(RecordSetInfo(cname, None), AccessLevel.Read), + RecordSetListInfo(RecordSetInfo(aaaa, None), AccessLevel.Read) + ), + startFrom, + None, + maxItems, + recordNameFilter, + recordTypeFilter, + None, + nameSort, + recordTypeSort ) ) case okZone.id if recordTypeFilter.contains(Set(CNAME)) => @@ -659,10 +697,10 @@ class RecordSetRoutingSpec recordTypeFilter, recordOwnerGroupFilter, nameSort, - recordTypeSort = RecordTypeSort.ASC + recordTypeSort=RecordTypeSort.ASC ) ) - case okZone.id if recordTypeFilter.isEmpty => + case okZone.id if recordTypeFilter.isEmpty && nameSort != NameSort.ASC || nameSort!=NameSort.DESC=> Right( ListRecordSetsByZoneResponse( List( @@ -677,7 +715,7 @@ class RecordSetRoutingSpec recordTypeFilter, None, nameSort, - recordTypeSort = RecordTypeSort.ASC + recordTypeSort=RecordTypeSort.ASC ) ) } @@ -1076,25 +1114,35 @@ class RecordSetRoutingSpec } } - "return all recordsets types in descending order" in { + "return all recordSets types in descending order" in { Get(s"/zones/${okZone.id}/recordsets?recordTypeSort=desc") ~> recordSetRoute ~> check { status shouldBe StatusCodes.OK val resultRs = responseAs[ListRecordSetsByZoneResponse] - (resultRs.recordSets.map(_.id) should contain) - .only(soa.id, cname.id, aaaa.id) + (resultRs.recordSets.map(_.typ) shouldBe List(soa.typ, cname.typ, aaaa.typ)) } } - "return all recordsets types in ascending order" in { + "return all recordSets types in ascending order" in { Get(s"/zones/${okZone.id}/recordsets?recordTypeSort=asc") ~> recordSetRoute ~> check { status shouldBe StatusCodes.OK val resultRs = responseAs[ListRecordSetsByZoneResponse] - (resultRs.recordSets.map(_.id) should contain) - .only(aaaa.id, cname.id, soa.id) + (resultRs.recordSets.map(_.typ) shouldBe List(aaaa.typ, cname.typ, soa.typ)) + + } + } + + "return all record name in descending order when recordSets and type sort simultaneously" in { + + Get(s"/zones/${okZone.id}/recordsets?nameSort=desc&recordTypeSort=asc") ~> recordSetRoute ~> check { + status shouldBe StatusCodes.OK + + val resultRs = responseAs[ListRecordSetsByZoneResponse] + (resultRs.recordSets.map(_.name) shouldBe List(soa.name, cname.name, aaaa.name)) + } } From 327061f3c0399ff721174cb7a1397b3965b06d69 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Wed, 14 Sep 2022 14:07:16 +0530 Subject: [PATCH 029/521] update tests --- .../api/route/RecordSetRoutingSpec.scala | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala index cbbaa8f85..73bce7738 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala @@ -629,7 +629,7 @@ class RecordSetRoutingSpec zoneId match { case zoneNotFound.id => Left(ZoneNotFoundError(s"$zoneId")) // NameSort will be in ASC by default - case okZone.id if recordTypeSort==DESC && nameSort == NameSort.ASC=> + case okZone.id if recordTypeSort==DESC => Right( ListRecordSetsByZoneResponse( List( @@ -648,7 +648,7 @@ class RecordSetRoutingSpec ) ) // NameSort will be in ASC by default - case okZone.id if recordTypeSort==ASC && nameSort == NameSort.ASC => + case okZone.id if recordTypeSort==ASC=> Right( ListRecordSetsByZoneResponse( List( @@ -666,24 +666,6 @@ class RecordSetRoutingSpec recordTypeSort ) ) - case okZone.id if recordTypeFilter.isEmpty && nameSort == NameSort.DESC && recordTypeSort==ASC => - Right( - ListRecordSetsByZoneResponse( - List( - RecordSetListInfo(RecordSetInfo(soa, None), AccessLevel.Read), - RecordSetListInfo(RecordSetInfo(cname, None), AccessLevel.Read), - RecordSetListInfo(RecordSetInfo(aaaa, None), AccessLevel.Read) - ), - startFrom, - None, - maxItems, - recordNameFilter, - recordTypeFilter, - None, - nameSort, - recordTypeSort - ) - ) case okZone.id if recordTypeFilter.contains(Set(CNAME)) => Right( ListRecordSetsByZoneResponse( @@ -700,7 +682,7 @@ class RecordSetRoutingSpec recordTypeSort=RecordTypeSort.ASC ) ) - case okZone.id if recordTypeFilter.isEmpty && nameSort != NameSort.ASC || nameSort!=NameSort.DESC=> + case okZone.id if recordTypeFilter.isEmpty => Right( ListRecordSetsByZoneResponse( List( @@ -1135,13 +1117,13 @@ class RecordSetRoutingSpec } } - "return all record name in descending order when recordSets and type sort simultaneously" in { + "return all record name in ascending order when recordSets and type sort simultaneously" in { Get(s"/zones/${okZone.id}/recordsets?nameSort=desc&recordTypeSort=asc") ~> recordSetRoute ~> check { status shouldBe StatusCodes.OK val resultRs = responseAs[ListRecordSetsByZoneResponse] - (resultRs.recordSets.map(_.name) shouldBe List(soa.name, cname.name, aaaa.name)) + (resultRs.recordSets.map(_.name) shouldBe List(aaaa.name, cname.name, soa.name)) } } From 2a60f7f2e1e933d554ea2fc256e0ad274edc3df4 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Wed, 14 Sep 2022 14:12:12 +0530 Subject: [PATCH 030/521] update --- .../test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala | 2 +- .../vinyldns/core/domain/record/ListRecordSetResults.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala index 73bce7738..d37eaba35 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala @@ -1117,7 +1117,7 @@ class RecordSetRoutingSpec } } - "return all record name in ascending order when recordSets and type sort simultaneously" in { + "return all record name in ascending order when name and type sort simultaneously" in { Get(s"/zones/${okZone.id}/recordsets?nameSort=desc&recordTypeSort=asc") ~> recordSetRoute ~> check { status shouldBe StatusCodes.OK diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala index d023d567e..1dc47182b 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetResults.scala @@ -38,7 +38,7 @@ object RecordTypeSort extends Enumeration { def find(value: String): Value = value.toUpperCase match { case "DESC" => RecordTypeSort.DESC case "ASC" => RecordTypeSort.ASC - case _ => NONE + case _ => RecordTypeSort.NONE } } From b6a20ed6c316cfbb3bbdfff030d62ab5d980f883 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Wed, 14 Sep 2022 17:49:35 +0530 Subject: [PATCH 031/521] remove css style --- .../portal/app/views/zones/zoneTabs/manageRecords.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html b/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html index 25cdedd86..d4bfc1462 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageRecords.scala.html @@ -109,7 +109,7 @@ - + Type From ff674219a288df923b7baa5fc97fb4550a0b3d3d Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Thu, 22 Sep 2022 12:36:00 +0530 Subject: [PATCH 032/521] typo corrections --- .../test/scala/vinyldns/api/domain/DomainValidationsSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala index a7ad20263..b101282c5 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala @@ -78,7 +78,7 @@ class DomainValidationsSpec validateHostName("asterisk.domain*.name.") shouldBe invalid } - property("Shortests fqdn name should be valid") { + property("Shortest fqdn name should be valid") { val fqdn = Fqdn("a.") validateCName(fqdn) shouldBe valid } From a6329251bf7dae67a09fd4cc2a0967f4c28c0a3a Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Thu, 22 Sep 2022 12:40:45 +0530 Subject: [PATCH 033/521] Resolved conflicts --- .../src/main/scala/vinyldns/api/domain/DomainValidations.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala index 019b41f91..b60011bf6 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala @@ -65,7 +65,7 @@ object DomainValidations { // Cname check - Cname should not be IP address def validateCName(name: Fqdn): ValidatedNel[DomainValidationError, Fqdn] = validateIpv4Address(name.fqdn.dropRight(1)).isValid match { - case true => InvalidCName(name.toString).invalidNel + case true => InvalidIPv4CName(name.toString).invalidNel case false => validateHostName(name.fqdn).map(_ => name) } From 05c676e7e3cccba3e1333d55f735b826ab2a8c22 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Thu, 22 Sep 2022 12:51:07 +0530 Subject: [PATCH 034/521] update --- .../main/scala/vinyldns/core/domain/SingleChangeError.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala index 862f4ae7e..8811478be 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala @@ -36,7 +36,7 @@ object DomainValidationErrorType extends Enumeration { CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch, RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError, NewMultiRecordError, CnameAtZoneApexError, RecordRequiresManualReview, UnsupportedOperation, - DeleteRecordDataDoesNotExist, InvalidCName = Value + DeleteRecordDataDoesNotExist, InvalidIPv4CName = Value // $COVERAGE-OFF$ def from(error: DomainValidationError): DomainValidationErrorType = @@ -73,7 +73,7 @@ object DomainValidationErrorType extends Enumeration { case _: RecordRequiresManualReview => RecordRequiresManualReview case _: UnsupportedOperation => UnsupportedOperation case _: DeleteRecordDataDoesNotExist => DeleteRecordDataDoesNotExist - case _: InvalidCName => InvalidCName + case _: InvalidIPv4CName => InvalidIPv4CName } // $COVERAGE-ON$ } From 3303de2548b3fb9cb237d07e5275f2859985ad83 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Thu, 22 Sep 2022 13:12:06 +0530 Subject: [PATCH 035/521] update --- .../vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index 35f10af94..60213d33f 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -727,7 +727,7 @@ class BatchChangeValidationsSpec ) val result = validateAddChangeInput(change, false) - result should haveInvalid[DomainValidationError](InvalidCName(s"Fqdn($invalidCNAMERecordData.)")) + result should haveInvalid[DomainValidationError](InvalidIPv4CName(s"Fqdn($invalidCNAMERecordData.)")) } property("""validateAddChangeInput: should fail with InvalidLength From 08e2d6b8672f83eee55d7a8222b98c35d1cf8386 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Thu, 22 Sep 2022 15:27:05 +0530 Subject: [PATCH 036/521] update in tests --- .../api/domain/DomainValidations.scala | 10 +-- .../api/domain/DomainValidationsSpec.scala | 89 +++++++++---------- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala index b60011bf6..091b79bcd 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala @@ -63,19 +63,19 @@ object DomainValidations { val MX_PREFERENCE_MAX_VALUE: Int = 65535 // Cname check - Cname should not be IP address - def validateCName(name: Fqdn): ValidatedNel[DomainValidationError, Fqdn] = + def validateCname(name: Fqdn, isReverse: Boolean): ValidatedNel[DomainValidationError, Fqdn] = validateIpv4Address(name.fqdn.dropRight(1)).isValid match { case true => InvalidIPv4CName(name.toString).invalidNel - case false => validateHostName(name.fqdn).map(_ => name) + case false => validateIsReverseCname(name, isReverse) } def validateHostName(name: Fqdn): ValidatedNel[DomainValidationError, Fqdn] = validateHostName(name.fqdn).map(_ => name) - def validateCname(name: Fqdn, isReverse: Boolean): ValidatedNel[DomainValidationError, Fqdn] = - validateCname(name.fqdn, isReverse).map(_ => name) + def validateIsReverseCname(name: Fqdn, isReverse: Boolean): ValidatedNel[DomainValidationError, Fqdn] = + validateIsReverseCname(name.fqdn, isReverse).map(_ => name) - def validateCname(name: String, isReverse: Boolean): ValidatedNel[DomainValidationError, String] = { + def validateIsReverseCname(name: String, isReverse: Boolean): ValidatedNel[DomainValidationError, String] = { isReverse match { case true => val checkRegex = validReverseZoneFQDNRegex diff --git a/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala index e447c2772..68dc9535e 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/DomainValidationsSpec.scala @@ -79,24 +79,23 @@ class DomainValidationsSpec property("Shortest fqdn name should be valid") { val fqdn = Fqdn("a.") - validateCName(fqdn) shouldBe valid + validateCname(fqdn, false) shouldBe valid } property("Ip address in cname should be invalid") { val fqdn = Fqdn("1.2.3.4") - println(validateCName(fqdn)) - validateCName(fqdn) shouldBe invalid + validateCname(fqdn, false) shouldBe invalid } property("Longest fqdn name should be valid") { val fqdn = Fqdn(("a" * 50 + ".") * 5) - validateCName(fqdn) shouldBe valid + validateCname(fqdn, false) shouldBe valid } property("fqdn name should pass property-based testing") { forAll(domainGenerator) { domain: String => val domains= Fqdn(domain) - whenever(validateCName(domains).isValid) { + whenever(validateHostName(domains).isValid) { domains.fqdn.length should be > 0 domains.fqdn.length should be < 256 (domains.fqdn should fullyMatch).regex(validFQDNRegex) @@ -105,25 +104,25 @@ class DomainValidationsSpec } } - property("fqdn names beginning with invalid characters should fail with InvalidDomainName") { - validateCName(Fqdn("/slash.domain.name.")).failWith[InvalidDomainName] - validateCName(Fqdn("-hyphen.domain.name.")).failWith[InvalidDomainName] + property("fqdn names beginning with invalid characters should fail with InvalidCname") { + validateCname(Fqdn("/slash.domain.name."), false).failWith[InvalidCname] + validateCname(Fqdn("-hyphen.domain.name."), false).failWith[InvalidCname] } property("fqdn names with underscores should pass property-based testing") { - validateCName(Fqdn("_underscore.domain.name.")).isValid - validateCName(Fqdn("under_score.domain.name.")).isValid - validateCName(Fqdn("underscore._domain.name.")).isValid + validateCname(Fqdn("_underscore.domain.name."), false).isValid + validateCname(Fqdn("under_score.domain.name."), false).isValid + validateCname(Fqdn("underscore._domain.name."), false).isValid } // For wildcard records. '*' can only be in the beginning followed by '.' and domain name property("fqdn names beginning with asterisk should pass property-based testing") { - validateCName(Fqdn("*.domain.name.")) shouldBe valid - validateCName(Fqdn("aste*risk.domain.name.")) shouldBe invalid - validateCName(Fqdn("*asterisk.domain.name.")) shouldBe invalid - validateCName(Fqdn("asterisk*.domain.name.")) shouldBe invalid - validateCName(Fqdn("asterisk.*domain.name."))shouldBe invalid - validateCName(Fqdn("asterisk.domain*.name.")) shouldBe invalid + validateCname(Fqdn("*.domain.name."), false) shouldBe valid + validateCname(Fqdn("aste*risk.domain.name."),false) shouldBe invalid + validateCname(Fqdn("*asterisk.domain.name."),false) shouldBe invalid + validateCname(Fqdn("asterisk*.domain.name."),false) shouldBe invalid + validateCname(Fqdn("asterisk.*domain.name."),false)shouldBe invalid + validateCname(Fqdn("asterisk.domain*.name."),false) shouldBe invalid } property("Valid Ipv4 addresses should pass property-based testing") { @@ -161,50 +160,50 @@ class DomainValidationsSpec } property("Shortest cname should be valid") { - validateCname("a.",true) shouldBe valid - validateCname("a.",false) shouldBe valid + validateIsReverseCname("a.",true) shouldBe valid + validateIsReverseCname("a.",false) shouldBe valid } property("Longest cname should be valid") { val name = ("a" * 50 + ".") * 5 - validateCname(name,true) shouldBe valid - validateCname(name,false) shouldBe valid + validateIsReverseCname(name,true) shouldBe valid + validateIsReverseCname(name,false) shouldBe valid } property("Cnames with underscores should pass property-based testing") { - validateCname("_underscore.domain.name.",true).isValid - validateCname("under_score.domain.name.",true).isValid - validateCname("underscore._domain.name.",true).isValid - validateCname("_underscore.domain.name.",false).isValid - validateCname("under_score.domain.name.",false).isValid - validateCname("underscore._domain.name.",false).isValid + validateIsReverseCname("_underscore.domain.name.",true).isValid + validateIsReverseCname("under_score.domain.name.",true).isValid + validateIsReverseCname("underscore._domain.name.",true).isValid + validateIsReverseCname("_underscore.domain.name.",false).isValid + validateIsReverseCname("under_score.domain.name.",false).isValid + validateIsReverseCname("underscore._domain.name.",false).isValid } // For wildcard records. '*' can only be in the beginning followed by '.' and domain name property("Cnames beginning with asterisk should pass property-based testing") { - validateCname("*.domain.name.",true) shouldBe valid - validateCname("aste*risk.domain.name.",true) shouldBe invalid - validateCname("*asterisk.domain.name.",true) shouldBe invalid - validateCname("asterisk*.domain.name.",true) shouldBe invalid - validateCname("asterisk.*domain.name.",true) shouldBe invalid - validateCname("asterisk.domain*.name.",true) shouldBe invalid - validateCname("*.domain.name.",false) shouldBe valid - validateCname("aste*risk.domain.name.",false) shouldBe invalid - validateCname("*asterisk.domain.name.",false) shouldBe invalid - validateCname("asterisk*.domain.name.",false) shouldBe invalid - validateCname("asterisk.*domain.name.",false) shouldBe invalid - validateCname("asterisk.domain*.name.",false) shouldBe invalid + validateIsReverseCname("*.domain.name.",true) shouldBe valid + validateIsReverseCname("aste*risk.domain.name.",true) shouldBe invalid + validateIsReverseCname("*asterisk.domain.name.",true) shouldBe invalid + validateIsReverseCname("asterisk*.domain.name.",true) shouldBe invalid + validateIsReverseCname("asterisk.*domain.name.",true) shouldBe invalid + validateIsReverseCname("asterisk.domain*.name.",true) shouldBe invalid + validateIsReverseCname("*.domain.name.",false) shouldBe valid + validateIsReverseCname("aste*risk.domain.name.",false) shouldBe invalid + validateIsReverseCname("*asterisk.domain.name.",false) shouldBe invalid + validateIsReverseCname("asterisk*.domain.name.",false) shouldBe invalid + validateIsReverseCname("asterisk.*domain.name.",false) shouldBe invalid + validateIsReverseCname("asterisk.domain*.name.",false) shouldBe invalid } property("Cname names with forward slash should pass with reverse zone") { - validateCname("/slash.cname.name.",true).isValid - validateCname("slash./cname.name.",true).isValid - validateCname("slash.cname./name.",true).isValid + validateIsReverseCname("/slash.cname.name.",true).isValid + validateIsReverseCname("slash./cname.name.",true).isValid + validateIsReverseCname("slash.cname./name.",true).isValid } property("Cname names with forward slash should fail with forward zone") { - validateCname("/slash.cname.name.",false).failWith[InvalidCname] - validateCname("slash./cname.name.",false).failWith[InvalidCname] - validateCname("slash.cname./name.",false).failWith[InvalidCname] + validateIsReverseCname("/slash.cname.name.",false).failWith[InvalidCname] + validateIsReverseCname("slash./cname.name.",false).failWith[InvalidCname] + validateIsReverseCname("slash.cname./name.",false).failWith[InvalidCname] } } From c2e364f6dae0f1a80cb840b708ed2de3347275e9 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 28 Sep 2022 14:23:51 +0530 Subject: [PATCH 037/521] Add zone autocomplete search --- .../repository/MySqlZoneRepository.scala | 15 ++++-- modules/portal/app/views/main.scala.html | 1 - .../portal/app/views/zones/zones.scala.html | 8 ++-- .../lib/controllers/controller.zones.js | 46 +++++++++++++++++++ 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala index e80402632..a2c2e972a 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala @@ -253,10 +253,17 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M val sb = new StringBuilder sb.append(withAccessorCheck) - val filters = List( - zoneNameFilter.map(flt => s"z.name LIKE '${ensureTrailingDot(flt.replace('*', '%'))}'"), - startFrom.map(os => s"z.name > '$os'") - ).flatten + val filters = if (zoneNameFilter.isDefined && (zoneNameFilter.get.takeRight(1) == "." || zoneNameFilter.get.contains("*"))) { + List( + zoneNameFilter.map(flt => s"z.name LIKE '${ensureTrailingDot(flt.replace('*', '%'))}'"), + startFrom.map(os => s"z.name > '$os'") + ).flatten + } else { + List( + zoneNameFilter.map(flt => s"z.name LIKE '${flt.concat("%")}'"), + startFrom.map(os => s"z.name > '$os'") + ).flatten + } if (filters.nonEmpty) { sb.append(" WHERE ") diff --git a/modules/portal/app/views/main.scala.html b/modules/portal/app/views/main.scala.html index ae72212c4..069c1ab49 100644 --- a/modules/portal/app/views/main.scala.html +++ b/modules/portal/app/views/main.scala.html @@ -16,7 +16,6 @@ - diff --git a/modules/portal/app/views/zones/zones.scala.html b/modules/portal/app/views/zones/zones.scala.html index 9c56548df..9494e63d4 100644 --- a/modules/portal/app/views/zones/zones.scala.html +++ b/modules/portal/app/views/zones/zones.scala.html @@ -28,8 +28,8 @@
@@ -59,7 +59,7 @@ - +
@@ -168,7 +168,7 @@ - +
diff --git a/modules/portal/public/lib/controllers/controller.zones.js b/modules/portal/public/lib/controllers/controller.zones.js index d4057fcfd..5d9ec5acd 100644 --- a/modules/portal/public/lib/controllers/controller.zones.js +++ b/modules/portal/public/lib/controllers/controller.zones.js @@ -23,6 +23,14 @@ angular.module('controller.zones', []) $scope.allZonesLoaded = false; $scope.hasZones = false; // Re-assigned each time zones are fetched without a query $scope.allGroups = []; + $scope.ignoreAccess = false; + $scope.allZonesAccess = function () { + $scope.ignoreAccess = true; + } + + $scope.myZonesAccess = function () { + $scope.ignoreAccess = false; + } $scope.query = ""; @@ -81,6 +89,44 @@ angular.module('controller.zones', []) } }; + // Autocomplete for zone search + $(".zone-search-text").autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "/api/zones?maxItems=100", + dataType: "json", + data: {nameFilter: request.term, ignoreAccess: $scope.ignoreAccess}, + success: function(data) { + const search = JSON.parse(JSON.stringify(data)); + response($.map(search.zones, function(zone) { + return {value: zone.name, label: zone.name} + })) + } + }); + }, + minLength: 1, + select: function (event, ui) { + $scope.query = ui.item.value; + $(".zone-search-text").val(ui.item.value); + return false; + }, + open: function() { + $(this).removeClass("ui-corner-all").addClass("ui-corner-top"); + }, + close: function() { + $(this).removeClass("ui-corner-top").addClass("ui-corner-all"); + } + }); + + // Autocomplete text-highlight + $.ui.autocomplete.prototype._renderItem = function(ul, item) { + let txt = String(item.label).replace(new RegExp(this.term, "gi"),"$&"); + return $("
  • ") + .data("ui-autocomplete-item", item.value) + .append("
    " + txt + "
    ") + .appendTo(ul); + }; + /* Refreshes zone data set and then re-displays */ $scope.refreshZones = function () { zonesPaging = pagingService.resetPaging(zonesPaging); From 0d2a47e827f0cda2bcc56ecaf1c4b66320353dd1 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 14 Oct 2022 12:26:45 +0530 Subject: [PATCH 038/521] Refactor crypto be strongly typed --- .../dns/DnsBackendIntegrationSpec.scala | 12 ++--- .../RecordSetServiceIntegrationSpec.scala | 6 +-- .../zone/ZoneViewLoaderIntegrationSpec.scala | 9 ++-- .../vinyldns/api/backend/dns/DnsBackend.scala | 2 +- .../api/repository/TestDataLoader.scala | 27 ++++++----- .../vinyldns/api/route/DnsJsonProtocol.scala | 15 +++++- .../api/backend/dns/DnsBackendSpec.scala | 3 +- .../api/config/VinylDNSConfigSpec.scala | 3 +- .../domain/access/AccessValidationsSpec.scala | 4 +- .../MembershipValidationsSpec.scala | 9 ++-- .../zone/ZoneConnectionValidatorSpec.scala | 18 +++---- .../api/domain/zone/ZoneServiceSpec.scala | 5 +- .../api/domain/zone/ZoneViewLoaderSpec.scala | 4 +- .../api/engine/ZoneSyncHandlerSpec.scala | 10 ++-- .../notifier/email/EmailNotifierSpec.scala | 10 ++-- .../api/route/VinylDNSJsonProtocolSpec.scala | 10 ++-- .../vinyldns/api/route/ZoneRoutingSpec.scala | 17 +++---- .../vinyldns/core/domain/Encryption.scala | 31 ++++++++++++ .../core/domain/auth/AuthPrincipal.scala | 2 +- .../core/domain/membership/User.scala | 7 +-- .../vinyldns/core/domain/zone/Zone.scala | 11 +++-- .../core/protobuf/ProtobufConversions.scala | 10 ++-- .../vinyldns/core/TestMembershipData.scala | 17 +++---- .../scala/vinyldns/core/TestZoneData.scala | 3 +- .../domain/membership/UserChangeSpec.scala | 3 +- .../core/domain/zone/ZoneConnectionSpec.scala | 9 ++-- .../protobuf/ProtobufConversionsSpec.scala | 48 +++++++++---------- ...lUserChangeRepositoryIntegrationSpec.scala | 3 +- .../MySqlUserRepositoryIntegrationSpec.scala | 23 ++++----- ...lZoneChangeRepositoryIntegrationSpec.scala | 3 +- .../MySqlZoneRepositoryIntegrationSpec.scala | 3 +- modules/portal/app/controllers/VinylDNS.scala | 7 +-- .../controllers/LdapAuthenticatorSpec.scala | 3 +- .../controllers/TestApplicationData.scala | 11 +++-- .../controllers/UserAccountAccessorSpec.scala | 5 +- .../test/controllers/VinylDNSSpec.scala | 4 +- .../portal/test/tasks/UserSyncTaskSpec.scala | 3 +- 37 files changed, 214 insertions(+), 156 deletions(-) create mode 100644 modules/core/src/main/scala/vinyldns/core/domain/Encryption.scala diff --git a/modules/api/src/it/scala/vinyldns/api/backend/dns/DnsBackendIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/backend/dns/DnsBackendIntegrationSpec.scala index 0fd274b79..fd55ed9a9 100644 --- a/modules/api/src/it/scala/vinyldns/api/backend/dns/DnsBackendIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/backend/dns/DnsBackendIntegrationSpec.scala @@ -21,14 +21,8 @@ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import vinyldns.api.backend.dns.DnsProtocol.NoError import vinyldns.core.crypto.NoOpCrypto -import vinyldns.core.domain.record.{ - AData, - RecordSet, - RecordSetChange, - RecordSetChangeType, - RecordSetStatus, - RecordType -} +import vinyldns.core.domain.Encrypted +import vinyldns.core.domain.record.{AData, RecordSet, RecordSetChange, RecordSetChangeType, RecordSetStatus, RecordType} import vinyldns.core.domain.zone.{Algorithm, Zone, ZoneConnection} class DnsBackendIntegrationSpec extends AnyWordSpec with Matchers { @@ -36,7 +30,7 @@ class DnsBackendIntegrationSpec extends AnyWordSpec with Matchers { private val testConnection = ZoneConnection( "vinyldns.", "vinyldns.", - "nzisn+4G2ldMn0q1CV3vsg==", + Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), sys.env.getOrElse("DEFAULT_DNS_ADDRESS", "127.0.0.1:19001"), Algorithm.HMAC_MD5 ) diff --git a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala index df845b477..89e0dc145 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala @@ -34,7 +34,7 @@ import vinyldns.api.engine.TestMessageQueue import vinyldns.mysql.TransactionProvider import vinyldns.core.TestMembershipData._ import vinyldns.core.TestZoneData.testConnection -import vinyldns.core.domain.{Fqdn, HighValueDomainError} +import vinyldns.core.domain.{Encrypted, Fqdn, HighValueDomainError} import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.backend.{Backend, BackendResolver} import vinyldns.core.domain.membership.{Group, GroupRepository, User, UserRepository} @@ -63,8 +63,8 @@ class RecordSetServiceIntegrationSpec private var testRecordSetService: RecordSetServiceAlgebra = _ - private val user = User("live-test-user", "key", "secret") - private val user2 = User("shared-record-test-user", "key-shared", "secret-shared") + private val user = User("live-test-user", "key", Encrypted("secret")) + private val user2 = User("shared-record-test-user", "key-shared", Encrypted("secret-shared")) private val group = Group(s"test-group", "test@test.com", adminUserIds = Set(user.id)) private val group2 = Group(s"test-group", "test@test.com", adminUserIds = Set(user.id, user2.id)) private val sharedGroup = diff --git a/modules/api/src/it/scala/vinyldns/api/domain/zone/ZoneViewLoaderIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/zone/ZoneViewLoaderIntegrationSpec.scala index b757d5518..caf2305cb 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/zone/ZoneViewLoaderIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/zone/ZoneViewLoaderIntegrationSpec.scala @@ -21,6 +21,7 @@ import org.scalatest.wordspec.AnyWordSpec import org.xbill.DNS.ZoneTransferException import vinyldns.api.backend.dns.DnsBackend import vinyldns.api.config.VinylDNSConfig +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.backend.BackendResolver import vinyldns.core.domain.zone.{Zone, ZoneConnection} @@ -50,12 +51,12 @@ class ZoneViewLoaderIntegrationSpec extends AnyWordSpec with Matchers { ZoneConnection( "vinyldns.", "vinyldns.", - "nzisn+4G2ldMn0q1CV3vsg==", + Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), sys.env.getOrElse("DEFAULT_DNS_ADDRESS", "127.0.0.1:19001") ) ), transferConnection = - Some(ZoneConnection("invalid-connection.", "bad-key", "invalid-key", "10.1.1.1")) + Some(ZoneConnection("invalid-connection.", "bad-key", Encrypted("invalid-key"), "10.1.1.1")) ) val backend = backendResolver.resolve(zone).asInstanceOf[DnsBackend] println(s"${backend.id}, ${backend.xfrInfo}, ${backend.resolver.getAddress}") @@ -83,7 +84,7 @@ class ZoneViewLoaderIntegrationSpec extends AnyWordSpec with Matchers { ZoneConnection( "vinyldns.", "vinyldns.", - "nzisn+4G2ldMn0q1CV3vsg==", + Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), sys.env.getOrElse("DEFAULT_DNS_ADDRESS", "127.0.0.1:19001") ) ), @@ -91,7 +92,7 @@ class ZoneViewLoaderIntegrationSpec extends AnyWordSpec with Matchers { ZoneConnection( "vinyldns.", "vinyldns.", - "nzisn+4G2ldMn0q1CV3vsg==", + Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), sys.env.getOrElse("DEFAULT_DNS_ADDRESS", "127.0.0.1:19001") ) ) diff --git a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala index afcc77054..ccc393e66 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala @@ -293,7 +293,7 @@ object DnsBackend { new DNS.TSIG( parseAlgorithm(conn.algorithm), decryptedConnection.keyName, - decryptedConnection.key + decryptedConnection.key.value ) } diff --git a/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala b/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala index c1e936b28..609b03be3 100644 --- a/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala +++ b/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala @@ -21,6 +21,7 @@ import cats.implicits._ import org.joda.time.DateTime import org.slf4j.{Logger, LoggerFactory} import scalikejdbc.DB +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.membership._ import vinyldns.core.domain.zone._ import vinyldns.mysql.TransactionProvider @@ -38,7 +39,7 @@ object TestDataLoader extends TransactionProvider { id = "testuser", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "testUserAccessKey", - secretKey = "testUserSecretKey", + secretKey = Encrypted("testUserSecretKey"), firstName = Some("Test"), lastName = Some("User"), email = Some("test@test.com"), @@ -49,7 +50,7 @@ object TestDataLoader extends TransactionProvider { id = "ok", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "okAccessKey", - secretKey = "okSecretKey", + secretKey = Encrypted("okSecretKey"), firstName = Some("ok"), lastName = Some("ok"), email = Some("test@test.com"), @@ -60,7 +61,7 @@ object TestDataLoader extends TransactionProvider { id = "dummy", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "dummyAccessKey", - secretKey = "dummySecretKey", + secretKey = Encrypted("dummySecretKey"), isTest = true ) final val sharedZoneUser = User( @@ -68,7 +69,7 @@ object TestDataLoader extends TransactionProvider { id = "sharedZoneUser", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "sharedZoneUserAccessKey", - secretKey = "sharedZoneUserSecretKey", + secretKey = Encrypted("sharedZoneUserSecretKey"), firstName = Some("sharedZoneUser"), lastName = Some("sharedZoneUser"), email = Some("test@test.com"), @@ -79,7 +80,7 @@ object TestDataLoader extends TransactionProvider { id = "locked", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "lockedAccessKey", - secretKey = "lockedSecretKey", + secretKey = Encrypted("lockedSecretKey"), firstName = Some("Locked"), lastName = Some("User"), email = Some("testlocked@test.com"), @@ -92,7 +93,7 @@ object TestDataLoader extends TransactionProvider { id = "dummy%03d".format(runner), created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "dummy", - secretKey = "dummy", + secretKey = Encrypted("dummy"), isTest = true ) } @@ -101,7 +102,7 @@ object TestDataLoader extends TransactionProvider { id = "list-group-user", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "listGroupAccessKey", - secretKey = "listGroupSecretKey", + secretKey = Encrypted("listGroupSecretKey"), firstName = Some("list-group"), lastName = Some("list-group"), email = Some("test@test.com"), @@ -113,7 +114,7 @@ object TestDataLoader extends TransactionProvider { id = "list-zones-user", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "listZonesAccessKey", - secretKey = "listZonesSecretKey", + secretKey = Encrypted("listZonesSecretKey"), firstName = Some("list-zones"), lastName = Some("list-zones"), email = Some("test@test.com"), @@ -125,7 +126,7 @@ object TestDataLoader extends TransactionProvider { id = "history-id", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "history-key", - secretKey = "history-secret", + secretKey = Encrypted("history-secret"), firstName = Some("history-first"), lastName = Some("history-last"), email = Some("history@history.com"), @@ -137,7 +138,7 @@ object TestDataLoader extends TransactionProvider { id = "list-records-user", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "listRecordsAccessKey", - secretKey = "listRecordsSecretKey", + secretKey = Encrypted("listRecordsSecretKey"), firstName = Some("list-records"), lastName = Some("list-records"), email = Some("test@test.com"), @@ -149,7 +150,7 @@ object TestDataLoader extends TransactionProvider { id = "list-batch-summaries-id", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "listBatchSummariesAccessKey", - secretKey = "listBatchSummariesSecretKey", + secretKey = Encrypted("listBatchSummariesSecretKey"), firstName = Some("list-batch-summaries"), lastName = Some("list-batch-summaries"), email = Some("test@test.com"), @@ -169,7 +170,7 @@ object TestDataLoader extends TransactionProvider { id = "list-zero-summaries-id", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "listZeroSummariesAccessKey", - secretKey = "listZeroSummariesSecretKey", + secretKey = Encrypted("listZeroSummariesSecretKey"), firstName = Some("list-zero-summaries"), lastName = Some("list-zero-summaries"), email = Some("test@test.com"), @@ -181,7 +182,7 @@ object TestDataLoader extends TransactionProvider { id = "support-user-id", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "supportUserAccessKey", - secretKey = "supportUserSecretKey", + secretKey = Encrypted("supportUserSecretKey"), firstName = Some("support-user"), lastName = Some("support-user"), email = Some("test@test.com"), diff --git a/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala b/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala index 025834995..e04f882af 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala @@ -26,7 +26,7 @@ import scodec.bits.{Bases, ByteVector} import vinyldns.api.domain.zone.{RecordSetGlobalInfo, RecordSetInfo, RecordSetListInfo} import vinyldns.core.domain.DomainHelpers.ensureTrailingDot import vinyldns.core.domain.DomainHelpers.removeWhitespace -import vinyldns.core.domain.Fqdn +import vinyldns.core.domain.{EncryptFromJson, Encrypted, Fqdn} import vinyldns.core.domain.record._ import vinyldns.core.domain.zone._ import vinyldns.core.Messages._ @@ -39,6 +39,7 @@ trait DnsJsonProtocol extends JsonValidation { UpdateZoneInputSerializer, ZoneConnectionSerializer, AlgorithmSerializer, + EncryptedSerializer, RecordSetSerializer, RecordSetListInfoSerializer, RecordSetGlobalInfoSerializer, @@ -129,12 +130,22 @@ trait DnsJsonProtocol extends JsonValidation { override def toJson(a: Algorithm): JValue = JString(a.name) } + case object EncryptedSerializer extends ValidationSerializer[Encrypted] { + override def fromJson(js: JValue): ValidatedNel[String, Encrypted] = + js match { + case JString(value) => EncryptFromJson.fromString(value).toValidatedNel + case _ => "Unsupported type for zone connection key, must be a string".invalidNel + } + + override def toJson(a: Encrypted): JValue = JString(a.value) + } + case object ZoneConnectionSerializer extends ValidationSerializer[ZoneConnection] { override def fromJson(js: JValue): ValidatedNel[String, ZoneConnection] = ( (js \ "name").required[String]("Missing ZoneConnection.name"), (js \ "keyName").required[String]("Missing ZoneConnection.keyName"), - (js \ "key").required[String]("Missing ZoneConnection.key"), + (js \ "key").required[Encrypted]("Missing ZoneConnection.key"), (js \ "primaryServer").required[String]("Missing ZoneConnection.primaryServer"), (js \ "algorithm").default[Algorithm](Algorithm.HMAC_MD5) ).mapN(ZoneConnection.apply) diff --git a/modules/api/src/test/scala/vinyldns/api/backend/dns/DnsBackendSpec.scala b/modules/api/src/test/scala/vinyldns/api/backend/dns/DnsBackendSpec.scala index a095c41f3..f85d2b6b0 100644 --- a/modules/api/src/test/scala/vinyldns/api/backend/dns/DnsBackendSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/backend/dns/DnsBackendSpec.scala @@ -29,6 +29,7 @@ import org.xbill.DNS import org.xbill.DNS.{Lookup, Name, TSIG} import vinyldns.api.backend.dns.DnsProtocol._ import vinyldns.core.crypto.{CryptoAlgebra, NoOpCrypto} +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.backend.BackendResponse import vinyldns.core.domain.record.RecordType._ import vinyldns.core.domain.record._ @@ -46,7 +47,7 @@ class DnsBackendSpec with EitherValues { private val zoneConnection = - ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1") + ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1") private val testZone = Zone("vinyldns", "test@test.com") private val testA = RecordSet( testZone.id, diff --git a/modules/api/src/test/scala/vinyldns/api/config/VinylDNSConfigSpec.scala b/modules/api/src/test/scala/vinyldns/api/config/VinylDNSConfigSpec.scala index 4317fc534..63d4e70e0 100644 --- a/modules/api/src/test/scala/vinyldns/api/config/VinylDNSConfigSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/config/VinylDNSConfigSpec.scala @@ -21,6 +21,7 @@ import org.scalatest.BeforeAndAfterAll import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import vinyldns.api.backend.dns.DnsBackendProviderConfig +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.zone.ZoneConnection import vinyldns.core.repository.RepositoryName._ @@ -68,7 +69,7 @@ class VinylDNSConfigSpec extends AnyWordSpec with Matchers with BeforeAndAfterAl } "load specified backends" in { - val zc = ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", sys.env.getOrElse("DEFAULT_DNS_ADDRESS", "127.0.0.1:19001")) + val zc = ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), sys.env.getOrElse("DEFAULT_DNS_ADDRESS", "127.0.0.1:19001")) val tc = zc.copy() val backends = underTest.backendConfigs.backendProviders diff --git a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala index f55df7783..562f458e8 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/access/AccessValidationsSpec.scala @@ -25,7 +25,7 @@ import vinyldns.api.domain.zone.{NotAuthorizedError, RecordSetInfo, RecordSetLis import vinyldns.core.TestMembershipData._ import vinyldns.core.TestRecordSetData._ import vinyldns.core.TestZoneData._ -import vinyldns.core.domain.Fqdn +import vinyldns.core.domain.{Encrypted, Fqdn} import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.membership.User import vinyldns.core.domain.record._ @@ -94,7 +94,7 @@ class AccessValidationsSpec VinylDNSTestHelpers.sharedApprovedTypes ) - private val testUser = User("test", "test", "test", isTest = true) + private val testUser = User("test", "test", Encrypted("test"), isTest = true) "canSeeZone" should { "return a NotAuthorizedError if the user is not admin or super user with no acl rules" in { diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipValidationsSpec.scala index 84e7e842c..24b592c2d 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipValidationsSpec.scala @@ -25,6 +25,7 @@ import vinyldns.api.ResultHelpers import vinyldns.core.TestMembershipData._ import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.api.domain.zone.NotAuthorizedError +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.membership.User class MembershipValidationsSpec @@ -64,13 +65,13 @@ class MembershipValidationsSpec canEditGroup(okGroup, superUserAuth) should be(right) } "return an error when the user is a support admin only" in { - val user = User("some", "new", "user", isSupport = true) + val user = User("some", "new", Encrypted("user"), isSupport = true) val supportAuth = AuthPrincipal(user, Seq()) val error = leftValue(canEditGroup(okGroup, supportAuth)) error shouldBe an[NotAuthorizedError] } "return an error when the user has no access and is not super" in { - val user = User("some", "new", "user") + val user = User("some", "new", Encrypted("user")) val nonSuperAuth = AuthPrincipal(user, Seq()) val error = leftValue(canEditGroup(okGroup, nonSuperAuth)) error shouldBe an[NotAuthorizedError] @@ -85,12 +86,12 @@ class MembershipValidationsSpec canSeeGroup(okGroup.id, superUserAuth) should be(right) } "return true when the user is a support admin" in { - val user = User("some", "new", "user", isSupport = true) + val user = User("some", "new", Encrypted("user"), isSupport = true) val supportAuth = AuthPrincipal(user, Seq()) canSeeGroup(okGroup.id, supportAuth) should be(right) } "return true even when a user is not a member of the group or super" in { - val user = User("some", "new", "user") + val user = User("some", "new", Encrypted("user")) val nonSuperAuth = AuthPrincipal(user, Seq()) canSeeGroup(okGroup.id, nonSuperAuth) should be(right) } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneConnectionValidatorSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneConnectionValidatorSpec.scala index 10e3e53ac..933f82b0b 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneConnectionValidatorSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneConnectionValidatorSpec.scala @@ -27,7 +27,7 @@ import vinyldns.core.domain.record._ import vinyldns.api.ResultHelpers import cats.effect._ import org.mockito.Matchers.any -import vinyldns.core.domain.Fqdn +import vinyldns.core.domain.{Encrypted, Fqdn} import vinyldns.core.domain.backend.{Backend, BackendResolver} import vinyldns.core.domain.zone.{ConfiguredDnsConnections, LegacyDnsBackend, Zone, ZoneConnection} @@ -85,9 +85,9 @@ class ZoneConnectionValidatorSpec "vinyldns.", "test@test.com", connection = - Some(ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1")), + Some(ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1")), transferConnection = - Some(ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1")) + Some(ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1")) ) private val successSoa = RecordSet( @@ -134,8 +134,8 @@ class ZoneConnectionValidatorSpec List(NSData(Fqdn("sub.some.test.ns."))) ) - val zc = ZoneConnection("zc.", "zc.", "zc", "10.1.1.1") - val transfer = ZoneConnection("transfer.", "transfer.", "transfer", "10.1.1.1") + val zc = ZoneConnection("zc.", "zc.", Encrypted("zc"), "10.1.1.1") + val transfer = ZoneConnection("transfer.", "transfer.", Encrypted("transfer"), "10.1.1.1") val backend = LegacyDnsBackend( "some-backend-id", zc.copy(name = "backend-conn"), @@ -195,9 +195,9 @@ class ZoneConnectionValidatorSpec "error.", "test@test.com", connection = - Some(ZoneConnection("error.", "error.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1")), + Some(ZoneConnection("error.", "error.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1")), transferConnection = - Some(ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1")) + Some(ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1")) ) doReturn(IO.pure(true)).when(mockBackend).zoneExists(any[Zone]) doReturn(mockBackend).when(mockBackendResolver).resolve(any[Zone]) @@ -211,9 +211,9 @@ class ZoneConnectionValidatorSpec "error.", "test@test.com", connection = - Some(ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1")), + Some(ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1")), transferConnection = - Some(ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1")) + Some(ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1")) ) doReturn(IO.pure(true)).when(mockBackend).zoneExists(any[Zone]) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index c6ab3e935..792cfb5ac 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -35,6 +35,7 @@ import vinyldns.core.queue.MessageQueue import vinyldns.core.TestMembershipData._ import vinyldns.core.TestZoneData._ import vinyldns.core.crypto.NoOpCrypto +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.backend.BackendResolver import scala.concurrent.duration._ @@ -53,7 +54,7 @@ class ZoneServiceSpec private val mockZoneChangeRepo = mock[ZoneChangeRepository] private val mockMessageQueue = mock[MessageQueue] private val mockBackendResolver = mock[BackendResolver] - private val badConnection = ZoneConnection("bad", "bad", "bad", "bad") + private val badConnection = ZoneConnection("bad", "bad", Encrypted("bad"), "bad") private val abcZoneSummary = ZoneSummaryInfo(abcZone, abcGroup.name, AccessLevel.Delete) private val xyzZoneSummary = ZoneSummaryInfo(xyzZone, xyzGroup.name, AccessLevel.NoAccess) @@ -442,7 +443,7 @@ class ZoneServiceSpec } "filter out ACL rules that have no matching group or user" in { - val goodUser = User("goodUser", "access", "secret") + val goodUser = User("goodUser", "access", Encrypted("secret")) val goodGroup = Group("goodGroup", "email") val goodUserRule = baseAclRule.copy(userId = Some(goodUser.id), groupId = None) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneViewLoaderSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneViewLoaderSpec.scala index 813814c3c..8d27568eb 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneViewLoaderSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneViewLoaderSpec.scala @@ -32,7 +32,7 @@ import vinyldns.core.domain.record._ import scala.collection.mutable import cats.effect._ import vinyldns.api.backend.dns.DnsConversions -import vinyldns.core.domain.Fqdn +import vinyldns.core.domain.{Encrypted, Fqdn} import vinyldns.core.domain.backend.{Backend, BackendResolver} import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordType.RecordType @@ -43,7 +43,7 @@ class ZoneViewLoaderSpec extends AnyWordSpec with Matchers with MockitoSugar wit private val testZoneName = "vinyldns." private val testZoneConnection: Option[ZoneConnection] = Some( - ZoneConnection(testZoneName, testZoneName, "nzisn+4G2ldMn0q1CV3vsg==", "127.0.0.1:19001") + ZoneConnection(testZoneName, testZoneName, Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "127.0.0.1:19001") ) private val mockBackendResolver = mock[BackendResolver] diff --git a/modules/api/src/test/scala/vinyldns/api/engine/ZoneSyncHandlerSpec.scala b/modules/api/src/test/scala/vinyldns/api/engine/ZoneSyncHandlerSpec.scala index 7972995ab..148fe4d77 100644 --- a/modules/api/src/test/scala/vinyldns/api/engine/ZoneSyncHandlerSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/engine/ZoneSyncHandlerSpec.scala @@ -29,7 +29,7 @@ import scalikejdbc.{ConnectionPool, DB} import vinyldns.api.VinylDNSTestHelpers import vinyldns.api.domain.record.RecordSetChangeGenerator import vinyldns.api.domain.zone.{DnsZoneViewLoader, VinylDNSZoneViewLoader, ZoneView} -import vinyldns.core.domain.Fqdn +import vinyldns.core.domain.{Encrypted, Fqdn} import vinyldns.core.domain.backend.{Backend, BackendResolver} import vinyldns.core.domain.record.NameSort.NameSort import vinyldns.core.domain.record.RecordType.RecordType @@ -180,16 +180,16 @@ class ZoneSyncHandlerSpec zoneName, "test@test.com", ZoneStatus.Active, - connection = Some(ZoneConnection(zoneName, dnsKeyName, dnsTsig, dnsServerAddress)), - transferConnection = Some(ZoneConnection(zoneName, dnsKeyName, dnsTsig, dnsServerAddress)) + connection = Some(ZoneConnection(zoneName, dnsKeyName, Encrypted(dnsTsig), dnsServerAddress)), + transferConnection = Some(ZoneConnection(zoneName, dnsKeyName, Encrypted(dnsTsig), dnsServerAddress)) ) private val testReverseZone = Zone( reverseZoneName, "test@test.com", ZoneStatus.Active, - connection = Some(ZoneConnection(zoneName, dnsKeyName, dnsTsig, dnsServerAddress)), - transferConnection = Some(ZoneConnection(zoneName, dnsKeyName, dnsTsig, dnsServerAddress)) + connection = Some(ZoneConnection(zoneName, dnsKeyName, Encrypted(dnsTsig), dnsServerAddress)), + transferConnection = Some(ZoneConnection(zoneName, dnsKeyName, Encrypted(dnsTsig), dnsServerAddress)) ) private val testRecord1 = RecordSet( diff --git a/modules/api/src/test/scala/vinyldns/api/notifier/email/EmailNotifierSpec.scala b/modules/api/src/test/scala/vinyldns/api/notifier/email/EmailNotifierSpec.scala index e3e26ba05..cdf4ce11f 100644 --- a/modules/api/src/test/scala/vinyldns/api/notifier/email/EmailNotifierSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/notifier/email/EmailNotifierSpec.scala @@ -22,22 +22,22 @@ import org.scalatestplus.mockito.MockitoSugar import vinyldns.api.CatsHelpers import javax.mail.{Provider, Session, Transport, URLName} import java.util.Properties - -import vinyldns.core.domain.membership.UserRepository +import vinyldns.core.domain.membership.{User, UserRepository} import vinyldns.core.notifier.Notification + import javax.mail.internet.InternetAddress import org.mockito.Matchers.{eq => eqArg, _} import org.mockito.Mockito._ import org.mockito.ArgumentCaptor import cats.effect.IO import javax.mail.{Address, Message} -import vinyldns.core.domain.membership.User import _root_.vinyldns.core.domain.batch._ import org.joda.time.DateTime import vinyldns.core.domain.record.RecordType import vinyldns.core.domain.record.AData import com.typesafe.config.Config import com.typesafe.config.ConfigFactory +import vinyldns.core.domain.Encrypted import scala.collection.JavaConverters._ import vinyldns.core.notifier.NotifierConfig @@ -122,7 +122,7 @@ class EmailNotifierSpec mockUserRepository ) - doReturn(IO.pure(Some(User("testUser", "access", "secret")))) + doReturn(IO.pure(Some(User("testUser", "access", Encrypted("secret"))))) .when(mockUserRepository) .getUser("test") @@ -156,7 +156,7 @@ class EmailNotifierSpec ) doReturn( - IO.pure(Some(User("testUser", "access", "secret", None, None, Some("testuser@test.com")))) + IO.pure(Some(User("testUser", "access", Encrypted("secret"), None, None, Some("testuser@test.com")))) ).when(mockUserRepository) .getUser("test") diff --git a/modules/api/src/test/scala/vinyldns/api/route/VinylDNSJsonProtocolSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/VinylDNSJsonProtocolSpec.scala index d566637c2..1cccf7f60 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/VinylDNSJsonProtocolSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/VinylDNSJsonProtocolSpec.scala @@ -26,7 +26,7 @@ import vinyldns.api.VinylDNSTestHelpers import vinyldns.core.domain.record._ import vinyldns.core.domain.zone.{CreateZoneInput, UpdateZoneInput, ZoneConnection} import vinyldns.core.TestRecordSetData._ -import vinyldns.core.domain.Fqdn +import vinyldns.core.domain.{Encrypted, Fqdn} import vinyldns.core.Messages._ class VinylDNSJsonProtocolSpec @@ -43,7 +43,7 @@ class VinylDNSJsonProtocolSpec ZoneConnection( "primaryConnection", "primaryConnectionKeyName", - "primaryConnectionKey", + Encrypted("primaryConnectionKey"), "10.1.1.1" ) ), @@ -51,7 +51,7 @@ class VinylDNSJsonProtocolSpec ZoneConnection( "transferConnection", "transferConnectionKeyName", - "transferConnectionKey", + Encrypted("transferConnectionKey"), "10.1.1.2" ) ), @@ -66,7 +66,7 @@ class VinylDNSJsonProtocolSpec ZoneConnection( "primaryConnection", "primaryConnectionKeyName", - "primaryConnectionKey", + Encrypted("primaryConnectionKey"), "10.1.1.1" ) ), @@ -74,7 +74,7 @@ class VinylDNSJsonProtocolSpec ZoneConnection( "transferConnection", "transferConnectionKeyName", - "transferConnectionKey", + Encrypted("transferConnectionKey"), "10.1.1.2" ) ), diff --git a/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala index 38927d2d8..80337cda6 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala @@ -33,6 +33,7 @@ import vinyldns.api.domain.zone.{ZoneServiceAlgebra, _} import vinyldns.core.TestMembershipData._ import vinyldns.core.TestZoneData._ import vinyldns.core.crypto.{JavaCrypto, NoOpCrypto} +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.record.RecordType import vinyldns.core.domain.zone._ @@ -86,18 +87,18 @@ class ZoneRoutingSpec private val connectionOk = Zone( "connection.ok.", "connection-ok@test.com", - connection = Some(ZoneConnection("connection.ok", "keyName", "key", "10.1.1.1")), - transferConnection = Some(ZoneConnection("connection.ok", "keyName", "key", "10.1.1.1")) + connection = Some(ZoneConnection("connection.ok", "keyName", Encrypted("key"), "10.1.1.1")), + transferConnection = Some(ZoneConnection("connection.ok", "keyName", Encrypted("key"), "10.1.1.1")) ) private val connectionFailed = Zone( "connection.fail", "connection-failed@test.com", - connection = Some(ZoneConnection("connection.fail", "keyName", "key", "10.1.1.1")) + connection = Some(ZoneConnection("connection.fail", "keyName", Encrypted("key"), "10.1.1.1")) ) private val zoneValidationFailed = Zone( "validation.fail", "zone-validation-failed@test.com", - connection = Some(ZoneConnection("validation.fail", "keyName", "key", "10.1.1.1")) + connection = Some(ZoneConnection("validation.fail", "keyName", Encrypted("key"), "10.1.1.1")) ) private val zone1 = Zone("zone1.", "zone1@test.com", ZoneStatus.Active) private val zoneSummaryInfo1 = ZoneSummaryInfo(zone1, okGroup.name, AccessLevel.NoAccess) @@ -664,10 +665,10 @@ class ZoneRoutingSpec val resultKey = result.zone.connection.get.key val resultTCKey = result.zone.transferConnection.get.key - val decrypted = crypto.decrypt(resultKey) - val decryptedTC = crypto.decrypt(resultTCKey) - decrypted shouldBe connectionOk.connection.get.key - decryptedTC shouldBe connectionOk.transferConnection.get.key + val decrypted = crypto.decrypt(resultKey.value) + val decryptedTC = crypto.decrypt(resultTCKey.value) + decrypted shouldBe connectionOk.connection.get.key.value + decryptedTC shouldBe connectionOk.transferConnection.get.key.value } } diff --git a/modules/core/src/main/scala/vinyldns/core/domain/Encryption.scala b/modules/core/src/main/scala/vinyldns/core/domain/Encryption.scala new file mode 100644 index 000000000..f9c851094 --- /dev/null +++ b/modules/core/src/main/scala/vinyldns/core/domain/Encryption.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vinyldns.core.domain + +import vinyldns.core.crypto.CryptoAlgebra + +final case class Encrypted private (value: String) extends AnyVal + +object Encryption { + def apply(crypto: CryptoAlgebra, value: String): Encrypted = Encrypted(crypto.encrypt(value)) + def decrypt(crypto: CryptoAlgebra, x: Encrypted): String = crypto.decrypt(x.value) +} + +object EncryptFromJson { + def fromString(name: String): Either[String, Encrypted] = + Option(Encrypted(name)).toRight[String](s"Unsupported format") +} diff --git a/modules/core/src/main/scala/vinyldns/core/domain/auth/AuthPrincipal.scala b/modules/core/src/main/scala/vinyldns/core/domain/auth/AuthPrincipal.scala index 03034e964..14e338a17 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/auth/AuthPrincipal.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/auth/AuthPrincipal.scala @@ -34,7 +34,7 @@ case class AuthPrincipal(signedInUser: User, memberGroupIds: Seq[String]) { def isTestUser: Boolean = signedInUser.isTest - val secretKey: String = signedInUser.secretKey + val secretKey: String = signedInUser.secretKey.value val userId: String = signedInUser.id diff --git a/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala b/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala index 9172dc9a3..1d59e1910 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/membership/User.scala @@ -21,6 +21,7 @@ import java.util.UUID import org.apache.commons.lang3.RandomStringUtils import org.joda.time.DateTime import vinyldns.core.crypto.CryptoAlgebra +import vinyldns.core.domain.{Encrypted, Encryption} import vinyldns.core.domain.membership.LockStatus.LockStatus object LockStatus extends Enumeration { @@ -31,7 +32,7 @@ object LockStatus extends Enumeration { final case class User( userName: String, accessKey: String, - secretKey: String, + secretKey: Encrypted, firstName: Option[String] = None, lastName: Option[String] = None, email: Option[String] = None, @@ -47,10 +48,10 @@ final case class User( this.copy(lockStatus = lockStatus) def regenerateCredentials(): User = - copy(accessKey = User.generateKey, secretKey = User.generateKey) + copy(accessKey = User.generateKey, secretKey = Encrypted(User.generateKey)) def withEncryptedSecretKey(cryptoAlgebra: CryptoAlgebra): User = - copy(secretKey = cryptoAlgebra.encrypt(secretKey)) + copy(secretKey = Encryption.apply(cryptoAlgebra, secretKey.value)) } object User { diff --git a/modules/core/src/main/scala/vinyldns/core/domain/zone/Zone.scala b/modules/core/src/main/scala/vinyldns/core/domain/zone/Zone.scala index 398bf6131..9ef0057a4 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/zone/Zone.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/zone/Zone.scala @@ -25,6 +25,7 @@ import pureconfig.{ConfigReader, ConfigSource} import pureconfig.error.CannotConvert import pureconfig.generic.auto._ import vinyldns.core.crypto.CryptoAlgebra +import vinyldns.core.domain.{Encrypted, Encryption} import scala.collection.JavaConverters._ object ZoneStatus extends Enumeration { @@ -178,16 +179,16 @@ object Algorithm { case class ZoneConnection( name: String, keyName: String, - key: String, + key: Encrypted, primaryServer: String, algorithm: Algorithm = Algorithm.HMAC_MD5 ) { def encrypted(crypto: CryptoAlgebra): ZoneConnection = - copy(key = crypto.encrypt(key)) + copy(key = Encryption.apply(crypto, key.value)) def decrypted(crypto: CryptoAlgebra): ZoneConnection = - copy(key = crypto.decrypt(key)) + copy(key = Encrypted(Encryption.decrypt(crypto, key))) } final case class LegacyDnsBackend( @@ -220,7 +221,7 @@ object ConfiguredDnsConnections { if (connectionConfig.hasPath("algorithm")) Algorithm.Map.getOrElse(connectionConfig.getString("algorithm"), Algorithm.HMAC_MD5) else Algorithm.HMAC_MD5 - ZoneConnection(name, keyName, key, primaryServer, algorithm).encrypted(crypto) + ZoneConnection(name, keyName, Encrypted(key), primaryServer, algorithm).encrypted(crypto) } val defaultTransferConnection = { @@ -233,7 +234,7 @@ object ConfiguredDnsConnections { if (connectionConfig.hasPath("algorithm")) Algorithm.Map.getOrElse(connectionConfig.getString("algorithm"), Algorithm.HMAC_MD5) else Algorithm.HMAC_MD5 - ZoneConnection(name, keyName, key, primaryServer, algorithm).encrypted(crypto) + ZoneConnection(name, keyName, Encrypted(key), primaryServer, algorithm).encrypted(crypto) } val dnsBackends = { diff --git a/modules/core/src/main/scala/vinyldns/core/protobuf/ProtobufConversions.scala b/modules/core/src/main/scala/vinyldns/core/protobuf/ProtobufConversions.scala index 3b302c442..8984cab62 100644 --- a/modules/core/src/main/scala/vinyldns/core/protobuf/ProtobufConversions.scala +++ b/modules/core/src/main/scala/vinyldns/core/protobuf/ProtobufConversions.scala @@ -25,7 +25,7 @@ import vinyldns.core.domain.membership.{LockStatus, User, UserChange, UserChange import vinyldns.core.domain.record.RecordType.RecordType import vinyldns.core.domain.record._ import vinyldns.core.domain.zone._ -import vinyldns.core.domain.{Fqdn, record, zone} +import vinyldns.core.domain.{Encrypted, Fqdn, record, zone} import vinyldns.proto.VinylDNSProto import scala.collection.JavaConverters._ @@ -132,7 +132,7 @@ trait ProtobufConversions { ZoneConnection( zc.getName, zc.getKeyName, - zc.getKey, + Encrypted(zc.getKey), zc.getPrimaryServer, fromPB(zc.getAlgorithm) ) @@ -417,7 +417,7 @@ trait ProtobufConversions { .newBuilder() .setName(conn.name) .setKeyName(conn.keyName) - .setKey(conn.key) + .setKey(conn.key.value) .setPrimaryServer(conn.primaryServer) .setAlgorithm(toPB(conn.algorithm)) .build() @@ -441,7 +441,7 @@ trait ProtobufConversions { User( data.getUserName, data.getAccessKey, - data.getSecretKey, + Encrypted(data.getSecretKey), if (data.hasFirstName) Some(data.getFirstName) else None, if (data.hasLastName) Some(data.getLastName) else None, if (data.hasEmail) Some(data.getEmail) else None, @@ -458,7 +458,7 @@ trait ProtobufConversions { .newBuilder() .setUserName(user.userName) .setAccessKey(user.accessKey) - .setSecretKey(user.secretKey) + .setSecretKey(user.secretKey.value) .setCreated(user.created.getMillis) .setId(user.id) .setIsSuper(user.isSuper) diff --git a/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala b/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala index 36ba54ec0..94608598b 100644 --- a/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala +++ b/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala @@ -17,6 +17,7 @@ package vinyldns.core import org.joda.time.DateTime +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.membership._ @@ -28,17 +29,17 @@ object TestMembershipData { id = "ok", created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "okAccessKey", - secretKey = "okSecretKey", + secretKey = Encrypted("okSecretKey"), firstName = Some("ok"), lastName = Some("ok"), email = Some("test@test.com") ) - val dummyUser = User("dummyName", "dummyAccess", "dummySecret") - val superUser = User("super", "superAccess", "superSecret", isSuper = true) - val supportUser = User("support", "supportAccess", "supportSecret", isSupport = true) - val lockedUser = User("locked", "lockedAccess", "lockedSecret", lockStatus = LockStatus.Locked) - val sharedZoneUser = User("sharedZoneAdmin", "sharedAccess", "sharedSecret") + val dummyUser = User("dummyName", "dummyAccess", Encrypted("dummySecret")) + val superUser = User("super", "superAccess", Encrypted("superSecret"), isSuper = true) + val supportUser = User("support", "supportAccess", Encrypted("supportSecret"), isSupport = true) + val lockedUser = User("locked", "lockedAccess", Encrypted("lockedSecret"), lockStatus = LockStatus.Locked) + val sharedZoneUser = User("sharedZoneAdmin", "sharedAccess", Encrypted("sharedSecret")) val listOfDummyUsers: List[User] = List.range(0, 200).map { runner => User( @@ -46,7 +47,7 @@ object TestMembershipData { id = "dummy%03d".format(runner), created = DateTime.now.secondOfDay().roundFloorCopy(), accessKey = "dummy", - secretKey = "dummy" + secretKey = Encrypted("dummy") ) } @@ -117,7 +118,7 @@ object TestMembershipData { val dummyAuth: AuthPrincipal = AuthPrincipal(dummyUser, Seq(dummyGroup.id)) - val notAuth: AuthPrincipal = AuthPrincipal(User("not", "auth", "secret"), Seq.empty) + val notAuth: AuthPrincipal = AuthPrincipal(User("not", "auth", Encrypted("secret")), Seq.empty) val sharedAuth: AuthPrincipal = AuthPrincipal(sharedZoneUser, Seq(abcGroup.id)) diff --git a/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala b/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala index 751d44359..a7b63201b 100644 --- a/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala +++ b/modules/core/src/test/scala/vinyldns/core/TestZoneData.scala @@ -19,12 +19,13 @@ package vinyldns.core import vinyldns.core.domain.zone._ import TestMembershipData._ import org.joda.time.DateTime +import vinyldns.core.domain.Encrypted object TestZoneData { /* ZONE CONNECTIONS */ val testConnection: Option[ZoneConnection] = Some( - ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1") + ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1") ) /* ZONES */ diff --git a/modules/core/src/test/scala/vinyldns/core/domain/membership/UserChangeSpec.scala b/modules/core/src/test/scala/vinyldns/core/domain/membership/UserChangeSpec.scala index 1cb853a1a..e06ef0733 100644 --- a/modules/core/src/test/scala/vinyldns/core/domain/membership/UserChangeSpec.scala +++ b/modules/core/src/test/scala/vinyldns/core/domain/membership/UserChangeSpec.scala @@ -20,10 +20,11 @@ import org.joda.time.DateTime import org.scalatest.EitherValues import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec +import vinyldns.core.domain.Encrypted class UserChangeSpec extends AnyWordSpec with Matchers with EitherMatchers with EitherValues { - private val newUser = User("foo", "key", "secret") + private val newUser = User("foo", "key", Encrypted("secret")) private val currentDate = DateTime.now "apply" should { diff --git a/modules/core/src/test/scala/vinyldns/core/domain/zone/ZoneConnectionSpec.scala b/modules/core/src/test/scala/vinyldns/core/domain/zone/ZoneConnectionSpec.scala index d536dfd0d..33b1efbd5 100644 --- a/modules/core/src/test/scala/vinyldns/core/domain/zone/ZoneConnectionSpec.scala +++ b/modules/core/src/test/scala/vinyldns/core/domain/zone/ZoneConnectionSpec.scala @@ -20,6 +20,7 @@ import cats.scalatest.EitherMatchers import vinyldns.core.crypto.CryptoAlgebra import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec +import vinyldns.core.domain.Encrypted class ZoneConnectionSpec extends AnyWordSpec with Matchers with EitherMatchers { @@ -31,16 +32,16 @@ class ZoneConnectionSpec extends AnyWordSpec with Matchers with EitherMatchers { "ZoneConnection" should { "encrypt clear connections" in { - val test = ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1") + val test = ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1") - test.encrypted(testCrypto).key shouldBe "encrypted!" + test.encrypted(testCrypto).key.value shouldBe "encrypted!" } "decrypt connections" in { - val test = ZoneConnection("vinyldns.", "vinyldns.", "nzisn+4G2ldMn0q1CV3vsg==", "10.1.1.1") + val test = ZoneConnection("vinyldns.", "vinyldns.", Encrypted("nzisn+4G2ldMn0q1CV3vsg=="), "10.1.1.1") val decrypted = test.decrypted(testCrypto) - decrypted.key shouldBe "decrypted!" + decrypted.key.value shouldBe "decrypted!" } } } diff --git a/modules/core/src/test/scala/vinyldns/core/protobuf/ProtobufConversionsSpec.scala b/modules/core/src/test/scala/vinyldns/core/protobuf/ProtobufConversionsSpec.scala index 63015254b..bf00d3107 100644 --- a/modules/core/src/test/scala/vinyldns/core/protobuf/ProtobufConversionsSpec.scala +++ b/modules/core/src/test/scala/vinyldns/core/protobuf/ProtobufConversionsSpec.scala @@ -19,7 +19,7 @@ package vinyldns.core.protobuf import org.joda.time.DateTime import org.scalatest.{Assertion, OptionValues} import vinyldns.core.TestRecordSetData.ds -import vinyldns.core.domain.Fqdn +import vinyldns.core.domain.{Encrypted, Fqdn} import vinyldns.core.domain.membership.UserChange.{CreateUser, UpdateUser} import vinyldns.core.domain.membership.{LockStatus, User, UserChangeType} import vinyldns.core.domain.record._ @@ -36,7 +36,7 @@ class ProtobufConversionsSpec with ProtobufConversions with OptionValues { - private val zoneConnection = ZoneConnection("name", "keyName", "key", "server") + private val zoneConnection = ZoneConnection("name", "keyName", Encrypted("key"), "server") private val zoneId = "test.zone.id" @@ -63,8 +63,8 @@ class ProtobufConversionsSpec private val zone = Zone( "test.zone.actor.zone", "test@test.com", - connection = Some(ZoneConnection("connection.ok", "keyName", "key", "10.1.1.1")), - transferConnection = Some(ZoneConnection("connection.ok", "keyName", "key", "10.1.1.2")), + connection = Some(ZoneConnection("connection.ok", "keyName", Encrypted("key"), "10.1.1.1")), + transferConnection = Some(ZoneConnection("connection.ok", "keyName", Encrypted("key"), "10.1.1.2")), shared = true, id = zoneId, acl = zoneAcl, @@ -269,7 +269,7 @@ class ProtobufConversionsSpec val pbconn = pb.getConnection val conn = zn.connection.get pbconn.getName shouldBe conn.name - pbconn.getKey shouldBe conn.key + pbconn.getKey shouldBe conn.key.value pbconn.getKeyName shouldBe conn.keyName pbconn.getPrimaryServer shouldBe conn.primaryServer } else { @@ -279,7 +279,7 @@ class ProtobufConversionsSpec val pbTransConn = pb.getTransferConnection val transConn = zn.transferConnection.get pbTransConn.getName shouldBe transConn.name - pbTransConn.getKey shouldBe transConn.key + pbTransConn.getKey shouldBe transConn.key.value pbTransConn.getKeyName shouldBe transConn.keyName pbTransConn.getPrimaryServer shouldBe transConn.primaryServer } else { @@ -368,7 +368,7 @@ class ProtobufConversionsSpec "convert from ZoneConnection" in { val pb = toPB(zoneConnection) - pb.getKey shouldBe zoneConnection.key + pb.getKey shouldBe zoneConnection.key.value pb.getKeyName shouldBe zoneConnection.keyName pb.getPrimaryServer shouldBe zoneConnection.primaryServer pb.getName shouldBe zoneConnection.name @@ -807,12 +807,12 @@ class ProtobufConversionsSpec "User conversion" should { "convert to/from protobuf with user defaults" in { - val user = User("testName", "testAccess", "testSecret") + val user = User("testName", "testAccess", Encrypted("testSecret")) val pb = toPB(user) pb.getUserName shouldBe user.userName pb.getAccessKey shouldBe user.accessKey - pb.getSecretKey shouldBe user.secretKey + pb.getSecretKey shouldBe user.secretKey.value pb.hasFirstName shouldBe false pb.hasLastName shouldBe false pb.hasEmail shouldBe false @@ -830,7 +830,7 @@ class ProtobufConversionsSpec val user = User( "testName", "testAccess", - "testSecret", + Encrypted("testSecret"), firstName = Some("testFirstName"), lastName = Some("testLastName"), email = Some("testEmail"), @@ -840,7 +840,7 @@ class ProtobufConversionsSpec pb.getUserName shouldBe user.userName pb.getAccessKey shouldBe user.accessKey - pb.getSecretKey shouldBe user.secretKey + pb.getSecretKey shouldBe user.secretKey.value Some(pb.getFirstName) shouldBe user.firstName Some(pb.getLastName) shouldBe user.lastName Some(pb.getEmail) shouldBe user.email @@ -855,12 +855,12 @@ class ProtobufConversionsSpec } "convert to/from protobuf with superUser true" in { - val user = User("testName", "testAccess", "testSecret", isSuper = true) + val user = User("testName", "testAccess", Encrypted("testSecret"), isSuper = true) val pb = toPB(user) pb.getUserName shouldBe user.userName pb.getAccessKey shouldBe user.accessKey - pb.getSecretKey shouldBe user.secretKey + pb.getSecretKey shouldBe user.secretKey.value pb.hasFirstName shouldBe false pb.hasLastName shouldBe false pb.hasEmail shouldBe false @@ -874,12 +874,12 @@ class ProtobufConversionsSpec } "convert to/from protobuf with locked user" in { - val user = User("testName", "testAccess", "testSecret", lockStatus = LockStatus.Locked) + val user = User("testName", "testAccess", Encrypted("testSecret"), lockStatus = LockStatus.Locked) val pb = toPB(user) pb.getUserName shouldBe user.userName pb.getAccessKey shouldBe user.accessKey - pb.getSecretKey shouldBe user.secretKey + pb.getSecretKey shouldBe user.secretKey.value pb.hasFirstName shouldBe false pb.hasLastName shouldBe false pb.hasEmail shouldBe false @@ -893,12 +893,12 @@ class ProtobufConversionsSpec } "convert to/from protobuf with test user" in { - val user = User("testName", "testAccess", "testSecret", isTest = true) + val user = User("testName", "testAccess", Encrypted("testSecret"), isTest = true) val pb = toPB(user) pb.getUserName shouldBe user.userName pb.getAccessKey shouldBe user.accessKey - pb.getSecretKey shouldBe user.secretKey + pb.getSecretKey shouldBe user.secretKey.value pb.hasFirstName shouldBe false pb.hasLastName shouldBe false pb.hasEmail shouldBe false @@ -912,12 +912,12 @@ class ProtobufConversionsSpec } "convert to/from protobuf with supportAdmin true" in { - val user = User("testName", "testAccess", "testSecret", isSupport = true) + val user = User("testName", "testAccess", Encrypted("testSecret"), isSupport = true) val pb = toPB(user) pb.getUserName shouldBe user.userName pb.getAccessKey shouldBe user.accessKey - pb.getSecretKey shouldBe user.secretKey + pb.getSecretKey shouldBe user.secretKey.value pb.hasFirstName shouldBe false pb.hasLastName shouldBe false pb.hasEmail shouldBe false @@ -933,7 +933,7 @@ class ProtobufConversionsSpec "User change conversion" should { "convert to/from protobuf for CreateUser" in { - val user = User("createUser", "createUserAccess", "createUserSecret") + val user = User("createUser", "createUserAccess", Encrypted("createUserSecret")) val createChange = CreateUser(user, "createUserId", user.created) val pb = toPb(createChange) @@ -942,7 +942,7 @@ class ProtobufConversionsSpec new User( pb.getNewUser.getUserName, pb.getNewUser.getAccessKey, - pb.getNewUser.getSecretKey, + Encrypted(pb.getNewUser.getSecretKey), created = user.created, id = user.id ) shouldBe user @@ -955,7 +955,7 @@ class ProtobufConversionsSpec } "convert to/from protobuf for UpdateUser" in { - val oldUser = User("updateUser", "updateUserAccess", "updateUserSecret") + val oldUser = User("updateUser", "updateUserAccess", Encrypted("updateUserSecret")) val newUser = oldUser.copy(userName = "updateUserNewName") val updateChange = UpdateUser(newUser, "createUserId", newUser.created, oldUser) val pb = toPb(updateChange) @@ -965,7 +965,7 @@ class ProtobufConversionsSpec new User( pb.getNewUser.getUserName, pb.getNewUser.getAccessKey, - pb.getNewUser.getSecretKey, + Encrypted(pb.getNewUser.getSecretKey), created = newUser.created, id = newUser.id ) shouldBe newUser @@ -976,7 +976,7 @@ class ProtobufConversionsSpec new User( pb.getOldUser.getUserName, pb.getOldUser.getAccessKey, - pb.getOldUser.getSecretKey, + Encrypted(pb.getOldUser.getSecretKey), created = oldUser.created, id = oldUser.id ) shouldBe oldUser diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlUserChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlUserChangeRepositoryIntegrationSpec.scala index e9c5341c2..bd009428c 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlUserChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlUserChangeRepositoryIntegrationSpec.scala @@ -20,6 +20,7 @@ import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import scalikejdbc.DB +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.membership.UserChange.{CreateUser, UpdateUser} import vinyldns.core.domain.membership.{User, UserChangeRepository} import vinyldns.mysql.TestMySqlInstance @@ -31,7 +32,7 @@ class MySqlUserChangeRepositoryIntegrationSpec with Matchers { private val repo: UserChangeRepository = TestMySqlInstance.userChangeRepository - private val user: User = User("user-id", "access-key", "secret-key") + private val user: User = User("user-id", "access-key", Encrypted("secret-key")) private val createUser = CreateUser(user, "creator-id", user.created) private val updateUser = UpdateUser(user.copy(userName = "new-username"), "creator-id", user.created, user) diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlUserRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlUserRepositoryIntegrationSpec.scala index 7516649c0..d0a278448 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlUserRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlUserRepositoryIntegrationSpec.scala @@ -20,6 +20,7 @@ import org.scalatest._ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import scalikejdbc.DB +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.membership.{LockStatus, User, UserRepository} import vinyldns.mysql.TestMySqlInstance @@ -35,15 +36,15 @@ class MySqlUserRepositoryIntegrationSpec private val testUserIds = (for { i <- 0 to 100 } yield s"test-user-$i").toList.sorted private val users = testUserIds.map { id => - User(id = id, userName = "name" + id, accessKey = s"abc$id", secretKey = "123") + User(id = id, userName = "name" + id, accessKey = s"abc$id", secretKey = Encrypted("123")) } private val caseInsensitiveUser1 = - User(id = "caseInsensitiveUser1", userName = "Name1", accessKey = "a1", secretKey = "s1") + User(id = "caseInsensitiveUser1", userName = "Name1", accessKey = "a1", secretKey = Encrypted("s1")) private val caseInsensitiveUser2 = - User(id = "caseInsensitiveUser2", userName = "namE2", accessKey = "a2", secretKey = "s2") + User(id = "caseInsensitiveUser2", userName = "namE2", accessKey = "a2", secretKey = Encrypted("s2")) private val caseInsensitiveUser3 = - User(id = "caseInsensitiveUser3", userName = "name3", accessKey = "a3", secretKey = "s3") + User(id = "caseInsensitiveUser3", userName = "name3", accessKey = "a3", secretKey = Encrypted("s3")) override protected def beforeAll(): Unit = { repo = TestMySqlInstance.userRepository @@ -81,7 +82,7 @@ class MySqlUserRepositoryIntegrationSpec } "save super user with super status" in { - val superUser = User("superName", "superAccess", "superSecret", isSuper = true) + val superUser = User("superName", "superAccess", Encrypted("superSecret"), isSuper = true) repo.save(superUser).unsafeRunSync() shouldBe superUser val result = repo.getUser(superUser.id).unsafeRunSync() result shouldBe Some(superUser) @@ -89,7 +90,7 @@ class MySqlUserRepositoryIntegrationSpec } "save non-super user with non-super status" in { - val nonSuperUser = User("nonSuperName", "nonSuperAccess", "nonSuperSecret") + val nonSuperUser = User("nonSuperName", "nonSuperAccess", Encrypted("nonSuperSecret")) repo.save(nonSuperUser).unsafeRunSync() shouldBe nonSuperUser val result = repo.getUser(nonSuperUser.id).unsafeRunSync() result shouldBe Some(nonSuperUser) @@ -98,7 +99,7 @@ class MySqlUserRepositoryIntegrationSpec "save locked user with locked status" in { val lockedUser = - User("lockedName", "lockedAccess", "lockedSecret", lockStatus = LockStatus.Locked) + User("lockedName", "lockedAccess", Encrypted("lockedSecret"), lockStatus = LockStatus.Locked) repo.save(lockedUser).unsafeRunSync() shouldBe lockedUser val result = repo.getUser(lockedUser.id).unsafeRunSync() result shouldBe Some(lockedUser) @@ -106,7 +107,7 @@ class MySqlUserRepositoryIntegrationSpec } "save unlocked user with unlocked status" in { - val unlockedUser = User("unlockedName", "unlockedAccess", "unlockedSecret") + val unlockedUser = User("unlockedName", "unlockedAccess", Encrypted("unlockedSecret")) repo.save(unlockedUser).unsafeRunSync() shouldBe unlockedUser val result = repo.getUser(unlockedUser.id).unsafeRunSync() result shouldBe Some(unlockedUser) @@ -114,7 +115,7 @@ class MySqlUserRepositoryIntegrationSpec } "save support user with support status" in { - val supportUser = User("lockedName", "lockedAccess", "lockedSecret", isSupport = true) + val supportUser = User("lockedName", "lockedAccess", Encrypted("lockedSecret"), isSupport = true) repo.save(supportUser).unsafeRunSync() shouldBe supportUser val result = repo.getUser(supportUser.id).unsafeRunSync() result shouldBe Some(supportUser) @@ -122,7 +123,7 @@ class MySqlUserRepositoryIntegrationSpec } "save non-support user with non-support status" in { - val nonSupportUser = User("unlockedName", "unlockedAccess", "unlockedSecret") + val nonSupportUser = User("unlockedName", "unlockedAccess", Encrypted("unlockedSecret")) repo.save(nonSupportUser).unsafeRunSync() shouldBe nonSupportUser val result = repo.getUser(nonSupportUser.id).unsafeRunSync() result shouldBe Some(nonSupportUser) @@ -131,7 +132,7 @@ class MySqlUserRepositoryIntegrationSpec "save a list of users" in { val userList = (0 to 10).toList.map { i => - User(userName = s"batch-save-user-$i", "accessKey", "secretKey") + User(userName = s"batch-save-user-$i", "accessKey", Encrypted("secretKey")) } repo.save(userList).unsafeRunSync() shouldBe userList diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala index 1a99cb7ad..f5bb31f32 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala @@ -30,6 +30,7 @@ import vinyldns.core.domain.zone.ZoneChangeStatus.ZoneChangeStatus import vinyldns.core.domain.zone._ import vinyldns.core.TestZoneData.okZone import vinyldns.core.TestZoneData.testConnection +import vinyldns.core.domain.Encrypted import vinyldns.mysql.TestMySqlInstance import scala.concurrent.duration._ @@ -58,7 +59,7 @@ class MySqlZoneChangeRepositoryIntegrationSpec systemMessage = Some("test") ) - val goodUser: User = User(s"live-test-acct", "key", "secret") + val goodUser: User = User(s"live-test-acct", "key", Encrypted("secret")) val zones: IndexedSeq[Zone] = for { i <- 1 to 3 } yield Zone( s"${goodUser.userName}.zone$i.", diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala index 369c35443..5466c8170 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala @@ -28,6 +28,7 @@ import vinyldns.core.domain.membership.User import vinyldns.core.domain.zone._ import vinyldns.core.TestZoneData.okZone import vinyldns.core.TestMembershipData._ +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.zone.ZoneRepository.DuplicateZoneError import vinyldns.mysql.TestMySqlInstance @@ -331,7 +332,7 @@ class MySqlZoneRepositoryIntegrationSpec "return an empty list of zones if the user is not authorized to any" in { val unauthorized = AuthPrincipal( - signedInUser = User("not-authorized", "not-authorized", "not-authorized"), + signedInUser = User("not-authorized", "not-authorized", Encrypted("not-authorized")), memberGroupIds = Seq.empty ) diff --git a/modules/portal/app/controllers/VinylDNS.scala b/modules/portal/app/controllers/VinylDNS.scala index 8e8908252..625c126fb 100644 --- a/modules/portal/app/controllers/VinylDNS.scala +++ b/modules/portal/app/controllers/VinylDNS.scala @@ -35,6 +35,7 @@ import play.api.libs.json._ import play.api.libs.ws.{BodyWritable, InMemoryBody, WSClient} import play.api.mvc._ import vinyldns.core.crypto.CryptoAlgebra +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.membership.LockStatus.LockStatus import vinyldns.core.domain.membership.{LockStatus, User} import vinyldns.core.logging.RequestTracing @@ -282,7 +283,7 @@ class VinylDNS @Inject() ( .format( user.userName, user.accessKey, - crypto.decrypt(user.secretKey), + crypto.decrypt(user.secretKey.value), vinyldnsServiceBackend ) ).as("text/csv") @@ -324,7 +325,7 @@ class VinylDNS @Inject() ( User( details.username, User.generateKey, - User.generateKey, + Encrypted(User.generateKey), details.firstName, details.lastName, details.email @@ -629,7 +630,7 @@ class VinylDNS @Inject() ( implicit userRequest: UserRequest[_] ) = { val signableRequest = new SignableVinylDNSRequest(request) - val credentials = new BasicAWSCredentials(user.accessKey, crypto.decrypt(user.secretKey)) + val credentials = new BasicAWSCredentials(user.accessKey, crypto.decrypt(user.secretKey.value)) signer.sign(signableRequest, credentials) logger.info(s"Request to send: [${signableRequest.getResourcePath}]") diff --git a/modules/portal/test/controllers/LdapAuthenticatorSpec.scala b/modules/portal/test/controllers/LdapAuthenticatorSpec.scala index f8bf03030..acfae211b 100644 --- a/modules/portal/test/controllers/LdapAuthenticatorSpec.scala +++ b/modules/portal/test/controllers/LdapAuthenticatorSpec.scala @@ -24,6 +24,7 @@ import org.specs2.mock.Mockito import org.specs2.mock.mockito.ArgumentCapture import org.specs2.mutable.Specification import play.api.{Configuration, Environment} +import vinyldns.core.domain.Encrypted import vinyldns.core.health.HealthCheck._ import vinyldns.core.domain.membership.User import vinyldns.core.health.HealthCheck.HealthCheckError @@ -65,7 +66,7 @@ class LdapAuthenticatorSpec extends Specification with Mockito { val testDomain1 = LdapSearchDomain("someDomain", "DC=test,DC=test,DC=com") val testDomain2 = LdapSearchDomain("anotherDomain", "DC=test,DC=com") - val nonexistentUser = User("does-not-exist", "accessKey", "secretKey") + val nonexistentUser = User("does-not-exist", "accessKey", Encrypted("secretKey")) "LdapAuthenticator" should { "apply method must create an LDAP Authenticator" in { diff --git a/modules/portal/test/controllers/TestApplicationData.scala b/modules/portal/test/controllers/TestApplicationData.scala index 6b94d4459..6885c7880 100644 --- a/modules/portal/test/controllers/TestApplicationData.scala +++ b/modules/portal/test/controllers/TestApplicationData.scala @@ -25,6 +25,7 @@ import play.api.inject.guice.GuiceApplicationBuilder import play.api.libs.json.{JsObject, JsValue, Json} import play.api.{Application, Configuration, Environment} import vinyldns.core.crypto.{CryptoAlgebra, NoOpCrypto} +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.membership._ import vinyldns.core.domain.record._ import vinyldns.core.health.HealthService @@ -41,7 +42,7 @@ trait TestApplicationData { this: Mockito => val frodoUser = User( "fbaggins", "key", - "secret", + Encrypted("secret"), Some("Frodo"), Some("Baggins"), Some("fbaggins@hobbitmail.me"), @@ -52,7 +53,7 @@ trait TestApplicationData { this: Mockito => val lockedFrodoUser = User( "lockedFbaggins", "lockedKey", - "lockedSecret", + Encrypted("lockedSecret"), Some("LockedFrodo"), Some("LockedBaggins"), Some("lockedfbaggins@hobbitmail.me"), @@ -65,7 +66,7 @@ trait TestApplicationData { this: Mockito => val superFrodoUser = User( "superBaggins", "superKey", - "superSecret", + Encrypted("superSecret"), Some("SuperFrodo"), Some("SuperBaggins"), Some("superfbaggins@hobbitmail.me"), @@ -87,7 +88,7 @@ trait TestApplicationData { this: Mockito => val serviceAccountDetails = LdapUserDetails("CN=frodo,OU=hobbits,DC=middle,DC=earth", "service", None, None, None) val serviceAccount = - User("service", "key", "secret", None, None, None, DateTime.now, "service-uuid") + User("service", "key", Encrypted("secret"), None, None, None, DateTime.now, "service-uuid") val frodoJsonString: String = s"""{ @@ -103,7 +104,7 @@ trait TestApplicationData { this: Mockito => val samAccount = User( "sgamgee", "key", - "secret", + Encrypted("secret"), Some("Samwise"), Some("Gamgee"), Some("sgamgee@hobbitmail.me"), diff --git a/modules/portal/test/controllers/UserAccountAccessorSpec.scala b/modules/portal/test/controllers/UserAccountAccessorSpec.scala index 5c7076c0b..151b5342c 100644 --- a/modules/portal/test/controllers/UserAccountAccessorSpec.scala +++ b/modules/portal/test/controllers/UserAccountAccessorSpec.scala @@ -21,6 +21,7 @@ import org.joda.time.DateTime import org.specs2.mock.Mockito import org.specs2.mutable.Specification import org.specs2.specification.BeforeEach +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.membership._ class UserAccountAccessorSpec extends Specification with Mockito with BeforeEach { @@ -28,7 +29,7 @@ class UserAccountAccessorSpec extends Specification with Mockito with BeforeEach private val user = User( "fbaggins", "key", - "secret", + Encrypted("secret"), Some("Frodo"), Some("Baggins"), Some("fbaggins@hobbitmail.me"), @@ -61,7 +62,7 @@ class UserAccountAccessorSpec extends Specification with Mockito with BeforeEach } "return the new user when storing a user that already exists in the store" in { - val newUser = user.copy(accessKey = "new-key", secretKey = "new-secret") + val newUser = user.copy(accessKey = "new-key", secretKey = Encrypted("new-secret")) mockRepo.save(any[User]).returns(IO.pure(newUser)) mockChangeRepo.save(any[UserChange]).returns(IO.pure(userLog)) underTest.update(newUser, user).unsafeRunSync() must beEqualTo(newUser) diff --git a/modules/portal/test/controllers/VinylDNSSpec.scala b/modules/portal/test/controllers/VinylDNSSpec.scala index 58ba4a6cf..737dbeef6 100644 --- a/modules/portal/test/controllers/VinylDNSSpec.scala +++ b/modules/portal/test/controllers/VinylDNSSpec.scala @@ -1277,8 +1277,8 @@ class VinylDNSSpec extends Specification with Mockito with TestApplicationData w content must contain("NT ID") content must contain(frodoUser.userName) content must contain(frodoUser.accessKey) - content must contain(frodoUser.secretKey) - there.was(one(crypto).decrypt(frodoUser.secretKey)) + content must contain(frodoUser.secretKey.value) + there.was(one(crypto).decrypt(frodoUser.secretKey.value)) } "redirect to login if user is not logged in" in new WithApplication(app) { import play.api.mvc.Result diff --git a/modules/portal/test/tasks/UserSyncTaskSpec.scala b/modules/portal/test/tasks/UserSyncTaskSpec.scala index 09047e114..348c9ce5a 100644 --- a/modules/portal/test/tasks/UserSyncTaskSpec.scala +++ b/modules/portal/test/tasks/UserSyncTaskSpec.scala @@ -20,10 +20,11 @@ import cats.effect.IO import controllers.{Authenticator, UserAccountAccessor} import org.specs2.mock.Mockito import org.specs2.mutable.Specification +import vinyldns.core.domain.Encrypted import vinyldns.core.domain.membership._ class UserSyncTaskSpec extends Specification with Mockito { - val notAuthUser: User = User("not-authorized", "accessKey", "secretKey") + val notAuthUser: User = User("not-authorized", "accessKey", Encrypted("secretKey")) val lockedNotAuthUser: User = notAuthUser.copy(lockStatus = LockStatus.Locked) val mockAuthenticator: Authenticator = { val mockObject = mock[Authenticator] From ac79452736d0c0beb7c1ffa5081744c1383f91ed Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 20 Oct 2022 13:06:49 +0530 Subject: [PATCH 039/521] Paginate using id in recordchange --- .../recordsets/list_recordset_changes_test.py | 35 ++----------------- ...ecordChangeRepositoryIntegrationSpec.scala | 16 ++++----- .../MySqlRecordChangeRepository.scala | 10 +++--- 3 files changed, 14 insertions(+), 47 deletions(-) diff --git a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py index 5c9f9a1c5..3d0d2fc9b 100644 --- a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py +++ b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py @@ -83,17 +83,6 @@ def test_list_recordset_changes_no_start(shared_zone_test_context): response = client.list_recordset_changes(original_zone["id"], start_from=None, max_items=None) check_changes_response(response, recordChanges=True, startFrom=False, nextId=False) - deleteChanges = response["recordSetChanges"][0:3] - updateChanges = response["recordSetChanges"][3:6] - createChanges = response["recordSetChanges"][6:9] - - for change in deleteChanges: - assert_that(change["changeType"], is_("Delete")) - for change in updateChanges: - assert_that(change["changeType"], is_("Update")) - for change in createChanges: - assert_that(change["changeType"], is_("Create")) - def test_list_recordset_changes_paging(shared_zone_test_context): """ @@ -112,13 +101,6 @@ def test_list_recordset_changes_paging(shared_zone_test_context): check_changes_response(response_2, recordChanges=True, nextId=True, startFrom=response_1["nextId"], maxItems=3) check_changes_response(response_3, recordChanges=True, nextId=False, startFrom=response_2["nextId"], maxItems=11) - for change in response_1["recordSetChanges"]: - assert_that(change["changeType"], is_("Delete")) - for change in response_2["recordSetChanges"]: - assert_that(change["changeType"], is_("Update")) - for change in response_3["recordSetChanges"]: - assert_that(change["changeType"], is_("Create")) - def test_list_recordset_changes_exhausted(shared_zone_test_context): """ @@ -129,26 +111,15 @@ def test_list_recordset_changes_exhausted(shared_zone_test_context): response = client.list_recordset_changes(original_zone["id"], start_from=None, max_items=17) check_changes_response(response, recordChanges=True, startFrom=False, nextId=False, maxItems=17) - deleteChanges = response["recordSetChanges"][0:3] - updateChanges = response["recordSetChanges"][3:6] - createChanges = response["recordSetChanges"][6:9] - - for change in deleteChanges: - assert_that(change["changeType"], is_("Delete")) - for change in updateChanges: - assert_that(change["changeType"], is_("Update")) - for change in createChanges: - assert_that(change["changeType"], is_("Create")) - def test_list_recordset_returning_no_changes(shared_zone_test_context): """ - Pass in startFrom of 0 should return empty list because start key is created time + Pass in startFrom of random should return empty list because start key is created time """ client = shared_zone_test_context.history_client original_zone = shared_zone_test_context.history_zone - response = client.list_recordset_changes(original_zone["id"], start_from="0", max_items=None) - check_changes_response(response, recordChanges=False, startFrom="0", nextId=False) + response = client.list_recordset_changes(original_zone["id"], start_from="random", max_items=None) + check_changes_response(response, recordChanges=False, startFrom="random", nextId=False) def test_list_recordset_changes_default_max_items(shared_zone_test_context): diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala index ef5ee59b6..a63b7f431 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala @@ -100,26 +100,22 @@ class MySqlRecordChangeRepositoryIntegrationSpec (result.items should have).length(5) } "page through record changes" in { - // sort by created desc, so adding additional seconds makes it more current, the last - val timeSpaced = - generateInserts(okZone, 5).zipWithIndex.map { - case (c, i) => c.copy(created = c.created.plusSeconds(i)) - } + val inserts = generateInserts(okZone, 5) - // expect to be sorted by created descending so reverse that - val expectedOrder = timeSpaced.sortBy(_.created.getMillis).reverse + // expect to be sorted by id in ascending order + val expectedOrder = inserts.sortBy(_.id) val saveRecChange = executeWithinTransaction { db: DB => - repo.save(db, ChangeSet(timeSpaced)) + repo.save(db, ChangeSet(inserts)) } saveRecChange.attempt.unsafeRunSync() shouldBe right val page1 = repo.listRecordSetChanges(okZone.id, None, 2).unsafeRunSync() - page1.nextId shouldBe Some(expectedOrder(1).created.getMillis.toString) + page1.nextId shouldBe Some(expectedOrder(1).id) page1.maxItems shouldBe 2 (page1.items should contain).theSameElementsInOrderAs(expectedOrder.take(2)) val page2 = repo.listRecordSetChanges(okZone.id, page1.nextId, 2).unsafeRunSync() - page2.nextId shouldBe Some(expectedOrder(3).created.getMillis.toString) + page2.nextId shouldBe Some(expectedOrder(3).id) page2.maxItems shouldBe 2 (page2.items should contain).theSameElementsInOrderAs(expectedOrder.slice(2, 4)) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala index 0c6ec6201..74e6003ea 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala @@ -35,8 +35,8 @@ class MySqlRecordChangeRepository |SELECT data | FROM record_change | WHERE zone_id = {zoneId} - | AND created < {created} - | ORDER BY created DESC + | AND id > {id} + | ORDER BY id ASC | LIMIT {limit} """.stripMargin @@ -45,7 +45,7 @@ class MySqlRecordChangeRepository |SELECT data | FROM record_change | WHERE zone_id = {zoneId} - | ORDER BY created DESC + | ORDER BY id ASC | LIMIT {limit} """.stripMargin @@ -98,7 +98,7 @@ class MySqlRecordChangeRepository val changes = startFrom match { case Some(start) => LIST_CHANGES_WITH_START - .bindByName('zoneId -> zoneId, 'created -> start.toLong, 'limit -> maxItems) + .bindByName('zoneId -> zoneId, 'id -> start, 'limit -> maxItems) .map(toRecordSetChange) .list() .apply() @@ -112,7 +112,7 @@ class MySqlRecordChangeRepository val nextId = if (changes.size < maxItems) None - else changes.lastOption.map(_.created.getMillis.toString) + else changes.lastOption.map(_.id) ListRecordSetChangesResults( changes, From a833097a912dece998d6e49fa136c67764f481ca Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 26 Oct 2022 12:35:31 +0530 Subject: [PATCH 040/521] Add test and resolve errors --- .../RecordSetServiceIntegrationSpec.scala | 6 +++--- .../api/route/VinylDNSJsonProtocolSpec.scala | 19 +++++++++++++++++++ .../vinyldns/core/TestMembershipData.scala | 12 ++++++------ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala index d11dd8b77..119812bc9 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/record/RecordSetServiceIntegrationSpec.scala @@ -62,9 +62,9 @@ class RecordSetServiceIntegrationSpec private var testRecordSetService: RecordSetServiceAlgebra = _ - private val user = User("live-test-user", "key", "secret") - private val testUser = User("testuser", "key", "secret") - private val user2 = User("shared-record-test-user", "key-shared", "secret-shared") + private val user = User("live-test-user", "key", Encrypted("secret")) + private val testUser = User("testuser", "key", Encrypted("secret")) + private val user2 = User("shared-record-test-user", "key-shared", Encrypted("secret-shared")) private val group = Group(s"test-group", "test@test.com", adminUserIds = Set(user.id)) private val dummyGroup = Group(s"dummy-group", "test@test.com", adminUserIds = Set(testUser.id)) diff --git a/modules/api/src/test/scala/vinyldns/api/route/VinylDNSJsonProtocolSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/VinylDNSJsonProtocolSpec.scala index 1cccf7f60..54b20718b 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/VinylDNSJsonProtocolSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/VinylDNSJsonProtocolSpec.scala @@ -312,6 +312,25 @@ class VinylDNSJsonProtocolSpec } } + "EncryptedSerializer" should { + "parse a secret key to Encrypted type" in { + val secretKeyInput: JValue = "primaryConnectionKey" + + val actual = secretKeyInput.extract[Encrypted] + val expected = Encrypted("primaryConnectionKey") + actual shouldBe expected + } + + "throw an error if there is a type mismatch during deserialization" in { + val secretKeyInput: JObject = + "key" -> List( + "primaryConnectionKey" + ) + + assertThrows[MappingException](secretKeyInput.extract[Encrypted]) + } + } + "RecordSetSerializer" should { "parse a record set with an absolute CNAME record passes" in { val recordSetJValue: JValue = diff --git a/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala b/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala index 37cf26b98..3921c135e 100644 --- a/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala +++ b/modules/core/src/test/scala/vinyldns/core/TestMembershipData.scala @@ -35,12 +35,12 @@ object TestMembershipData { email = Some("test@test.com") ) - val dummyUser = User("dummyName", "dummyAccess", "dummySecret") - val superUser = User("super", "superAccess", "superSecret", isSuper = true) - val xyzUser = User("xyz", "xyzAccess", "xyzSecret") - val supportUser = User("support", "supportAccess", "supportSecret", isSupport = true) - val lockedUser = User("locked", "lockedAccess", "lockedSecret", lockStatus = LockStatus.Locked) - val sharedZoneUser = User("sharedZoneAdmin", "sharedAccess", "sharedSecret") + val dummyUser = User("dummyName", "dummyAccess", Encrypted("dummySecret")) + val superUser = User("super", "superAccess", Encrypted("superSecret"), isSuper = true) + val xyzUser = User("xyz", "xyzAccess", Encrypted("xyzSecret")) + val supportUser = User("support", "supportAccess", Encrypted("supportSecret"), isSupport = true) + val lockedUser = User("locked", "lockedAccess", Encrypted("lockedSecret"), lockStatus = LockStatus.Locked) + val sharedZoneUser = User("sharedZoneAdmin", "sharedAccess", Encrypted("sharedSecret")) val listOfDummyUsers: List[User] = List.range(0, 200).map { runner => User( From 72895d6f98fd08179ec8e11c05bdab1ab3f7afee Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 27 Oct 2022 11:46:14 +0530 Subject: [PATCH 041/521] Update test comment --- .../functional/tests/recordsets/list_recordset_changes_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py index 3d0d2fc9b..94450e8d7 100644 --- a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py +++ b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py @@ -114,7 +114,7 @@ def test_list_recordset_changes_exhausted(shared_zone_test_context): def test_list_recordset_returning_no_changes(shared_zone_test_context): """ - Pass in startFrom of random should return empty list because start key is created time + Pass in startFrom of random should return empty list because start key is id """ client = shared_zone_test_context.history_client original_zone = shared_zone_test_context.history_zone From 1e1b105eb8ebbcf4fc3b6dbee62562c8d98d547d Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 1 Nov 2022 13:54:57 +0530 Subject: [PATCH 042/521] Use LIMIT and OFFSET --- .../record/ListRecordSetChangesResponse.scala | 4 +-- .../api/domain/record/RecordSetService.scala | 2 +- .../record/RecordSetServiceAlgebra.scala | 2 +- .../vinyldns/api/route/RecordSetRouting.scala | 4 +-- .../recordsets/list_recordset_changes_test.py | 35 +++++++++++++++++-- .../api/route/RecordSetRoutingSpec.scala | 2 +- .../record/ListRecordSetChangesResults.scala | 4 +-- .../record/RecordChangeRepository.scala | 2 +- ...ecordChangeRepositoryIntegrationSpec.scala | 16 +++++---- .../MySqlRecordChangeRepository.scala | 22 ++++++------ 10 files changed, 62 insertions(+), 31 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/ListRecordSetChangesResponse.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/ListRecordSetChangesResponse.scala index 0dcda1985..248d20327 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/ListRecordSetChangesResponse.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/ListRecordSetChangesResponse.scala @@ -22,8 +22,8 @@ import vinyldns.core.domain.record.ListRecordSetChangesResults case class ListRecordSetChangesResponse( zoneId: String, recordSetChanges: List[RecordSetChangeInfo] = Nil, - nextId: Option[String], - startFrom: Option[String], + nextId: Option[Int], + startFrom: Option[Int], maxItems: Int ) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 0a38bae89..e0b9bb8d3 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -569,7 +569,7 @@ class RecordSetService( def listRecordSetChanges( zoneId: String, - startFrom: Option[String] = None, + startFrom: Option[Int] = None, maxItems: Int = 100, authPrincipal: AuthPrincipal ): Result[ListRecordSetChangesResponse] = diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala index 5d7089196..c91cd5086 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala @@ -98,7 +98,7 @@ trait RecordSetServiceAlgebra { def listRecordSetChanges( zoneId: String, - startFrom: Option[String], + startFrom: Option[Int], maxItems: Int, authPrincipal: AuthPrincipal ): Result[ListRecordSetChangesResponse] diff --git a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala index 72cd04596..2c7f7f6b8 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala @@ -215,8 +215,8 @@ class RecordSetRoute( } ~ path("zones" / Segment / "recordsetchanges") { zoneId => (get & monitor("Endpoint.listRecordSetChanges")) { - parameters("startFrom".?, "maxItems".as[Int].?(DEFAULT_MAX_ITEMS)) { - (startFrom: Option[String], maxItems: Int) => + parameters("startFrom".as[Int].?, "maxItems".as[Int].?(DEFAULT_MAX_ITEMS)) { + (startFrom: Option[Int], maxItems: Int) => handleRejections(invalidQueryHandler) { validate( check = 0 < maxItems && maxItems <= DEFAULT_MAX_ITEMS, diff --git a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py index 94450e8d7..1269fdc17 100644 --- a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py +++ b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py @@ -83,6 +83,17 @@ def test_list_recordset_changes_no_start(shared_zone_test_context): response = client.list_recordset_changes(original_zone["id"], start_from=None, max_items=None) check_changes_response(response, recordChanges=True, startFrom=False, nextId=False) + deleteChanges = response["recordSetChanges"][0:3] + updateChanges = response["recordSetChanges"][3:6] + createChanges = response["recordSetChanges"][6:9] + + for change in deleteChanges: + assert_that(change["changeType"], is_("Delete")) + for change in updateChanges: + assert_that(change["changeType"], is_("Update")) + for change in createChanges: + assert_that(change["changeType"], is_("Create")) + def test_list_recordset_changes_paging(shared_zone_test_context): """ @@ -101,6 +112,13 @@ def test_list_recordset_changes_paging(shared_zone_test_context): check_changes_response(response_2, recordChanges=True, nextId=True, startFrom=response_1["nextId"], maxItems=3) check_changes_response(response_3, recordChanges=True, nextId=False, startFrom=response_2["nextId"], maxItems=11) + for change in response_1["recordSetChanges"]: + assert_that(change["changeType"], is_("Delete")) + for change in response_2["recordSetChanges"]: + assert_that(change["changeType"], is_("Update")) + for change in response_3["recordSetChanges"]: + assert_that(change["changeType"], is_("Create")) + def test_list_recordset_changes_exhausted(shared_zone_test_context): """ @@ -111,15 +129,26 @@ def test_list_recordset_changes_exhausted(shared_zone_test_context): response = client.list_recordset_changes(original_zone["id"], start_from=None, max_items=17) check_changes_response(response, recordChanges=True, startFrom=False, nextId=False, maxItems=17) + deleteChanges = response["recordSetChanges"][0:3] + updateChanges = response["recordSetChanges"][3:6] + createChanges = response["recordSetChanges"][6:9] + + for change in deleteChanges: + assert_that(change["changeType"], is_("Delete")) + for change in updateChanges: + assert_that(change["changeType"], is_("Update")) + for change in createChanges: + assert_that(change["changeType"], is_("Create")) + def test_list_recordset_returning_no_changes(shared_zone_test_context): """ - Pass in startFrom of random should return empty list because start key is id + Pass in startFrom of "2000" should return empty list because start key exceeded no.of.recordset changes """ client = shared_zone_test_context.history_client original_zone = shared_zone_test_context.history_zone - response = client.list_recordset_changes(original_zone["id"], start_from="random", max_items=None) - check_changes_response(response, recordChanges=False, startFrom="random", nextId=False) + response = client.list_recordset_changes(original_zone["id"], start_from="2000", max_items=None) + check_changes_response(response, recordChanges=False, startFrom="2000", nextId=False) def test_list_recordset_changes_default_max_items(shared_zone_test_context): diff --git a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala index 93f7d6d38..8a763ed4b 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala @@ -674,7 +674,7 @@ class RecordSetRoutingSpec def listRecordSetChanges( zoneId: String, - startFrom: Option[String], + startFrom: Option[Int], maxItems: Int, authPrincipal: AuthPrincipal ): Result[ListRecordSetChangesResponse] = { diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetChangesResults.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetChangesResults.scala index b5d1141d5..c9f163355 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetChangesResults.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/ListRecordSetChangesResults.scala @@ -18,7 +18,7 @@ package vinyldns.core.domain.record case class ListRecordSetChangesResults( items: List[RecordSetChange] = List[RecordSetChange](), - nextId: Option[String] = None, - startFrom: Option[String] = None, + nextId: Option[Int] = None, + startFrom: Option[Int] = None, maxItems: Int = 100 ) diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala index b873dc378..106bd3e62 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala @@ -26,7 +26,7 @@ trait RecordChangeRepository extends Repository { def listRecordSetChanges( zoneId: String, - startFrom: Option[String] = None, + startFrom: Option[Int] = None, maxItems: Int = 100 ): IO[ListRecordSetChangesResults] diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala index a63b7f431..a6a320f86 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala @@ -100,22 +100,26 @@ class MySqlRecordChangeRepositoryIntegrationSpec (result.items should have).length(5) } "page through record changes" in { - val inserts = generateInserts(okZone, 5) + // sort by created desc, so adding additional seconds makes it more current, the last + val timeSpaced = + generateInserts(okZone, 5).zipWithIndex.map { + case (c, i) => c.copy(created = c.created.plusSeconds(i)) + } - // expect to be sorted by id in ascending order - val expectedOrder = inserts.sortBy(_.id) + // expect to be sorted by created descending so reverse that + val expectedOrder = timeSpaced.sortBy(_.created.getMillis).reverse val saveRecChange = executeWithinTransaction { db: DB => - repo.save(db, ChangeSet(inserts)) + repo.save(db, ChangeSet(timeSpaced)) } saveRecChange.attempt.unsafeRunSync() shouldBe right val page1 = repo.listRecordSetChanges(okZone.id, None, 2).unsafeRunSync() - page1.nextId shouldBe Some(expectedOrder(1).id) + page1.nextId shouldBe Some(2) page1.maxItems shouldBe 2 (page1.items should contain).theSameElementsInOrderAs(expectedOrder.take(2)) val page2 = repo.listRecordSetChanges(okZone.id, page1.nextId, 2).unsafeRunSync() - page2.nextId shouldBe Some(expectedOrder(3).id) + page2.nextId shouldBe Some(4) page2.maxItems shouldBe 2 (page2.items should contain).theSameElementsInOrderAs(expectedOrder.slice(2, 4)) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala index 74e6003ea..b3bc388c1 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala @@ -33,20 +33,19 @@ class MySqlRecordChangeRepository private val LIST_CHANGES_WITH_START = sql""" |SELECT data - | FROM record_change + | FROM record_change | WHERE zone_id = {zoneId} - | AND id > {id} - | ORDER BY id ASC - | LIMIT {limit} + | ORDER BY created DESC + | LIMIT {limit} OFFSET {startFrom} """.stripMargin private val LIST_CHANGES_NO_START = sql""" |SELECT data - | FROM record_change + | FROM record_change | WHERE zone_id = {zoneId} - | ORDER BY id ASC - | LIMIT {limit} + | ORDER BY created DESC + | LIMIT {limit} """.stripMargin private val GET_CHANGE = @@ -89,7 +88,7 @@ class MySqlRecordChangeRepository def listRecordSetChanges( zoneId: String, - startFrom: Option[String], + startFrom: Option[Int], maxItems: Int ): IO[ListRecordSetChangesResults] = monitor("repo.RecordChange.listRecordSetChanges") { @@ -98,7 +97,7 @@ class MySqlRecordChangeRepository val changes = startFrom match { case Some(start) => LIST_CHANGES_WITH_START - .bindByName('zoneId -> zoneId, 'id -> start, 'limit -> maxItems) + .bindByName('zoneId -> zoneId, 'startFrom -> start, 'limit -> maxItems) .map(toRecordSetChange) .list() .apply() @@ -110,9 +109,8 @@ class MySqlRecordChangeRepository .apply() } - val nextId = - if (changes.size < maxItems) None - else changes.lastOption.map(_.id) + val startValue = startFrom.getOrElse(0) + val nextId = if (changes.size < maxItems) None else Some(startValue + maxItems) ListRecordSetChangesResults( changes, From 356169200681bc808734ce4e35a587f1ca33e1be Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 1 Nov 2022 15:05:28 +0530 Subject: [PATCH 043/521] Resolve test --- .../tests/recordsets/list_recordset_changes_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py index 1269fdc17..bbddda69f 100644 --- a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py +++ b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py @@ -147,8 +147,8 @@ def test_list_recordset_returning_no_changes(shared_zone_test_context): """ client = shared_zone_test_context.history_client original_zone = shared_zone_test_context.history_zone - response = client.list_recordset_changes(original_zone["id"], start_from="2000", max_items=None) - check_changes_response(response, recordChanges=False, startFrom="2000", nextId=False) + response = client.list_recordset_changes(original_zone["id"], start_from=2000, max_items=None) + check_changes_response(response, recordChanges=False, startFrom=2000, nextId=False) def test_list_recordset_changes_default_max_items(shared_zone_test_context): From 2b78b4439d6458c71caac72d8dc9a08f6622be54 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 8 Nov 2022 11:59:05 +0530 Subject: [PATCH 044/521] Resolve tests --- .../batch/BatchChangeConverterSpec.scala | 7 +++--- .../membership/MembershipServiceSpec.scala | 22 +++++++++---------- .../domain/record/RecordSetServiceSpec.scala | 22 +++++++++---------- .../api/domain/zone/ZoneServiceSpec.scala | 4 ++-- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala index 76acca731..38a044efa 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala @@ -554,7 +554,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { singleChangesOneDelete, approvalStatus = BatchChangeApprovalStatus.AutoApproved ) - val result = rightResultOf( + val result = underTest .sendBatchForProcessing( batchWithBadChange, @@ -562,8 +562,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { ChangeForValidationMap(changeForValidationOneDelete.map(_.validNel), existingRecordSets), None ) - .value - ) + .value.unsafeRunSync().toOption.get val returnedBatch = result.batchChange @@ -576,7 +575,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { // check the update has been made in the DB val savedBatch: Option[BatchChange] = - await(batchChangeRepo.getBatchChange(batchWithBadChange.id)) + batchChangeRepo.getBatchChange(batchWithBadChange.id).unsafeRunSync() savedBatch shouldBe Some(returnedBatch) } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index e7f12443d..bbceb1dba 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -275,7 +275,7 @@ class MembershipServiceSpec .when(underTest) .groupValidation(groupInfo.copy(name = "", email = "")) - val error = leftResultOf(underTest.createGroup(groupInfo.copy(name = "", email = ""), okAuth).value) + val error = underTest.createGroup(groupInfo.copy(name = "", email = ""), okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[GroupValidationError] verify(mockGroupRepo, never()).save(any[DB], any[Group]) @@ -412,7 +412,7 @@ class MembershipServiceSpec .when(underTest) .groupValidation(existingGroup.copy(name = "", email = "")) - val error = leftResultOf( + val error = underTest .updateGroup( updatedInfo.id, @@ -423,8 +423,8 @@ class MembershipServiceSpec updatedInfo.adminUserIds, okAuth ) - .value - ) + .value.unsafeRunSync().swap.toOption.get + error shouldBe a[GroupValidationError] } @@ -641,7 +641,7 @@ class MembershipServiceSpec doReturn(IO.pure(listOfDummyGroups.toSet)) .when(mockGroupRepo) .getGroups(any[Set[String]]) - val result: ListMyGroupsResponse = rightResultOf( + val result: ListMyGroupsResponse = underTest .listMyGroups( groupNameFilter = Some("Name-Dummy01"), @@ -650,8 +650,8 @@ class MembershipServiceSpec listOfDummyGroupsAuth, false ) - .value - ) + .value.unsafeRunSync().toOption.get + result shouldBe ListMyGroupsResponse( groups = listOfDummyGroupInfo.slice(10, 20), groupNameFilter = Some("Name-Dummy01"), @@ -794,7 +794,7 @@ class MembershipServiceSpec listOfDummyGroupChanges.map(change => GroupChangeInfo.apply(change.copy(userName = userMap.get(change.userId)))).take(1).head val result: GroupChangeInfo = - rightResultOf(underTest.getGroupChange(dummyGroup.id, dummyAuth).value) + underTest.getGroupChange(dummyGroup.id, dummyAuth).value.unsafeRunSync().toOption.get result shouldBe expected } @@ -813,7 +813,7 @@ class MembershipServiceSpec listOfDummyGroupChanges.map(change => GroupChangeInfo.apply(change.copy(userName = userMap.get(change.userId)))).take(1).head val result: GroupChangeInfo = - rightResultOf(underTest.getGroupChange(dummyGroup.id, okAuth).value) + underTest.getGroupChange(dummyGroup.id, okAuth).value.unsafeRunSync().toOption.get result shouldBe expected } @@ -826,7 +826,7 @@ class MembershipServiceSpec .when(mockUserRepo) .getUsers(any[Set[String]], any[Option[String]], any[Option[Int]]) - val result = leftResultOf(underTest.getGroupChange(dummyGroup.id, okAuth).value) + val result = underTest.getGroupChange(dummyGroup.id, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe a[InvalidGroupRequestError] } } @@ -886,7 +886,7 @@ class MembershipServiceSpec "determine group difference" should { "return difference between two groups" in { val groupChange = Seq(okGroupChange, dummyGroupChangeUpdate, okGroupChange.copy(changeType = GroupChangeType.Delete)) - val result: Seq[String] = rightResultOf(underTest.determineGroupDifference(groupChange).value) + val result: Seq[String] = underTest.determineGroupDifference(groupChange).value.unsafeRunSync().toOption.get // Newly created group's change message result(0) shouldBe "Group Created." // Updated group's change message diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 45a802571..17ad98ea1 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -288,7 +288,7 @@ class RecordSetServiceSpec .when(mockUserRepo) .getUsers(Set.empty, None, None) - val result = leftResultOf(underTestWithEmptyDottedHostsConfig.addRecordSet(record, okAuth).value) + val result = underTestWithEmptyDottedHostsConfig.addRecordSet(record, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "fail if the record is relative with trailing dot" in { @@ -585,9 +585,8 @@ class RecordSetServiceSpec .getUsers(dummyGroup.memberIds, None, None) // passes as all three properties within dotted hosts config (allowed zones, users and record types) are satisfied - val result: RecordSetChange = rightResultOf( - underTest.addRecordSet(record, xyzAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.addRecordSet(record, xyzAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.name shouldBe record.name } @@ -629,9 +628,8 @@ class RecordSetServiceSpec .getUsers(xyzGroup.memberIds, None, None) // passes as all three properties within dotted hosts config (allowed zones, users and record types) are satisfied - val result: RecordSetChange = rightResultOf( - underTest.addRecordSet(record, xyzAuth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result: RecordSetChange = + underTest.addRecordSet(record, xyzAuth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.name shouldBe record.name } @@ -673,7 +671,7 @@ class RecordSetServiceSpec .getUsers(xyzGroup.memberIds, None, None) // fails as dotted host record name has dot at the end and is not an apex record - val result = leftResultOf(underTest.addRecordSet(record, xyzAuth).value) + val result = underTest.addRecordSet(record, xyzAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "fail if the record is dotted and zone, user, record type is allowed but number of dots allowed in config is 0" in { @@ -714,7 +712,7 @@ class RecordSetServiceSpec .getUsers(dummyGroup.memberIds, None, None) // fails as no.of.dots allowed for the zone in the config is 0 - val result = leftResultOf(underTest.addRecordSet(record, xyzAuth).value) + val result = underTest.addRecordSet(record, xyzAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "fail if the record is dotted and user, record type is in allowed dotted hosts config except zone" in { @@ -750,7 +748,7 @@ class RecordSetServiceSpec .getUsers(Set.empty, None, None) // fails as only two properties within dotted hosts config (users and record types) are satisfied while zone is not allowed - val result = leftResultOf(underTest.addRecordSet(record, okAuth).value) + val result = underTest.addRecordSet(record, okAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "fail if the record is dotted and zone, record type is in allowed dotted hosts config except user" in { @@ -791,7 +789,7 @@ class RecordSetServiceSpec .getUsers(dummyGroup.memberIds, None, None) // fails as only two properties within dotted hosts config (zones and record types) are satisfied while user is not allowed - val result = leftResultOf(underTest.addRecordSet(record, abcAuth).value) + val result = underTest.addRecordSet(record, abcAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } "fail if the record is dotted and zone, user is in allowed dotted hosts config except record type" in { @@ -834,7 +832,7 @@ class RecordSetServiceSpec .getUsers(dummyGroup.memberIds, None, None) // fails as only two properties within dotted hosts config (zone and user) are satisfied while record type is not allowed - val result = leftResultOf(underTest.addRecordSet(record, xyzAuth).value) + val result = underTest.addRecordSet(record, xyzAuth).value.unsafeRunSync().swap.toOption.get result shouldBe an[InvalidRequest] } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index f04c7f95c..d1a6c7bbe 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -561,7 +561,7 @@ class ZoneServiceSpec // When searchByAdminGroup is true, zones are filtered by admin group name given in nameFilter val result: ListZonesResponse = - rightResultOf(underTest.listZones(abcAuth, Some("abcGroup"), None, 100, searchByAdminGroup = true, ignoreAccess = true).value) + underTest.listZones(abcAuth, Some("abcGroup"), None, 100, searchByAdminGroup = true, ignoreAccess = true).value.unsafeRunSync().toOption.get result.zones shouldBe List(abcZoneSummary) result.maxItems shouldBe 100 result.startFrom shouldBe None @@ -580,7 +580,7 @@ class ZoneServiceSpec // When searchByAdminGroup is false, zone name given in nameFilter is returned val result: ListZonesResponse = - rightResultOf(underTest.listZones(abcAuth, Some("abcZone"), None, 100, searchByAdminGroup = false, ignoreAccess = true).value) + underTest.listZones(abcAuth, Some("abcZone"), None, 100, searchByAdminGroup = false, ignoreAccess = true).value.unsafeRunSync().toOption.get result.zones shouldBe List(abcZoneSummary) result.maxItems shouldBe 100 result.startFrom shouldBe None From 63050e90bc78ca3ae2afe6773341b9a977d10f4c Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 9 Nov 2022 11:53:41 +0530 Subject: [PATCH 046/521] Resolve group edit details update --- .../lib/controllers/controller.groups.js | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/modules/portal/public/lib/controllers/controller.groups.js b/modules/portal/public/lib/controllers/controller.groups.js index b5776ed5e..101d37a42 100644 --- a/modules/portal/public/lib/controllers/controller.groups.js +++ b/modules/portal/public/lib/controllers/controller.groups.js @@ -231,48 +231,60 @@ angular.module('controller.groups', []).controller('GroupsController', function $("#modal_edit_group").modal("show"); }; + $scope.getGroupAndUpdate = function(groupId, name, email, description) { + function success(response) { + $log.debug('groupsService::getGroup-success'); + $scope.currentGroup = response.data; + + //data from user form values + var payload = + { + 'id': $scope.currentGroup.id, + 'name': name, + 'email': email, + 'members': $scope.currentGroup.members, + 'admins': $scope.currentGroup.admins + }; + if (description) { + payload['description'] = description; + } + + //update group success callback + function success(response) { + var alert = utilityService.success('Successfully Updated Group: ' + name, response, 'updateGroup::updateGroup successful'); + $scope.alerts.push(alert); + $scope.closeEditModal(); + $scope.reset(); + $scope.refresh(); + return response.data; + } + return groupsService.updateGroup(groupId, payload) + .then(success) + .catch(function (error) { + handleError(error, 'groupsService::updateGroup-failure'); + }); + } + + return groupsService + .getGroup(groupId) + .then(success) + .catch(function (error) { + handleError(error, 'groupsService::getGroup-failure'); + }); + }; + $scope.submitEditGroup = function (name, email, description) { //prevent user executing service call multiple times //if true prevent, if false allow for execution of rest of code //ng-href='/groups' - $log.log('updateGroup::called', $scope.data); - if ($scope.processing) { - $log.log('updateGroup::processing is true; exiting'); + $log.debug('updateGroup::processing is true; exiting'); return; } + //flag to prevent multiple clicks until previous promise has resolved. $scope.processing = true; - - //data from user form values - var payload = - { - 'id': $scope.currentGroup.id, - 'name': name, - 'email': email, - 'members': $scope.currentGroup.members, - 'admins': $scope.currentGroup.admins - }; - - if (description) { - payload['description'] = description; - } - - //update group success callback - function success(response) { - var alert = utilityService.success('Successfully Updated Group: ' + name, response, 'updateGroup::updateGroup successful'); - $scope.alerts.push(alert); - $scope.closeEditModal(); - $scope.reset(); - $scope.refresh(); - return response.data; - } - - return groupsService.updateGroup($scope.currentGroup.id, payload) - .then(success) - .catch(function (error) { - handleError(error, 'groupsService::updateGroup-failure'); - }); + $scope.getGroupAndUpdate($scope.currentGroup.id, name, email, description); }; $scope.confirmDeleteGroup = function (groupInfo) { From bc66767b853d859125b64a91ef3e8547c6134ea2 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Fri, 11 Nov 2022 16:55:05 -0500 Subject: [PATCH 047/521] Add gorups maxitems config to meta class --- build/docker/api/application.conf | 11 +++++++++++ modules/api/src/main/resources/application.conf | 11 +++++++++++ modules/api/src/universal/conf/application.conf | 12 ++++++++++++ modules/portal/app/models/Meta.scala | 6 ++++-- .../app/views/dnsChanges/dnsChangeNew.scala.html | 2 +- .../app/views/zones/zoneTabs/manageZone.scala.html | 2 +- .../public/lib/controllers/controller.groups.js | 3 ++- .../public/lib/services/groups/service.groups.js | 4 ++-- modules/portal/test/models/MetaSpec.scala | 8 ++++++++ 9 files changed, 52 insertions(+), 7 deletions(-) diff --git a/build/docker/api/application.conf b/build/docker/api/application.conf index e87fef2ab..761eb78e7 100644 --- a/build/docker/api/application.conf +++ b/build/docker/api/application.conf @@ -155,6 +155,17 @@ vinyldns { port=${?API_SERVICE_PORT} } + api { + limits { + batchchange-routing-max-items-limit = 100 + membership-routing-default-max-items = 100 + membership-routing-max-items-limit = 1000 + membership-routing-max-groups-list-limit = 3000 + recordset-routing-default-max-items= 100 + zone-routing-default-max-items = 100 + zone-routing-max-items-limit = 100 + } + } approved-name-servers = [ "172.17.42.1.", diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 7407c07e4..c5cba2936 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -155,6 +155,17 @@ vinyldns { port=${?API_SERVICE_PORT} } + api { + limits { + batchchange-routing-max-items-limit = 100 + membership-routing-default-max-items = 100 + membership-routing-max-items-limit = 1000 + membership-routing-max-groups-list-limit = 3000 + recordset-routing-default-max-items= 100 + zone-routing-default-max-items = 100 + zone-routing-max-items-limit = 100 + } + } approved-name-servers = [ "172.17.42.1.", diff --git a/modules/api/src/universal/conf/application.conf b/modules/api/src/universal/conf/application.conf index a8aa7175c..fc4f79c09 100644 --- a/modules/api/src/universal/conf/application.conf +++ b/modules/api/src/universal/conf/application.conf @@ -155,6 +155,18 @@ vinyldns { port=${?API_SERVICE_PORT} } + api { + limits { + batchchange-routing-max-items-limit = 100 + membership-routing-default-max-items = 100 + membership-routing-max-items-limit = 1000 + membership-routing-max-groups-list-limit = 3000 + recordset-routing-default-max-items= 100 + zone-routing-default-max-items = 100 + zone-routing-max-items-limit = 100 + } + } + approved-name-servers = [ "172.17.42.1.", diff --git a/modules/portal/app/models/Meta.scala b/modules/portal/app/models/Meta.scala index f1e4cbf9a..850b8176b 100644 --- a/modules/portal/app/models/Meta.scala +++ b/modules/portal/app/models/Meta.scala @@ -23,7 +23,8 @@ case class Meta( batchChangeLimit: Int, defaultTtl: Long, manualBatchChangeReviewEnabled: Boolean, - scheduledBatchChangesEnabled: Boolean + scheduledBatchChangesEnabled: Boolean, + maxGroupItemsDisplay: Int ) object Meta { def apply(config: Configuration): Meta = @@ -33,6 +34,7 @@ object Meta { config.getOptional[Int]("batch-change-limit").getOrElse(1000), config.getOptional[Long]("default-ttl").getOrElse(7200L), config.getOptional[Boolean]("manual-batch-review-enabled").getOrElse(false), - config.getOptional[Boolean]("scheduled-changes-enabled").getOrElse(false) + config.getOptional[Boolean]("scheduled-changes-enabled").getOrElse(false), + config.getOptional[Int]("vinyldns.api.limits.membership-routing-max-groups-list-limit").getOrElse(2500) ) } diff --git a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html index f74dce805..fdb4a6f0a 100644 --- a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html +++ b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html @@ -3,7 +3,7 @@ @content = {
    + manualReviewEnabled = @meta.manualBatchChangeReviewEnabled; maxGroupItemsDisplay = @meta.maxGroupItemsDisplay">
    -
    +
    diff --git a/modules/portal/public/lib/controllers/controller.groups.js b/modules/portal/public/lib/controllers/controller.groups.js index b5776ed5e..529121ac0 100644 --- a/modules/portal/public/lib/controllers/controller.groups.js +++ b/modules/portal/public/lib/controllers/controller.groups.js @@ -28,6 +28,7 @@ angular.module('controller.groups', []).controller('GroupsController', function $scope.ignoreAccess = false; $scope.hasGroups = false; $scope.query = ""; + $scope.maxGroupItemsDisplay = 2000; // Paging status for group sets var groupsPaging = pagingService.getNewPagingParams(100); @@ -201,7 +202,7 @@ angular.module('controller.groups', []).controller('GroupsController', function } return groupsService - .getGroups($scope.ignoreAccess, $scope.query) + .getGroups($scope.ignoreAccess, $scope.query, $scope.maxGroupItemsDisplay) .then(success) .catch(function (error) { handleError(error, 'groupsService::getGroups-failure'); diff --git a/modules/portal/public/lib/services/groups/service.groups.js b/modules/portal/public/lib/services/groups/service.groups.js index 41f1a7f5e..37d9f7491 100644 --- a/modules/portal/public/lib/services/groups/service.groups.js +++ b/modules/portal/public/lib/services/groups/service.groups.js @@ -70,12 +70,12 @@ angular.module('service.groups', []) return $http.delete(url, {headers: utilityService.getCsrfHeader()}); }; - this.getGroups = function (ignoreAccess, query) { + this.getGroups = function (ignoreAccess, query, maxItems) { if (query == "") { query = null; } var params = { - "maxItems": 3000, + "maxItems": maxItems, "groupNameFilter": query, "ignoreAccess": ignoreAccess }; diff --git a/modules/portal/test/models/MetaSpec.scala b/modules/portal/test/models/MetaSpec.scala index e75fc9f7c..0a2bb1671 100644 --- a/modules/portal/test/models/MetaSpec.scala +++ b/modules/portal/test/models/MetaSpec.scala @@ -65,5 +65,13 @@ class MetaSpec extends Specification with Mockito { val config = Map("scheduled-changes-enabled" -> true) Meta(Configuration.from(config)).scheduledBatchChangesEnabled must beTrue } + "default to 3000 if membership-routing-max-groups-list-limit is not found" in { + val config = Map("vinyldns.version" -> "foo-bar") + Meta(Configuration.from(config)).maxGroupItemsDisplay must beEqualTo(2500) + } + "get the membership-routing-max-groups-list-limit value in config" in { + val config = Map("vinyldns.api.limits.membership-routing-max-groups-list-limit" -> 3100) + Meta(Configuration.from(config)).maxGroupItemsDisplay must beEqualTo(3100) + } } } From da5f6497836a492221a58c46d25a05afaf2766b6 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino <37352107+nspadaccino@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:36:39 -0500 Subject: [PATCH 048/521] Bump version to v0.17.0 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index a716b7c34..48456ba3e 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.16.2" +version in ThisBuild := "0.17.0" From 33e3165cc16fdd3dad5c5ba5296a0d8b479ce9a6 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Tue, 15 Nov 2022 15:31:32 +0530 Subject: [PATCH 049/521] Added metrics api for zone change failure and record change failure --- .../record/ListRecordSetChangesResponse.scala | 6 ++- .../api/domain/record/RecordSetService.scala | 19 +++++++ .../record/RecordSetServiceAlgebra.scala | 4 ++ .../domain/zone/ListZoneChangesResponse.scala | 4 ++ .../api/domain/zone/ZoneService.scala | 19 +++++++ .../api/domain/zone/ZoneServiceAlgebra.scala | 3 ++ .../vinyldns/api/route/RecordSetRouting.scala | 10 ++++ .../vinyldns/api/route/ZoneRouting.scala | 10 ++++ .../domain/record/RecordSetServiceSpec.scala | 24 +++++++++ .../api/domain/zone/ZoneServiceSpec.scala | 17 +++++++ .../api/route/RecordSetRoutingSpec.scala | 31 ++++++++++- .../vinyldns/api/route/ZoneRoutingSpec.scala | 25 +++++++++ .../record/RecordChangeRepository.scala | 3 ++ .../domain/zone/ZoneChangeRepository.scala | 3 ++ ...ecordChangeRepositoryIntegrationSpec.scala | 36 +++++++++++++ ...lZoneChangeRepositoryIntegrationSpec.scala | 51 +++++++++++++++++++ .../MySqlRecordChangeRepository.scala | 20 ++++++++ .../MySqlZoneChangeRepository.scala | 23 +++++++++ 18 files changed, 306 insertions(+), 2 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/ListRecordSetChangesResponse.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/ListRecordSetChangesResponse.scala index 0dcda1985..b7c9efd83 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/ListRecordSetChangesResponse.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/ListRecordSetChangesResponse.scala @@ -17,7 +17,7 @@ package vinyldns.api.domain.record import vinyldns.api.domain.zone.RecordSetChangeInfo -import vinyldns.core.domain.record.ListRecordSetChangesResults +import vinyldns.core.domain.record.{ListRecordSetChangesResults, RecordSetChange} case class ListRecordSetChangesResponse( zoneId: String, @@ -41,3 +41,7 @@ object ListRecordSetChangesResponse { listResults.maxItems ) } + +case class ListFailedRecordSetChangesResponse( + failedRecordSetChanges: List[RecordSetChange] = Nil, + ) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 67f3a0ac1..6ec7894d2 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -581,6 +581,25 @@ class RecordSetService( recordSetChangesInfo <- buildRecordSetChangeInfo(recordSetChangesResults.items) } yield ListRecordSetChangesResponse(zoneId, recordSetChangesResults, recordSetChangesInfo) + + def listFailedRecordSetChanges( + authPrincipal: AuthPrincipal + ): Result[ListFailedRecordSetChangesResponse] = + for { + recordSetChangesFailedResults <- recordChangeRepository + .listFailedRecordSetChanges() + .toResult[List[RecordSetChange]] + _ <- zoneAccess(recordSetChangesFailedResults, authPrincipal).toResult + } yield ListFailedRecordSetChangesResponse(recordSetChangesFailedResults) + + def zoneAccess( + RecordSetCh: List[RecordSetChange], + auth: AuthPrincipal + ): List[Result[Unit]] = + RecordSetCh.map { zn => + canSeeZone(auth, zn.zone).toResult + } + def getZone(zoneId: String): Result[Zone] = zoneRepository .getZone(zoneId) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala index 5d7089196..a8de4156b 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala @@ -103,4 +103,8 @@ trait RecordSetServiceAlgebra { authPrincipal: AuthPrincipal ): Result[ListRecordSetChangesResponse] + def listFailedRecordSetChanges( + authPrincipal: AuthPrincipal + ): Result[ListFailedRecordSetChangesResponse] + } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ListZoneChangesResponse.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ListZoneChangesResponse.scala index 90997b927..08eed0ead 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ListZoneChangesResponse.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ListZoneChangesResponse.scala @@ -37,3 +37,7 @@ object ListZoneChangesResponse { listResults.maxItems ) } + +case class ListFailedZoneChangesResponse( + failedZoneChanges: List[ZoneChange] = Nil, + ) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala index 3b8d66a36..1bac73b34 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala @@ -226,6 +226,25 @@ class ZoneService( .toResult[ListZoneChangesResults] } yield ListZoneChangesResponse(zone.id, zoneChangesResults) + def listFailedZoneChanges( + authPrincipal: AuthPrincipal + ): Result[ListFailedZoneChangesResponse] = + for { + zoneChangesFailedResults <- zoneChangeRepository + .listFailedZoneChanges() + .toResult[List[ZoneChange]] + _ <- zoneAccess(zoneChangesFailedResults, authPrincipal).toResult + } yield { + ListFailedZoneChangesResponse(zoneChangesFailedResults)} + + def zoneAccess( + zoneCh: List[ZoneChange], + auth: AuthPrincipal + ): List[Result[Unit]] = + zoneCh.map { zn => + canSeeZone(auth, zn.zone).toResult + } + def addACLRule( zoneId: String, aclRuleInfo: ACLRuleInfo, diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala index cbcec8f92..965214ce9 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala @@ -67,4 +67,7 @@ trait ZoneServiceAlgebra { def getBackendIds(): Result[List[String]] + def listFailedZoneChanges( + authPrincipal: AuthPrincipal + ): Result[ListFailedZoneChangesResponse] } diff --git a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala index 72cd04596..1b0b1c3ed 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala @@ -233,7 +233,17 @@ class RecordSetRoute( } } } + } ~ + path("metrics" / "health" / "recordsetchangesfailure") { + (get & monitor("Endpoint.listFailedRecordSetChanges")) { + handleRejections(invalidQueryHandler) { + authenticateAndExecute(recordSetService.listFailedRecordSetChanges(_)) { + changes => + complete(StatusCodes.OK, changes) + } + } } +} private val invalidQueryHandler = RejectionHandler .newBuilder() diff --git a/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala index 440f346c3..f77f6e2e6 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala @@ -163,6 +163,16 @@ class ZoneRoute( } } } ~ + path("metrics" / "health" / "zonechangesfailure") { + (get & monitor("Endpoint.listFailedZoneChanges")) { + handleRejections(invalidQueryHandler) { + authenticateAndExecute(zoneService.listFailedZoneChanges(_)) { + changes => + complete(StatusCodes.OK, changes) + } + } + } + } ~ path("zones" / Segment / "acl" / "rules") { id => (put & monitor("Endpoint.addZoneACLRule")) { authenticateAndExecuteWithEntity[ZoneCommandResult, ACLRuleInfo]( diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 7f704f437..01bc41a3c 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -1928,6 +1928,30 @@ class RecordSetServiceSpec result shouldBe expectedResults } + "listFailedRecordSetChanges" should { + "retrieve the recordset changes" in { + val completeRecordSetChanges: List[RecordSetChange] = List( + pendingCreateAAAA.copy(status = RecordSetChangeStatus.Failed), + pendingCreateCNAME.copy(status = RecordSetChangeStatus.Failed), + completeCreateAAAA.copy(status = RecordSetChangeStatus.Failed), + completeCreateCNAME.copy(status = RecordSetChangeStatus.Failed) + ) + //val recordSetChange= List[RecordSetChange] + doReturn(IO.pure(completeRecordSetChanges)) + .when(mockRecordChangeRepo) + .listFailedRecordSetChanges() + + + val result: ListFailedRecordSetChangesResponse = + underTest.listFailedRecordSetChanges(authPrincipal = okAuth).value.unsafeRunSync().toOption.get + + val changesWithName = + ListFailedRecordSetChangesResponse(completeRecordSetChanges) + + result shouldBe changesWithName + } + } + "return a NotAuthorizedError" in { val error = leftResultOf( underTest.listRecordSetChanges(zoneNotAuthorized.id, authPrincipal = okAuth).value diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index 69faad71a..6a5b626a6 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -771,6 +771,23 @@ class ZoneServiceSpec } } + "listFailedZoneChanges" should { + "retrieve the zone changes" in { + + doReturn(IO.pure(List[ZoneChange]( + zoneUpdate.copy(status = ZoneChangeStatus.Failed) + ))) + .when(mockZoneChangeRepo) + .listFailedZoneChanges() + + val result: ListFailedZoneChangesResponse = + underTest.listFailedZoneChanges(okAuth).value.unsafeRunSync().toOption.get + + result.failedZoneChanges shouldBe List( + zoneUpdate.copy(status = ZoneChangeStatus.Failed)) + } + } + "AddAclRule" should { "fail if the user is not authorized for the zone" in { doReturn(IO.pure(Some(zoneNotAuthorized))).when(mockZoneRepo).getZone(anyString) diff --git a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala index ccd70508c..cee4abe1a 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala @@ -19,6 +19,7 @@ package vinyldns.api.route import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpRequest, StatusCodes} import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.ScalatestRouteTest + import java.time.Instant import java.time.temporal.ChronoUnit import org.json4s.JsonDSL._ @@ -28,7 +29,7 @@ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import vinyldns.api.Interfaces._ import vinyldns.api.config.LimitsConfig -import vinyldns.api.domain.record.{ListRecordSetChangesResponse, RecordSetServiceAlgebra} +import vinyldns.api.domain.record.{ListFailedRecordSetChangesResponse, ListRecordSetChangesResponse, RecordSetServiceAlgebra} import vinyldns.api.domain.zone._ import vinyldns.core.TestMembershipData.okAuth import vinyldns.core.domain.Fqdn @@ -407,6 +408,12 @@ class RecordSetRoutingSpec maxItems = 100 ) + private val failedChangesWithUserName = + List(rsChange1.copy(status = RecordSetChangeStatus.Failed) , rsChange2.copy(status = RecordSetChangeStatus.Failed)) + private val listFailedRecordSetChangeResponse = ListFailedRecordSetChangesResponse( + failedChangesWithUserName + ) + class TestService extends RecordSetServiceAlgebra { def evaluate( @@ -564,6 +571,15 @@ class RecordSetRoutingSpec } }.toResult + def listFailedRecordSetChanges( + authPrincipal: AuthPrincipal + ): Result[ListFailedRecordSetChangesResponse] = { + val outcome = authPrincipal match { + case _ => Right(listFailedRecordSetChangeResponse) + } + outcome.toResult + } + def searchRecordSets( startFrom: Option[String], maxItems: Option[Int], @@ -821,6 +837,19 @@ class RecordSetRoutingSpec } } + "GET failed record set changes" should { + "return the failed record set changes" in { + val rsChangeFailed1 = rsChange1.copy(status = RecordSetChangeStatus.Failed) + val rsChangeFailed2 = rsChange2.copy(status = RecordSetChangeStatus.Failed) + + Get(s"/metrics/health/recordsetchangesfailure") ~> recordSetRoute ~> check { + val changes = responseAs[ListFailedRecordSetChangesResponse] + changes.failedRecordSetChanges.map(_.id) shouldBe List(rsChangeFailed1.id, rsChangeFailed2.id) + + } + } + } + "GET recordset" should { "return the recordset summary info" in { Get(s"/zones/${okZone.id}/recordsets/${rsOk.id}") ~> recordSetRoute ~> check { diff --git a/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala index f3750a5b6..bec3d26a3 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala @@ -133,6 +133,10 @@ class ZoneRoutingSpec maxItems = 100 ) + private val listFailedZoneChangeResponse = ListFailedZoneChangesResponse( + List(zoneCreate.copy(status=ZoneChangeStatus.Failed), zoneUpdate.copy(status=ZoneChangeStatus.Failed)) + ) + val crypto = new JavaCrypto( ConfigFactory.parseString( """secret = "8B06A7F3BC8A2497736F1916A123AA40E88217BE9264D8872597EF7A6E5DCE61"""" @@ -353,6 +357,15 @@ class ZoneRoutingSpec outcome.toResult } + def listFailedZoneChanges( + authPrincipal: AuthPrincipal + ): Result[ListFailedZoneChangesResponse] = { + val outcome = authPrincipal match { + case _ => Right(listFailedZoneChangeResponse) + } + outcome.toResult + } + def addACLRule( zoneId: String, aclRuleInfo: ACLRuleInfo, @@ -992,6 +1005,18 @@ class ZoneRoutingSpec } } + "GET failed zone changes" should { + "return the failed zone changes" in { + val zoneCreateFailed = zoneCreate.copy(status = ZoneChangeStatus.Failed) + val zoneUpdateFailed = zoneUpdate.copy(status = ZoneChangeStatus.Failed) + Get(s"/metrics/health/zonechangesfailure") ~> zoneRoute ~> check { + val changes = responseAs[ListFailedZoneChangesResponse] + changes.failedZoneChanges.map(_.id) shouldBe List(zoneCreateFailed.id, zoneUpdateFailed.id) + + } + } + } + "PUT zone" should { "return 202 when the zone is updated" in { Put(s"/zones/${ok.id}") diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala index b873dc378..f4e66e033 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala @@ -31,4 +31,7 @@ trait RecordChangeRepository extends Repository { ): IO[ListRecordSetChangesResults] def getRecordSetChange(zoneId: String, changeId: String): IO[Option[RecordSetChange]] + + def listFailedRecordSetChanges(): IO[List[RecordSetChange]] + } diff --git a/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneChangeRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneChangeRepository.scala index 57a44e217..72b5b28f6 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneChangeRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneChangeRepository.scala @@ -28,4 +28,7 @@ trait ZoneChangeRepository extends Repository { startFrom: Option[String] = None, maxItems: Int = 100 ): IO[ListZoneChangesResults] + + def listFailedZoneChanges( + ): IO[List[ZoneChange]] } diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala index fbada6769..4f42d9e20 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala @@ -61,6 +61,15 @@ class MySqlRecordChangeRepositoryIntegrationSpec newRecordSets.map(makeTestAddChange(_, zone)).toList } + def generateFailedInserts(zone: Zone, count: Int): List[RecordSetChange] = { + val newRecordSets = + for { + i <- 1 to count + } yield aaaa.copy(zoneId = zone.id, name = s"$i-apply-test", id = UUID.randomUUID().toString) + + newRecordSets.map(makeTestAddChange(_, zone).copy(status= RecordSetChangeStatus.Failed)).toList + } + "saving record changes" should { "save a batch of inserts" in { val inserts = generateInserts(okZone, 1000) @@ -129,4 +138,31 @@ class MySqlRecordChangeRepositoryIntegrationSpec page3.items should contain theSameElementsAs expectedOrder.slice(4, 5) } } + + "list failed record changes" should { + "return records for failed record changes" in { + val inserts = generateFailedInserts(okZone, 10) + val saveRecChange = executeWithinTransaction { db: DB => + repo.save(db, ChangeSet(inserts)) + } + saveRecChange.attempt.unsafeRunSync() shouldBe right + val result = repo.listFailedRecordSetChanges().unsafeRunSync() + (result should have).length(10) + result should contain theSameElementsAs(inserts) + + } + "return empty for success record changes" in { + val inserts = generateInserts(okZone, 10) + val saveRecChange = executeWithinTransaction { db: DB => + repo.save(db, ChangeSet(inserts)) + } + saveRecChange.attempt.unsafeRunSync() shouldBe right + val result = repo.listFailedRecordSetChanges().unsafeRunSync() + (result should have).length(0) + result shouldBe List() + } + } } +} + + diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala index fb2bd5dc5..76844e834 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala @@ -48,6 +48,8 @@ class MySqlZoneChangeRepositoryIntegrationSpec IO.contextShift(scala.concurrent.ExecutionContext.global) private var repo: ZoneChangeRepository = _ + private val zoneRepo= TestMySqlInstance.zoneRepository.asInstanceOf[MySqlZoneRepository] + object TestData { def randomZoneChange: ZoneChange = @@ -81,6 +83,23 @@ class MySqlZoneChangeRepositoryIntegrationSpec ) } + val failedChanges + : IndexedSeq[ZoneChange] = for { zone <- zones } yield ZoneChange( + zone, + zone.account, + ZoneChangeType.Update, + status= ZoneChangeStatus.Failed, + created = Instant.now.truncatedTo(ChronoUnit.MILLIS).minusSeconds(Random.nextInt(1000)) + ) + val successChanges + : IndexedSeq[ZoneChange] = for { zone <- zones } yield ZoneChange( + zone, + zone.account, + ZoneChangeType.Update, + status= ZoneChangeStatus.Synced, + created = Instant.now.truncatedTo(ChronoUnit.MILLIS).minusSeconds(Random.nextInt(1000)) + ) + import TestData._ override protected def beforeAll(): Unit = @@ -147,6 +166,38 @@ class MySqlZoneChangeRepositoryIntegrationSpec listResponse.startFrom should equal(None) } + "get all failedChanges for a failed zone changes" in { + zones.map(zoneRepo.save(_)).toList.parSequence.unsafeRunTimed(5.minutes) + .getOrElse( + fail("timeout waiting for changes to save in MySqlZoneChangeRepositoryIntegrationSpec") + ) + + val changeSetupResults = failedChanges.map(repo.save(_)).toList.parSequence + changeSetupResults + .unsafeRunTimed(5.minutes) + .getOrElse( + fail("timeout waiting for changes to save in MySqlZoneChangeRepositoryIntegrationSpec") + ) + + val expectedChanges = + failedChanges.toList + + val listResponse = repo.listFailedZoneChanges().unsafeRunSync() + listResponse should contain theSameElementsAs(expectedChanges) + } + + "get empty list in failedChanges for a success zone changes" in { + val changeSetupResults = successChanges.map(repo.save(_)).toList.parSequence + changeSetupResults + .unsafeRunTimed(5.minutes) + .getOrElse( + fail("timeout waiting for changes to save in MySqlZoneChangeRepositoryIntegrationSpec") + ) + + val listResponse = repo.listFailedZoneChanges().unsafeRunSync() + listResponse shouldBe List() + } + "get zone changes using a maxItems of 1" in { val changeSetupResults = changes.map(repo.save(_)).toList.parSequence changeSetupResults diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala index 18f1902dc..a82ca39db 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala @@ -40,6 +40,12 @@ class MySqlRecordChangeRepository | LIMIT {limit} """.stripMargin + private val LIST_RECORD_CHANGES = + sql""" + |SELECT data + | FROM record_change + """.stripMargin + private val LIST_CHANGES_NO_START = sql""" |SELECT data @@ -124,6 +130,20 @@ class MySqlRecordChangeRepository } } + def listFailedRecordSetChanges(): IO[List[RecordSetChange]] = + monitor("repo.RecordChange.listFailedRecordSetChanges") { + IO { + DB.readOnly { implicit s => + val queryResult = LIST_RECORD_CHANGES + .map(toRecordSetChange) + .list() + .apply() + val maxQueries = queryResult.filter(zc => zc.status == RecordSetChangeStatus.Failed) + maxQueries + } + } + } + def getRecordSetChange(zoneId: String, changeId: String): IO[Option[RecordSetChange]] = monitor("repo.RecordChange.listRecordSetChanges") { IO { diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala index 2f9c0368b..715499e3a 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala @@ -47,6 +47,13 @@ class MySqlZoneChangeRepository | LIMIT {maxItems} """.stripMargin + private final val LIST_ZONES_CHANGES_DATA = + sql""" + |SELECT zc.data + | FROM zone_change zc + | JOIN zone z ON z.id = zc.zone_id + """.stripMargin + override def save(zoneChange: ZoneChange): IO[ZoneChange] = monitor("repo.ZoneChange.save") { IO { @@ -102,6 +109,22 @@ class MySqlZoneChangeRepository } } + def listFailedZoneChanges(): IO[List[ZoneChange]] = + monitor("repo.ZoneChange.listFailedZoneChanges") { + IO { + DB.readOnly { implicit s => + val queryResult = LIST_ZONES_CHANGES_DATA + .map(extractZoneChange(1)) + .list() + .apply() + + val failedZoneChanges = queryResult.filter(zc => zc.status == ZoneChangeStatus.Failed) + + failedZoneChanges + } + } + } + private def extractZoneChange(colIndex: Int): WrappedResultSet => ZoneChange = res => { fromPB(VinylDNSProto.ZoneChange.parseFrom(res.bytes(colIndex))) } From c697b938a8f565735f073aa1ac1291b16929c27a Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Tue, 15 Nov 2022 15:55:57 +0530 Subject: [PATCH 050/521] Resolved failed tests --- ...ecordChangeRepositoryIntegrationSpec.scala | 4 +-- ...lZoneChangeRepositoryIntegrationSpec.scala | 34 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala index 4f42d9e20..7b93ea6b8 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala @@ -17,13 +17,12 @@ package vinyldns.mysql.repository import java.util.UUID - import cats.scalatest.EitherMatchers import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import scalikejdbc._ -import vinyldns.core.domain.record.{ChangeSet, RecordChangeRepository, RecordSetChange, RecordSetChangeType} +import vinyldns.core.domain.record.{ChangeSet, RecordChangeRepository, RecordSetChange, RecordSetChangeStatus, RecordSetChangeType} import vinyldns.core.domain.zone.Zone import vinyldns.mysql.TestMySqlInstance import vinyldns.mysql.TransactionProvider @@ -163,6 +162,5 @@ class MySqlRecordChangeRepositoryIntegrationSpec } } } -} diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala index 76844e834..2e243a304 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala @@ -81,24 +81,24 @@ class MySqlZoneChangeRepositoryIntegrationSpec status, created = Instant.now.truncatedTo(ChronoUnit.MILLIS).minusSeconds(Random.nextInt(1000)) ) - } - val failedChanges - : IndexedSeq[ZoneChange] = for { zone <- zones } yield ZoneChange( - zone, - zone.account, - ZoneChangeType.Update, - status= ZoneChangeStatus.Failed, - created = Instant.now.truncatedTo(ChronoUnit.MILLIS).minusSeconds(Random.nextInt(1000)) - ) - val successChanges - : IndexedSeq[ZoneChange] = for { zone <- zones } yield ZoneChange( - zone, - zone.account, - ZoneChangeType.Update, - status= ZoneChangeStatus.Synced, - created = Instant.now.truncatedTo(ChronoUnit.MILLIS).minusSeconds(Random.nextInt(1000)) - ) + val failedChanges + : IndexedSeq[ZoneChange] = for { zone <- zones } yield ZoneChange( + zone, + zone.account, + ZoneChangeType.Update, + status= ZoneChangeStatus.Failed, + created = Instant.now.truncatedTo(ChronoUnit.MILLIS).minusSeconds(Random.nextInt(1000)) + ) + val successChanges + : IndexedSeq[ZoneChange] = for { zone <- zones } yield ZoneChange( + zone, + zone.account, + ZoneChangeType.Update, + status= ZoneChangeStatus.Synced, + created = Instant.now.truncatedTo(ChronoUnit.MILLIS).minusSeconds(Random.nextInt(1000)) + ) + } import TestData._ From a24436766c2dca71e25ae9d79d5fd8c509d548ff Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino Date: Thu, 17 Nov 2022 17:07:08 -0500 Subject: [PATCH 051/521] Update limits config Signed-off-by: Nicholas Spadaccino --- build/docker/portal/application.conf | 12 ++++++++++++ modules/portal/app/models/Meta.scala | 2 +- modules/portal/conf/application.conf | 12 ++++++++++++ modules/portal/test/models/MetaSpec.scala | 2 +- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/build/docker/portal/application.conf b/build/docker/portal/application.conf index 48ca474b6..6aa062c23 100644 --- a/build/docker/portal/application.conf +++ b/build/docker/portal/application.conf @@ -70,6 +70,18 @@ crypto { secret = ${?CRYPTO_SECRET} } +api { + limits { + batchchange-routing-max-items-limit = 100 + membership-routing-default-max-items = 100 + membership-routing-max-items-limit = 1000 + membership-routing-max-groups-list-limit = 3000 + recordset-routing-default-max-items= 100 + zone-routing-default-max-items = 100 + zone-routing-max-items-limit = 100 + } +} + http.port = 9001 http.port = ${?PORTAL_PORT} diff --git a/modules/portal/app/models/Meta.scala b/modules/portal/app/models/Meta.scala index 850b8176b..c5e045176 100644 --- a/modules/portal/app/models/Meta.scala +++ b/modules/portal/app/models/Meta.scala @@ -35,6 +35,6 @@ object Meta { config.getOptional[Long]("default-ttl").getOrElse(7200L), config.getOptional[Boolean]("manual-batch-review-enabled").getOrElse(false), config.getOptional[Boolean]("scheduled-changes-enabled").getOrElse(false), - config.getOptional[Int]("vinyldns.api.limits.membership-routing-max-groups-list-limit").getOrElse(2500) + config.getOptional[Int]("api.limits.membership-routing-max-groups-list-limit").getOrElse(2500) ) } diff --git a/modules/portal/conf/application.conf b/modules/portal/conf/application.conf index f6324fd54..26fc64305 100644 --- a/modules/portal/conf/application.conf +++ b/modules/portal/conf/application.conf @@ -44,6 +44,18 @@ crypto { secret = ${?CRYPTO_SECRET} } +api { + limits { + batchchange-routing-max-items-limit = 100 + membership-routing-default-max-items = 100 + membership-routing-max-items-limit = 1000 + membership-routing-max-groups-list-limit = 3000 + recordset-routing-default-max-items= 100 + zone-routing-default-max-items = 100 + zone-routing-max-items-limit = 100 + } +} + http.port = 9001 http.port = ${?PORTAL_PORT} diff --git a/modules/portal/test/models/MetaSpec.scala b/modules/portal/test/models/MetaSpec.scala index 0a2bb1671..92dbcd9be 100644 --- a/modules/portal/test/models/MetaSpec.scala +++ b/modules/portal/test/models/MetaSpec.scala @@ -70,7 +70,7 @@ class MetaSpec extends Specification with Mockito { Meta(Configuration.from(config)).maxGroupItemsDisplay must beEqualTo(2500) } "get the membership-routing-max-groups-list-limit value in config" in { - val config = Map("vinyldns.api.limits.membership-routing-max-groups-list-limit" -> 3100) + val config = Map("api.limits.membership-routing-max-groups-list-limit" -> 3100) Meta(Configuration.from(config)).maxGroupItemsDisplay must beEqualTo(3100) } } From 379178931c8d527e5015704f23db7d0ad3d14385 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 18 Nov 2022 11:29:57 +0530 Subject: [PATCH 052/521] Update group history message --- .../domain/membership/MembershipService.scala | 32 +++++++++++++++---- .../membership/MembershipServiceSpec.scala | 5 +-- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala index 08e7cc240..a70aab0fd 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala @@ -245,7 +245,9 @@ class MembershipService( .toResult[Option[GroupChange]] _ <- isGroupChangePresent(result).toResult _ <- canSeeGroup(result.get.newGroup.id, authPrincipal).toResult - groupChangeMessage <- determineGroupDifference(Seq(result.get)) + allUserIds = getGroupUserIds(Seq(result.get)) + allUserMap <- getUsers(allUserIds).map(_.users.map(x => x.id -> x.userName).toMap) + groupChangeMessage <- determineGroupDifference(Seq(result.get), allUserMap) groupChanges = (groupChangeMessage, Seq(result.get)).zipped.map{ (a, b) => b.copy(groupChangeMessage = Some(a)) } userIds = Seq(result.get).map(_.userId).toSet users <- getUsers(userIds).map(_.users) @@ -263,7 +265,9 @@ class MembershipService( result <- groupChangeRepo .getGroupChanges(groupId, startFrom, maxItems) .toResult[ListGroupChangesResults] - groupChangeMessage <- determineGroupDifference(result.changes) + allUserIds = getGroupUserIds(result.changes) + allUserMap <- getUsers(allUserIds).map(_.users.map(x => x.id -> x.userName).toMap) + groupChangeMessage <- determineGroupDifference(result.changes, allUserMap) groupChanges = (groupChangeMessage, result.changes).zipped.map{ (a, b) => b.copy(groupChangeMessage = Some(a)) } userIds = result.changes.map(_.userId).toSet users <- getUsers(userIds).map(_.users) @@ -275,7 +279,21 @@ class MembershipService( maxItems ) - def determineGroupDifference(groupChange: Seq[GroupChange]): Result[Seq[String]] = { + def getGroupUserIds(groupChange: Seq[GroupChange]): Set[String] = { + var userIds: Set[String] = Set.empty[String] + for (change <- groupChange) { + if (change.oldGroup.isDefined) { + val adminAddDifference = change.newGroup.adminUserIds.diff(change.oldGroup.get.adminUserIds) + val adminRemoveDifference = change.oldGroup.get.adminUserIds.diff(change.newGroup.adminUserIds) + val memberAddDifference = change.newGroup.memberIds.diff(change.oldGroup.get.memberIds) + val memberRemoveDifference = change.oldGroup.get.memberIds.diff(change.newGroup.memberIds) + userIds = userIds ++ adminAddDifference ++ adminRemoveDifference ++ memberAddDifference ++ memberRemoveDifference + } + } + userIds + } + + def determineGroupDifference(groupChange: Seq[GroupChange], allUserMap: Map[String, String]): Result[Seq[String]] = { var groupChangeMessage: Seq[String] = Seq.empty[String] for (change <- groupChange) { @@ -292,19 +310,19 @@ class MembershipService( } val adminAddDifference = change.newGroup.adminUserIds.diff(change.oldGroup.get.adminUserIds) if (adminAddDifference.nonEmpty) { - sb.append(s"Group admin/s with userId/s (${adminAddDifference.mkString(",")}) added. ") + sb.append(s"Group admin/s with user name/s '${adminAddDifference.map(x => allUserMap(x)).mkString("','")}' added. ") } val adminRemoveDifference = change.oldGroup.get.adminUserIds.diff(change.newGroup.adminUserIds) if (adminRemoveDifference.nonEmpty) { - sb.append(s"Group admin/s with userId/s (${adminRemoveDifference.mkString(",")}) removed. ") + sb.append(s"Group admin/s with user name/s '${adminRemoveDifference.map(x => allUserMap(x)).mkString("','")}' removed. ") } val memberAddDifference = change.newGroup.memberIds.diff(change.oldGroup.get.memberIds) if (memberAddDifference.nonEmpty) { - sb.append(s"Group member/s with userId/s (${memberAddDifference.mkString(",")}) added. ") + sb.append(s"Group member/s with user name/s '${memberAddDifference.map(x => allUserMap(x)).mkString("','")}' added. ") } val memberRemoveDifference = change.oldGroup.get.memberIds.diff(change.newGroup.memberIds) if (memberRemoveDifference.nonEmpty) { - sb.append(s"Group member/s with userId/s (${memberRemoveDifference.mkString(",")}) removed. ") + sb.append(s"Group member/s with user name/s '${memberRemoveDifference.map(x => allUserMap(x)).mkString("','")}' removed. ") } groupChangeMessage = groupChangeMessage :+ sb.toString().trim } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index 923f89959..2f91c5fa6 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -891,11 +891,12 @@ class MembershipServiceSpec "determine group difference" should { "return difference between two groups" in { val groupChange = Seq(okGroupChange, dummyGroupChangeUpdate, okGroupChange.copy(changeType = GroupChangeType.Delete)) - val result: Seq[String] = rightResultOf(underTest.determineGroupDifference(groupChange).value) + val allUserMap = Map("ok" -> "ok", "12345-abcde-6789" -> "dummyName", "56789-edcba-1234" -> "super") + val result: Seq[String] = rightResultOf(underTest.determineGroupDifference(groupChange, allUserMap).value) // Newly created group's change message result(0) shouldBe "Group Created." // Updated group's change message - result(1) shouldBe "Group name changed to 'dummy-group'. Group email changed to 'dummy@test.com'. Group description changed to 'dummy group'. Group admin/s with userId/s (12345-abcde-6789,56789-edcba-1234) added. Group admin/s with userId/s (ok) removed. Group member/s with userId/s (12345-abcde-6789,56789-edcba-1234) added. Group member/s with userId/s (ok) removed." + result(1) shouldBe "Group name changed to 'dummy-group'. Group email changed to 'dummy@test.com'. Group description changed to 'dummy group'. Group admin/s with user name/s 'dummyName','super' added. Group admin/s with user name/s 'ok' removed. Group member/s with user name/s 'dummyName','super' added. Group member/s with user name/s 'ok' removed." // Deleted group's change message result(2) shouldBe "Group Deleted." } From e7fec97c7a225a630e4d64cb945a7d4738daeba2 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 18 Nov 2022 12:15:44 +0530 Subject: [PATCH 053/521] Add test --- .../api/domain/membership/MembershipServiceSpec.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index 2f91c5fa6..d1aa45cd0 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -888,6 +888,14 @@ class MembershipServiceSpec } } + "get group user ids" should { + "get all users in a group change" in { + val groupChange = Seq(okGroupChange, dummyGroupChangeUpdate, okGroupChange.copy(changeType = GroupChangeType.Delete)) + val result: Set[String] = underTest.getGroupUserIds(groupChange) + result shouldBe Set("12345-abcde-6789", "56789-edcba-1234", "ok") + } + } + "determine group difference" should { "return difference between two groups" in { val groupChange = Seq(okGroupChange, dummyGroupChangeUpdate, okGroupChange.copy(changeType = GroupChangeType.Delete)) From 96fdb969807a186abff45db691a55ddd3a696a4f Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 19 Dec 2022 20:44:23 +0530 Subject: [PATCH 054/521] Trim multi line log messages to single line --- .../scala/vinyldns/api/backend/CommandHandler.scala | 6 +++--- .../scala/vinyldns/api/backend/dns/DnsBackend.scala | 11 ++++++++--- .../scala/vinyldns/api/engine/ZoneSyncHandler.scala | 2 +- .../scala/vinyldns/api/notifier/sns/SnsNotifier.scala | 2 +- .../main/scala/vinyldns/core/health/HealthCheck.scala | 2 +- .../scala/vinyldns/core/notifier/AllNotifiers.scala | 2 +- .../src/main/scala/vinyldns/core/route/Monitor.scala | 2 +- .../main/scala/vinyldns/core/task/TaskScheduler.scala | 2 +- .../scala/vinyldns/mysql/TransactionProvider.scala | 2 +- .../vinyldns/mysql/queue/MySqlMessageQueue.scala | 2 +- .../mysql/repository/MySqlDataStoreProvider.scala | 2 +- .../repository/MySqlRecordSetCacheRepository.scala | 2 +- .../mysql/repository/MySqlRecordSetRepository.scala | 2 +- .../portal/app/controllers/LdapAuthenticator.scala | 2 +- .../portal/app/controllers/OidcAuthenticator.scala | 4 ++-- .../scala/vinyldns/sqs/queue/SqsMessageQueue.scala | 2 +- 16 files changed, 26 insertions(+), 21 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala index 1eeaf3c7a..c2d973dcf 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala @@ -94,7 +94,7 @@ object CommandHandler { ) .parJoin(maxOpen) .handleErrorWith { error => - logger.error("Encountered unexpected error in main flow", error) + logger.error("Encountered unexpected error in main flow", error.getMessage.replaceAll("\n",";")) // just continue, the flow should never stop unless explicitly told to do so flow() @@ -123,7 +123,7 @@ object CommandHandler { .handleErrorWith { error => // on error, we make sure we still continue; should only stop when the app stops // or processing is disabled - logger.error("Encountered error polling message queue", error) + logger.error("Encountered error polling message queue", error.getMessage.replaceAll("\n",";")) // just keep going on the stream pollingStream() @@ -182,7 +182,7 @@ object CommandHandler { .attempt .map { case Left(e) => - logger.warn(s"Failed processing message need to retry; $message", e) + logger.warn(s"Failed processing message need to retry; $message. Error: ${e.getMessage.replaceAll("\n",";")}") RetryMessage(message) case Right(ok) => ok } diff --git a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala index afcc77054..a080bd82a 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala @@ -213,8 +213,13 @@ class DnsBackend(val id: String, val resolver: DNS.SimpleResolver, val xfrInfo: resp <- toDnsResponse(resp) } yield resp + val receivedResponse = result match { + case Right(value) => value.toString.replaceAll("\n",";") + case Left(e) => e.toString.replaceAll("\n",";") + } + logger.info( - s"DnsConnection.send - Sending DNS Message ${obscuredDnsMessage(msg).toString}\n...received response $result" + s"DnsConnection.send - Sending DNS Message ${obscuredDnsMessage(msg).toString.replaceAll("\n",";")}. Received response: $receivedResponse" ) result @@ -234,10 +239,10 @@ class DnsBackend(val id: String, val resolver: DNS.SimpleResolver, val xfrInfo: // so if we can parse the error into an rcode, then we need to handle it properly; otherwise, we can try again // The DNS.Rcode.value function will return -1 if the error cannot be parsed into an integer if (DNS.Rcode.value(query.error) >= 0) { - logger.info(s"Received TRY_AGAIN from DNS lookup; converting error: ${query.error}") + logger.warn(s"Received TRY_AGAIN from DNS lookup; converting error: ${query.error.replaceAll("\n",";")}") fromDnsRcodeToError(DNS.Rcode.value(query.error), query.error) } else { - logger.warn(s"Unparseable error code returned from DNS: ${query.error}") + logger.warn(s"Unparseable error code returned from DNS: ${query.error.replaceAll("\n",";")}") Left(TryAgain(query.error)) } diff --git a/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala b/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala index 3db1dff7f..c6884de17 100644 --- a/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala @@ -171,7 +171,7 @@ object ZoneSyncHandler extends DnsConversions with Monitored with TransactionPro case Left(e: Throwable) => logger.error( s"Encountered error syncing ; zoneName='${zoneChange.zone.name}'; zoneChange='${zoneChange.id}'", - e + e.getMessage.replaceAll("\n",";") ) // We want to just move back to an active status, do not update latest sync zoneChange.copy( diff --git a/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala b/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala index 74c34b536..7b12ffd28 100644 --- a/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala +++ b/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala @@ -52,6 +52,6 @@ class SnsNotifier(config: SnsNotifierConfig, sns: AmazonSNS) sns.publish(request) logger.info(s"Sending batch change success; batchChange='${bc.id}'") }.handleErrorWith { e => - IO(logger.error(s"Failed sending batch change; batchChange='${bc.id}'", e)) + IO(logger.error(s"Failed sending batch change; batchChange='${bc.id}'", e.getMessage.replaceAll("\n",";"))) }.void } diff --git a/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala b/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala index 0432a8581..cfc8bf7f5 100644 --- a/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala +++ b/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala @@ -31,7 +31,7 @@ object HealthCheck { def asHealthCheck(caller: Class[_]): HealthCheck = io.map { case Left(err) => - logger.error(s"HealthCheck for ${caller.getCanonicalName} Failed", err) + logger.error(s"HealthCheck for ${caller.getCanonicalName} Failed", err.getMessage.replaceAll("\n",";")) val msg = Option(err.getMessage).getOrElse("no message from error") Left( HealthCheckError(s"${caller.getCanonicalName} health check failed with msg='${msg}'") diff --git a/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala b/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala index 7a13c3134..b749dba9c 100644 --- a/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala +++ b/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala @@ -35,7 +35,7 @@ final case class AllNotifiers(notifiers: List[Notifier])(implicit val cs: Contex monitor(notifier.getClass.getSimpleName) { notifier.notify(notification).handleErrorWith { e => IO { - logger.error(s"Notifier ${notifier.getClass.getSimpleName} failed.", e) + logger.error(s"Notifier ${notifier.getClass.getSimpleName} failed.", e.getMessage.replaceAll("\n",";")) } } } diff --git a/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala b/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala index d0aa569d5..1dfb70318 100644 --- a/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala +++ b/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala @@ -52,7 +52,7 @@ trait Monitored { IO(t) case Left(e) => - logger.error(s"Finished $id; success=false; duration=$duration seconds", e) + logger.error(s"Finished $id; success=false; duration=$duration seconds", e.getMessage.replaceAll("\n",";")) IO.raiseError(e) } } diff --git a/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala b/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala index 32f8c5cad..a3ef4097e 100644 --- a/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala +++ b/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala @@ -91,7 +91,7 @@ object TaskScheduler extends Monitored { def runOnceSafely(task: Task): IO[Unit] = monitor(s"task.${task.name}") { claimTask().bracket(runTask)(releaseTask).handleError { error => - logger.error(s"""Unexpected error running task; taskName="${task.name}" """, error) + logger.error(s"""Unexpected error running task; taskName="${task.name}" """, error.getMessage.replaceAll("\n",";")) } } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala b/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala index 72ca80cde..004462bcc 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala @@ -52,7 +52,7 @@ trait TransactionProvider { result } catch { case e: Throwable => - logger.error(s"Encountered error executing function within a database transaction ($txId). Rolling back transaction.", e) + logger.error(s"Encountered error executing function within a database transaction ($txId). Rolling back transaction.", e.getMessage.replaceAll("\n",";")) db.rollbackIfActive() throw e } finally { diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala b/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala index 8f8e77bcb..14469e2a3 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala @@ -210,7 +210,7 @@ class MySqlMessageQueue(maxRetries: Int) // Errors could not be deserialized, have an invalid type, or exceeded retries val errors = claimed.collect { case Left((e, id)) => - logger.error(s"Encountered error for message with id $id", e) + logger.error(s"Encountered error for message with id $id", e.getMessage.replaceAll("\n",";")) id } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala index b3c6a8618..4e4448efd 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala @@ -99,7 +99,7 @@ class MySqlDataStoreProvider extends DataStoreProvider { private def shutdown(): IO[Unit] = IO(DBs.close()) - .handleError(e => logger.error(s"Exception occurred while shutting down", e)) + .handleError(e => logger.error(s"Exception occurred while shutting down", e.getMessage.replaceAll("\n",";"))) private final val HEALTH_CHECK = sql""" diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala index 1a8e30ece..e887712a6 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala @@ -139,7 +139,7 @@ class MySqlRecordSetCacheRepository } logger.info(s"Deleted $numDeleted records from zone $zoneName (zone id: $zone_id)") }.handleErrorWith { error => - logger.error(s"Failed deleting records from zone $zoneName (zone id: $zone_id)", error) + logger.error(s"Failed deleting records from zone $zoneName (zone id: $zone_id)", error.getMessage.replaceAll("\n",";")) IO.raiseError(error) } } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala index adf811525..e82551785 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala @@ -380,7 +380,7 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored { } logger.debug(s"Deleted $numDeleted records from zone $zoneName (zone id: $zoneId)") }.handleErrorWith { error => - logger.error(s"Failed deleting records from zone $zoneName (zone id: $zoneId)", error) + logger.error(s"Failed deleting records from zone $zoneName (zone id: $zoneId)", error.getMessage.replaceAll("\n",";")) IO.raiseError(error) } } diff --git a/modules/portal/app/controllers/LdapAuthenticator.scala b/modules/portal/app/controllers/LdapAuthenticator.scala index 2b445cd67..bd0ab1fab 100644 --- a/modules/portal/app/controllers/LdapAuthenticator.scala +++ b/modules/portal/app/controllers/LdapAuthenticator.scala @@ -136,7 +136,7 @@ object LdapAuthenticator { case unexpectedError: Throwable => logger.error( s"LDAP Unexpected Error searching for user; userName='$lookupUserName'", - unexpectedError + unexpectedError.getMessage.replaceAll("\n",";") ) Left(LdapServiceException(unexpectedError.getMessage)) } finally { diff --git a/modules/portal/app/controllers/OidcAuthenticator.scala b/modules/portal/app/controllers/OidcAuthenticator.scala index 4fa8ed4af..ea78d51ac 100644 --- a/modules/portal/app/controllers/OidcAuthenticator.scala +++ b/modules/portal/app/controllers/OidcAuthenticator.scala @@ -186,7 +186,7 @@ class OidcAuthenticator @Inject() (wsClient: WSClient, configuration: Configurat val claimsSet = Try(JWTClaimsSet.parse(jwtClaimsSetString)) match { case Success(s) => Some(s) case Failure(e) => - logger.error(s"oidc session token parse error: ${e.getMessage}") + logger.error(s"oidc session token parse error: ${e.getMessage.replaceAll("\n",";")}") None } @@ -260,7 +260,7 @@ class OidcAuthenticator @Inject() (wsClient: WSClient, configuration: Configurat Either .fromTry(Try(t)) .leftMap { err => - logger.error(s"Unexpected error in OIDC flow: ${err.getMessage}") + logger.error(s"Unexpected error in OIDC flow: ${err.getMessage.replaceAll("\n",";")}") ErrorResponse(500, err.getMessage) } } diff --git a/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala b/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala index d1d8beda8..68a09ad3f 100644 --- a/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala +++ b/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala @@ -105,7 +105,7 @@ class SqsMessageQueue(val queueUrl: String, val client: AmazonSQSAsync) // This is tricky, we need to attempt to parse the message. If we cannot, delete it; otherwise return ok IO(SqsMessage.parseSqsMessage(message)).flatMap { case Left(e) => - logger.error(s"Failed handling message with id '${message.getMessageId}'", e) + logger.error(s"Failed handling message with id '${message.getMessageId}'", e.getMessage.replaceAll("\n",";")) delete(message.getReceiptHandle).as(Left(e)) case Right(ok) => IO.pure(Right(ok)) } From 75882fa9dfe6dfc02d1591a946858b7d85b09ee6 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 20 Dec 2022 12:49:32 +0530 Subject: [PATCH 055/521] Trim multi line throwables to single line --- .../scala/vinyldns/api/backend/CommandHandler.scala | 13 ++++++++++--- .../scala/vinyldns/api/backend/dns/DnsBackend.scala | 7 +++++-- .../scala/vinyldns/api/engine/ZoneSyncHandler.scala | 9 +++++++-- .../vinyldns/api/notifier/sns/SnsNotifier.scala | 5 ++++- .../scala/vinyldns/core/health/HealthCheck.scala | 5 ++++- .../scala/vinyldns/core/notifier/AllNotifiers.scala | 5 ++++- .../main/scala/vinyldns/core/route/Monitor.scala | 6 ++++-- .../scala/vinyldns/core/task/TaskScheduler.scala | 6 ++++-- .../scala/vinyldns/mysql/TransactionProvider.scala | 6 ++++-- .../vinyldns/mysql/queue/MySqlMessageQueue.scala | 5 ++++- .../mysql/repository/MySqlDataStoreProvider.scala | 7 ++++++- .../repository/MySqlRecordSetCacheRepository.scala | 8 +++++--- .../mysql/repository/MySqlRecordSetRepository.scala | 6 ++++-- .../portal/app/controllers/LdapAuthenticator.scala | 7 ++++--- .../portal/app/controllers/OidcAuthenticator.scala | 9 +++++++-- .../scala/vinyldns/sqs/queue/SqsMessageQueue.scala | 6 ++++-- 16 files changed, 80 insertions(+), 30 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala index c2d973dcf..497eb0888 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala @@ -39,6 +39,7 @@ import vinyldns.core.queue.{CommandMessage, MessageCount, MessageQueue} import scala.concurrent.duration._ import vinyldns.core.notifier.AllNotifiers +import java.io.{PrintWriter, StringWriter} object CommandHandler { @@ -94,7 +95,9 @@ object CommandHandler { ) .parJoin(maxOpen) .handleErrorWith { error => - logger.error("Encountered unexpected error in main flow", error.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + error.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Encountered unexpected error in main flow. Error: ${errorMessage.toString.replaceAll("\n",";")}") // just continue, the flow should never stop unless explicitly told to do so flow() @@ -123,7 +126,9 @@ object CommandHandler { .handleErrorWith { error => // on error, we make sure we still continue; should only stop when the app stops // or processing is disabled - logger.error("Encountered error polling message queue", error.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + error.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Encountered error polling message queue. Error: ${errorMessage.toString.replaceAll("\n",";")}") // just keep going on the stream pollingStream() @@ -182,7 +187,9 @@ object CommandHandler { .attempt .map { case Left(e) => - logger.warn(s"Failed processing message need to retry; $message. Error: ${e.getMessage.replaceAll("\n",";")}") + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) + logger.warn(s"Failed processing message need to retry; $message. Error: ${errorMessage.toString.replaceAll("\n",";")}") RetryMessage(message) case Right(ok) => ok } diff --git a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala index a080bd82a..48b33503d 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala @@ -28,7 +28,7 @@ import vinyldns.core.domain.backend.{Backend, BackendResponse} import vinyldns.core.domain.record.RecordType.RecordType import vinyldns.core.domain.record.{RecordSet, RecordSetChange, RecordSetChangeType, RecordType} import vinyldns.core.domain.zone.{Algorithm, Zone, ZoneConnection} - +import java.io.{PrintWriter, StringWriter} import scala.collection.JavaConverters._ object DnsProtocol { @@ -215,7 +215,10 @@ class DnsBackend(val id: String, val resolver: DNS.SimpleResolver, val xfrInfo: val receivedResponse = result match { case Right(value) => value.toString.replaceAll("\n",";") - case Left(e) => e.toString.replaceAll("\n",";") + case Left(e) => + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) + errorMessage.toString.replaceAll("\n",";") } logger.info( diff --git a/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala b/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala index c6884de17..27938a488 100644 --- a/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala @@ -29,6 +29,7 @@ import vinyldns.core.domain.record._ import vinyldns.core.domain.zone._ import vinyldns.core.route.Monitored import vinyldns.mysql.TransactionProvider +import java.io.{PrintWriter, StringWriter} object ZoneSyncHandler extends DnsConversions with Monitored with TransactionProvider { @@ -155,6 +156,9 @@ object ZoneSyncHandler extends DnsConversions with Monitored with TransactionPro recordSetCacheRepository.save(db,changeSet) ) + val Str: Option[String] = None + println(Str.get) + // join together the results of saving both the record changes as well as the record sets for { _ <- saveRecordChanges @@ -169,9 +173,10 @@ object ZoneSyncHandler extends DnsConversions with Monitored with TransactionPro } }.attempt.map { case Left(e: Throwable) => + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) logger.error( - s"Encountered error syncing ; zoneName='${zoneChange.zone.name}'; zoneChange='${zoneChange.id}'", - e.getMessage.replaceAll("\n",";") + s"Encountered error syncing ; zoneName='${zoneChange.zone.name}'; zoneChange='${zoneChange.id}'. Error: ${errorMessage.toString.replaceAll("\n",";")}" ) // We want to just move back to an active status, do not update latest sync zoneChange.copy( diff --git a/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala b/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala index 7b12ffd28..482e16240 100644 --- a/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala +++ b/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala @@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory import vinyldns.api.route.VinylDNSJsonProtocol import vinyldns.core.domain.batch.BatchChange import vinyldns.core.notifier.{Notification, Notifier} +import java.io.{PrintWriter, StringWriter} class SnsNotifier(config: SnsNotifierConfig, sns: AmazonSNS) extends Notifier @@ -52,6 +53,8 @@ class SnsNotifier(config: SnsNotifierConfig, sns: AmazonSNS) sns.publish(request) logger.info(s"Sending batch change success; batchChange='${bc.id}'") }.handleErrorWith { e => - IO(logger.error(s"Failed sending batch change; batchChange='${bc.id}'", e.getMessage.replaceAll("\n",";"))) + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) + IO(logger.error(s"Failed sending batch change; batchChange='${bc.id}'. Error: ${errorMessage.toString.replaceAll("\n",";")}")) }.void } diff --git a/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala b/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala index cfc8bf7f5..f6bb29737 100644 --- a/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala +++ b/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala @@ -18,6 +18,7 @@ package vinyldns.core.health import cats.effect.IO import org.slf4j.LoggerFactory +import java.io.{PrintWriter, StringWriter} object HealthCheck { @@ -31,7 +32,9 @@ object HealthCheck { def asHealthCheck(caller: Class[_]): HealthCheck = io.map { case Left(err) => - logger.error(s"HealthCheck for ${caller.getCanonicalName} Failed", err.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + err.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"HealthCheck for ${caller.getCanonicalName} failed. Error: ${errorMessage.toString.replaceAll("\n",";")}") val msg = Option(err.getMessage).getOrElse("no message from error") Left( HealthCheckError(s"${caller.getCanonicalName} health check failed with msg='${msg}'") diff --git a/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala b/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala index b749dba9c..b64095b98 100644 --- a/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala +++ b/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala @@ -20,6 +20,7 @@ import cats.effect.{ContextShift, IO} import cats.implicits._ import org.slf4j.LoggerFactory import vinyldns.core.route.Monitored +import java.io.{PrintWriter, StringWriter} final case class AllNotifiers(notifiers: List[Notifier])(implicit val cs: ContextShift[IO]) extends Monitored { @@ -34,8 +35,10 @@ final case class AllNotifiers(notifiers: List[Notifier])(implicit val cs: Contex def notify(notifier: Notifier, notification: Notification[_]): IO[Unit] = monitor(notifier.getClass.getSimpleName) { notifier.notify(notification).handleErrorWith { e => + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) IO { - logger.error(s"Notifier ${notifier.getClass.getSimpleName} failed.", e.getMessage.replaceAll("\n",";")) + logger.error(s"Notifier ${notifier.getClass.getSimpleName} failed. Error: ${errorMessage.toString.replaceAll("\n",";")}") } } } diff --git a/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala b/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala index 1dfb70318..fdb8e91dc 100644 --- a/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala +++ b/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala @@ -20,7 +20,7 @@ import cats.effect._ import nl.grons.metrics.scala.{Histogram, Meter, MetricName} import org.slf4j.{Logger, LoggerFactory} import vinyldns.core.Instrumented - +import java.io.{PrintWriter, StringWriter} import scala.collection._ trait Monitored { @@ -52,7 +52,9 @@ trait Monitored { IO(t) case Left(e) => - logger.error(s"Finished $id; success=false; duration=$duration seconds", e.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Finished $id; success=false; duration=$duration seconds. Error: ${errorMessage.toString.replaceAll("\n",";")}") IO.raiseError(e) } } diff --git a/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala b/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala index a3ef4097e..7ffdcd582 100644 --- a/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala +++ b/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala @@ -20,7 +20,7 @@ import cats.implicits._ import fs2._ import org.slf4j.LoggerFactory import vinyldns.core.route.Monitored - +import java.io.{PrintWriter, StringWriter} import scala.concurrent.duration.FiniteDuration // Interface for all Tasks that need to be run @@ -91,7 +91,9 @@ object TaskScheduler extends Monitored { def runOnceSafely(task: Task): IO[Unit] = monitor(s"task.${task.name}") { claimTask().bracket(runTask)(releaseTask).handleError { error => - logger.error(s"""Unexpected error running task; taskName="${task.name}" """, error.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + error.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"""Unexpected error running task; taskName="${task.name}". Error: ${errorMessage.toString.replaceAll("\n",";")} """) } } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala b/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala index 004462bcc..5d3b338de 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala @@ -19,7 +19,7 @@ package vinyldns.mysql import cats.effect.IO import org.slf4j.{Logger, LoggerFactory} import scalikejdbc.{ConnectionPool, DB} - +import java.io.{PrintWriter, StringWriter} import java.sql.SQLException import java.util.UUID @@ -52,7 +52,9 @@ trait TransactionProvider { result } catch { case e: Throwable => - logger.error(s"Encountered error executing function within a database transaction ($txId). Rolling back transaction.", e.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Encountered error executing function within a database transaction ($txId). Rolling back transaction. Error: ${errorMessage.toString.replaceAll("\n",";")}") db.rollbackIfActive() throw e } finally { diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala b/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala index 14469e2a3..9f7fa0e4d 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala @@ -34,6 +34,7 @@ import vinyldns.mysql.queue.MessageType.{ RecordChangeMessageType, ZoneChangeMessageType } +import java.io.{PrintWriter, StringWriter} import vinyldns.proto.VinylDNSProto import java.time.temporal.ChronoUnit import scala.concurrent.duration._ @@ -210,7 +211,9 @@ class MySqlMessageQueue(maxRetries: Int) // Errors could not be deserialized, have an invalid type, or exceeded retries val errors = claimed.collect { case Left((e, id)) => - logger.error(s"Encountered error for message with id $id", e.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Encountered error for message with id $id. Error: ${errorMessage.toString.replaceAll("\n",";")}") id } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala index 4e4448efd..44d7d2801 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala @@ -29,6 +29,7 @@ import vinyldns.core.repository._ import vinyldns.core.health.HealthCheck._ import vinyldns.mysql.{HikariCloser, MySqlConnectionConfig, MySqlDataSourceSettings} import vinyldns.mysql.MySqlConnector._ +import java.io.{PrintWriter, StringWriter} class MySqlDataStoreProvider extends DataStoreProvider { @@ -99,7 +100,11 @@ class MySqlDataStoreProvider extends DataStoreProvider { private def shutdown(): IO[Unit] = IO(DBs.close()) - .handleError(e => logger.error(s"Exception occurred while shutting down", e.getMessage.replaceAll("\n",";"))) + .handleError{ e => + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Exception occurred while shutting down. Error: ${errorMessage.toString.replaceAll("\n",";")}") + } private final val HEALTH_CHECK = sql""" diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala index e887712a6..a061ef091 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala @@ -28,8 +28,8 @@ import vinyldns.core.domain.record.NameSort.{ASC, NameSort} import vinyldns.core.domain.record.RecordType.RecordType import vinyldns.mysql.repository.MySqlRecordSetRepository.{PagingKey, fromRecordType, toFQDN} import vinyldns.proto.VinylDNSProto - -import scala.util.{Try, Success, Failure} +import java.io.{PrintWriter, StringWriter} +import scala.util.{Failure, Success, Try} class MySqlRecordSetCacheRepository extends RecordSetCacheRepository @@ -139,7 +139,9 @@ class MySqlRecordSetCacheRepository } logger.info(s"Deleted $numDeleted records from zone $zoneName (zone id: $zone_id)") }.handleErrorWith { error => - logger.error(s"Failed deleting records from zone $zoneName (zone id: $zone_id)", error.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + error.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Failed deleting records from zone $zoneName (zone id: $zone_id). Error: ${errorMessage.toString.replaceAll("\n",";")}") IO.raiseError(error) } } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala index e82551785..5748cc21d 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala @@ -26,7 +26,7 @@ import vinyldns.core.domain.record._ import vinyldns.core.protobuf.ProtobufConversions import vinyldns.core.route.Monitored import vinyldns.proto.VinylDNSProto - +import java.io.{PrintWriter, StringWriter} import scala.util.Try class MySqlRecordSetRepository extends RecordSetRepository with Monitored { @@ -380,7 +380,9 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored { } logger.debug(s"Deleted $numDeleted records from zone $zoneName (zone id: $zoneId)") }.handleErrorWith { error => - logger.error(s"Failed deleting records from zone $zoneName (zone id: $zoneId)", error.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + error.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Failed deleting records from zone $zoneName (zone id: $zoneId). Error: ${errorMessage.toString.replaceAll("\n",";")}") IO.raiseError(error) } } diff --git a/modules/portal/app/controllers/LdapAuthenticator.scala b/modules/portal/app/controllers/LdapAuthenticator.scala index bd0ab1fab..d4ba54846 100644 --- a/modules/portal/app/controllers/LdapAuthenticator.scala +++ b/modules/portal/app/controllers/LdapAuthenticator.scala @@ -27,7 +27,7 @@ import javax.naming.directory._ import org.slf4j.LoggerFactory import vinyldns.core.domain.membership.User import vinyldns.core.health.HealthCheck._ - +import java.io.{PrintWriter, StringWriter} import scala.collection.JavaConverters._ import scala.util.{Failure, Success, Try} @@ -134,9 +134,10 @@ object LdapAuthenticator { else Left(UserDoesNotExistException(s"[$lookupUserName] LDAP entity does not exist")) } catch { case unexpectedError: Throwable => + val errorMessage = new StringWriter + unexpectedError.printStackTrace(new PrintWriter(errorMessage)) logger.error( - s"LDAP Unexpected Error searching for user; userName='$lookupUserName'", - unexpectedError.getMessage.replaceAll("\n",";") + s"LDAP Unexpected Error searching for user; userName='$lookupUserName'. Error: ${errorMessage.toString.replaceAll("\n",";")}" ) Left(LdapServiceException(unexpectedError.getMessage)) } finally { diff --git a/modules/portal/app/controllers/OidcAuthenticator.scala b/modules/portal/app/controllers/OidcAuthenticator.scala index ea78d51ac..29a7fa19f 100644 --- a/modules/portal/app/controllers/OidcAuthenticator.scala +++ b/modules/portal/app/controllers/OidcAuthenticator.scala @@ -46,6 +46,7 @@ import scala.concurrent.ExecutionContext import scala.util.{Failure, Success, Try} import scala.collection.JavaConverters._ import pureconfig.ConfigSource +import java.io.{PrintWriter, StringWriter} object OidcAuthenticator { final case class OidcConfig( @@ -186,7 +187,9 @@ class OidcAuthenticator @Inject() (wsClient: WSClient, configuration: Configurat val claimsSet = Try(JWTClaimsSet.parse(jwtClaimsSetString)) match { case Success(s) => Some(s) case Failure(e) => - logger.error(s"oidc session token parse error: ${e.getMessage.replaceAll("\n",";")}") + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"oidc session token parse error: ${errorMessage.toString.replaceAll("\n",";")}") None } @@ -260,7 +263,9 @@ class OidcAuthenticator @Inject() (wsClient: WSClient, configuration: Configurat Either .fromTry(Try(t)) .leftMap { err => - logger.error(s"Unexpected error in OIDC flow: ${err.getMessage.replaceAll("\n",";")}") + val errorMessage = new StringWriter + err.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Unexpected error in OIDC flow: ${errorMessage.toString.replaceAll("\n",";")}") ErrorResponse(500, err.getMessage) } } diff --git a/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala b/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala index 68a09ad3f..f83fec8b7 100644 --- a/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala +++ b/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala @@ -39,7 +39,7 @@ import vinyldns.sqs.queue.SqsMessageType.{ SqsRecordSetChangeMessage, SqsZoneChangeMessage } - +import java.io.{PrintWriter, StringWriter} import scala.collection.JavaConverters._ import scala.concurrent.duration.FiniteDuration @@ -105,7 +105,9 @@ class SqsMessageQueue(val queueUrl: String, val client: AmazonSQSAsync) // This is tricky, we need to attempt to parse the message. If we cannot, delete it; otherwise return ok IO(SqsMessage.parseSqsMessage(message)).flatMap { case Left(e) => - logger.error(s"Failed handling message with id '${message.getMessageId}'", e.getMessage.replaceAll("\n",";")) + val errorMessage = new StringWriter + e.printStackTrace(new PrintWriter(errorMessage)) + logger.error(s"Failed handling message with id '${message.getMessageId}'. Error: ${errorMessage.toString.replaceAll("\n",";")}") delete(message.getReceiptHandle).as(Left(e)) case Right(ok) => IO.pure(Right(ok)) } From 0129ca88b98c1c1a63f6ee062c6d1c8c4606f73a Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 20 Dec 2022 12:51:49 +0530 Subject: [PATCH 056/521] Remove test code --- .../src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala b/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala index 27938a488..0f313af19 100644 --- a/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala @@ -156,9 +156,6 @@ object ZoneSyncHandler extends DnsConversions with Monitored with TransactionPro recordSetCacheRepository.save(db,changeSet) ) - val Str: Option[String] = None - println(Str.get) - // join together the results of saving both the record changes as well as the record sets for { _ <- saveRecordChanges From 6c33a7aa896b4e4c8f659b5e873d27dfb30081df Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 20 Dec 2022 15:12:56 +0530 Subject: [PATCH 057/521] Resolve test --- .../core/src/test/scala/vinyldns/core/route/MonitorSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/test/scala/vinyldns/core/route/MonitorSpec.scala b/modules/core/src/test/scala/vinyldns/core/route/MonitorSpec.scala index 42ef31a28..eb94af8cf 100644 --- a/modules/core/src/test/scala/vinyldns/core/route/MonitorSpec.scala +++ b/modules/core/src/test/scala/vinyldns/core/route/MonitorSpec.scala @@ -116,7 +116,7 @@ class MonitorSpec val msgCaptor = ArgumentCaptor.forClass(classOf[String]) val errorCaptor = ArgumentCaptor.forClass(classOf[String]) verify(traitTest.logger, times(1)).info(msgCaptor.capture()) - verify(traitTest.logger, times(1)).error(errorCaptor.capture(), any(classOf[Throwable])) + verify(traitTest.logger, times(1)).error(errorCaptor.capture()) msgCaptor.getValue shouldBe "Starting timeSomethingBad" errorCaptor.getValue should include("Finished timeSomethingBad; success=false; duration=") From 8cfc8560c71acddbefa610b98f14e4b599c95f48 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 20 Dec 2022 15:44:48 +0530 Subject: [PATCH 058/521] Remove tab characters from log message --- .../main/scala/vinyldns/api/backend/CommandHandler.scala | 6 +++--- .../main/scala/vinyldns/api/backend/dns/DnsBackend.scala | 6 +++--- .../main/scala/vinyldns/api/engine/ZoneSyncHandler.scala | 2 +- .../main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala | 2 +- .../src/main/scala/vinyldns/core/health/HealthCheck.scala | 2 +- .../main/scala/vinyldns/core/notifier/AllNotifiers.scala | 2 +- .../core/src/main/scala/vinyldns/core/route/Monitor.scala | 2 +- .../src/main/scala/vinyldns/core/task/TaskScheduler.scala | 2 +- .../src/main/scala/vinyldns/mysql/TransactionProvider.scala | 2 +- .../main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala | 2 +- .../vinyldns/mysql/repository/MySqlDataStoreProvider.scala | 2 +- .../mysql/repository/MySqlRecordSetCacheRepository.scala | 2 +- .../mysql/repository/MySqlRecordSetRepository.scala | 2 +- modules/portal/app/controllers/LdapAuthenticator.scala | 2 +- modules/portal/app/controllers/OidcAuthenticator.scala | 4 ++-- .../src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala | 2 +- 16 files changed, 21 insertions(+), 21 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala index 497eb0888..8ab42d537 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala @@ -97,7 +97,7 @@ object CommandHandler { .handleErrorWith { error => val errorMessage = new StringWriter error.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Encountered unexpected error in main flow. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Encountered unexpected error in main flow. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") // just continue, the flow should never stop unless explicitly told to do so flow() @@ -128,7 +128,7 @@ object CommandHandler { // or processing is disabled val errorMessage = new StringWriter error.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Encountered error polling message queue. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Encountered error polling message queue. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") // just keep going on the stream pollingStream() @@ -189,7 +189,7 @@ object CommandHandler { case Left(e) => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) - logger.warn(s"Failed processing message need to retry; $message. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.warn(s"Failed processing message need to retry; $message. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") RetryMessage(message) case Right(ok) => ok } diff --git a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala index 48b33503d..02c019b9c 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala @@ -214,15 +214,15 @@ class DnsBackend(val id: String, val resolver: DNS.SimpleResolver, val xfrInfo: } yield resp val receivedResponse = result match { - case Right(value) => value.toString.replaceAll("\n",";") + case Right(value) => value.toString.replaceAll("\n"," ").replaceAll("\t"," ") case Left(e) => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) - errorMessage.toString.replaceAll("\n",";") + errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ") } logger.info( - s"DnsConnection.send - Sending DNS Message ${obscuredDnsMessage(msg).toString.replaceAll("\n",";")}. Received response: $receivedResponse" + s"DnsConnection.send - Sending DNS Message ${obscuredDnsMessage(msg).toString.replaceAll("\n",";").replaceAll("\t"," ")}. Received response: $receivedResponse" ) result diff --git a/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala b/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala index 0f313af19..c372277fe 100644 --- a/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/engine/ZoneSyncHandler.scala @@ -173,7 +173,7 @@ object ZoneSyncHandler extends DnsConversions with Monitored with TransactionPro val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) logger.error( - s"Encountered error syncing ; zoneName='${zoneChange.zone.name}'; zoneChange='${zoneChange.id}'. Error: ${errorMessage.toString.replaceAll("\n",";")}" + s"Encountered error syncing ; zoneName='${zoneChange.zone.name}'; zoneChange='${zoneChange.id}'. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}" ) // We want to just move back to an active status, do not update latest sync zoneChange.copy( diff --git a/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala b/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala index 482e16240..b2eb3f29b 100644 --- a/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala +++ b/modules/api/src/main/scala/vinyldns/api/notifier/sns/SnsNotifier.scala @@ -55,6 +55,6 @@ class SnsNotifier(config: SnsNotifierConfig, sns: AmazonSNS) }.handleErrorWith { e => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) - IO(logger.error(s"Failed sending batch change; batchChange='${bc.id}'. Error: ${errorMessage.toString.replaceAll("\n",";")}")) + IO(logger.error(s"Failed sending batch change; batchChange='${bc.id}'. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}")) }.void } diff --git a/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala b/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala index f6bb29737..00b872ced 100644 --- a/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala +++ b/modules/core/src/main/scala/vinyldns/core/health/HealthCheck.scala @@ -34,7 +34,7 @@ object HealthCheck { case Left(err) => val errorMessage = new StringWriter err.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"HealthCheck for ${caller.getCanonicalName} failed. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"HealthCheck for ${caller.getCanonicalName} failed. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") val msg = Option(err.getMessage).getOrElse("no message from error") Left( HealthCheckError(s"${caller.getCanonicalName} health check failed with msg='${msg}'") diff --git a/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala b/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala index b64095b98..d721f40e0 100644 --- a/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala +++ b/modules/core/src/main/scala/vinyldns/core/notifier/AllNotifiers.scala @@ -38,7 +38,7 @@ final case class AllNotifiers(notifiers: List[Notifier])(implicit val cs: Contex val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) IO { - logger.error(s"Notifier ${notifier.getClass.getSimpleName} failed. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Notifier ${notifier.getClass.getSimpleName} failed. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") } } } diff --git a/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala b/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala index fdb8e91dc..34edda917 100644 --- a/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala +++ b/modules/core/src/main/scala/vinyldns/core/route/Monitor.scala @@ -54,7 +54,7 @@ trait Monitored { case Left(e) => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Finished $id; success=false; duration=$duration seconds. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Finished $id; success=false; duration=$duration seconds. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") IO.raiseError(e) } } diff --git a/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala b/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala index 7ffdcd582..701fd1fa9 100644 --- a/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala +++ b/modules/core/src/main/scala/vinyldns/core/task/TaskScheduler.scala @@ -93,7 +93,7 @@ object TaskScheduler extends Monitored { claimTask().bracket(runTask)(releaseTask).handleError { error => val errorMessage = new StringWriter error.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"""Unexpected error running task; taskName="${task.name}". Error: ${errorMessage.toString.replaceAll("\n",";")} """) + logger.error(s"""Unexpected error running task; taskName="${task.name}". Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")} """) } } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala b/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala index 5d3b338de..338bc8360 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/TransactionProvider.scala @@ -54,7 +54,7 @@ trait TransactionProvider { case e: Throwable => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Encountered error executing function within a database transaction ($txId). Rolling back transaction. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Encountered error executing function within a database transaction ($txId). Rolling back transaction. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") db.rollbackIfActive() throw e } finally { diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala b/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala index 9f7fa0e4d..d7f7d9c9a 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/queue/MySqlMessageQueue.scala @@ -213,7 +213,7 @@ class MySqlMessageQueue(maxRetries: Int) case Left((e, id)) => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Encountered error for message with id $id. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Encountered error for message with id $id. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") id } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala index 44d7d2801..2a85a7fe6 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlDataStoreProvider.scala @@ -103,7 +103,7 @@ class MySqlDataStoreProvider extends DataStoreProvider { .handleError{ e => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Exception occurred while shutting down. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Exception occurred while shutting down. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") } private final val HEALTH_CHECK = diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala index a061ef091..e643bfe00 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetCacheRepository.scala @@ -141,7 +141,7 @@ class MySqlRecordSetCacheRepository }.handleErrorWith { error => val errorMessage = new StringWriter error.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Failed deleting records from zone $zoneName (zone id: $zone_id). Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Failed deleting records from zone $zoneName (zone id: $zone_id). Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") IO.raiseError(error) } } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala index 5748cc21d..51b006476 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordSetRepository.scala @@ -382,7 +382,7 @@ class MySqlRecordSetRepository extends RecordSetRepository with Monitored { }.handleErrorWith { error => val errorMessage = new StringWriter error.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Failed deleting records from zone $zoneName (zone id: $zoneId). Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Failed deleting records from zone $zoneName (zone id: $zoneId). Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") IO.raiseError(error) } } diff --git a/modules/portal/app/controllers/LdapAuthenticator.scala b/modules/portal/app/controllers/LdapAuthenticator.scala index d4ba54846..e5c1fcddd 100644 --- a/modules/portal/app/controllers/LdapAuthenticator.scala +++ b/modules/portal/app/controllers/LdapAuthenticator.scala @@ -137,7 +137,7 @@ object LdapAuthenticator { val errorMessage = new StringWriter unexpectedError.printStackTrace(new PrintWriter(errorMessage)) logger.error( - s"LDAP Unexpected Error searching for user; userName='$lookupUserName'. Error: ${errorMessage.toString.replaceAll("\n",";")}" + s"LDAP Unexpected Error searching for user; userName='$lookupUserName'. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}" ) Left(LdapServiceException(unexpectedError.getMessage)) } finally { diff --git a/modules/portal/app/controllers/OidcAuthenticator.scala b/modules/portal/app/controllers/OidcAuthenticator.scala index 29a7fa19f..08e99cfc7 100644 --- a/modules/portal/app/controllers/OidcAuthenticator.scala +++ b/modules/portal/app/controllers/OidcAuthenticator.scala @@ -189,7 +189,7 @@ class OidcAuthenticator @Inject() (wsClient: WSClient, configuration: Configurat case Failure(e) => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"oidc session token parse error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"oidc session token parse error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") None } @@ -265,7 +265,7 @@ class OidcAuthenticator @Inject() (wsClient: WSClient, configuration: Configurat .leftMap { err => val errorMessage = new StringWriter err.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Unexpected error in OIDC flow: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Unexpected error in OIDC flow: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") ErrorResponse(500, err.getMessage) } } diff --git a/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala b/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala index f83fec8b7..495c464ba 100644 --- a/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala +++ b/modules/sqs/src/main/scala/vinyldns/sqs/queue/SqsMessageQueue.scala @@ -107,7 +107,7 @@ class SqsMessageQueue(val queueUrl: String, val client: AmazonSQSAsync) case Left(e) => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) - logger.error(s"Failed handling message with id '${message.getMessageId}'. Error: ${errorMessage.toString.replaceAll("\n",";")}") + logger.error(s"Failed handling message with id '${message.getMessageId}'. Error: ${errorMessage.toString.replaceAll("\n",";").replaceAll("\t"," ")}") delete(message.getReceiptHandle).as(Left(e)) case Right(ok) => IO.pure(Right(ok)) } From 8edda53454d33502bed9dbd4ade1455ae5d30311 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 20 Dec 2022 15:46:50 +0530 Subject: [PATCH 059/521] Revert change --- .../src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala index 02c019b9c..15159718c 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/dns/DnsBackend.scala @@ -214,7 +214,7 @@ class DnsBackend(val id: String, val resolver: DNS.SimpleResolver, val xfrInfo: } yield resp val receivedResponse = result match { - case Right(value) => value.toString.replaceAll("\n"," ").replaceAll("\t"," ") + case Right(value) => value.toString.replaceAll("\n",";").replaceAll("\t"," ") case Left(e) => val errorMessage = new StringWriter e.printStackTrace(new PrintWriter(errorMessage)) From 4fdb5f5e1df8c38ac027d098652e4958eb9e1791 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino <37352107+nspadaccino@users.noreply.github.com> Date: Tue, 20 Dec 2022 12:07:04 -0500 Subject: [PATCH 060/521] Bump version to v0.17.1 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 48456ba3e..74835c38d 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.17.0" +version in ThisBuild := "0.17.1" From 7ecaca3e54d5046a00ab14c19ed0f0d3f9054028 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 22 Dec 2022 11:45:41 +0530 Subject: [PATCH 061/521] Resolve instant json response --- .../scala/vinyldns/api/route/DnsJsonProtocol.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala b/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala index 1858d5685..e986d5d98 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala @@ -84,6 +84,18 @@ trait DnsJsonProtocol extends JsonValidation { (js \ "id").default[String](UUID.randomUUID.toString), (js \ "singleBatchChangeIds").default[List[String]](List()) ).mapN(RecordSetChange.apply) + + override def toJson(rs: RecordSetChange): JValue = + ("zone" -> Extraction.decompose(rs.zone)) ~ + ("recordSet" -> Extraction.decompose(rs.recordSet)) ~ + ("userId" -> rs.userId) ~ + ("changeType" -> Extraction.decompose(rs.changeType)) ~ + ("status" -> Extraction.decompose(rs.status)) ~ + ("created" -> Extraction.decompose(rs.created)) ~ + ("systemMessage" -> rs.systemMessage) ~ + ("updates" -> Extraction.decompose(rs.updates)) ~ + ("id" -> rs.id) ~ + ("singleBatchChangeIds" -> Extraction.decompose(rs.singleBatchChangeIds)) } case object CreateZoneInputSerializer extends ValidationSerializer[CreateZoneInput] { From 94277086a691efc904e6b3ab13202d6b84d1560e Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino <37352107+nspadaccino@users.noreply.github.com> Date: Thu, 22 Dec 2022 13:53:33 -0500 Subject: [PATCH 062/521] Bump version to v0.17.2 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 74835c38d..b2722e8c5 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.17.1" +version in ThisBuild := "0.17.2" From 1a9bf5cd8946a05e238b94418088f2ee39344eef Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 2 Jan 2023 18:20:53 +0530 Subject: [PATCH 063/521] Add zone sync scheduler --- .../src/main/scala/vinyldns/api/Boot.scala | 10 +++ .../api/domain/zone/ZoneChangeGenerator.scala | 8 +++ .../api/domain/zone/ZoneProtocol.scala | 4 ++ .../api/domain/zone/ZoneService.scala | 9 ++- .../domain/zone/zoneSyncScheduleHandler.scala | 72 +++++++++++++++++++ .../vinyldns/api/route/DnsJsonProtocol.scala | 8 ++- .../src/main/protobuf/VinylDNSProto.proto | 2 + .../vinyldns/core/domain/zone/Zone.scala | 18 ++++- .../core/domain/zone/ZoneRepository.scala | 2 + .../core/protobuf/ProtobufConversions.scala | 6 +- .../db/migration/V3.28__ZoneSchedule.sql | 5 ++ .../repository/MySqlZoneRepository.scala | 27 ++++++- modules/portal/Gruntfile.js | 2 + .../app/controllers/FrontendController.scala | 3 +- modules/portal/app/views/main.scala.html | 3 +- .../app/views/zones/zoneDetail.scala.html | 4 +- .../zones/zoneTabs/manageZone.scala.html | 13 +++- modules/portal/karma.conf.js | 1 + modules/portal/package.json | 3 +- .../lib/controllers/controller.manageZones.js | 13 +++- project/Dependencies.scala | 3 +- 21 files changed, 199 insertions(+), 17 deletions(-) create mode 100644 modules/api/src/main/scala/vinyldns/api/domain/zone/zoneSyncScheduleHandler.scala create mode 100644 modules/mysql/src/main/resources/db/migration/V3.28__ZoneSchedule.sql diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index ae4077f49..4af7c7e43 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -46,10 +46,15 @@ import scala.concurrent.{ExecutionContext, Future} import scala.io.{Codec, Source} import vinyldns.core.notifier.NotifierLoader import vinyldns.core.repository.DataStoreLoader +import java.util.concurrent.{Executors, ScheduledExecutorService, TimeUnit} object Boot extends App { private val logger = LoggerFactory.getLogger("Boot") + + // Create a ScheduledExecutorService with a single thread + private val executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() + private implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global private implicit val cs: ContextShift[IO] = IO.contextShift(ec) private implicit val timer: Timer[IO] = IO.timer(ec) @@ -93,6 +98,11 @@ object Boot extends App { repositories.userRepository ) _ <- APIMetrics.initialize(vinyldnsConfig.apiMetricSettings) + // Schedule the task to be executed every 5 seconds + _ <- IO(executor.scheduleAtFixedRate(() => { + val zoneChanges = zoneSyncScheduleHandler.zoneSyncScheduler(repositories.zoneRepository).unsafeRunSync() + zoneChanges.foreach(zone => messageQueue.send(zone).unsafeRunSync()) + }, 0, 5, TimeUnit.SECONDS)) _ <- CommandHandler.run( messageQueue, msgsPerPoll, diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneChangeGenerator.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneChangeGenerator.scala index 4de2d2bdb..644db2540 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneChangeGenerator.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneChangeGenerator.scala @@ -61,6 +61,14 @@ object ZoneChangeGenerator { ZoneChangeStatus.Pending ) + def forSyncs(zone: Zone): ZoneChange = + ZoneChange( + zone.copy(updated = Some(Instant.now.truncatedTo(ChronoUnit.MILLIS)), status = ZoneStatus.Syncing), + zone.scheduleRequestor.get, + ZoneChangeType.Sync, + ZoneChangeStatus.Pending + ) + def forDelete(zone: Zone, authPrincipal: AuthPrincipal): ZoneChange = ZoneChange( zone.copy(updated = Some(Instant.now.truncatedTo(ChronoUnit.MILLIS)), status = ZoneStatus.Deleted), diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala index 7ebdd77ea..6f9c0939a 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala @@ -44,6 +44,7 @@ case class ZoneInfo( adminGroupName: String, latestSync: Option[Instant], backendId: Option[String], + recurrenceSchedule: Option[String], accessLevel: AccessLevel ) @@ -70,6 +71,7 @@ object ZoneInfo { adminGroupName = groupName, latestSync = zone.latestSync, backendId = zone.backendId, + recurrenceSchedule = zone.recurrenceSchedule, accessLevel = accessLevel ) } @@ -90,6 +92,7 @@ case class ZoneSummaryInfo( adminGroupName: String, latestSync: Option[Instant], backendId: Option[String], + recurrenceSchedule: Option[String], accessLevel: AccessLevel ) @@ -111,6 +114,7 @@ object ZoneSummaryInfo { adminGroupName = groupName, latestSync = zone.latestSync, zone.backendId, + recurrenceSchedule = zone.recurrenceSchedule, accessLevel = accessLevel ) } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala index 3b8d66a36..adf56bc1a 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala @@ -28,6 +28,12 @@ import vinyldns.core.domain.zone._ import vinyldns.core.queue.MessageQueue import vinyldns.core.domain.DomainHelpers.ensureTrailingDot import vinyldns.core.domain.backend.BackendResolver +import com.cronutils.model.CronType +import com.cronutils.model.definition.{CronDefinition, CronDefinitionBuilder} +import com.cronutils.model.time.ExecutionTime +import com.cronutils.parser.CronParser +import java.time.{Instant, ZoneId} +import java.time.temporal.ChronoUnit object ZoneService { def apply( @@ -101,7 +107,8 @@ class ZoneService( _ <- adminGroupExists(updateZoneInput.adminGroupId) // if admin group changes, this confirms user has access to new group _ <- canChangeZone(auth, updateZoneInput.name, updateZoneInput.adminGroupId).toResult - zoneWithUpdates = Zone(updateZoneInput, existingZone) + updatedZoneInput = if(updateZoneInput.recurrenceSchedule.isDefined) updateZoneInput.copy(scheduleRequestor = Some(auth.signedInUser.userName)) else updateZoneInput + zoneWithUpdates = Zone(updatedZoneInput, existingZone) _ <- validateZoneConnectionIfChanged(zoneWithUpdates, existingZone) updateZoneChange <- ZoneChangeGenerator .forUpdate(zoneWithUpdates, existingZone, auth, crypto) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/zoneSyncScheduleHandler.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/zoneSyncScheduleHandler.scala new file mode 100644 index 000000000..6a91d4bb3 --- /dev/null +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/zoneSyncScheduleHandler.scala @@ -0,0 +1,72 @@ +/* + * Copyright 2018 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vinyldns.api.domain.zone + +import cats.effect.IO +import com.cronutils.model.CronType +import com.cronutils.model.definition.{CronDefinition, CronDefinitionBuilder} +import com.cronutils.model.time.ExecutionTime +import com.cronutils.parser.CronParser +import vinyldns.core.domain.zone.{Zone, ZoneChange, ZoneRepository} +import java.time.{Instant, ZoneId} +import java.time.temporal.ChronoUnit + +object zoneSyncScheduleHandler { + + // Define the function you want to repeat + def zoneSyncScheduler(zoneRepository: ZoneRepository): IO[Set[ZoneChange]] = { + for { + zones <- zoneRepository.getAllZonesWithSyncSchedule + zoneScheduleIds = getZoneWithSchedule(zones.toList) + zoneChanges <- getZoneChanges(zoneRepository, zoneScheduleIds) + } yield zoneChanges + } + + def getZoneChanges(zoneRepository: ZoneRepository, zoneScheduleIds: List[String]): IO[Set[ZoneChange]] = { + if(zoneScheduleIds.nonEmpty) { + for{ + getZones <- zoneRepository.getZones(zoneScheduleIds.toSet) + syncZoneChange = getZones.map(zone => ZoneChangeGenerator.forSyncs(zone)) + } yield syncZoneChange + } else { + IO(Set.empty) + } + } + + def getZoneWithSchedule(zone: List[Zone]): List[String] = { + var zonesWithSchedule: List[String] = List.empty + for(z <- zone) { + if (z.recurrenceSchedule.isDefined) { + val now = Instant.now() + val cronDefinition: CronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ) + val parser: CronParser = new CronParser(cronDefinition) + val executionTime: ExecutionTime = ExecutionTime.forCron(parser.parse(z.recurrenceSchedule.get)) + val nextExecution = executionTime.nextExecution(now.atZone(ZoneId.systemDefault())).get() + val diff = ChronoUnit.SECONDS.between(now, nextExecution) + if (diff <= 5) { + zonesWithSchedule = zonesWithSchedule :+ z.id + } else { + List.empty + } + } else { + List.empty + } + } + zonesWithSchedule + } + +} diff --git a/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala b/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala index e986d5d98..9a65bd3bd 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala @@ -111,7 +111,9 @@ trait DnsJsonProtocol extends JsonValidation { (js \ "shared").default[Boolean](false), (js \ "acl").default[ZoneACL](ZoneACL()), (js \ "adminGroupId").required[String]("Missing Zone.adminGroupId"), - (js \ "backendId").optional[String] + (js \ "backendId").optional[String], + (js \ "recurrenceSchedule").optional[String], + (js \ "scheduleRequestor").optional[String], ).mapN(CreateZoneInput.apply) } @@ -128,7 +130,9 @@ trait DnsJsonProtocol extends JsonValidation { (js \ "shared").default[Boolean](false), (js \ "acl").default[ZoneACL](ZoneACL()), (js \ "adminGroupId").required[String]("Missing Zone.adminGroupId"), - (js \ "backendId").optional[String] + (js \ "recurrenceSchedule").optional[String], + (js \ "scheduleRequestor").optional[String], + (js \ "backendId").optional[String], ).mapN(UpdateZoneInput.apply) } diff --git a/modules/core/src/main/protobuf/VinylDNSProto.proto b/modules/core/src/main/protobuf/VinylDNSProto.proto index 25f3dee3f..94883d599 100644 --- a/modules/core/src/main/protobuf/VinylDNSProto.proto +++ b/modules/core/src/main/protobuf/VinylDNSProto.proto @@ -49,6 +49,8 @@ message Zone { optional int64 latestSync = 13; optional bool isTest = 14 [default = false]; optional string backendId = 15; + optional string recurrenceSchedule = 16; + optional string scheduleRequestor = 17; } message AData { diff --git a/modules/core/src/main/scala/vinyldns/core/domain/zone/Zone.scala b/modules/core/src/main/scala/vinyldns/core/domain/zone/Zone.scala index 4ed0f2a60..4762d77e0 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/zone/Zone.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/zone/Zone.scala @@ -47,6 +47,8 @@ final case class Zone( shared: Boolean = false, acl: ZoneACL = ZoneACL(), adminGroupId: String = "system", + recurrenceSchedule: Option[String] = None, + scheduleRequestor: Option[String] = None, latestSync: Option[Instant] = None, isTest: Boolean = false, backendId: Option[String] = None @@ -75,6 +77,8 @@ final case class Zone( sb.append("reverse=\"").append(isReverse).append("\"; ") sb.append("isTest=\"").append(isTest).append("\"; ") sb.append("created=\"").append(created).append("\"; ") + recurrenceSchedule.map(sb.append("recurrenceSchedule=\"").append(_).append("\"; ")) + scheduleRequestor.map(sb.append("scheduleRequestor=\"").append(_).append("\"; ")) updated.map(sb.append("updated=\"").append(_).append("\"; ")) latestSync.map(sb.append("latestSync=\"").append(_).append("\"; ")) sb.append("]") @@ -95,7 +99,9 @@ object Zone { acl = acl, adminGroupId = adminGroupId, backendId = backendId, - isTest = isTest + isTest = isTest, + recurrenceSchedule = recurrenceSchedule, + scheduleRequestor = scheduleRequestor ) } @@ -110,7 +116,9 @@ object Zone { shared = shared, acl = acl, adminGroupId = adminGroupId, - backendId = backendId + backendId = backendId, + recurrenceSchedule = recurrenceSchedule, + scheduleRequestor = scheduleRequestor ) } } @@ -123,7 +131,9 @@ final case class CreateZoneInput( shared: Boolean = false, acl: ZoneACL = ZoneACL(), adminGroupId: String, - backendId: Option[String] = None + backendId: Option[String] = None, + recurrenceSchedule: Option[String] = None, + scheduleRequestor: Option[String] = None ) final case class UpdateZoneInput( @@ -135,6 +145,8 @@ final case class UpdateZoneInput( shared: Boolean = false, acl: ZoneACL = ZoneACL(), adminGroupId: String, + recurrenceSchedule: Option[String] = None, + scheduleRequestor: Option[String] = None, backendId: Option[String] = None ) diff --git a/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneRepository.scala index ec8e82af9..0250840b8 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneRepository.scala @@ -29,6 +29,8 @@ trait ZoneRepository extends Repository { def getZones(zoneId: Set[String]): IO[Set[Zone]] + def getAllZonesWithSyncSchedule: IO[Set[Zone]] + def getZoneByName(zoneName: String): IO[Option[Zone]] def getZonesByNames(zoneNames: Set[String]): IO[Set[Zone]] diff --git a/modules/core/src/main/scala/vinyldns/core/protobuf/ProtobufConversions.scala b/modules/core/src/main/scala/vinyldns/core/protobuf/ProtobufConversions.scala index f61bb3d87..b12ae8820 100644 --- a/modules/core/src/main/scala/vinyldns/core/protobuf/ProtobufConversions.scala +++ b/modules/core/src/main/scala/vinyldns/core/protobuf/ProtobufConversions.scala @@ -125,7 +125,9 @@ trait ProtobufConversions { adminGroupId = zn.getAdminGroupId, latestSync = if (zn.hasLatestSync) Some(Instant.ofEpochMilli(zn.getLatestSync)) else None, isTest = zn.getIsTest, - backendId = if (zn.hasBackendId) Some(zn.getBackendId) else None + backendId = if (zn.hasBackendId) Some(zn.getBackendId) else None, + recurrenceSchedule = if (zn.hasRecurrenceSchedule) Some(zn.getRecurrenceSchedule) else None, + scheduleRequestor = if (zn.hasScheduleRequestor) Some(zn.getScheduleRequestor) else None ) } @@ -401,6 +403,8 @@ trait ProtobufConversions { zone.transferConnection.foreach(cn => builder.setTransferConnection(toPB(cn))) zone.latestSync.foreach(dt => builder.setLatestSync(dt.toEpochMilli)) zone.backendId.foreach(bid => builder.setBackendId(bid)) + zone.recurrenceSchedule.foreach(rs => builder.setRecurrenceSchedule(rs)) + zone.scheduleRequestor.foreach(rs => builder.setScheduleRequestor(rs)) builder.build() } diff --git a/modules/mysql/src/main/resources/db/migration/V3.28__ZoneSchedule.sql b/modules/mysql/src/main/resources/db/migration/V3.28__ZoneSchedule.sql new file mode 100644 index 000000000..c764fcdec --- /dev/null +++ b/modules/mysql/src/main/resources/db/migration/V3.28__ZoneSchedule.sql @@ -0,0 +1,5 @@ +CREATE SCHEMA IF NOT EXISTS ${dbName}; + +USE ${dbName}; + +ALTER TABLE zone ADD zone_sync_schedule VARCHAR(256) NULL; diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala index d08b90309..2bb06ae0d 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala @@ -48,10 +48,11 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M */ private final val PUT_ZONE = sql""" - |INSERT INTO zone(id, name, admin_group_id, data) - | VALUES ({id}, {name}, {adminGroupId}, {data}) ON DUPLICATE KEY + |INSERT INTO zone(id, name, admin_group_id, zone_sync_schedule, data) + | VALUES ({id}, {name}, {adminGroupId}, {recurrenceSchedule}, {data}) ON DUPLICATE KEY | UPDATE name=VALUES(name), | admin_group_id=VALUES(admin_group_id), + | zone_sync_schedule=VALUES(zone_sync_schedule), | data=VALUES(data); """.stripMargin @@ -116,6 +117,13 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M | FROM zone """.stripMargin + private final val BASE_GET_ALL_ZONES_SQL = + """ + |SELECT data + | FROM zone + | WHERE zone_sync_schedule IS NOT NULL + """.stripMargin + private final val GET_ZONE_ACCESS_BY_ADMIN_GROUP_ID = sql""" |SELECT zone_id @@ -207,6 +215,19 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M } } + def getAllZonesWithSyncSchedule: IO[Set[Zone]] = + monitor("repo.ZoneJDBC.getAllZonesWithSyncSchedule") { + IO { + DB.readOnly { implicit s => + SQL( + BASE_GET_ALL_ZONES_SQL + ).map(extractZone(1)) + .list() + .apply() + }.toSet + } + } + def getZonesByFilters(zoneNames: Set[String]): IO[Set[Zone]] = if (zoneNames.isEmpty) { IO.pure(Set()) @@ -414,6 +435,7 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M 'id -> zone.id, 'name -> zone.name, 'adminGroupId -> zone.adminGroupId, + 'recurrenceSchedule -> zone.recurrenceSchedule, 'data -> toPB(zone).toByteArray ): _* ) @@ -475,6 +497,7 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M def saveTx(zone: Zone): IO[Either[DuplicateZoneError, Zone]] = monitor("repo.ZoneJDBC.save") { IO { + println(zone.recurrenceSchedule) DB.localTx { implicit s => getZoneByNameInSession(zone.name) match { case Some(foundZone) if zone.id != foundZone.id => DuplicateZoneError(zone.name).asLeft diff --git a/modules/portal/Gruntfile.js b/modules/portal/Gruntfile.js index 7a3123934..d04d031ca 100644 --- a/modules/portal/Gruntfile.js +++ b/modules/portal/Gruntfile.js @@ -35,10 +35,12 @@ module.exports = function(grunt) { {expand: true, flatten: true, src: ['node_modules/jquery/dist/jquery.min.js'], dest: 'public/js'}, {expand: true, flatten: true, src: ['node_modules/moment/min/moment.min.js'], dest: 'public/js'}, {expand: true, flatten: true, src: ['node_modules/jquery-ui-dist/jquery-ui.js'], dest: 'public/js'}, + {expand: true, flatten: true, src: ['node_modules/angular-cron-jobs/dist/angular-cron-jobs.min.js'], dest: 'public/js'}, {expand: true, flatten: true, src: ['node_modules/bootstrap/dist/css/bootstrap.min.css'], dest: 'public/css'}, {expand: true, flatten: true, src: ['node_modules/font-awesome/css/font-awesome.min.css'], dest: 'public/css'}, {expand: true, flatten: true, src: ['node_modules/jquery-ui-dist/jquery-ui.css'], dest: 'public/css'}, + {expand: true, flatten: true, src: ['node_modules/angular-cron-jobs/dist/angular-cron-jobs.min.css'], dest: 'public/css'}, // We're picking just the resources we need from the gentelella UI framework and temporarily storing them in mapped/ui/ {expand: true, flatten: true, cwd: 'node_modules/gentelella', dest: 'mapped/ui', src: '**/jquery.{smartWizard,dataTables.min,mousewheel.min}.js'}, diff --git a/modules/portal/app/controllers/FrontendController.scala b/modules/portal/app/controllers/FrontendController.scala index 53c1ff17b..e67ee1bf2 100644 --- a/modules/portal/app/controllers/FrontendController.scala +++ b/modules/portal/app/controllers/FrontendController.scala @@ -71,7 +71,8 @@ class FrontendController @Inject() ( } def viewZone(zoneId: String): Action[AnyContent] = userAction.async { implicit request => - Future(Ok(views.html.zones.zoneDetail(request.user.userName, zoneId))) + val canReview = request.user.isSuper || request.user.isSupport + Future(Ok(views.html.zones.zoneDetail(request.user.userName, canReview, zoneId))) } def viewRecordSets(): Action[AnyContent] = userAction.async { implicit request => diff --git a/modules/portal/app/views/main.scala.html b/modules/portal/app/views/main.scala.html index ae72212c4..9b4314d0f 100644 --- a/modules/portal/app/views/main.scala.html +++ b/modules/portal/app/views/main.scala.html @@ -21,7 +21,7 @@ - + @@ -158,6 +158,7 @@ + @pagePlugins diff --git a/modules/portal/app/views/zones/zoneDetail.scala.html b/modules/portal/app/views/zones/zoneDetail.scala.html index 00663da8a..2831926f3 100644 --- a/modules/portal/app/views/zones/zoneDetail.scala.html +++ b/modules/portal/app/views/zones/zoneDetail.scala.html @@ -1,4 +1,4 @@ -@(rootAccountName: String, zoneId: String)(implicit request: play.api.mvc.Request[Any], customLinks: models.CustomLinks, meta: models.Meta) +@(rootAccountName: String, rootAccountCanReview: Boolean, zoneId: String)(implicit request: play.api.mvc.Request[Any], customLinks: models.CustomLinks, meta: models.Meta) @import zoneTabs._ @content = { @@ -50,7 +50,7 @@ @manageRecords(request, meta)
    - @manageZone(request, meta) + @manageZone(rootAccountCanReview, request, meta)
    @changeHistory(request) diff --git a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html index 269a1bfbd..3f8be3c0f 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html @@ -1,4 +1,4 @@ -@(implicit request: play.api.mvc.Request[Any], meta: models.Meta) +@(implicit rootAccountCanReview: Boolean, request: play.api.mvc.Request[Any], meta: models.Meta)
    @@ -233,6 +233,17 @@
    +
    +
    +
    + @if(rootAccountCanReview) { +

    Schedule Zone Sync

    + + } +
    +
    +
    +
    diff --git a/modules/portal/karma.conf.js b/modules/portal/karma.conf.js index e2d7dbf1a..ac682192c 100644 --- a/modules/portal/karma.conf.js +++ b/modules/portal/karma.conf.js @@ -21,6 +21,7 @@ module.exports = function(config) { 'js/angular.min.js', 'js/moment.min.js', 'js/ui.js', + 'js/angular-cron-jobs.min.js', 'test_frameworks/*.js', 'js/vinyldns.js', 'lib/services/**/*.spec.js', diff --git a/modules/portal/package.json b/modules/portal/package.json index 0a0c4f319..8275f7109 100644 --- a/modules/portal/package.json +++ b/modules/portal/package.json @@ -32,7 +32,8 @@ "karma-phantomjs-launcher": "^1.0.0", "karma-spec-reporter": "^0.0.26", "malihu-custom-scrollbar-plugin": "^3.1.5", - "phantomjs-prebuilt": "^2.1.16" + "phantomjs-prebuilt": "^2.1.16", + "angular-cron-jobs": "^3.2.1" }, "scripts": { "test": "unit" diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index bd9b6ae83..4f2de8957 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -14,7 +14,7 @@ * limitations under the License. */ -angular.module('controller.manageZones', []) +angular.module('controller.manageZones', ['angular-cron-jobs']) .controller('ManageZonesController', function ($scope, $timeout, $log, recordsService, zonesService, groupsService, profileService, utilityService, pagingService) { @@ -94,6 +94,15 @@ angular.module('controller.manageZones', []) $("#delete_zone_connection_modal").modal("show"); }; + $scope.myZoneSyncScheduleConfig = { + allowMultiple: true, + quartz: true, + options: { + allowMinute : false, + allowHour : false + } + } + $scope.submitDeleteZone = function() { zonesService.delZone($scope.zoneInfo.id) .then(function (response) { @@ -278,6 +287,8 @@ angular.module('controller.manageZones', []) $scope.updateZoneInfo = angular.copy($scope.zoneInfo); $scope.updateZoneInfo.hiddenKey = ''; $scope.updateZoneInfo.hiddenTransferKey = ''; + $log.log('recordsService::getZone-success schedule: ', $scope.updateZoneInfo.recurrenceSchedule); + $log.log('recordsService::getZone-success: ', $scope.zoneInfo); $scope.currentManageZoneState = $scope.manageZoneState.UPDATE; $scope.refreshAclRuleDisplay(); $scope.refreshZoneChange(); diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 908c4095e..a6162f0a3 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -52,7 +52,8 @@ object Dependencies { "com.sun.mail" % "javax.mail" % "1.6.2", "javax.mail" % "javax.mail-api" % "1.6.2", "com.amazonaws" % "aws-java-sdk-sns" % awsV withSources(), - "co.elastic.logging" % "logback-ecs-encoder" % "1.3.2" + "co.elastic.logging" % "logback-ecs-encoder" % "1.3.2", + "com.cronutils" % "cron-utils" % "9.1.6" ) lazy val coreDependencies = Seq( From a59b5801740ecd6fbd89d52476f7d1d0b2ff79a5 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 2 Jan 2023 18:30:58 +0530 Subject: [PATCH 064/521] remove unused imports --- .../main/scala/vinyldns/api/domain/zone/ZoneService.scala | 6 ------ 1 file changed, 6 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala index adf56bc1a..e0a8fa8c3 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala @@ -28,12 +28,6 @@ import vinyldns.core.domain.zone._ import vinyldns.core.queue.MessageQueue import vinyldns.core.domain.DomainHelpers.ensureTrailingDot import vinyldns.core.domain.backend.BackendResolver -import com.cronutils.model.CronType -import com.cronutils.model.definition.{CronDefinition, CronDefinitionBuilder} -import com.cronutils.model.time.ExecutionTime -import com.cronutils.parser.CronParser -import java.time.{Instant, ZoneId} -import java.time.temporal.ChronoUnit object ZoneService { def apply( From bd5cdd094b59d30c1364d26f5413ad3c30e8ecfa Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 3 Jan 2023 13:38:08 +0530 Subject: [PATCH 065/521] change scheduler format --- modules/api/src/main/scala/vinyldns/api/Boot.scala | 13 +++++++++++-- .../lib/controllers/controller.manageZones.js | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index 4af7c7e43..b8aafff54 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -20,6 +20,7 @@ import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.stream.{Materializer, ActorMaterializer} import cats.effect.{Timer, IO, ContextShift} +import cats.data.NonEmptyList import com.typesafe.config.ConfigFactory import fs2.concurrent.SignallingRef import io.prometheus.client.CollectorRegistry @@ -100,8 +101,16 @@ object Boot extends App { _ <- APIMetrics.initialize(vinyldnsConfig.apiMetricSettings) // Schedule the task to be executed every 5 seconds _ <- IO(executor.scheduleAtFixedRate(() => { - val zoneChanges = zoneSyncScheduleHandler.zoneSyncScheduler(repositories.zoneRepository).unsafeRunSync() - zoneChanges.foreach(zone => messageQueue.send(zone).unsafeRunSync()) + val zoneChanges = for { + zoneChanges <- zoneSyncScheduleHandler.zoneSyncScheduler(repositories.zoneRepository) + _ <- if (zoneChanges.nonEmpty) messageQueue.sendBatch(NonEmptyList.fromList(zoneChanges.toList).get) else IO.unit + } yield () + zoneChanges.unsafeRunAsync { + case Right(_) => + logger.debug("Zone sync scheduler ran successfully!") + case Left(error) => + logger.error(s"An error occurred while performing scheduled zone sync $error") + } }, 0, 5, TimeUnit.SECONDS)) _ <- CommandHandler.run( messageQueue, diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 4f2de8957..699a31cf1 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -99,7 +99,9 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) quartz: true, options: { allowMinute : false, - allowHour : false + allowHour : true, + allowMonth : false, + allowYear : false } } From e5e33aab35b85c5fc6978549beafa72ece643cb7 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 3 Jan 2023 14:37:25 +0530 Subject: [PATCH 066/521] add requestor field --- .../main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala | 4 ++++ .../scala/vinyldns/mysql/repository/MySqlZoneRepository.scala | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala index 6f9c0939a..856a86909 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneProtocol.scala @@ -45,6 +45,7 @@ case class ZoneInfo( latestSync: Option[Instant], backendId: Option[String], recurrenceSchedule: Option[String], + scheduleRequestor: Option[String], accessLevel: AccessLevel ) @@ -72,6 +73,7 @@ object ZoneInfo { latestSync = zone.latestSync, backendId = zone.backendId, recurrenceSchedule = zone.recurrenceSchedule, + scheduleRequestor = zone.scheduleRequestor, accessLevel = accessLevel ) } @@ -93,6 +95,7 @@ case class ZoneSummaryInfo( latestSync: Option[Instant], backendId: Option[String], recurrenceSchedule: Option[String], + scheduleRequestor: Option[String], accessLevel: AccessLevel ) @@ -115,6 +118,7 @@ object ZoneSummaryInfo { latestSync = zone.latestSync, zone.backendId, recurrenceSchedule = zone.recurrenceSchedule, + scheduleRequestor = zone.scheduleRequestor, accessLevel = accessLevel ) } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala index 2bb06ae0d..26ab58d15 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneRepository.scala @@ -497,7 +497,6 @@ class MySqlZoneRepository extends ZoneRepository with ProtobufConversions with M def saveTx(zone: Zone): IO[Either[DuplicateZoneError, Zone]] = monitor("repo.ZoneJDBC.save") { IO { - println(zone.recurrenceSchedule) DB.localTx { implicit s => getZoneByNameInSession(zone.name) match { case Some(foundZone) if zone.id != foundZone.id => DuplicateZoneError(zone.name).asLeft From 72f1a1149fc884851f95396c8eda323393b47113 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 3 Jan 2023 16:43:50 +0530 Subject: [PATCH 067/521] resolve test --- .../vinyldns/api/domain/batch/BatchChangeServiceSpec.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala index 5ed6526ca..12e2a674d 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala @@ -306,6 +306,10 @@ class BatchChangeServiceSpec } object AlwaysExistsZoneRepo extends EmptyZoneRepo { + + override def getAllZonesWithSyncSchedule: IO[Set[Zone]] = + IO.pure(Set(Zone("dummyZone", "test@test.com"))) + override def getZones(zoneIds: Set[String]): IO[Set[Zone]] = { val zones = zoneIds.map(Zone(_, "test@test.com")) IO.pure(zones) @@ -353,6 +357,9 @@ class BatchChangeServiceSpec ipv6PTR18Zone ) + override def getAllZonesWithSyncSchedule: IO[Set[Zone]] = + IO.pure(dbZones.filter(zn => zn.recurrenceSchedule.isDefined)) + override def getZones(zoneIds: Set[String]): IO[Set[Zone]] = IO.pure(dbZones.filter(zn => zoneIds.contains(zn.id))) From 275048ec3cdc71a477b448272629ed179560b3c3 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 3 Jan 2023 18:40:35 +0530 Subject: [PATCH 068/521] add test --- .../tests/zones/update_zone_test.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index 9affc45e5..3da86ff9e 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -4,6 +4,7 @@ import pytest from utils import * from vinyldns_context import VinylDNSTestContext +from datetime import datetime, timezone, timedelta @pytest.mark.serial @@ -67,6 +68,82 @@ def test_update_zone_success(shared_zone_test_context): client.abandon_zones([result_zone["id"]], status=202) +def test_update_zone_schedule_success(shared_zone_test_context): + """ + Test updating a zone with a schedule for zone sync successfully sync zone at scheduled time + """ + client = shared_zone_test_context.ok_vinyldns_client + result_zone = None + try: + zone_name = f"one-time{shared_zone_test_context.partition_id}" + + zone = { + "name": zone_name, + "email": "test@test.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "connection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + }, + "transferConnection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + } + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + result_zone["recurrenceSchedule"] = "0/5 0 0 ? * * *" + + # Get the current time in the local timezone + now = datetime.now() + + # Convert the time to the UTC timezone + utc_time = now.astimezone(timezone.utc) + + update_result = client.update_zone(result_zone, status=202) + client.wait_until_zone_change_status_synced(update_result) + + assert_that(update_result["changeType"], is_("Update")) + assert_that(update_result["userId"], is_("ok")) + assert_that(update_result, has_key("created")) + + get_result = client.get_zone(result_zone["id"]) + + uz = get_result["zone"] + + # schedule zone sync every 5 seconds + assert_that(uz["recurrenceSchedule"], is_("0/5 0 0 ? * * *")) + assert_that(uz["updated"], is_not(none())) + + # Add 5 seconds to the current time as there may be a slight change than the exact scheduled time + utc_time_1 = utc_time + timedelta(seconds=1) + utc_time_2 = utc_time + timedelta(seconds=2) + utc_time_3 = utc_time + timedelta(seconds=3) + utc_time_4 = utc_time + timedelta(seconds=4) + utc_time_5 = utc_time + timedelta(seconds=5) + + # Format the time as a string in the desired format + time_str = utc_time.strftime('%Y-%m-%dT%H:%M:%SZ') + time_str_1 = utc_time_1.strftime('%Y-%m-%dT%H:%M:%SZ') + time_str_2 = utc_time_2.strftime('%Y-%m-%dT%H:%M:%SZ') + time_str_3 = utc_time_3.strftime('%Y-%m-%dT%H:%M:%SZ') + time_str_4 = utc_time_4.strftime('%Y-%m-%dT%H:%M:%SZ') + time_str_5 = utc_time_5.strftime('%Y-%m-%dT%H:%M:%SZ') + + time_list = [time_str, time_str_1, time_str_2, time_str_3, time_str_4, time_str_5] + + assert_that(time_list, has_item(uz["latestSync"])) + + finally: + if result_zone: + client.abandon_zones([result_zone["id"]], status=202) + + def test_update_bad_acl_fails(shared_zone_test_context): """ Test that updating a zone with a bad ACL rule fails @@ -831,6 +908,7 @@ def test_normal_user_cannot_update_shared_zone_flag(shared_zone_test_context): error = shared_zone_test_context.ok_vinyldns_client.update_zone(zone_update, status=403) assert_that(error, contains_string("Not authorized to update zone shared status from false to true.")) + @pytest.mark.serial def test_update_connection_info_success(shared_zone_test_context): """ From ca5702c0b3fdcf8e3cbee5e7f7b1442a1a61ad45 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 3 Jan 2023 18:45:02 +0530 Subject: [PATCH 069/521] update test comment --- .../api/src/test/functional/tests/zones/update_zone_test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index 3da86ff9e..01cc006d5 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -97,6 +97,8 @@ def test_update_zone_schedule_success(shared_zone_test_context): result = client.create_zone(zone, status=202) result_zone = result["zone"] client.wait_until_zone_active(result_zone["id"]) + + # schedule zone sync every 5 seconds result_zone["recurrenceSchedule"] = "0/5 0 0 ? * * *" # Get the current time in the local timezone @@ -115,8 +117,6 @@ def test_update_zone_schedule_success(shared_zone_test_context): get_result = client.get_zone(result_zone["id"]) uz = get_result["zone"] - - # schedule zone sync every 5 seconds assert_that(uz["recurrenceSchedule"], is_("0/5 0 0 ? * * *")) assert_that(uz["updated"], is_not(none())) @@ -137,6 +137,7 @@ def test_update_zone_schedule_success(shared_zone_test_context): time_list = [time_str, time_str_1, time_str_2, time_str_3, time_str_4, time_str_5] + # Check if zone sync was performed at scheduled time assert_that(time_list, has_item(uz["latestSync"])) finally: From 8554b705d7038b292965988267904fdb7defe7ca Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 3 Jan 2023 19:37:20 +0530 Subject: [PATCH 070/521] add integration tests --- .../MySqlZoneRepositoryIntegrationSpec.scala | 16 +++++++++++ .../repository/MySqlZoneRepositorySpec.scala | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala index 8daa0e58b..bcd1f6086 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneRepositoryIntegrationSpec.scala @@ -848,5 +848,21 @@ class MySqlZoneRepositoryIntegrationSpec f.unsafeRunSync() shouldBe None } + + "return zones which have zone sync scheduled" in { + // okZone with recurrence schedule + repo.save(okZone).unsafeRunSync() shouldBe Right(okZone) + val updatedOkZone = okZone.copy(recurrenceSchedule = Some("0/5 0 0 ? * * *")) + repo.save(updatedOkZone).unsafeRunSync() shouldBe Right(updatedOkZone) + repo.getZoneByName(updatedOkZone.name).unsafeRunSync().get.recurrenceSchedule shouldBe Some("0/5 0 0 ? * * *") + + // dummyZone without recurrence schedule + val dummyZone = okZone.copy(name = "dummy.", id = "5615c19c-cb00-4734-9acd-fbfdca0e6fce") + repo.save(dummyZone).unsafeRunSync() shouldBe Right(dummyZone) + repo.getZoneByName(dummyZone.name).unsafeRunSync().get.recurrenceSchedule shouldBe None + + // Only get zone with recurrence schedule + repo.getAllZonesWithSyncSchedule.unsafeRunSync() shouldBe Set(updatedOkZone) + } } } diff --git a/modules/mysql/src/test/scala/vinyldns/mysql/repository/MySqlZoneRepositorySpec.scala b/modules/mysql/src/test/scala/vinyldns/mysql/repository/MySqlZoneRepositorySpec.scala index 8d2f5f6ff..190f786a1 100644 --- a/modules/mysql/src/test/scala/vinyldns/mysql/repository/MySqlZoneRepositorySpec.scala +++ b/modules/mysql/src/test/scala/vinyldns/mysql/repository/MySqlZoneRepositorySpec.scala @@ -169,5 +169,33 @@ class MySqlZoneRepositorySpec result shouldEqual Right(zoneInput) } } + "MySqlZoneRepository.getAllZonesWithSyncSchedule" should { + "get zones which have zone sync scheduled" in { + // save a zone with recurrence schedule + val zoneInput1 = Zone("ok.", "test@test.com", ZoneStatus.Active, recurrenceSchedule = Some("0/5 0 0 ? * * *")) + doReturn(IO.pure(Right(zoneInput1))) + .when(repo) + .saveTx(zoneInput1) + val zone1 = repo.save(zoneInput1).unsafeRunSync() + verify(repo).save(zoneInput1) + zone1 shouldEqual Right(zoneInput1) + + // save a zone without recurrence schedule + val zoneInput2 = Zone("dummy.", "test@test.com", ZoneStatus.Active) + doReturn(IO.pure(Right(zoneInput2))) + .when(repo) + .saveTx(zoneInput2) + val zone2 = repo.save(zoneInput2).unsafeRunSync() + verify(repo).save(zoneInput2) + zone2 shouldEqual Right(zoneInput2) + + // Only get zones with schedule + doReturn(IO.pure(Set(zoneInput1))) + .when(repo) + .getAllZonesWithSyncSchedule + val result = repo.getAllZonesWithSyncSchedule.unsafeRunSync() + result shouldEqual Set(zoneInput1) + } + } } From 7965310d1148b9cff7660b6b2b276caef7a0be00 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 3 Jan 2023 20:49:12 +0530 Subject: [PATCH 071/521] add functional tests --- .../api/domain/zone/ZoneService.scala | 12 +++- .../tests/zones/update_zone_test.py | 65 +++++++++++++++---- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala index e0a8fa8c3..8526afb62 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala @@ -80,8 +80,10 @@ class ZoneService( _ <- validateSharedZoneAuthorized(createZoneInput.shared, auth.signedInUser).toResult _ <- zoneDoesNotExist(createZoneInput.name) _ <- adminGroupExists(createZoneInput.adminGroupId) + _ <- if(createZoneInput.recurrenceSchedule.isDefined) canScheduleZoneSync(auth).toResult else IO.unit.toResult _ <- canChangeZone(auth, createZoneInput.name, createZoneInput.adminGroupId).toResult - zoneToCreate = Zone(createZoneInput, auth.isTestUser) + createdZoneInput = if(createZoneInput.recurrenceSchedule.isDefined) createZoneInput.copy(scheduleRequestor = Some(auth.signedInUser.userName)) else createZoneInput + zoneToCreate = Zone(createdZoneInput, auth.isTestUser) _ <- connectionValidator.validateZoneConnections(zoneToCreate) createZoneChange <- ZoneChangeGenerator.forAdd(zoneToCreate, auth).toResult _ <- messageQueue.send(createZoneChange).toResult[Unit] @@ -98,6 +100,7 @@ class ZoneService( auth.signedInUser ).toResult _ <- canChangeZone(auth, existingZone.name, existingZone.adminGroupId).toResult + _ <- if(updateZoneInput.recurrenceSchedule.isDefined) canScheduleZoneSync(auth).toResult else IO.unit.toResult _ <- adminGroupExists(updateZoneInput.adminGroupId) // if admin group changes, this confirms user has access to new group _ <- canChangeZone(auth, updateZoneInput.name, updateZoneInput.adminGroupId).toResult @@ -290,6 +293,13 @@ class ZoneService( } .toResult + def canScheduleZoneSync(auth: AuthPrincipal): Either[Throwable, Unit] = + ensuring( + NotAuthorizedError(s"User '${auth.signedInUser.userName}' is not authorized to schedule zone sync in this zone.") + )( + auth.isSystemAdmin + ) + def adminGroupExists(groupId: String): Result[Unit] = groupRepository .getGroup(groupId) diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index 01cc006d5..721632820 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -68,7 +68,48 @@ def test_update_zone_success(shared_zone_test_context): client.abandon_zones([result_zone["id"]], status=202) -def test_update_zone_schedule_success(shared_zone_test_context): +def test_update_zone_sync_schedule_fails(shared_zone_test_context): + """ + Test updating a zone with a schedule for zone sync fails when the user is not an admin user + """ + client = shared_zone_test_context.ok_vinyldns_client + result_zone = None + try: + zone_name = f"one-time{shared_zone_test_context.partition_id}" + + zone = { + "name": zone_name, + "email": "test@test.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "connection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + }, + "transferConnection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + } + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + + # schedule zone sync every 5 seconds + result_zone["recurrenceSchedule"] = "0/5 0 0 ? * * *" + error = client.update_zone(result_zone, status=403) + + assert_that(error, contains_string("User 'ok' is not authorized to schedule zone sync in this zone.")) + + finally: + if result_zone: + client.abandon_zones([result_zone["id"]], status=202) + + +def test_update_zone_sync_schedule_success(shared_zone_test_context): """ Test updating a zone with a schedule for zone sync successfully sync zone at scheduled time """ @@ -107,11 +148,13 @@ def test_update_zone_schedule_success(shared_zone_test_context): # Convert the time to the UTC timezone utc_time = now.astimezone(timezone.utc) - update_result = client.update_zone(result_zone, status=202) - client.wait_until_zone_change_status_synced(update_result) + super_user_client = shared_zone_test_context.support_user_client + + update_result = super_user_client.update_zone(result_zone, status=202) + super_user_client.wait_until_zone_change_status_synced(update_result) assert_that(update_result["changeType"], is_("Update")) - assert_that(update_result["userId"], is_("ok")) + assert_that(update_result["userId"], is_("support-user-id")) assert_that(update_result, has_key("created")) get_result = client.get_zone(result_zone["id"]) @@ -120,12 +163,11 @@ def test_update_zone_schedule_success(shared_zone_test_context): assert_that(uz["recurrenceSchedule"], is_("0/5 0 0 ? * * *")) assert_that(uz["updated"], is_not(none())) - # Add 5 seconds to the current time as there may be a slight change than the exact scheduled time - utc_time_1 = utc_time + timedelta(seconds=1) - utc_time_2 = utc_time + timedelta(seconds=2) - utc_time_3 = utc_time + timedelta(seconds=3) - utc_time_4 = utc_time + timedelta(seconds=4) - utc_time_5 = utc_time + timedelta(seconds=5) + # Add + or - 2 seconds to the current time as there may be a slight change than the exact scheduled time + utc_time_1 = utc_time - timedelta(seconds=1) + utc_time_2 = utc_time - timedelta(seconds=2) + utc_time_3 = utc_time + timedelta(seconds=1) + utc_time_4 = utc_time + timedelta(seconds=2) # Format the time as a string in the desired format time_str = utc_time.strftime('%Y-%m-%dT%H:%M:%SZ') @@ -133,9 +175,8 @@ def test_update_zone_schedule_success(shared_zone_test_context): time_str_2 = utc_time_2.strftime('%Y-%m-%dT%H:%M:%SZ') time_str_3 = utc_time_3.strftime('%Y-%m-%dT%H:%M:%SZ') time_str_4 = utc_time_4.strftime('%Y-%m-%dT%H:%M:%SZ') - time_str_5 = utc_time_5.strftime('%Y-%m-%dT%H:%M:%SZ') - time_list = [time_str, time_str_1, time_str_2, time_str_3, time_str_4, time_str_5] + time_list = [time_str, time_str_1, time_str_2, time_str_3, time_str_4] # Check if zone sync was performed at scheduled time assert_that(time_list, has_item(uz["latestSync"])) From dc08eb3bd6834277d1d4283035ff112079c11ec2 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 5 Jan 2023 12:33:42 +0530 Subject: [PATCH 072/521] group email validation code along with unit test --- .../api/src/main/resources/application.conf | 3 ++ modules/api/src/main/resources/reference.conf | 4 ++- .../src/main/scala/vinyldns/api/Boot.scala | 2 +- .../api/config/ValidEmailConfig.scala | 33 +++++++++++++++++++ .../vinyldns/api/config/VinylDNSConfig.scala | 3 ++ .../membership/MembershipProtocol.scala | 2 ++ .../domain/membership/MembershipService.scala | 24 ++++++++++++-- .../api/route/MembershipRouting.scala | 1 + .../membership/MembershipServiceSpec.scala | 17 +++++++++- .../main/scala/vinyldns/core/Messages.scala | 2 ++ 10 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 modules/api/src/main/scala/vinyldns/api/config/ValidEmailConfig.scala diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 7407c07e4..7dc642a25 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -131,6 +131,9 @@ vinyldns { from = ${?EMAIL_FROM} } } + validEmailConfig{ + email-domains = ["test.com"] + } sns { class-name = "vinyldns.apadi.notifier.sns.SnsNotifierProvider" diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index 4d1db46a1..2278ae9f1 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -169,7 +169,9 @@ vinyldns { from = "VinylDNS " } } - + validEmailConfig{ + email-domains = ["test.com"] + } sns { class-name = "vinyldns.api.notifier.sns.SnsNotifierProvider" settings { diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index ae4077f49..1a577aba1 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -123,7 +123,7 @@ object Boot extends App { vinyldnsConfig.batchChangeConfig, vinyldnsConfig.scheduledChangesConfig ) - val membershipService = MembershipService(repositories) + val membershipService = MembershipService(repositories,vinyldnsConfig.validEmailConfig) val connectionValidator = new ZoneConnectionValidator( diff --git a/modules/api/src/main/scala/vinyldns/api/config/ValidEmailConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/ValidEmailConfig.scala new file mode 100644 index 000000000..c7316d5cc --- /dev/null +++ b/modules/api/src/main/scala/vinyldns/api/config/ValidEmailConfig.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package vinyldns.api.config + +import pureconfig.ConfigReader + + case class ValidEmailConfig( + valid_domains : List[String] + ) +object ValidEmailConfig { + implicit val configReader: ConfigReader[ValidEmailConfig] = + ConfigReader.forProduct1[ValidEmailConfig,List[String]]( + "email-domains" + + ) { + case valid_domains => ValidEmailConfig(valid_domains) + } + +} diff --git a/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala index dd41bb3a0..c0c0d7695 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/VinylDNSConfig.scala @@ -38,6 +38,7 @@ import scala.reflect.ClassTag final case class VinylDNSConfig( serverConfig: ServerConfig, limitsconfig: LimitsConfig, + validEmailConfig: ValidEmailConfig, httpConfig: HttpConfig, highValueDomainConfig: HighValueDomainConfig, manualReviewConfig: ManualReviewConfig, @@ -83,6 +84,7 @@ object VinylDNSConfig { for { config <- IO.delay(ConfigFactory.load()) limitsconfig <- loadIO[LimitsConfig](config, "vinyldns.api.limits") //Added Limitsconfig to fetch data from the reference.config and pass to LimitsConfig.config + validEmailConfig <- loadIO[ValidEmailConfig](config, path="vinyldns.validEmailConfig") serverConfig <- loadIO[ServerConfig](config, "vinyldns") batchChangeConfig <- loadIO[BatchChangeConfig](config, "vinyldns") backendConfigs <- loadIO[BackendConfigs](config, "vinyldns.backend") @@ -103,6 +105,7 @@ object VinylDNSConfig { } yield VinylDNSConfig( serverConfig, limitsconfig, + validEmailConfig, httpConfig, hvdConfig, manualReviewConfig, diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipProtocol.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipProtocol.scala index 5f7951085..b7cac3152 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipProtocol.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipProtocol.scala @@ -178,6 +178,8 @@ final case class GroupAlreadyExistsError(msg: String) extends Throwable(msg) final case class GroupValidationError(msg: String) extends Throwable(msg) +final case class EmailValidationError(msg: String) extends Throwable(msg) + final case class UserNotFoundError(msg: String) extends Throwable(msg) final case class InvalidGroupError(msg: String) extends Throwable(msg) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala index 08e7cc240..0ff29607e 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala @@ -20,6 +20,7 @@ import cats.effect.IO import cats.implicits._ import scalikejdbc.DB import vinyldns.api.Interfaces._ +import vinyldns.api.config.ValidEmailConfig import vinyldns.api.repository.ApiDataAccessor import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.membership.LockStatus.LockStatus @@ -30,14 +31,15 @@ import vinyldns.core.Messages._ import vinyldns.mysql.TransactionProvider object MembershipService { - def apply(dataAccessor: ApiDataAccessor): MembershipService = + def apply(dataAccessor: ApiDataAccessor,emailConfig:ValidEmailConfig): MembershipService = new MembershipService( dataAccessor.groupRepository, dataAccessor.userRepository, dataAccessor.membershipRepository, dataAccessor.zoneRepository, dataAccessor.groupChangeRepository, - dataAccessor.recordSetRepository + dataAccessor.recordSetRepository, + emailConfig ) } @@ -47,7 +49,8 @@ class MembershipService( membershipRepo: MembershipRepository, zoneRepo: ZoneRepository, groupChangeRepo: GroupChangeRepository, - recordSetRepo: RecordSetRepository + recordSetRepo: RecordSetRepository, + validDomains: ValidEmailConfig ) extends MembershipServiceAlgebra with TransactionProvider { import MembershipValidations._ @@ -58,6 +61,7 @@ class MembershipService( val nonAdminMembers = inputGroup.memberIds.diff(adminMembers) for { _ <- groupValidation(newGroup) + _ <- EmailValidation(newGroup.email) _ <- hasMembersAndAdmins(newGroup).toResult _ <- groupWithSameNameDoesNotExist(newGroup.name) _ <- usersExist(newGroup.memberIds) @@ -78,6 +82,7 @@ class MembershipService( existingGroup <- getExistingGroup(groupId) newGroup = existingGroup.withUpdates(name, email, description, memberIds, adminUserIds) _ <- groupValidation(newGroup) + _ <- EmailValidation(newGroup.email) _ <- canEditGroup(existingGroup, authPrincipal).toResult addedAdmins = newGroup.adminUserIds.diff(existingGroup.adminUserIds) // new non-admin members ++ admins converted to non-admins @@ -364,6 +369,19 @@ class MembershipService( } }.toResult + def EmailValidation(email: String): Result[Unit] = { + + val emailDomains = validDomains.valid_domains + val emailRegex = ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString("|") + "$").r + Option(email) match { + case Some(value) if (emailRegex.findFirstIn(value) == None) => + EmailValidationError(EmailValidationErrorMsg + " " + emailDomains.mkString(",")).asLeft + case _ => + ().asRight + } + }.toResult + + def groupWithSameNameDoesNotExist(name: String): Result[Unit] = groupRepo .getGroupByName(name) diff --git a/modules/api/src/main/scala/vinyldns/api/route/MembershipRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/MembershipRouting.scala index 8b4b59509..17842464c 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/MembershipRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/MembershipRouting.scala @@ -49,6 +49,7 @@ class MembershipRoute( case InvalidGroupError(msg) => complete(StatusCodes.BadRequest, msg) case UserNotFoundError(msg) => complete(StatusCodes.NotFound, msg) case InvalidGroupRequestError(msg) => complete(StatusCodes.BadRequest, msg) + case EmailValidationError(msg) => complete(StatusCodes.BadRequest, msg) } val membershipRoute: Route = path("groups" / Segment) { groupId => diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index bbceb1dba..62d7a7d9b 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -29,6 +29,7 @@ import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.zone.ZoneRepository import cats.effect._ import scalikejdbc.{ConnectionPool, DB} +import vinyldns.api.config.ValidEmailConfig import vinyldns.api.domain.zone.NotAuthorizedError import vinyldns.core.TestMembershipData._ import vinyldns.core.TestZoneData._ @@ -48,6 +49,7 @@ class MembershipServiceSpec private val mockZoneRepo = mock[ZoneRepository] private val mockGroupChangeRepo = mock[GroupChangeRepository] private val mockRecordSetRepo = mock[RecordSetRepository] + private val mockValidEmailConfig = mock[ValidEmailConfig] private val backingService = new MembershipService( mockGroupRepo, @@ -55,7 +57,8 @@ class MembershipServiceSpec mockMembershipRepo, mockZoneRepo, mockGroupChangeRepo, - mockRecordSetRepo + mockRecordSetRepo, + mockValidEmailConfig ) private val underTest = spy(backingService) @@ -282,6 +285,18 @@ class MembershipServiceSpec verify(mockMembershipRepo, never()) .saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean) } + + "return an error if an invalid email is entered" in { + doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUser("ok") + doReturn(result(EmailValidationError("fail"))) + .when(underTest) + .EmailValidation(email = "test@test.com") + + + val error = underTest.createGroup(groupInfo.copy(email = "test@test.com"), okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + + } } "update an existing group" should { diff --git a/modules/core/src/main/scala/vinyldns/core/Messages.scala b/modules/core/src/main/scala/vinyldns/core/Messages.scala index 0c6929f4f..33ddfe99c 100644 --- a/modules/core/src/main/scala/vinyldns/core/Messages.scala +++ b/modules/core/src/main/scala/vinyldns/core/Messages.scala @@ -81,4 +81,6 @@ object Messages { // Error displayed when group name or email is empty val GroupValidationErrorMsg = "Group name and email cannot be empty." + + val EmailValidationErrorMsg = "Please enter a valid Email ID.Valid domains are" } From e304f6859b0988da7a291d86fb30c9069ed9aad8 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 5 Jan 2023 14:44:54 +0530 Subject: [PATCH 073/521] MembershipServiceSpec unit test changes --- .../api/domain/membership/MembershipServiceSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index 62d7a7d9b..3bbce7fa7 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -49,7 +49,7 @@ class MembershipServiceSpec private val mockZoneRepo = mock[ZoneRepository] private val mockGroupChangeRepo = mock[GroupChangeRepository] private val mockRecordSetRepo = mock[RecordSetRepository] - private val mockValidEmailConfig = mock[ValidEmailConfig] + private val mockValidEmailConfig = ValidEmailConfig(valid_domains = List("test.com")) private val backingService = new MembershipService( mockGroupRepo, @@ -85,7 +85,7 @@ class MembershipServiceSpec // the update will remove users 3 and 4, add users 5 and 6, as well as a new admin user 7 and remove user2 as admin private val updatedInfo = Group( name = "new.name", - email = "new.email", + email = "test@test.com", description = Some("new desc"), id = "id", memberIds = Set("user1", "user2", "user5", "user6", "user7"), From 5208983575c3abd3435931d93f98b4002d7079fe Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 6 Jan 2023 13:02:25 +0530 Subject: [PATCH 074/521] add unit tests --- .../src/main/scala/vinyldns/api/Boot.scala | 2 +- ...er.scala => ZoneSyncScheduleHandler.scala} | 6 +- .../zone/ZoneSyncScheduleHandlerSpec.scala | 62 +++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) rename modules/api/src/main/scala/vinyldns/api/domain/zone/{zoneSyncScheduleHandler.scala => ZoneSyncScheduleHandler.scala} (94%) create mode 100644 modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandlerSpec.scala diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index b8aafff54..87e8c3464 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -102,7 +102,7 @@ object Boot extends App { // Schedule the task to be executed every 5 seconds _ <- IO(executor.scheduleAtFixedRate(() => { val zoneChanges = for { - zoneChanges <- zoneSyncScheduleHandler.zoneSyncScheduler(repositories.zoneRepository) + zoneChanges <- ZoneSyncScheduleHandler.zoneSyncScheduler(repositories.zoneRepository) _ <- if (zoneChanges.nonEmpty) messageQueue.sendBatch(NonEmptyList.fromList(zoneChanges.toList).get) else IO.unit } yield () zoneChanges.unsafeRunAsync { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/zoneSyncScheduleHandler.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala similarity index 94% rename from modules/api/src/main/scala/vinyldns/api/domain/zone/zoneSyncScheduleHandler.scala rename to modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala index 6a91d4bb3..2771cdd6f 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/zoneSyncScheduleHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala @@ -25,13 +25,13 @@ import vinyldns.core.domain.zone.{Zone, ZoneChange, ZoneRepository} import java.time.{Instant, ZoneId} import java.time.temporal.ChronoUnit -object zoneSyncScheduleHandler { +object ZoneSyncScheduleHandler { // Define the function you want to repeat def zoneSyncScheduler(zoneRepository: ZoneRepository): IO[Set[ZoneChange]] = { for { zones <- zoneRepository.getAllZonesWithSyncSchedule - zoneScheduleIds = getZoneWithSchedule(zones.toList) + zoneScheduleIds = getZonesWithSchedule(zones.toList) zoneChanges <- getZoneChanges(zoneRepository, zoneScheduleIds) } yield zoneChanges } @@ -47,7 +47,7 @@ object zoneSyncScheduleHandler { } } - def getZoneWithSchedule(zone: List[Zone]): List[String] = { + def getZonesWithSchedule(zone: List[Zone]): List[String] = { var zonesWithSchedule: List[String] = List.empty for(z <- zone) { if (z.recurrenceSchedule.isDefined) { diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandlerSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandlerSpec.scala new file mode 100644 index 000000000..857c3fbab --- /dev/null +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandlerSpec.scala @@ -0,0 +1,62 @@ +package vinyldns.api.domain.zone + +import cats.effect.IO +import cats.scalatest.ValidatedMatchers +import org.mockito.Mockito.doReturn +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import org.scalatestplus.mockito.MockitoSugar.mock +import vinyldns.core.TestZoneData._ +import vinyldns.core.domain.zone.{ZoneRepository, ZoneStatus} + +class ZoneSyncScheduleHandlerSpec extends AnyWordSpec with Matchers with ValidatedMatchers { + import vinyldns.api.domain.zone.ZoneSyncScheduleHandler._ + + private val mockZoneRepo = mock[ZoneRepository] + private val okZoneWithSchedule = okZone.copy(recurrenceSchedule = Some("0/2 * * ? * *"), scheduleRequestor = Some("okUser")) + private val xyzZoneWithSchedule = xyzZone.copy(recurrenceSchedule = Some("0/2 * * ? * *"), scheduleRequestor = Some("xyzUser")) + + + "getZoneWithSchedule" should { + "get zones which are scheduled for zone sync" in { + val zones = List(okZoneWithSchedule, abcZone, xyzZoneWithSchedule) + val result = getZonesWithSchedule(zones) + + result shouldBe List(okZoneWithSchedule.id, xyzZoneWithSchedule.id) + } + + "get empty list when no zone is scheduled for zone sync" in { + val zones = List(xyzZone, abcZone) + val result = getZonesWithSchedule(zones) + + result shouldBe List.empty + } + } + + "getZoneChanges" should { + "return zone changes for zones that have zone sync scheduled" in { + doReturn(IO.pure(Set(okZoneWithSchedule, xyzZoneWithSchedule))).when(mockZoneRepo).getZones(Set(okZoneWithSchedule.id, xyzZoneWithSchedule.id)) + val result = getZoneChanges(mockZoneRepo, List(okZoneWithSchedule.id, xyzZoneWithSchedule.id)).unsafeRunSync() + + result.map(zoneChange => zoneChange.zone.name) shouldBe Set(okZoneWithSchedule.name, xyzZoneWithSchedule.name) + result.map(zoneChange => zoneChange.zone.recurrenceSchedule) shouldBe Set(okZoneWithSchedule.recurrenceSchedule, xyzZoneWithSchedule.recurrenceSchedule) + result.map(zoneChange => zoneChange.zone.scheduleRequestor) shouldBe Set(okZoneWithSchedule.scheduleRequestor, xyzZoneWithSchedule.scheduleRequestor) + result.map(zoneChange => zoneChange.zone.status) shouldBe Set(okZoneWithSchedule.copy(status = ZoneStatus.Syncing).status, xyzZoneWithSchedule.copy(status = ZoneStatus.Syncing).status) + } + } + + "zoneSyncScheduler" should { + "return zones that have zone sync scheduled" in { + doReturn(IO.pure(Set(okZoneWithSchedule, xyzZoneWithSchedule))).when(mockZoneRepo).getAllZonesWithSyncSchedule + doReturn(IO.pure(Set(okZoneWithSchedule, xyzZoneWithSchedule))).when(mockZoneRepo).getZones(Set(okZoneWithSchedule.id, xyzZoneWithSchedule.id, xyzZone.id, okZone.id)) + val result = zoneSyncScheduler(mockZoneRepo).unsafeRunSync() + + // We only get the 2 zones which have zone sync schedule + result.size shouldBe 2 + result.map(zoneChange => zoneChange.zone.name) shouldBe Set(okZoneWithSchedule.name, xyzZoneWithSchedule.name) + result.map(zoneChange => zoneChange.zone.recurrenceSchedule) shouldBe Set(okZoneWithSchedule.recurrenceSchedule, xyzZoneWithSchedule.recurrenceSchedule) + result.map(zoneChange => zoneChange.zone.scheduleRequestor) shouldBe Set(okZoneWithSchedule.scheduleRequestor, xyzZoneWithSchedule.scheduleRequestor) + result.map(zoneChange => zoneChange.zone.status) shouldBe Set(okZoneWithSchedule.copy(status = ZoneStatus.Syncing).status, xyzZoneWithSchedule.copy(status = ZoneStatus.Syncing).status) + } + } +} From 37cd0fd19f3d911326dd7dd75a044cd5fccd9857 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 6 Jan 2023 13:10:05 +0530 Subject: [PATCH 075/521] add header --- .../zone/ZoneSyncScheduleHandlerSpec.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandlerSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandlerSpec.scala index 857c3fbab..94b6bfc1f 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandlerSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandlerSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2018 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package vinyldns.api.domain.zone import cats.effect.IO From 766c551bf98cae89cc9536cf909ece26377a4f40 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Fri, 6 Jan 2023 15:25:43 +0530 Subject: [PATCH 076/521] Functional test for invalid email --- .../tests/membership/create_group_test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/api/src/test/functional/tests/membership/create_group_test.py b/modules/api/src/test/functional/tests/membership/create_group_test.py index 0fcaaa95f..c5e1a08d9 100644 --- a/modules/api/src/test/functional/tests/membership/create_group_test.py +++ b/modules/api/src/test/functional/tests/membership/create_group_test.py @@ -114,7 +114,21 @@ def test_create_group_without_name_or_email(shared_zone_test_context): "Missing Group.name", "Missing Group.email" )) +def test_create_group_with_invalid_email(shared_zone_test_context): + """ + Tests that creating a group With Invalid email fails + """ + client = shared_zone_test_context.ok_vinyldns_client + new_group = { + "name": "invalid-email", + "email": "test@abc.com" + "description": "this is a description", + "members": [{"id": "ok"}], + "admins": [{"id": "ok"}] + } + errors = client.create_group(new_group, status=400)["errors"] + assert_that(errors[0], is_("Please enter a valid Email ID.Valid domains are test.com")) def test_create_group_without_members_or_admins(shared_zone_test_context): """ From 1b52390eaff72b06bf106eb08519b4221170ffc2 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 6 Jan 2023 16:18:56 +0530 Subject: [PATCH 077/521] update comments --- modules/api/src/main/scala/vinyldns/api/Boot.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index 87e8c3464..c9cd424b1 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -99,7 +99,7 @@ object Boot extends App { repositories.userRepository ) _ <- APIMetrics.initialize(vinyldnsConfig.apiMetricSettings) - // Schedule the task to be executed every 5 seconds + // Schedule the zone sync task to be executed every 5 seconds _ <- IO(executor.scheduleAtFixedRate(() => { val zoneChanges = for { zoneChanges <- ZoneSyncScheduleHandler.zoneSyncScheduler(repositories.zoneRepository) @@ -109,7 +109,7 @@ object Boot extends App { case Right(_) => logger.debug("Zone sync scheduler ran successfully!") case Left(error) => - logger.error(s"An error occurred while performing scheduled zone sync $error") + logger.error(s"An error occurred while performing the scheduled zone sync. Error: $error") } }, 0, 5, TimeUnit.SECONDS)) _ <- CommandHandler.run( From 1a398f42618699264394c557670966892eb10691 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 6 Jan 2023 16:26:40 +0530 Subject: [PATCH 078/521] update comments and remove unnecessary logs --- modules/api/src/main/scala/vinyldns/api/Boot.scala | 2 +- .../vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala | 1 - modules/portal/public/lib/controllers/controller.manageZones.js | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index c9cd424b1..3c7003413 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -53,7 +53,7 @@ object Boot extends App { private val logger = LoggerFactory.getLogger("Boot") - // Create a ScheduledExecutorService with a single thread + // Create a ScheduledExecutorService with a new single thread private val executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor() private implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala index 2771cdd6f..e18516bf5 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala @@ -27,7 +27,6 @@ import java.time.temporal.ChronoUnit object ZoneSyncScheduleHandler { - // Define the function you want to repeat def zoneSyncScheduler(zoneRepository: ZoneRepository): IO[Set[ZoneChange]] = { for { zones <- zoneRepository.getAllZonesWithSyncSchedule diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 699a31cf1..1176e81ca 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -289,8 +289,6 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) $scope.updateZoneInfo = angular.copy($scope.zoneInfo); $scope.updateZoneInfo.hiddenKey = ''; $scope.updateZoneInfo.hiddenTransferKey = ''; - $log.log('recordsService::getZone-success schedule: ', $scope.updateZoneInfo.recurrenceSchedule); - $log.log('recordsService::getZone-success: ', $scope.zoneInfo); $scope.currentManageZoneState = $scope.manageZoneState.UPDATE; $scope.refreshAclRuleDisplay(); $scope.refreshZoneChange(); From 9a12f1a865ee06307e11b2b237b49722875e4a38 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 9 Jan 2023 16:39:30 +0530 Subject: [PATCH 079/521] add option to remove zone sync schedule --- .../portal/app/views/zones/zoneTabs/manageZone.scala.html | 6 ++++++ .../portal/public/lib/controllers/controller.manageZones.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html index 3f8be3c0f..c83fa0256 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html @@ -239,6 +239,12 @@ @if(rootAccountCanReview) {

    Schedule Zone Sync

    +
    +
    + +
    }
    diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 1176e81ca..f8856a357 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -45,6 +45,7 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) CONFIRM_UPDATE: 1 }; $scope.allGroups = []; + $scope.recurrenceScheduleExist = false; $scope.keyAlgorithms = ['HMAC-MD5', 'HMAC-SHA1', 'HMAC-SHA224', 'HMAC-SHA256', 'HMAC-SHA384', 'HMAC-SHA512']; @@ -187,6 +188,7 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) */ $scope.submitUpdateZone = function () { + delete $scope.updateZoneInfo.removeRecurrenceSchedule; var zone = angular.copy($scope.updateZoneInfo); zone = zonesService.normalizeZoneDates(zone); zone = zonesService.setConnectionKeys(zone); @@ -242,6 +244,9 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) */ $scope.objectsDiffer = function(left, right) { + if($scope.updateZoneInfo.removeRecurrenceSchedule){ + $scope.updateZoneInfo.recurrenceSchedule = undefined; + } var l = $scope.normalizeZone(left); var r = $scope.normalizeZone(right); return !angular.equals(l, r); @@ -289,6 +294,7 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) $scope.updateZoneInfo = angular.copy($scope.zoneInfo); $scope.updateZoneInfo.hiddenKey = ''; $scope.updateZoneInfo.hiddenTransferKey = ''; + $scope.recurrenceScheduleExist = $scope.updateZoneInfo.recurrenceSchedule ? true : false; $scope.currentManageZoneState = $scope.manageZoneState.UPDATE; $scope.refreshAclRuleDisplay(); $scope.refreshZoneChange(); From f6b03f806f373e52b8cd394c9077572e7c1f71e7 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Tue, 10 Jan 2023 09:54:08 +0530 Subject: [PATCH 080/521] Syntax error in functional test --- .../src/test/functional/tests/membership/create_group_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/test/functional/tests/membership/create_group_test.py b/modules/api/src/test/functional/tests/membership/create_group_test.py index c5e1a08d9..d8d23fc87 100644 --- a/modules/api/src/test/functional/tests/membership/create_group_test.py +++ b/modules/api/src/test/functional/tests/membership/create_group_test.py @@ -122,7 +122,7 @@ def test_create_group_with_invalid_email(shared_zone_test_context): new_group = { "name": "invalid-email", - "email": "test@abc.com" + "email": "test@abc.com", "description": "this is a description", "members": [{"id": "ok"}], "admins": [{"id": "ok"}] From 9b4c1bf31b5739445018fb4d1761783a36e43596 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Tue, 10 Jan 2023 15:05:47 +0530 Subject: [PATCH 081/521] Email validation *comcast.com changes --- modules/api/src/main/resources/application.conf | 2 +- modules/api/src/main/resources/reference.conf | 4 ++-- .../api/domain/membership/MembershipService.scala | 12 +++++++++--- .../core/src/main/scala/vinyldns/core/Messages.scala | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 7dc642a25..d4401ba49 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -132,7 +132,7 @@ vinyldns { } } validEmailConfig{ - email-domains = ["test.com"] + email-domains = ["test.com","*comcast.com"] } sns { diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index 2278ae9f1..c6f103de0 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -170,8 +170,8 @@ vinyldns { } } validEmailConfig{ - email-domains = ["test.com"] - } + email-domains = ["test.com","*comcast.com"] + } sns { class-name = "vinyldns.api.notifier.sns.SnsNotifierProvider" settings { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala index 0ff29607e..f50c75b54 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala @@ -370,12 +370,18 @@ class MembershipService( }.toResult def EmailValidation(email: String): Result[Unit] = { - val emailDomains = validDomains.valid_domains - val emailRegex = ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString("|") + "$").r + println("valid domains",emailDomains); + val emailRegex = if(emailDomains.mkString(",").contains("*")){ + println("demo"); + ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString(",").replaceAllLiterally("*","[A-Za-z0-9.]*").replaceAllLiterally(",","|") + "$").r + } else { + ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString("|") + "$").r + } + println("email regex",emailRegex); Option(email) match { case Some(value) if (emailRegex.findFirstIn(value) == None) => - EmailValidationError(EmailValidationErrorMsg + " " + emailDomains.mkString(",")).asLeft + EmailValidationError(EmailValidationErrorMsg + " " + validDomains.valid_domains.mkString(",").replace("*","")).asLeft case _ => ().asRight } diff --git a/modules/core/src/main/scala/vinyldns/core/Messages.scala b/modules/core/src/main/scala/vinyldns/core/Messages.scala index 33ddfe99c..f9518b814 100644 --- a/modules/core/src/main/scala/vinyldns/core/Messages.scala +++ b/modules/core/src/main/scala/vinyldns/core/Messages.scala @@ -82,5 +82,5 @@ object Messages { // Error displayed when group name or email is empty val GroupValidationErrorMsg = "Group name and email cannot be empty." - val EmailValidationErrorMsg = "Please enter a valid Email ID.Valid domains are" + val EmailValidationErrorMsg = "Please enter a valid Email ID.Valid domains should end with" } From fca6bdaa1a92293ef58bf9498de73bc2cf803792 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Tue, 10 Jan 2023 15:12:24 +0530 Subject: [PATCH 082/521] Removing println statements --- .../vinyldns/api/domain/membership/MembershipService.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala index f50c75b54..4bc103cbe 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala @@ -373,12 +373,10 @@ class MembershipService( val emailDomains = validDomains.valid_domains println("valid domains",emailDomains); val emailRegex = if(emailDomains.mkString(",").contains("*")){ - println("demo"); ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString(",").replaceAllLiterally("*","[A-Za-z0-9.]*").replaceAllLiterally(",","|") + "$").r } else { ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString("|") + "$").r } - println("email regex",emailRegex); Option(email) match { case Some(value) if (emailRegex.findFirstIn(value) == None) => EmailValidationError(EmailValidationErrorMsg + " " + validDomains.valid_domains.mkString(",").replace("*","")).asLeft From 23950ebecfa82fb8e4aa8f03493c7a8c5f153225 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 11 Jan 2023 10:25:56 +0530 Subject: [PATCH 083/521] fix for functional test --- .../src/test/functional/tests/membership/create_group_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/test/functional/tests/membership/create_group_test.py b/modules/api/src/test/functional/tests/membership/create_group_test.py index d8d23fc87..2bb89365a 100644 --- a/modules/api/src/test/functional/tests/membership/create_group_test.py +++ b/modules/api/src/test/functional/tests/membership/create_group_test.py @@ -128,7 +128,7 @@ def test_create_group_with_invalid_email(shared_zone_test_context): "admins": [{"id": "ok"}] } errors = client.create_group(new_group, status=400)["errors"] - assert_that(errors[0], is_("Please enter a valid Email ID.Valid domains are test.com")) + assert_that(errors, is_("Please enter a valid Email ID.Valid domains are test.com")) def test_create_group_without_members_or_admins(shared_zone_test_context): """ From cb758304af770bc79a3dcbc727188c32024a19f0 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 12 Jan 2023 10:56:01 +0530 Subject: [PATCH 084/521] Functional test config changes --- modules/api/src/test/resources/application.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/api/src/test/resources/application.conf b/modules/api/src/test/resources/application.conf index 962b2bbcb..0ee16a9e2 100644 --- a/modules/api/src/test/resources/application.conf +++ b/modules/api/src/test/resources/application.conf @@ -127,7 +127,9 @@ vinyldns { from = ${?EMAIL_FROM} } } - + validEmailConfig{ + email-domains = ["test.com","*comcast.com"] + } sns { class-name = "vinyldns.apadi.notifier.sns.SnsNotifierProvider" class-name = ${?SNS_CLASS_NAME} From 73ced4bc1f77dbc09a5bd8d4e9b58407ca5cfa20 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 12 Jan 2023 14:40:54 +0530 Subject: [PATCH 085/521] functional test fix for invalid email --- .../src/test/functional/tests/membership/create_group_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/test/functional/tests/membership/create_group_test.py b/modules/api/src/test/functional/tests/membership/create_group_test.py index 2bb89365a..f965742c4 100644 --- a/modules/api/src/test/functional/tests/membership/create_group_test.py +++ b/modules/api/src/test/functional/tests/membership/create_group_test.py @@ -127,8 +127,8 @@ def test_create_group_with_invalid_email(shared_zone_test_context): "members": [{"id": "ok"}], "admins": [{"id": "ok"}] } - errors = client.create_group(new_group, status=400)["errors"] - assert_that(errors, is_("Please enter a valid Email ID.Valid domains are test.com")) + error = client.create_group(new_group, status=400) + assert_that(error, is_("Please enter a valid Email ID.Valid domains should end with test.com,comcast.com")) def test_create_group_without_members_or_admins(shared_zone_test_context): """ From ba15d7d35907534f634675108f5039289c8a77d0 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 18 Jan 2023 10:24:14 +0530 Subject: [PATCH 086/521] Removing comcast.com from config file --- modules/api/src/main/resources/application.conf | 2 +- .../src/test/functional/tests/membership/create_group_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index d4401ba49..76cd631d0 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -132,7 +132,7 @@ vinyldns { } } validEmailConfig{ - email-domains = ["test.com","*comcast.com"] + email-domains = ["test.com","*dummy.com"] } sns { diff --git a/modules/api/src/test/functional/tests/membership/create_group_test.py b/modules/api/src/test/functional/tests/membership/create_group_test.py index f965742c4..a69f0adc9 100644 --- a/modules/api/src/test/functional/tests/membership/create_group_test.py +++ b/modules/api/src/test/functional/tests/membership/create_group_test.py @@ -128,7 +128,7 @@ def test_create_group_with_invalid_email(shared_zone_test_context): "admins": [{"id": "ok"}] } error = client.create_group(new_group, status=400) - assert_that(error, is_("Please enter a valid Email ID.Valid domains should end with test.com,comcast.com")) + assert_that(error, is_("Please enter a valid Email ID.Valid domains should end with test.com,dummy.com")) def test_create_group_without_members_or_admins(shared_zone_test_context): """ From c18b6c59d44fa9a8c1172fb8e40f983293521266 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 18 Jan 2023 10:53:58 +0530 Subject: [PATCH 087/521] reference.conf changes --- modules/api/src/main/resources/reference.conf | 2 +- .../vinyldns/api/domain/membership/MembershipService.scala | 1 - modules/api/src/test/resources/application.conf | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index c6f103de0..c97f7064d 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -170,7 +170,7 @@ vinyldns { } } validEmailConfig{ - email-domains = ["test.com","*comcast.com"] + email-domains = ["test.com","*dummy.com"] } sns { class-name = "vinyldns.api.notifier.sns.SnsNotifierProvider" diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala index 4bc103cbe..9674df8b0 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala @@ -371,7 +371,6 @@ class MembershipService( def EmailValidation(email: String): Result[Unit] = { val emailDomains = validDomains.valid_domains - println("valid domains",emailDomains); val emailRegex = if(emailDomains.mkString(",").contains("*")){ ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString(",").replaceAllLiterally("*","[A-Za-z0-9.]*").replaceAllLiterally(",","|") + "$").r } else { diff --git a/modules/api/src/test/resources/application.conf b/modules/api/src/test/resources/application.conf index 0ee16a9e2..86dab8c42 100644 --- a/modules/api/src/test/resources/application.conf +++ b/modules/api/src/test/resources/application.conf @@ -128,7 +128,7 @@ vinyldns { } } validEmailConfig{ - email-domains = ["test.com","*comcast.com"] + email-domains = ["test.com","*dummy.com"] } sns { class-name = "vinyldns.apadi.notifier.sns.SnsNotifierProvider" From a40ec652f4be863d534a46262747c22542efa00c Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 18 Jan 2023 14:02:01 +0530 Subject: [PATCH 088/521] code changes when domain is empty --- .../api/domain/membership/MembershipService.scala | 13 +++++++++---- .../domain/membership/MembershipServiceSpec.scala | 5 +++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala index 9674df8b0..47e7851b6 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala @@ -371,10 +371,15 @@ class MembershipService( def EmailValidation(email: String): Result[Unit] = { val emailDomains = validDomains.valid_domains - val emailRegex = if(emailDomains.mkString(",").contains("*")){ - ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString(",").replaceAllLiterally("*","[A-Za-z0-9.]*").replaceAllLiterally(",","|") + "$").r - } else { - ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString("|") + "$").r + val emailRegex = if(emailDomains.isEmpty){ + """^[a-zA-Z0-9\.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r + } + else { + if (emailDomains.mkString(",").contains("*")) { + ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString(",").replaceAllLiterally("*", "[A-Za-z0-9.]*").replaceAllLiterally(",", "|") + "$").r + } else { + ("^[A-Za-z0-9._%+-]+@" + emailDomains.mkString("|") + "$").r + } } Option(email) match { case Some(value) if (emailRegex.findFirstIn(value) == None) => diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index 3bbce7fa7..d83d30104 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -290,11 +290,12 @@ class MembershipServiceSpec doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUser("ok") doReturn(result(EmailValidationError("fail"))) .when(underTest) - .EmailValidation(email = "test@test.com") + .EmailValidation(email = "test@ok.com") - val error = underTest.createGroup(groupInfo.copy(email = "test@test.com"), okAuth).value.unsafeRunSync().swap.toOption.get + val error = underTest.createGroup(groupInfo.copy(email = "test@ok.com"), okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[EmailValidationError] + println(error) } } From f4d1f5ff82c1da4c0e9c8115e01f85283a8d505e Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 19 Jan 2023 11:38:15 +0530 Subject: [PATCH 089/521] committing the config md file --- modules/docs/src/main/mdoc/operator/config-api.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index fa8b69ba3..4e40ac339 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -475,6 +475,16 @@ sns { } } ``` +### Email Domain Configuration + This configuration setting determines the valid domains which are + allowed in the email fields.`*dummy.com` means it will allow any + subdomain within dummy.com like apac.dummy.com. If email-domains is + left empty then it will accept any domain name. +```yaml +validEmailConfig { + email-domains = ["test.com","*dummy.com"] +} + ``` ### Batch Manual Review Enabled From 9dfa2b0627fdc92aefbc10767b7a32b2e51946ec Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 19 Jan 2023 12:02:08 +0530 Subject: [PATCH 090/521] unit test modifications --- .../api/domain/membership/MembershipServiceSpec.scala | 8 -------- 1 file changed, 8 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index d83d30104..18dfe1c60 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -287,16 +287,8 @@ class MembershipServiceSpec } "return an error if an invalid email is entered" in { - doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUser("ok") - doReturn(result(EmailValidationError("fail"))) - .when(underTest) - .EmailValidation(email = "test@ok.com") - - val error = underTest.createGroup(groupInfo.copy(email = "test@ok.com"), okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[EmailValidationError] - println(error) - } } From 45f389c1027b841477deb099734fee04a81e6b7e Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Fri, 20 Jan 2023 13:15:09 +0530 Subject: [PATCH 091/521] *dummy.com related test --- .../membership/MembershipServiceSpec.scala | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index 18dfe1c60..6bf693838 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -49,7 +49,7 @@ class MembershipServiceSpec private val mockZoneRepo = mock[ZoneRepository] private val mockGroupChangeRepo = mock[GroupChangeRepository] private val mockRecordSetRepo = mock[RecordSetRepository] - private val mockValidEmailConfig = ValidEmailConfig(valid_domains = List("test.com")) + private val mockValidEmailConfig = ValidEmailConfig(valid_domains = List("test.com","*dummy.com")) private val backingService = new MembershipService( mockGroupRepo, @@ -291,7 +291,33 @@ class MembershipServiceSpec error shouldBe a[EmailValidationError] } } + "Create Group when email has domain *dummy.com" in { + doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUser("ok") + doReturn(().toResult).when(underTest).groupValidation(groupInfo) + doReturn(().toResult).when(underTest).groupWithSameNameDoesNotExist(groupInfo.name) + doReturn(().toResult).when(underTest).usersExist(groupInfo.memberIds) + doReturn(IO.pure(okGroup)).when(mockGroupRepo).save(any[DB], any[Group]) + doReturn(IO.pure(Set(okUser.id))) + .when(mockMembershipRepo) + .saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean) + doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange]) + val result = underTest.createGroup(groupInfo.copy(email = "test@ok.dummy.com"), okAuth).value.unsafeRunSync().toOption.get + result shouldBe groupInfo.copy(email = "test@ok.dummy.com") + + val groupCaptor = ArgumentCaptor.forClass(classOf[Group]) + + verify(mockMembershipRepo, times(2)) + .saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean) + verify(mockGroupRepo).save(any[DB], groupCaptor.capture()) + + val savedGroup = groupCaptor.getValue + (savedGroup.memberIds should contain).only(okUser.id) + (savedGroup.adminUserIds should contain).only(okUser.id) + savedGroup.name shouldBe groupInfo.name + savedGroup.email shouldBe groupInfo.copy(email = "test@ok.dummy.com").email + savedGroup.description shouldBe groupInfo.description + } "update an existing group" should { "save the update and add new members and remove deleted members" in { doReturn(IO.pure(Some(existingGroup))).when(mockGroupRepo).getGroup(any[String]) From 6fa28811512eb329304db786011f2b3b7ea5a204 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Fri, 20 Jan 2023 14:08:24 +0530 Subject: [PATCH 092/521] test.com related test --- .../membership/MembershipServiceSpec.scala | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index 6bf693838..963fc4944 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -317,6 +317,33 @@ class MembershipServiceSpec savedGroup.name shouldBe groupInfo.name savedGroup.email shouldBe groupInfo.copy(email = "test@ok.dummy.com").email savedGroup.description shouldBe groupInfo.description + } + "Create Group when email has domain test.com" in { + doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUser("ok") + doReturn(().toResult).when(underTest).groupValidation(groupInfo) + doReturn(().toResult).when(underTest).groupWithSameNameDoesNotExist(groupInfo.name) + doReturn(().toResult).when(underTest).usersExist(groupInfo.memberIds) + doReturn(IO.pure(okGroup)).when(mockGroupRepo).save(any[DB], any[Group]) + doReturn(IO.pure(Set(okUser.id))) + .when(mockMembershipRepo) + .saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean) + doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange]) + + val result = underTest.createGroup(groupInfo, okAuth).value.unsafeRunSync().toOption.get + result shouldBe groupInfo + + val groupCaptor = ArgumentCaptor.forClass(classOf[Group]) + + verify(mockMembershipRepo, times(2)) + .saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean) + verify(mockGroupRepo).save(any[DB], groupCaptor.capture()) + + val savedGroup = groupCaptor.getValue + (savedGroup.memberIds should contain).only(okUser.id) + (savedGroup.adminUserIds should contain).only(okUser.id) + savedGroup.name shouldBe groupInfo.name + savedGroup.email shouldBe groupInfo.email + savedGroup.description shouldBe groupInfo.description } "update an existing group" should { "save the update and add new members and remove deleted members" in { From e0ee86f35daefac46e5d7436bf4cb86462ae7d38 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 3 Feb 2023 14:42:28 +0530 Subject: [PATCH 093/521] fix batch delete bug --- .../domain/batch/BatchChangeValidations.scala | 56 +++++++++++++++++-- .../domain/batch/BatchTransformations.scala | 13 +++-- .../batch/BatchChangeValidationsSpec.scala | 26 +++++---- .../core/domain/DomainValidationErrors.scala | 6 ++ .../core/domain/SingleChangeError.scala | 3 +- 5 files changed, 81 insertions(+), 23 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index d9e0bce97..c8cc38855 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -214,9 +214,9 @@ class BatchChangeValidations( } def validateDeleteRRSetChangeInput( - deleteRRSetChangeInput: DeleteRRSetChangeInput, - isApproved: Boolean - ): SingleValidation[Unit] = { + deleteRRSetChangeInput: DeleteRRSetChangeInput, + isApproved: Boolean + ): SingleValidation[Unit] = { val validRecord = deleteRRSetChangeInput.record match { case Some(recordData) => validateRecordData(recordData, deleteRRSetChangeInput) case None => ().validNel @@ -344,13 +344,25 @@ class BatchChangeValidations( isApproved: Boolean ): SingleValidation[ChangeForValidation] = { + // To handle add and delete for the record with same record data is present in the batch + val recordData = change match { + case AddChangeForValidation(_, _, inputChange, _, _) => inputChange.record.toString + case DeleteRRSetChangeForValidation(_, _, inputChange) => if(inputChange.record.isDefined) inputChange.record.get.toString else "" + } + + val addInBatch = groupedChanges.getProposedAdds(change.recordKey) + val isSameRecordUpdateInBatch = if(recordData.nonEmpty){ + if(addInBatch.contains(RecordData.fromString(recordData, change.inputChange.typ).get)) true else false + } else false + val validations = groupedChanges.getExistingRecordSet(change.recordKey) match { case Some(rs) => userCanDeleteRecordSet(change, auth, rs.ownerGroupId, rs.records) |+| zoneDoesNotRequireManualReview(change, isApproved) |+| ensureRecordExists(change, groupedChanges) - case None => RecordDoesNotExist(change.inputChange.inputName).validNel + case None => + if(isSameRecordUpdateInBatch) InvalidUpdateRequest(change.inputChange.inputName).invalidNel else ().validNel } validations.map(_ => change) } @@ -381,7 +393,7 @@ class BatchChangeValidations( ) |+| zoneDoesNotRequireManualReview(change, isApproved) case None => - RecordDoesNotExist(change.inputChange.inputName).invalidNel + InvalidUpdateRequest(change.inputChange.inputName).invalidNel } } @@ -396,6 +408,18 @@ class BatchChangeValidations( auth: AuthPrincipal, isApproved: Boolean ): SingleValidation[ChangeForValidation] = { + + // To handle add and delete for the record with same record data is present in the batch + val recordData = change match { + case AddChangeForValidation(_, _, inputChange, _, _) => inputChange.record.toString + case DeleteRRSetChangeForValidation(_, _, inputChange) => if(inputChange.record.isDefined) inputChange.record.get.toString else "" + } + + val addInBatch = groupedChanges.getProposedAdds(change.recordKey) + val isSameRecordUpdateInBatch = if(recordData.nonEmpty){ + if(addInBatch.contains(RecordData.fromString(recordData, change.inputChange.typ).get)) true else false + } else false + val validations = groupedChanges.getExistingRecordSet(change.recordKey) match { case Some(rs) => @@ -404,7 +428,7 @@ class BatchChangeValidations( zoneDoesNotRequireManualReview(change, isApproved) |+| ensureRecordExists(change, groupedChanges) case None => - RecordDoesNotExist(change.inputChange.inputName).validNel + if(isSameRecordUpdateInBatch) InvalidUpdateRequest(change.inputChange.inputName).invalidNel else ().validNel } validations.map(_ => change) @@ -429,8 +453,28 @@ class BatchChangeValidations( InvalidBatchRecordType(other.toString, SupportedBatchChangeRecordTypes.get).invalidNel } + // To handle add and delete for the record with same record data is present in the batch + val recordData = change match { + case AddChangeForValidation(_, _, inputChange, _, _) => inputChange.record.toString + } + + val deletes = groupedChanges.getProposedDeletes(change.recordKey) + val isSameRecordUpdateInBatch = if(recordData.nonEmpty){ + if(deletes.contains(RecordData.fromString(recordData, change.inputChange.typ).get)) true else false + } else false + + val commonValidations: SingleValidation[Unit] = { + groupedChanges.getExistingRecordSet(change.recordKey) match { + case Some(_) => + ().validNel + case None => + if(isSameRecordUpdateInBatch) InvalidUpdateRequest(change.inputChange.inputName).invalidNel else ().validNel + } + } + val validations = typedValidations |+| + commonValidations |+| noIncompatibleRecordExists(change, groupedChanges) |+| userCanAddRecordSet(change, auth) |+| recordDoesNotExist( diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchTransformations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchTransformations.scala index 5afa5da02..36876b79a 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchTransformations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchTransformations.scala @@ -180,6 +180,9 @@ object BatchTransformations { def getProposedAdds(recordKey: RecordKey): Set[RecordData] = innerMap.get(recordKey).map(_.proposedAdds).toSet.flatten + def getProposedDeletes(recordKey: RecordKey): Set[RecordData] = + innerMap.get(recordKey).map(_.proposedDeletes).toSet.flatten + // The new, net record data factoring in existing records, deletes and adds // If record is not edited in batch, will fallback to look up record in existing // records @@ -237,10 +240,11 @@ object BatchTransformations { // New proposed record data (assuming all validations pass) val proposedRecordData = existingRecords -- deleteChangeSet ++ addChangeRecordDataSet - // Note: "Update" where an Add and DeleteRecordSet is provided for a DNS record that does not exist will be - // treated as a logical Add since the delete validation will fail (on record does not exist) + // Note: "Add" where an Add and DeleteRecordSet is provided for a DNS record that does not exist. + // Adds the record if it doesn't exist and ignores the delete. val logicalChangeType = (addChangeRecordDataSet.nonEmpty, deleteChangeSet.nonEmpty) match { - case (true, true) => LogicalChangeType.Update + case (true, true) => + if((deleteChangeSet -- existingRecords).nonEmpty) LogicalChangeType.Add else LogicalChangeType.Update case (true, false) => LogicalChangeType.Add case (false, true) => if ((existingRecords -- deleteChangeSet).isEmpty) { @@ -251,12 +255,13 @@ object BatchTransformations { case (false, false) => LogicalChangeType.NotEditedInBatch } - new ValidationChanges(addChangeRecordDataSet, proposedRecordData, logicalChangeType) + new ValidationChanges(addChangeRecordDataSet, deleteChangeSet, proposedRecordData, logicalChangeType) } } final case class ValidationChanges( proposedAdds: Set[RecordData], + proposedDeletes: Set[RecordData], proposedRecordData: Set[RecordData], logicalChangeType: LogicalChangeType ) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index 85960b107..5fb44b35d 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -526,7 +526,7 @@ class BatchChangeValidationsSpec } property("""validateInputName: should fail with a HighValueDomainError - |if inputName is a High Value Domain""".stripMargin) { + |if inputName is a High Value Domain""".stripMargin) { val changeA = AddChangeInput("high-value-domain.foo.", RecordType.A, ttl, AData("1.1.1.1")) val changeIpV4 = AddChangeInput("192.0.2.252", RecordType.PTR, ttl, PTRData(Fqdn("test."))) val changeIpV6 = @@ -571,7 +571,7 @@ class BatchChangeValidationsSpec } property("""zoneDoesNotRequireManualReview: should fail with RecordRequiresManualReview - |if zone name matches domain requiring manual review""".stripMargin) { + |if zone name matches domain requiring manual review""".stripMargin) { val addChangeInput = AddChangeInput("not-allowed.zone.NEEDS.review", RecordType.A, ttl, AData("1.1.1.1")) val addChangeForValidation = AddChangeForValidation( @@ -599,14 +599,14 @@ class BatchChangeValidationsSpec } property("""validateInputName: should fail with a DomainValidationError for deletes - |if validateHostName fails for an invalid domain name""".stripMargin) { + |if validateHostName fails for an invalid domain name""".stripMargin) { val change = DeleteRRSetChangeInput("invalidDomainName$", RecordType.A) val result = validateInputName(change, false) result should haveInvalid[DomainValidationError](InvalidDomainName("invalidDomainName$.")) } property("""validateInputName: should fail with a DomainValidationError for deletes - |if validateHostName fails for an invalid domain name length""".stripMargin) { + |if validateHostName fails for an invalid domain name length""".stripMargin) { val invalidDomainName = Random.alphanumeric.take(256).mkString val change = DeleteRRSetChangeInput(invalidDomainName, RecordType.AAAA) val result = validateInputName(change, false) @@ -615,7 +615,7 @@ class BatchChangeValidationsSpec } property("""validateInputName: PTR should fail with InvalidIPAddress for deletes - |if inputName is not a valid ipv4 or ipv6 address""".stripMargin) { + |if inputName is not a valid ipv4 or ipv6 address""".stripMargin) { val invalidIp = "invalidIp.111" val change = DeleteRRSetChangeInput(invalidIp, RecordType.PTR) val result = validateInputName(change, false) @@ -639,14 +639,14 @@ class BatchChangeValidationsSpec } property("""validateAddChangeInput: should fail with a DomainValidationError - |if validateHostName fails for an invalid domain name""".stripMargin) { + |if validateHostName fails for an invalid domain name""".stripMargin) { val change = AddChangeInput("invalidDomainName$", RecordType.A, ttl, AData("1.1.1.1")) val result = validateAddChangeInput(change, false) result should haveInvalid[DomainValidationError](InvalidDomainName("invalidDomainName$.")) } property("""validateAddChangeInput: should fail with a DomainValidationError - |if validateHostName fails for an invalid domain name length""".stripMargin) { + |if validateHostName fails for an invalid domain name length""".stripMargin) { val invalidDomainName = Random.alphanumeric.take(256).mkString val change = AddChangeInput(invalidDomainName, RecordType.A, ttl, AData("1.1.1.1")) val result = validateAddChangeInput(change, false) @@ -676,7 +676,7 @@ class BatchChangeValidationsSpec } property("""validateAddChangeInput: should fail with InvalidIpv6Address - |if validateRecordData fails for an invalid ipv6 address""".stripMargin) { + |if validateRecordData fails for an invalid ipv6 address""".stripMargin) { val invalidIpv6 = "invalidIpv6:123" val change = AddChangeInput("test.comcast.com.", RecordType.AAAA, ttl, AAAAData(invalidIpv6)) val result = validateAddChangeInput(change, false) @@ -703,7 +703,7 @@ class BatchChangeValidationsSpec } property("""validateAddChangeInput: should fail with InvalidDomainName - |if validateRecordData fails for invalid CNAME record data""".stripMargin) { + |if validateRecordData fails for invalid CNAME record data""".stripMargin) { val invalidCNAMERecordData = "$$$" val change = AddChangeInput( @@ -718,7 +718,7 @@ class BatchChangeValidationsSpec } property("""validateAddChangeInput: should fail with InvalidLength - |if validateRecordData fails for invalid CNAME record data""".stripMargin) { + |if validateRecordData fails for invalid CNAME record data""".stripMargin) { val invalidCNAMERecordData = "s" * 256 val change = AddChangeInput( @@ -735,7 +735,7 @@ class BatchChangeValidationsSpec } property("""validateAddChangeInput: PTR should fail with InvalidIPAddress - |if inputName is not a valid ipv4 or ipv6 address""".stripMargin) { + |if inputName is not a valid ipv4 or ipv6 address""".stripMargin) { val invalidIp = "invalidip.111." val change = AddChangeInput(invalidIp, RecordType.PTR, ttl, PTRData(Fqdn("test.comcast.com"))) val result = validateAddChangeInput(change, false) @@ -1030,7 +1030,9 @@ class BatchChangeValidationsSpec result(0) shouldBe valid result(1) shouldBe valid result(3) shouldBe valid - result(4) shouldBe valid + result(4) should haveInvalid[DomainValidationError]( + RecordAlreadyExists(makeAddUpdateRecord("ok").inputChange.inputName, makeAddUpdateRecord("ok").inputChange.record, false) + ) deleteNonExistentEntry.inputChange.record.foreach { record => result(5) should haveInvalid[DomainValidationError]( DeleteRecordDataDoesNotExist(deleteNonExistentEntry.inputChange.inputName, record) diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index 1fe3ab85a..a6e48b179 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -137,6 +137,12 @@ final case class RecordDoesNotExist(name: String) extends DomainValidationError s"""Record "$name" Does Not Exist: cannot delete a record that does not exist.""" } +final case class InvalidUpdateRequest(name: String) extends DomainValidationError { + def message: String = + s"""Cannot perform request for the record "$name". """ + + "Add and Delete for the record with same record data exists in the batch." +} + final case class CnameIsNotUniqueError(name: String, typ: RecordType) extends DomainValidationError { def message: String = diff --git a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala index c7805e74c..774be6a8b 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala @@ -32,7 +32,7 @@ object DomainValidationErrorType extends Enumeration { val ChangeLimitExceeded, BatchChangeIsEmpty, GroupDoesNotExist, NotAMemberOfOwnerGroup, InvalidDomainName, InvalidCname, InvalidLength, InvalidEmail, InvalidRecordType, InvalidPortNumber, InvalidIpv4Address, InvalidIpv6Address, InvalidIPAddress, InvalidTTL, InvalidMxPreference, - InvalidBatchRecordType, ZoneDiscoveryError, RecordAlreadyExists, RecordDoesNotExist, + InvalidBatchRecordType, ZoneDiscoveryError, RecordAlreadyExists, RecordDoesNotExist, InvalidUpdateRequest, CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch, RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError, NewMultiRecordError, CnameAtZoneApexError, RecordRequiresManualReview, UnsupportedOperation, @@ -60,6 +60,7 @@ object DomainValidationErrorType extends Enumeration { case _: ZoneDiscoveryError => ZoneDiscoveryError case _: RecordAlreadyExists => RecordAlreadyExists case _: RecordDoesNotExist => RecordDoesNotExist + case _: InvalidUpdateRequest => InvalidUpdateRequest case _: CnameIsNotUniqueError => CnameIsNotUniqueError case _: UserIsNotAuthorized => UserIsNotAuthorized case _: UserIsNotAuthorizedError => UserIsNotAuthorizedError From 7640b30757a4517870666b3e042ea3c29a159a70 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 3 Feb 2023 15:15:38 +0530 Subject: [PATCH 094/521] add test --- .../domain/batch/BatchChangeValidations.scala | 6 ++--- .../batch/BatchChangeValidationsSpec.scala | 25 ++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index c8cc38855..2f26d2f60 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -214,9 +214,9 @@ class BatchChangeValidations( } def validateDeleteRRSetChangeInput( - deleteRRSetChangeInput: DeleteRRSetChangeInput, - isApproved: Boolean - ): SingleValidation[Unit] = { + deleteRRSetChangeInput: DeleteRRSetChangeInput, + isApproved: Boolean + ): SingleValidation[Unit] = { val validRecord = deleteRRSetChangeInput.record match { case Some(recordData) => validateRecordData(recordData, deleteRRSetChangeInput) case None => ().validNel diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index 5fb44b35d..3a253684f 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -1006,7 +1006,30 @@ class BatchChangeValidationsSpec ) } - property("validateChangesWithContext: should complete for update if record does not exist") { + property("validateChangesWithContext: should fail for update if same record data is provided for add and delete") { + val deleteRecord = makeDeleteUpdateDeleteRRSet("deleteRecord", Some(AData("1.2.3.4"))) + val result = validateChangesWithContext( + ChangeForValidationMap( + List( + makeAddUpdateRecord("deleteRecord"), // Record does not exist + deleteRecord + ).map(_.validNel), + ExistingRecordSets(List(rsOk)) + ), + okAuth, + false, + None + ) + + result(0) should haveInvalid[DomainValidationError]( + InvalidUpdateRequest(makeAddUpdateRecord("deleteRecord").inputChange.inputName) + ) + result(1) should haveInvalid[DomainValidationError]( + InvalidUpdateRequest(deleteRecord.inputChange.inputName) + ) + } + + property("validateChangesWithContext: should fail for update if record exist") { val deleteRRSet = makeDeleteUpdateDeleteRRSet("deleteRRSet") val deleteRecord = makeDeleteUpdateDeleteRRSet("deleteRecord", Some(AData("1.1.1.1"))) val deleteNonExistentEntry = makeDeleteUpdateDeleteRRSet("ok", Some(AData("1.1.1.1"))) From 6feacc7f32c207f0c86c366e1bf2115ccec7778e Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 3 Feb 2023 18:58:10 +0530 Subject: [PATCH 095/521] code cleanup --- .../domain/batch/BatchChangeValidations.scala | 34 +++++-------------- .../batch/BatchChangeValidationsSpec.scala | 12 ++----- 2 files changed, 11 insertions(+), 35 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index 2f26d2f60..8c4b31947 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -318,25 +318,6 @@ class BatchChangeValidations( } } - def ensureRecordExists( - change: ChangeForValidation, - groupedChanges: ChangeForValidationMap - ): SingleValidation[Unit] = - change match { - // For DeleteRecord inputs, need to verify that the record data actually exists - case DeleteRRSetChangeForValidation( - _, - _, - DeleteRRSetChangeInput(inputName, _, Some(recordData)) - ) - if !groupedChanges - .getExistingRecordSet(change.recordKey) - .exists(rs => matchRecordData(rs.records, recordData)) => - DeleteRecordDataDoesNotExist(inputName, recordData).invalidNel - case _ => - ().validNel - } - def validateDeleteWithContext( change: ChangeForValidation, groupedChanges: ChangeForValidationMap, @@ -359,8 +340,7 @@ class BatchChangeValidations( groupedChanges.getExistingRecordSet(change.recordKey) match { case Some(rs) => userCanDeleteRecordSet(change, auth, rs.ownerGroupId, rs.records) |+| - zoneDoesNotRequireManualReview(change, isApproved) |+| - ensureRecordExists(change, groupedChanges) + zoneDoesNotRequireManualReview(change, isApproved) case None => if(isSameRecordUpdateInBatch) InvalidUpdateRequest(change.inputChange.inputName).invalidNel else ().validNel } @@ -425,8 +405,7 @@ class BatchChangeValidations( case Some(rs) => val adds = groupedChanges.getProposedAdds(change.recordKey).toList userCanUpdateRecordSet(change, auth, rs.ownerGroupId, adds) |+| - zoneDoesNotRequireManualReview(change, isApproved) |+| - ensureRecordExists(change, groupedChanges) + zoneDoesNotRequireManualReview(change, isApproved) case None => if(isSameRecordUpdateInBatch) InvalidUpdateRequest(change.inputChange.inputName).invalidNel else ().validNel } @@ -459,6 +438,7 @@ class BatchChangeValidations( } val deletes = groupedChanges.getProposedDeletes(change.recordKey) + val isDeleteExists = deletes.nonEmpty val isSameRecordUpdateInBatch = if(recordData.nonEmpty){ if(deletes.contains(RecordData.fromString(recordData, change.inputChange.typ).get)) true else false } else false @@ -484,7 +464,8 @@ class BatchChangeValidations( change.inputChange.typ, change.inputChange.record, groupedChanges, - isApproved + isApproved, + isDeleteExists ) |+| ownerGroupProvidedIfNeeded(change, None, ownerGroupId) |+| zoneDoesNotRequireManualReview(change, isApproved) @@ -527,13 +508,14 @@ class BatchChangeValidations( typ: RecordType, recordData: RecordData, groupedChanges: ChangeForValidationMap, - isApproved: Boolean + isApproved: Boolean, + isDeleteExist: Boolean ): SingleValidation[Unit] = { val record = groupedChanges.getExistingRecordSetData(RecordKeyData(zoneId, recordName, typ, recordData)) if(record.isDefined) { record.get.records.contains(recordData) match { case true => ().validNel - case false => RecordAlreadyExists(inputName, recordData, isApproved).invalidNel} + case false => if(isDeleteExist) ().validNel else RecordAlreadyExists(inputName, recordData, isApproved).invalidNel} } else ().validNel } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index 3a253684f..29b21d62b 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -1029,7 +1029,7 @@ class BatchChangeValidationsSpec ) } - property("validateChangesWithContext: should fail for update if record exist") { + property("validateChangesWithContext: should complete for update if record does not exist") { val deleteRRSet = makeDeleteUpdateDeleteRRSet("deleteRRSet") val deleteRecord = makeDeleteUpdateDeleteRRSet("deleteRecord", Some(AData("1.1.1.1"))) val deleteNonExistentEntry = makeDeleteUpdateDeleteRRSet("ok", Some(AData("1.1.1.1"))) @@ -1053,14 +1053,8 @@ class BatchChangeValidationsSpec result(0) shouldBe valid result(1) shouldBe valid result(3) shouldBe valid - result(4) should haveInvalid[DomainValidationError]( - RecordAlreadyExists(makeAddUpdateRecord("ok").inputChange.inputName, makeAddUpdateRecord("ok").inputChange.record, false) - ) - deleteNonExistentEntry.inputChange.record.foreach { record => - result(5) should haveInvalid[DomainValidationError]( - DeleteRecordDataDoesNotExist(deleteNonExistentEntry.inputChange.inputName, record) - ) - } + result(4) shouldBe valid + result(5) shouldBe valid } property( From 82dc9b028ae2b24db8c729e0a4ef1034530745d8 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 15 Feb 2023 11:15:16 +0530 Subject: [PATCH 096/521] address review comment --- .../functional/tests/recordsets/list_recordset_changes_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py index bbddda69f..19422afae 100644 --- a/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py +++ b/modules/api/src/test/functional/tests/recordsets/list_recordset_changes_test.py @@ -143,7 +143,7 @@ def test_list_recordset_changes_exhausted(shared_zone_test_context): def test_list_recordset_returning_no_changes(shared_zone_test_context): """ - Pass in startFrom of "2000" should return empty list because start key exceeded no.of.recordset changes + Pass in startFrom of "2000" should return empty list because start key exceeded number of recordset changes """ client = shared_zone_test_context.history_client original_zone = shared_zone_test_context.history_zone From 67f4e43bd2717b3078059ad1d7a2deeadbb8e632 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 15 Feb 2023 14:41:40 +0530 Subject: [PATCH 097/521] add autocomplete for search by group --- .../portal/app/views/zones/zones.scala.html | 4 +-- .../lib/controllers/controller.zones.js | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/modules/portal/app/views/zones/zones.scala.html b/modules/portal/app/views/zones/zones.scala.html index 356378c39..c41d71366 100644 --- a/modules/portal/app/views/zones/zones.scala.html +++ b/modules/portal/app/views/zones/zones.scala.html @@ -63,7 +63,7 @@
    @@ -178,7 +178,7 @@
    diff --git a/modules/portal/public/lib/controllers/controller.zones.js b/modules/portal/public/lib/controllers/controller.zones.js index 91e219dfd..75bf4aa04 100644 --- a/modules/portal/public/lib/controllers/controller.zones.js +++ b/modules/portal/public/lib/controllers/controller.zones.js @@ -127,6 +127,39 @@ angular.module('controller.zones', []) .appendTo(ul); }; + $('.isGroupSearch').change(function() { + if(this.checked) { + // Autocomplete for search by admin group + $(".zone-search-text").autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "/api/groups?maxItems=100&abridged=true", + dataType: "json", + data: {groupNameFilter: request.term, ignoreAccess: $scope.ignoreAccess}, + success: function(data) { + const search = JSON.parse(JSON.stringify(data)); + response($.map(search.groups, function(group) { + return {value: group.name, label: group.name} + })) + } + }); + }, + minLength: 1, + select: function (event, ui) { + $scope.query = ui.item.value; + $(".zone-search-text").val(ui.item.value); + return false; + }, + open: function() { + $(this).removeClass("ui-corner-all").addClass("ui-corner-top"); + }, + close: function() { + $(this).removeClass("ui-corner-top").addClass("ui-corner-all"); + } + }); + } + }); + /* Refreshes zone data set and then re-displays */ $scope.refreshZones = function () { zonesPaging = pagingService.resetPaging(zonesPaging); From b6c91a631210af8df223bf8aff613419b7936538 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 17 Feb 2023 12:47:01 +0530 Subject: [PATCH 098/521] address css changes requested --- modules/portal/public/css/vinyldns.css | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/modules/portal/public/css/vinyldns.css b/modules/portal/public/css/vinyldns.css index f18b28d37..468f8bfe8 100644 --- a/modules/portal/public/css/vinyldns.css +++ b/modules/portal/public/css/vinyldns.css @@ -522,3 +522,22 @@ input[type="file"] { top: 0; z-index: 1000; } + +.cron-select-wrap .cron-select { + border-radius: 0px !important; +} + +.select-options { + vertical-align: bottom; +} + +/** For safari browser, since the settings are different from other browsers */ +@media not all and (min-resolution: 0.001dpcm) { +.select-options { + vertical-align: sub; +} +} + +.select-options span { + vertical-align: super; +} From af827565778c560c738981d44f1857165eb3a320 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 20 Feb 2023 08:37:36 +0530 Subject: [PATCH 099/521] address css changes requested --- modules/portal/public/css/vinyldns.css | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/modules/portal/public/css/vinyldns.css b/modules/portal/public/css/vinyldns.css index 468f8bfe8..0b25c0e66 100644 --- a/modules/portal/public/css/vinyldns.css +++ b/modules/portal/public/css/vinyldns.css @@ -523,6 +523,7 @@ input[type="file"] { z-index: 1000; } +/* Starting of css override for cron library used in zone sync scheduling */ .cron-select-wrap .cron-select { border-radius: 0px !important; } @@ -531,13 +532,25 @@ input[type="file"] { vertical-align: bottom; } -/** For safari browser, since the settings are different from other browsers */ +.select-options span { + vertical-align: super; + position: relative; + bottom: 0.5rem; +} + +.cron-wrap > span + .cron-select-wrap > .cron-select { + margin-bottom: 0.2rem; +} + +/* For safari browser, since the settings are different from other browsers */ @media not all and (min-resolution: 0.001dpcm) { .select-options { vertical-align: sub; } -} .select-options span { - vertical-align: super; + position: initial; + bottom: 0px; } +} +/* Ending of css override for cron library used in zone sync scheduling */ From 4aab2e8e17666f3e14828e54e960f3fcf6d28f2c Mon Sep 17 00:00:00 2001 From: nspadaccino Date: Mon, 20 Feb 2023 17:05:07 -0500 Subject: [PATCH 100/521] Change Datetime instance to Instant --- .../src/main/scala/vinyldns/api/repository/TestDataLoader.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala b/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala index b562edac7..5e0f04d80 100644 --- a/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala +++ b/modules/api/src/main/scala/vinyldns/api/repository/TestDataLoader.scala @@ -193,7 +193,7 @@ object TestDataLoader extends TransactionProvider { final val superUser = User( userName = "super-user", id = "super-user-id", - created = DateTime.now.secondOfDay().roundFloorCopy(), + created = Instant.now.truncatedTo(ChronoUnit.SECONDS), accessKey = "superUserAccessKey", secretKey = "superUserSecretKey", firstName = Some("super-user"), From e4a1357d7edd90b7a2bb235ce2bc819b898ec7cb Mon Sep 17 00:00:00 2001 From: nspadaccino Date: Mon, 20 Feb 2023 17:19:08 -0500 Subject: [PATCH 101/521] Replace old leftResultOf and rightResultOf calls with IO methods --- .../vinyldns/api/domain/record/RecordSetServiceSpec.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index a2d9c4c45..27356e7bf 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -1257,9 +1257,7 @@ class RecordSetServiceSpec .when(mockGroupRepo) .getGroup(okGroup.id) - val result = rightResultOf( - underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value - ) + val result = underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get result.recordSet.ownerGroupId shouldBe Some(okGroup.id) } @@ -1288,7 +1286,7 @@ class RecordSetServiceSpec .when(mockGroupRepo) .getGroup(oneUserDummyGroup.id) - val result = leftResultOf(underTest.updateRecordSet(newRecord, auth).value) + val result = underTest.updateRecordSet(newRecord, auth).value.unsafeRunSync().swap.toOption.get result shouldBe an[NotAuthorizedError] } "succeed if user is in owner group and zone is shared and new owner group is none" in { From 8309ed187a2b2e716c1d38269450680a4b3f318e Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Feb 2023 15:20:32 +0530 Subject: [PATCH 102/521] update message for additional info column --- .../domain/batch/BatchChangeConverter.scala | 40 ++++++++++++++++--- .../api/engine/RecordSetChangeHandler.scala | 2 +- .../core/domain/batch/SingleChange.scala | 4 +- ...BatchChangeRepositoryIntegrationSpec.scala | 10 ++--- .../dnsChanges/dnsChangeDetail.scala.html | 9 ++++- 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala index e4fd32b22..3f23c0f44 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala @@ -30,11 +30,14 @@ import vinyldns.core.domain.zone.Zone import vinyldns.core.domain.batch._ import vinyldns.core.domain.record.RecordType.{RecordType, UNKNOWN} import vinyldns.core.queue.MessageQueue +import java.net.InetAddress class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: MessageQueue) extends BatchChangeConverterAlgebra { - private val notExistCompletedMessage: String = "This record does not exist." + + private val NonExistentRecordDeleteMessage: String = "This record does not exist." + + "No further action is required." + private val NonExistentRecordDataDeleteMessage: String = "Record data entered does not exist." + "No further action is required." private val failedMessage: String = "Error queueing RecordSetChange for processing" private val logger = LoggerFactory.getLogger(classOf[BatchChangeConverter]) @@ -49,16 +52,17 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: s"Converting BatchChange [${batchChange.id}] with SingleChanges [${batchChange.changes.map(_.id)}]" ) for { + updatedBatchChange <- updateBatchChange(batchChange, groupedChanges).toRightBatchResult recordSetChanges <- createRecordSetChangesForBatch( - batchChange.changes, + updatedBatchChange.changes, existingZones, groupedChanges, batchChange.userId, ownerGroupId ).toRightBatchResult - _ <- allChangesWereConverted(batchChange.changes, recordSetChanges) + _ <- allChangesWereConverted(updatedBatchChange.changes, recordSetChanges) _ <- batchChangeRepo - .save(batchChange) + .save(updatedBatchChange) .toBatchResult // need to save the change before queueing, backend processing expects the changes to exist queued <- putChangesOnQueue(recordSetChanges, batchChange.id) changeToStore = updateWithQueueingFailures(batchChange, queued) @@ -125,7 +129,7 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: change match { case _: SingleDeleteRRSetChange if change.recordSetId.isEmpty => // Mark as Complete since we don't want to throw it as an error - change.withDoesNotExistMessage(notExistCompletedMessage) + change.withDoesNotExistMessage(NonExistentRecordDeleteMessage) case _ => // Failure here means there was a message queue issue for this change change.withFailureMessage(failedMessage) @@ -138,11 +142,35 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: def storeQueuingFailures(batchChange: BatchChange): BatchResult[Unit] = { // Update if Single change is Failed or if a record that does not exist is deleted val failedAndNotExistsChanges = batchChange.changes.collect { - case change if change.status == SingleChangeStatus.Failed || change.systemMessage.contains(notExistCompletedMessage) => change + case change if change.status == SingleChangeStatus.Failed || change.systemMessage.contains(NonExistentRecordDeleteMessage) => change } batchChangeRepo.updateSingleChanges(failedAndNotExistsChanges).as(()) }.toBatchResult + def matchRecordData(existingRecordSetData: List[RecordData], recordData: RecordData): Boolean = + existingRecordSetData.exists { rd => + (rd, recordData) match { + case (AAAAData(rdAddress), AAAAData(proposedAddress)) => + InetAddress.getByName(proposedAddress).getHostName == InetAddress + .getByName(rdAddress) + .getHostName + case _ => rd == recordData + } + } + + def updateBatchChange(batchChange: BatchChange, groupedChanges: ChangeForValidationMap): BatchChange = { + // Update system message to be display the information if record data doesn't exist for the delete request + val singleChanges = batchChange.changes.map { + case change@(sd: SingleDeleteRRSetChange) => + if (sd.recordData.isDefined && !groupedChanges.getExistingRecordSet(change.recordKey.get).exists(rs => matchRecordData(rs.records, sd.recordData.get))) { + sd.copy(systemMessage = Some(NonExistentRecordDataDeleteMessage)) + } + else change + case change => change + } + batchChange.copy(changes = singleChanges) + } + def createRecordSetChangesForBatch( changes: List[SingleChange], existingZones: ExistingZones, diff --git a/modules/api/src/main/scala/vinyldns/api/engine/RecordSetChangeHandler.scala b/modules/api/src/main/scala/vinyldns/api/engine/RecordSetChangeHandler.scala index 553e7a06d..ad0585250 100644 --- a/modules/api/src/main/scala/vinyldns/api/engine/RecordSetChangeHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/engine/RecordSetChangeHandler.scala @@ -106,7 +106,7 @@ object RecordSetChangeHandler extends TransactionProvider { ): List[SingleChange] = recordSetChange.status match { case RecordSetChangeStatus.Complete => - singleChanges.map(_.complete(recordSetChange.systemMessage, recordSetChange.id, recordSetChange.recordSet.id)) + singleChanges.map(_.complete(recordSetChange.id, recordSetChange.recordSet.id)) case RecordSetChangeStatus.Failed => singleChanges.map(_.withProcessingError(recordSetChange.systemMessage, recordSetChange.id)) case _ => singleChanges diff --git a/modules/core/src/main/scala/vinyldns/core/domain/batch/SingleChange.scala b/modules/core/src/main/scala/vinyldns/core/domain/batch/SingleChange.scala index 5483820a4..254246ebf 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/batch/SingleChange.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/batch/SingleChange.scala @@ -69,18 +69,16 @@ sealed trait SingleChange { ) } - def complete(message: Option[String], completeRecordChangeId: String, recordSetId: String): SingleChange = this match { + def complete(completeRecordChangeId: String, recordSetId: String): SingleChange = this match { case add: SingleAddChange => add.copy( status = SingleChangeStatus.Complete, - systemMessage = message, recordChangeId = Some(completeRecordChangeId), recordSetId = Some(recordSetId) ) case delete: SingleDeleteRRSetChange => delete.copy( status = SingleChangeStatus.Complete, - systemMessage = message, recordChangeId = Some(completeRecordChangeId), recordSetId = Some(recordSetId) ) diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlBatchChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlBatchChangeRepositoryIntegrationSpec.scala index 6ad03ab21..ac544d770 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlBatchChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlBatchChangeRepositoryIntegrationSpec.scala @@ -116,7 +116,7 @@ class MySqlBatchChangeRepositoryIntegrationSpec val pendingBatchChange: BatchChange = randomBatchChange().copy(createdTimestamp = Instant.now.truncatedTo(ChronoUnit.MILLIS)) val completeBatchChange: BatchChange = randomBatchChangeWithList( - randomBatchChange().changes.map(_.complete(Some("Complete"),"recordChangeId", "recordSetId")) + randomBatchChange().changes.map(_.complete("recordChangeId", "recordSetId")) ).copy(createdTimestamp = Instant.now.truncatedTo(ChronoUnit.MILLIS).plusMillis(1000)) val failedBatchChange: BatchChange = @@ -124,7 +124,7 @@ class MySqlBatchChangeRepositoryIntegrationSpec .copy(createdTimestamp = Instant.now.truncatedTo(ChronoUnit.MILLIS).plusMillis(100000)) val partialFailureBatchChange: BatchChange = randomBatchChangeWithList( - randomBatchChange().changes.take(2).map(_.complete(Some("Complete"),"recordChangeId", "recordSetId")) + randomBatchChange().changes.take(2).map(_.complete("recordChangeId", "recordSetId")) ++ randomBatchChange().changes.drop(2).map(_.withFailureMessage("failed")) ).copy(createdTimestamp = Instant.now.truncatedTo(ChronoUnit.MILLIS).plusMillis(1000000)) @@ -411,7 +411,7 @@ class MySqlBatchChangeRepositoryIntegrationSpec "update single changes" in { val batchChange = randomBatchChange() - val completed = batchChange.changes.map(_.complete(Some("Complete"),"aaa", "bbb")) + val completed = batchChange.changes.map(_.complete("aaa", "bbb")) val f = for { _ <- repo.save(batchChange) @@ -430,7 +430,7 @@ class MySqlBatchChangeRepositoryIntegrationSpec "update some changes in a batch" in { val batchChange = randomBatchChange() - val completed = batchChange.changes.take(2).map(_.complete(Some("Complete"),"recordChangeId", "recordSetId")) + val completed = batchChange.changes.take(2).map(_.complete("recordChangeId", "recordSetId")) val incomplete = batchChange.changes.drop(2) val f = for { @@ -444,7 +444,7 @@ class MySqlBatchChangeRepositoryIntegrationSpec "return the batch when updating single changes" in { val batchChange = randomBatchChange() - val completed = batchChange.changes.take(2).map(_.complete(Some("Complete"),"recordChangeId", "recordSetId")) + val completed = batchChange.changes.take(2).map(_.complete("recordChangeId", "recordSetId")) val f = for { _ <- repo.save(batchChange) diff --git a/modules/portal/app/views/dnsChanges/dnsChangeDetail.scala.html b/modules/portal/app/views/dnsChanges/dnsChangeDetail.scala.html index 1d05c1613..c3e9f9cf8 100644 --- a/modules/portal/app/views/dnsChanges/dnsChangeDetail.scala.html +++ b/modules/portal/app/views/dnsChanges/dnsChangeDetail.scala.html @@ -165,9 +165,14 @@ {{error.message ? error.message : error}}

    {{change.systemMessage}}
    -
    - ℹ️ No further action is required.
    + ℹ️ No further action is required. + +
    + ℹ️ {{change.systemMessage}} +
    From edb5f568178af3a106941579c5ad33326af96d09 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Feb 2023 16:44:22 +0530 Subject: [PATCH 103/521] add test --- .../batch/BatchChangeConverterSpec.scala | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala index 0fb8d1497..5a6a4c11f 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala @@ -37,7 +37,9 @@ import vinyldns.core.domain.record._ import vinyldns.core.domain.zone.Zone class BatchChangeConverterSpec extends AnyWordSpec with Matchers { - private val notExistCompletedMessage: String = "This record does not exist." + + private val NonExistentRecordDeleteMessage: String = "This record does not exist." + + "No further action is required." + private val NonExistentRecordDataDeleteMessage: String = "Record data entered does not exist." + "No further action is required." private def makeSingleAddChange( @@ -166,10 +168,18 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { makeSingleDeleteRRSetChange("DoesNotExistToDelete", A) ) + private val singleChangesOneDeleteGood = List( + makeSingleDeleteRRSetChange("aToDelete", A).copy(recordData = Some(AData("2.3.4.6"))), + ) + private val changeForValidationOneDelete = List( makeDeleteRRSetChangeForValidation("DoesNotExistToDelete", A) ) + private val changeForValidationOneDeleteGood = List( + makeDeleteRRSetChangeForValidation("aToDelete", A) + ) + private val singleChangesOneBad = List( makeSingleAddChange("one", AData("1.1.1.1")), makeSingleAddChange("two", AData("1.1.1.2")), @@ -571,8 +581,8 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { val receivedChange = returnedBatch.changes(0) receivedChange.status shouldBe SingleChangeStatus.Complete receivedChange.recordChangeId shouldBe None - receivedChange.systemMessage shouldBe Some(notExistCompletedMessage) - returnedBatch.changes(0) shouldBe singleChangesOneDelete(0).copy(systemMessage = Some(notExistCompletedMessage), status = SingleChangeStatus.Complete) + receivedChange.systemMessage shouldBe Some(NonExistentRecordDeleteMessage) + returnedBatch.changes(0) shouldBe singleChangesOneDelete(0).copy(systemMessage = Some(NonExistentRecordDeleteMessage), status = SingleChangeStatus.Complete) // check the update has been made in the DB val savedBatch: Option[BatchChange] = @@ -611,6 +621,31 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { } } + "updateBatchChange" should { + "update the batch change system message when there is a delete request with non-existent record data" in { + val batchWithBadChange = + BatchChange( + okUser.id, + okUser.userName, + None, + Instant.now.truncatedTo(ChronoUnit.MILLIS), + singleChangesOneDeleteGood, + approvalStatus = BatchChangeApprovalStatus.AutoApproved + ) + val result = + underTest + .updateBatchChange( + batchWithBadChange, + ChangeForValidationMap(changeForValidationOneDeleteGood.map(_.validNel), existingRecordSets), + ) + + // validate the batch change returned + val receivedChange = result.changes(0) + receivedChange.systemMessage shouldBe Some(NonExistentRecordDataDeleteMessage) + result.changes(0) shouldBe singleChangesOneDeleteGood(0).copy(systemMessage = Some(NonExistentRecordDataDeleteMessage)) + } + } + "generateAddChange" should { val singleAddChange = makeSingleAddChange("shared-rs", AData("1.2.3.4"), A, sharedZone) val ownerGroupId = Some("some-owner-group-id") From 80a31c09c7916dfdc3b69464fa4d9a685d63a897 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Feb 2023 16:47:02 +0530 Subject: [PATCH 104/521] fix styling --- .../api/domain/batch/BatchChangeConverter.scala | 10 +++++----- .../api/domain/batch/BatchChangeConverterSpec.scala | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala index 3f23c0f44..510bfdabf 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala @@ -35,9 +35,9 @@ import java.net.InetAddress class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: MessageQueue) extends BatchChangeConverterAlgebra { - private val NonExistentRecordDeleteMessage: String = "This record does not exist." + + private val nonExistentRecordDeleteMessage: String = "This record does not exist." + "No further action is required." - private val NonExistentRecordDataDeleteMessage: String = "Record data entered does not exist." + + private val nonExistentRecordDataDeleteMessage: String = "Record data entered does not exist." + "No further action is required." private val failedMessage: String = "Error queueing RecordSetChange for processing" private val logger = LoggerFactory.getLogger(classOf[BatchChangeConverter]) @@ -129,7 +129,7 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: change match { case _: SingleDeleteRRSetChange if change.recordSetId.isEmpty => // Mark as Complete since we don't want to throw it as an error - change.withDoesNotExistMessage(NonExistentRecordDeleteMessage) + change.withDoesNotExistMessage(nonExistentRecordDeleteMessage) case _ => // Failure here means there was a message queue issue for this change change.withFailureMessage(failedMessage) @@ -142,7 +142,7 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: def storeQueuingFailures(batchChange: BatchChange): BatchResult[Unit] = { // Update if Single change is Failed or if a record that does not exist is deleted val failedAndNotExistsChanges = batchChange.changes.collect { - case change if change.status == SingleChangeStatus.Failed || change.systemMessage.contains(NonExistentRecordDeleteMessage) => change + case change if change.status == SingleChangeStatus.Failed || change.systemMessage.contains(nonExistentRecordDeleteMessage) => change } batchChangeRepo.updateSingleChanges(failedAndNotExistsChanges).as(()) }.toBatchResult @@ -163,7 +163,7 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: val singleChanges = batchChange.changes.map { case change@(sd: SingleDeleteRRSetChange) => if (sd.recordData.isDefined && !groupedChanges.getExistingRecordSet(change.recordKey.get).exists(rs => matchRecordData(rs.records, sd.recordData.get))) { - sd.copy(systemMessage = Some(NonExistentRecordDataDeleteMessage)) + sd.copy(systemMessage = Some(nonExistentRecordDataDeleteMessage)) } else change case change => change diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala index 5a6a4c11f..0c1e3e4b6 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala @@ -37,9 +37,9 @@ import vinyldns.core.domain.record._ import vinyldns.core.domain.zone.Zone class BatchChangeConverterSpec extends AnyWordSpec with Matchers { - private val NonExistentRecordDeleteMessage: String = "This record does not exist." + + private val nonExistentRecordDeleteMessage: String = "This record does not exist." + "No further action is required." - private val NonExistentRecordDataDeleteMessage: String = "Record data entered does not exist." + + private val nonExistentRecordDataDeleteMessage: String = "Record data entered does not exist." + "No further action is required." private def makeSingleAddChange( @@ -581,8 +581,8 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { val receivedChange = returnedBatch.changes(0) receivedChange.status shouldBe SingleChangeStatus.Complete receivedChange.recordChangeId shouldBe None - receivedChange.systemMessage shouldBe Some(NonExistentRecordDeleteMessage) - returnedBatch.changes(0) shouldBe singleChangesOneDelete(0).copy(systemMessage = Some(NonExistentRecordDeleteMessage), status = SingleChangeStatus.Complete) + receivedChange.systemMessage shouldBe Some(nonExistentRecordDeleteMessage) + returnedBatch.changes(0) shouldBe singleChangesOneDelete(0).copy(systemMessage = Some(nonExistentRecordDeleteMessage), status = SingleChangeStatus.Complete) // check the update has been made in the DB val savedBatch: Option[BatchChange] = @@ -641,8 +641,8 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { // validate the batch change returned val receivedChange = result.changes(0) - receivedChange.systemMessage shouldBe Some(NonExistentRecordDataDeleteMessage) - result.changes(0) shouldBe singleChangesOneDeleteGood(0).copy(systemMessage = Some(NonExistentRecordDataDeleteMessage)) + receivedChange.systemMessage shouldBe Some(nonExistentRecordDataDeleteMessage) + result.changes(0) shouldBe singleChangesOneDeleteGood(0).copy(systemMessage = Some(nonExistentRecordDataDeleteMessage)) } } From 5850dbcb9aa39bb401d5d185fed2e2b50273c23d Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Feb 2023 17:04:50 +0530 Subject: [PATCH 105/521] add test coverage --- .../domain/batch/BatchChangeConverterSpec.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala index 0c1e3e4b6..fa7775063 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala @@ -646,6 +646,20 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { } } + "matchRecordData" should { + "check if the record data given matches the record data present" in { + val recordDatas = List(AData("1.2.3.5"), AAAAData("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78")) + val result1 = underTest.matchRecordData(recordDatas, AData("1.2.3.5")) + result1 shouldBe true + val result2 = underTest.matchRecordData(recordDatas, AData("1.2.3.4")) + result2 shouldBe false + val result3 = underTest.matchRecordData(recordDatas, AAAAData("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78")) + result3 shouldBe true + val result4 = underTest.matchRecordData(recordDatas, AAAAData("abcd:cec6:c4ef:bb7b:1a78:d055:216d:3a78")) + result4 shouldBe false + } + } + "generateAddChange" should { val singleAddChange = makeSingleAddChange("shared-rs", AData("1.2.3.4"), A, sharedZone) val ownerGroupId = Some("some-owner-group-id") From d7203cbbd4fc0455869b530db35da46c0e19c2f8 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Feb 2023 17:07:53 +0530 Subject: [PATCH 106/521] update test --- .../api/domain/batch/BatchChangeConverterSpec.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala index fa7775063..69ab9eebd 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala @@ -648,14 +648,14 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers { "matchRecordData" should { "check if the record data given matches the record data present" in { - val recordDatas = List(AData("1.2.3.5"), AAAAData("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78")) - val result1 = underTest.matchRecordData(recordDatas, AData("1.2.3.5")) + val recordData = List(AData("1.2.3.5"), AAAAData("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78")) + val result1 = underTest.matchRecordData(recordData, AData("1.2.3.5")) result1 shouldBe true - val result2 = underTest.matchRecordData(recordDatas, AData("1.2.3.4")) + val result2 = underTest.matchRecordData(recordData, AData("1.2.3.4")) result2 shouldBe false - val result3 = underTest.matchRecordData(recordDatas, AAAAData("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78")) + val result3 = underTest.matchRecordData(recordData, AAAAData("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78")) result3 shouldBe true - val result4 = underTest.matchRecordData(recordDatas, AAAAData("abcd:cec6:c4ef:bb7b:1a78:d055:216d:3a78")) + val result4 = underTest.matchRecordData(recordData, AAAAData("abcd:cec6:c4ef:bb7b:1a78:d055:216d:3a78")) result4 shouldBe false } } From 066b8dc9723af17b732e42d0cd17fe3db49442dd Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 22 Feb 2023 09:07:34 +0530 Subject: [PATCH 107/521] address pr comment --- .../vinyldns/api/domain/batch/BatchChangeConverter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala index 510bfdabf..d561bc121 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeConverter.scala @@ -35,9 +35,9 @@ import java.net.InetAddress class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: MessageQueue) extends BatchChangeConverterAlgebra { - private val nonExistentRecordDeleteMessage: String = "This record does not exist." + + private val nonExistentRecordDeleteMessage: String = "This record does not exist. " + "No further action is required." - private val nonExistentRecordDataDeleteMessage: String = "Record data entered does not exist." + + private val nonExistentRecordDataDeleteMessage: String = "Record data entered does not exist. " + "No further action is required." private val failedMessage: String = "Error queueing RecordSetChange for processing" private val logger = LoggerFactory.getLogger(classOf[BatchChangeConverter]) From 9265cb0af10c77e6926d09fdf71faf52706ab15a Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 22 Feb 2023 09:22:05 +0530 Subject: [PATCH 108/521] resolve tests --- .../test/functional/tests/batch/create_batch_change_test.py | 2 +- .../vinyldns/api/domain/batch/BatchChangeConverterSpec.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index 919265d37..b90a2c418 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -3734,7 +3734,7 @@ def test_create_batch_delete_record_that_does_not_exists_completes(shared_zone_t response = client.create_batch_change(batch_change_input, status=202) get_batch = client.get_batch_change(response["id"]) - assert_that(get_batch["changes"][0]["systemMessage"], is_("This record does not exist." + + assert_that(get_batch["changes"][0]["systemMessage"], is_("This record does not exist. " + "No further action is required.")) assert_successful_change_in_error_response(response["changes"][0], input_name=f"delete-non-existent-record.{ok_zone_name}", record_data="1.1.1.1", change_type="DeleteRecordSet") diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala index 69ab9eebd..0305d0830 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeConverterSpec.scala @@ -37,9 +37,9 @@ import vinyldns.core.domain.record._ import vinyldns.core.domain.zone.Zone class BatchChangeConverterSpec extends AnyWordSpec with Matchers { - private val nonExistentRecordDeleteMessage: String = "This record does not exist." + + private val nonExistentRecordDeleteMessage: String = "This record does not exist. " + "No further action is required." - private val nonExistentRecordDataDeleteMessage: String = "Record data entered does not exist." + + private val nonExistentRecordDataDeleteMessage: String = "Record data entered does not exist. " + "No further action is required." private def makeSingleAddChange( From 8719cf254cd0d096d2a1850efdeb3811583080a8 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 22 Feb 2023 18:03:26 +0530 Subject: [PATCH 109/521] add scheduler in separate section --- .../zones/zoneTabs/manageZone.scala.html | 56 +++++++++++++------ modules/portal/public/css/vinyldns.css | 33 +++-------- .../lib/controllers/controller.manageZones.js | 34 ++++++++--- 3 files changed, 74 insertions(+), 49 deletions(-) diff --git a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html index c83fa0256..7b076dc4b 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html @@ -232,24 +232,6 @@ ng-click="clearUpdateTransferConnection()" type="button">Clear Connection - -
    -
    -
    - @if(rootAccountCanReview) { -

    Schedule Zone Sync

    - -
    -
    - -
    - } -
    -
    -
    - @@ -366,6 +348,44 @@ + +@if(rootAccountCanReview) { +
    +
    +

    + Schedule Zone Sync +

    +
    +
    +
    + +
    + + + Schedule for automatic zone sync at the provided time. + +
    + +
    +
    + +
    +} + +
    diff --git a/modules/portal/public/css/vinyldns.css b/modules/portal/public/css/vinyldns.css index 0b25c0e66..bac00790f 100644 --- a/modules/portal/public/css/vinyldns.css +++ b/modules/portal/public/css/vinyldns.css @@ -523,34 +523,19 @@ input[type="file"] { z-index: 1000; } -/* Starting of css override for cron library used in zone sync scheduling */ +/* Starting of css override for cron library and it's associated elements used in zone sync scheduling */ .cron-select-wrap .cron-select { border-radius: 0px !important; } -.select-options { - vertical-align: bottom; +#cron-lib { + padding-right: 0px; + padding-left: 0px; + padding-top: 15px; } -.select-options span { - vertical-align: super; - position: relative; - bottom: 0.5rem; +#cron-lib-check { + padding: 0px; + margin: 0px; } - -.cron-wrap > span + .cron-select-wrap > .cron-select { - margin-bottom: 0.2rem; -} - -/* For safari browser, since the settings are different from other browsers */ -@media not all and (min-resolution: 0.001dpcm) { -.select-options { - vertical-align: sub; -} - -.select-options span { - position: initial; - bottom: 0px; -} -} -/* Ending of css override for cron library used in zone sync scheduling */ +/* Ending of css override for cron library and it's associated elements used in zone sync scheduling */ diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index f8856a357..aedaa89e5 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -40,6 +40,10 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) $scope.zoneInfo = {}; $scope.zoneChanges = {}; $scope.updateZoneInfo = {}; + $scope.zoneSyncRecurrenceSchedule = undefined; + $scope.removeZoneSyncSchedule = { + isChecked: false + }; $scope.manageZoneState = { UPDATE: 0, CONFIRM_UPDATE: 1 @@ -96,11 +100,11 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) }; $scope.myZoneSyncScheduleConfig = { - allowMultiple: true, + allowMultiple: false, quartz: true, options: { allowMinute : false, - allowHour : true, + allowHour : false, allowMonth : false, allowYear : false } @@ -188,7 +192,6 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) */ $scope.submitUpdateZone = function () { - delete $scope.updateZoneInfo.removeRecurrenceSchedule; var zone = angular.copy($scope.updateZoneInfo); zone = zonesService.normalizeZoneDates(zone); zone = zonesService.setConnectionKeys(zone); @@ -198,6 +201,13 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) $scope.updateZone(zone, 'Zone Update'); }; + $scope.submitUpdateZoneSyncSchedule = function () { + var newZone = angular.copy($scope.zoneInfo); + newZone = zonesService.normalizeZoneDates(newZone); + newZone.recurrenceSchedule = $scope.zoneSyncRecurrenceSchedule; + $scope.updateZone(newZone, 'Zone Sync Schedule'); + } + $scope.submitDeleteAclRule = function() { var newZone = angular.copy($scope.zoneInfo); newZone = zonesService.normalizeZoneDates(newZone); @@ -244,14 +254,21 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) */ $scope.objectsDiffer = function(left, right) { - if($scope.updateZoneInfo.removeRecurrenceSchedule){ - $scope.updateZoneInfo.recurrenceSchedule = undefined; - } var l = $scope.normalizeZone(left); var r = $scope.normalizeZone(right); return !angular.equals(l, r); }; + $scope.zoneSyncScheduleDiffer = function(left, right) { + var updatedZoneSchedule = left; + var existingZoneSchedule = right; + if($scope.removeZoneSyncSchedule.isChecked){ + $scope.zoneSyncRecurrenceSchedule = undefined; + updatedZoneSchedule = $scope.zoneSyncRecurrenceSchedule; + } + return !angular.equals(updatedZoneSchedule, existingZoneSchedule); + }; + $scope.normalizeZone = function(zone) { var vinyldnsZone = angular.copy(zone); delete vinyldnsZone.adminGroupName; @@ -294,7 +311,10 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) $scope.updateZoneInfo = angular.copy($scope.zoneInfo); $scope.updateZoneInfo.hiddenKey = ''; $scope.updateZoneInfo.hiddenTransferKey = ''; - $scope.recurrenceScheduleExist = $scope.updateZoneInfo.recurrenceSchedule ? true : false; + $scope.recurrenceScheduleExist = $scope.zoneInfo.recurrenceSchedule ? true : false; + if($scope.recurrenceScheduleExist){ + $scope.zoneSyncRecurrenceSchedule = $scope.zoneInfo.recurrenceSchedule; + } $scope.currentManageZoneState = $scope.manageZoneState.UPDATE; $scope.refreshAclRuleDisplay(); $scope.refreshZoneChange(); From 6631019eace7d212e2bdedbc72bc5e609229443c Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 22 Feb 2023 18:08:10 +0530 Subject: [PATCH 110/521] update help-block --- modules/portal/app/views/zones/zoneTabs/manageZone.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html index 7b076dc4b..c31382642 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html @@ -364,7 +364,7 @@ - Schedule for automatic zone sync at the provided time. + Schedule for zone sync at the provided time.
    From 804d0c54c8de6075d1e78e8afc378c9ee40dd0c1 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 22 Feb 2023 21:00:57 +0530 Subject: [PATCH 112/521] resolve automated sync issue --- .../src/main/scala/vinyldns/api/backend/CommandHandler.scala | 4 ++-- .../portal/public/lib/controllers/controller.manageZones.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala index 8ab42d537..797328494 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala @@ -142,7 +142,7 @@ object CommandHandler { _.evalMap[IO, Any] { message => message.command match { case sync: ZoneChange - if sync.changeType == ZoneChangeType.Sync || sync.changeType == ZoneChangeType.Create => + if sync.changeType == ZoneChangeType.Sync || sync.changeType == ZoneChangeType.AutomatedSync || sync.changeType == ZoneChangeType.Create => logger.info(s"Updating visibility timeout for zone change; changeId=${sync.id}") mq.changeMessageTimeout(message, 1.hour) @@ -163,7 +163,7 @@ object CommandHandler { _.evalMap[IO, MessageOutcome] { message => message.command match { case sync: ZoneChange - if sync.changeType == ZoneChangeType.Sync || sync.changeType == ZoneChangeType.Create => + if sync.changeType == ZoneChangeType.Sync || sync.changeType == ZoneChangeType.AutomatedSync || sync.changeType == ZoneChangeType.Create => outcomeOf(message)(zoneSyncProcessor(sync)) case zoneChange: ZoneChange => diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index aedaa89e5..6b5e32ebf 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -105,6 +105,7 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) options: { allowMinute : false, allowHour : false, + allowWeek : false, allowMonth : false, allowYear : false } From 39095c56557c5902049c8f633b676f98db11558e Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 23 Feb 2023 10:42:59 +0530 Subject: [PATCH 113/521] resolve minor issues --- .../domain/zone/ZoneSyncScheduleHandler.scala | 2 +- .../zones/zoneTabs/manageZone.scala.html | 17 ++++++------- .../lib/controllers/controller.manageZones.js | 24 ++++++++++++------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala index e18516bf5..7f455ff37 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneSyncScheduleHandler.scala @@ -56,7 +56,7 @@ object ZoneSyncScheduleHandler { val executionTime: ExecutionTime = ExecutionTime.forCron(parser.parse(z.recurrenceSchedule.get)) val nextExecution = executionTime.nextExecution(now.atZone(ZoneId.systemDefault())).get() val diff = ChronoUnit.SECONDS.between(now, nextExecution) - if (diff <= 5) { + if (diff < 5) { zonesWithSchedule = zonesWithSchedule :+ z.id } else { List.empty diff --git a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html index c31382642..3609e0d6f 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html @@ -357,25 +357,26 @@
    -
    - -
    - + Schedule for zone sync at the provided time.
    + + diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 219d0e2ec..88caec6e4 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -36,6 +36,8 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) * Zone scope data initial setup */ + $scope.time = undefined; + $scope.utcTime = undefined; $scope.alerts = []; $scope.zoneInfo = {}; $scope.zoneChanges = {}; @@ -99,6 +101,23 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) $("#delete_zone_connection_modal").modal("show"); }; + $scope.openTimeConverter = function() { + $("#time_converter_modal").modal("show"); + }; + + $scope.getLocalTimeZone = function() { + return new Date().toLocaleString('en-us', {timeZoneName:'short'}).split(' ')[3]; + }; + + $scope.getUtcTime = function() { + $scope.utcTime = moment($scope.time, 'hh:mm A').utc().format('hh:mm A'); + }; + + $scope.resetTime = function () { + $scope.time = undefined; + $scope.utcTime = undefined; + }; + $scope.myZoneSyncScheduleConfig = { allowMultiple: false, quartz: true, @@ -506,5 +525,16 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) }); }; + $('input[name="time"]').daterangepicker({ + singleDatePicker: true, + startDate: moment().startOf('day'), + minDate: moment().startOf('day'), + maxDate: moment().endOf('day'), + timePicker: true, + locale: { + format: 'hh:mm A' + } + }); + $timeout($scope.refreshZone, 0); }); From 4ac406f92d079fbe3db41f11e026e9069f5dc637 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino <37352107+nspadaccino@users.noreply.github.com> Date: Fri, 17 Mar 2023 16:00:15 -0400 Subject: [PATCH 153/521] Bump version to v0.18.3 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index a445ff984..bd2a569a0 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.18.2" +version in ThisBuild := "0.18.3" From 5a5488925ace38b43d89d98a6553708cd3536f16 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Tue, 21 Mar 2023 12:45:46 +0530 Subject: [PATCH 154/521] Zone related email validation with unit tests --- .../src/main/scala/vinyldns/api/Boot.scala | 3 +- .../api/domain/zone/ZoneService.scala | 12 +- .../vinyldns/api/route/ZoneRouting.scala | 2 + .../api/domain/zone/ZoneServiceSpec.scala | 207 +++++++++++++++++- 4 files changed, 219 insertions(+), 5 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index 0b60125c5..cd792736f 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -169,7 +169,8 @@ object Boot extends App { zoneValidations, recordAccessValidations, backendResolver, - vinyldnsConfig.crypto + vinyldnsConfig.crypto, + membershipService ) //limits configured in reference.conf passing here val limits = LimitsConfig( diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala index 8d8c268f1..be3260fa6 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala @@ -32,6 +32,7 @@ import com.cronutils.model.definition.CronDefinition import com.cronutils.model.definition.CronDefinitionBuilder import com.cronutils.parser.CronParser import com.cronutils.model.CronType +import vinyldns.api.domain.membership.MembershipService object ZoneService { def apply( @@ -41,7 +42,8 @@ object ZoneService { zoneValidations: ZoneValidations, accessValidation: AccessValidationsAlgebra, backendResolver: BackendResolver, - crypto: CryptoAlgebra + crypto: CryptoAlgebra, + membershipService:MembershipService ): ZoneService = new ZoneService( dataAccessor.zoneRepository, @@ -53,7 +55,8 @@ object ZoneService { zoneValidations, accessValidation, backendResolver, - crypto + crypto, + membershipService ) } @@ -67,7 +70,8 @@ class ZoneService( zoneValidations: ZoneValidations, accessValidation: AccessValidationsAlgebra, backendResolver: BackendResolver, - crypto: CryptoAlgebra + crypto: CryptoAlgebra, + membershipService:MembershipService ) extends ZoneServiceAlgebra { import accessValidation._ @@ -80,6 +84,7 @@ class ZoneService( ): Result[ZoneCommandResult] = for { _ <- isValidZoneAcl(createZoneInput.acl).toResult + _ <- membershipService.emailValidation(createZoneInput.email) _ <- connectionValidator.isValidBackendId(createZoneInput.backendId).toResult _ <- validateSharedZoneAuthorized(createZoneInput.shared, auth.signedInUser).toResult _ <- zoneDoesNotExist(createZoneInput.name) @@ -98,6 +103,7 @@ class ZoneService( def updateZone(updateZoneInput: UpdateZoneInput, auth: AuthPrincipal): Result[ZoneCommandResult] = for { _ <- isValidZoneAcl(updateZoneInput.acl).toResult + _ <- membershipService.emailValidation(updateZoneInput.email) _ <- connectionValidator.isValidBackendId(updateZoneInput.backendId).toResult existingZone <- getZoneOrFail(updateZoneInput.id) _ <- validateSharedZoneAuthorized( diff --git a/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala index f77f6e2e6..12e4b6499 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala @@ -21,6 +21,7 @@ import akka.http.scaladsl.server._ import akka.util.Timeout import org.slf4j.{Logger, LoggerFactory} import vinyldns.api.config.LimitsConfig +import vinyldns.api.domain.membership.EmailValidationError import vinyldns.api.domain.zone._ import vinyldns.core.crypto.CryptoAlgebra import vinyldns.core.domain.zone._ @@ -62,6 +63,7 @@ class ZoneRoute( case RecentSyncError(msg) => complete(StatusCodes.Forbidden, msg) case ZoneInactiveError(msg) => complete(StatusCodes.BadRequest, msg) case InvalidRequest(msg) => complete(StatusCodes.BadRequest, msg) + case EmailValidationError(msg) => complete(StatusCodes.BadRequest, msg) } val zoneRoute: Route = path("zones") { diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index b528c8143..f74bfa327 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -25,7 +25,11 @@ import cats.implicits._ import vinyldns.api.Interfaces._ import cats.effect._ import org.scalatest.{BeforeAndAfterEach, EitherValues} +import vinyldns.api.config.ValidEmailConfig import vinyldns.api.domain.access.AccessValidations +import vinyldns.api.domain.membership.{EmailValidationError, MembershipService} +import vinyldns.core.domain.record.RecordSetRepository +//import vinyldns.api.domain.membership.{EmailValidationError, MembershipService} import vinyldns.api.repository.TestDataLoader import vinyldns.core.domain.auth.AuthPrincipal import vinyldns.core.domain.membership._ @@ -53,6 +57,20 @@ class ZoneServiceSpec private val badConnection = ZoneConnection("bad", "bad", Encrypted("bad"), "bad") private val abcZoneSummary = ZoneSummaryInfo(abcZone, abcGroup.name, AccessLevel.Delete) private val xyzZoneSummary = ZoneSummaryInfo(xyzZone, xyzGroup.name, AccessLevel.NoAccess) + private val mockMembershipRepo = mock[MembershipRepository] + private val mockGroupChangeRepo = mock[GroupChangeRepository] + private val mockRecordSetRepo = mock[RecordSetRepository] + private val mockValidEmailConfig = ValidEmailConfig(valid_domains = List("test.com", "*dummy.com")) + private val mockValidEmailConfigNew = ValidEmailConfig(valid_domains = List()) + private val mockMembershipService = new MembershipService(mockGroupRepo, + mockUserRepo, + mockMembershipRepo, + mockZoneRepo, + mockGroupChangeRepo, + mockRecordSetRepo, + mockValidEmailConfig) + + object TestConnectionValidator extends ZoneConnectionValidatorAlgebra { def validateZoneConnections(zone: Zone): Result[Unit] = @@ -78,7 +96,27 @@ class ZoneServiceSpec new ZoneValidations(1000), new AccessValidations(), mockBackendResolver, - NoOpCrypto.instance + NoOpCrypto.instance, + mockMembershipService + ) + private val underTestNew = new ZoneService( + mockZoneRepo, + mockGroupRepo, + mockUserRepo, + mockZoneChangeRepo, + TestConnectionValidator, + mockMessageQueue, + new ZoneValidations(1000), + new AccessValidations(), + mockBackendResolver, + NoOpCrypto.instance, + new MembershipService(mockGroupRepo, + mockUserRepo, + mockMembershipRepo, + mockZoneRepo, + mockGroupChangeRepo, + mockRecordSetRepo, + mockValidEmailConfigNew) ) private val createZoneAuthorized = CreateZoneInput( @@ -211,6 +249,99 @@ class ZoneServiceSpec val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidRequest] } + "return the result if the zone created includes an valid email" in { + doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) + + val newZone = createZoneAuthorized.copy(email ="test@ok.dummy.com") + val resultChange: ZoneChange = + underTest.connectToZone(newZone, okAuth).map(_.asInstanceOf[ZoneChange]).value.unsafeRunSync().toOption.get + resultChange.changeType shouldBe ZoneChangeType.Create + Option(resultChange.created) shouldBe defined + resultChange.status shouldBe ZoneChangeStatus.Pending + resultChange.userId shouldBe okAuth.userId + + val resultZone = resultChange.zone + Option(resultZone.id) shouldBe defined + resultZone.email shouldBe newZone.email + resultZone.name shouldBe newZone.name + resultZone.status shouldBe ZoneStatus.Syncing + resultZone.connection shouldBe newZone.connection + resultZone.shared shouldBe false + } + "return the result if the zone created includes empty Domain" in { + doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) + + val newZone = createZoneAuthorized.copy(email = "test@abc.com") + val resultChange: ZoneChange = + underTestNew.connectToZone(newZone, okAuth).map(_.asInstanceOf[ZoneChange]).value.unsafeRunSync().toOption.get + resultChange.changeType shouldBe ZoneChangeType.Create + Option(resultChange.created) shouldBe defined + resultChange.status shouldBe ZoneChangeStatus.Pending + resultChange.userId shouldBe okAuth.userId + + val resultZone = resultChange.zone + Option(resultZone.id) shouldBe defined + resultZone.email shouldBe newZone.email + resultZone.name shouldBe newZone.name + resultZone.status shouldBe ZoneStatus.Syncing + resultZone.connection shouldBe newZone.connection + resultZone.shared shouldBe false + } + "return an EmailValidationError if an email is invalid" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) + val newZone = createZoneAuthorized.copy(email = "test@my.com") + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 1" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) + val newZone = createZoneAuthorized.copy(email = "test.ok.com") + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if a domain is invalid test case 1" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) + val newZone = createZoneAuthorized.copy(email = "test@ok.com") + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 2" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) + val newZone = createZoneAuthorized.copy(email = "test@.@.test.com") + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 3" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) + val newZone = createZoneAuthorized.copy(email = "test@.@@.test.com") + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 4" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) + val newZone = createZoneAuthorized.copy(email = "@te@st@test.com") + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 5" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) + val newZone = createZoneAuthorized.copy(email = ".test@test.com") + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 6" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) + val newZone = createZoneAuthorized.copy(email = "te.....st@test.com") + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } "succeed if zone is shared and user is a super user" in { val newZone = createZoneAuthorized.copy(shared = true) @@ -363,6 +494,40 @@ class ZoneServiceSpec val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidRequest] } + "return the result if the zone updated includes an valid email" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZone(anyString) + + val doubleAuth = AuthPrincipal(TestDataLoader.okUser, Seq(twoUserGroup.id, okGroup.id)) + val updateZoneInput = updateZoneAuthorized.copy(adminGroupId = twoUserGroup.id,email="test@dummy.com") + + val resultChange: ZoneChange = + underTest + .updateZone(updateZoneInput, doubleAuth) + .map(_.asInstanceOf[ZoneChange]) + .value.unsafeRunSync().toOption.get + + resultChange.zone.id shouldBe okZone.id + resultChange.changeType shouldBe ZoneChangeType.Update + resultChange.zone.adminGroupId shouldBe updateZoneInput.adminGroupId + resultChange.zone.adminGroupId should not be updateZoneAuthorized.adminGroupId + } + "return the result if the zone updated includes an empty domain" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZone(anyString) + + val doubleAuth = AuthPrincipal(TestDataLoader.okUser, Seq(twoUserGroup.id, okGroup.id)) + val updateZoneInput = updateZoneAuthorized.copy(adminGroupId = twoUserGroup.id, email = "test@ok.com") + + val resultChange: ZoneChange = + underTestNew + .updateZone(updateZoneInput, doubleAuth) + .map(_.asInstanceOf[ZoneChange]) + .value.unsafeRunSync().toOption.get + + resultChange.zone.id shouldBe okZone.id + resultChange.changeType shouldBe ZoneChangeType.Update + resultChange.zone.adminGroupId shouldBe updateZoneInput.adminGroupId + resultChange.zone.adminGroupId should not be updateZoneAuthorized.adminGroupId + } "succeed if zone shared flag is updated and user is a super user" in { val newZone = updateZoneAuthorized.copy(shared = false) @@ -418,6 +583,46 @@ class ZoneServiceSpec val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe an[InvalidRequest] } + "return an EmailValidationError if an invalid email is entered while updating the zone" in { + val newZone = updateZoneAuthorized.copy(email ="test.ok.com") + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe an[EmailValidationError] + } + "return an EmailValidationError if a domain is invalid test case 1" in { + val newZone = updateZoneAuthorized.copy(email = "test@ok.com") + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe an[EmailValidationError] + } + + "return an EmailValidationError if an email is invalid test case 2" in { + val newZone = updateZoneAuthorized.copy(email = "test@.@.test.com") + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 3" in { + val newZone = updateZoneAuthorized.copy(email = "test@.@@.test.com") + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 4" in { + val newZone=updateZoneAuthorized.copy(email = "@te@st@test.com") + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 5" in { + val newZone = updateZoneAuthorized.copy(email = ".test@test.com") + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + + "return an error if an email is invalid test case 6" in { + val newZone = updateZoneAuthorized.copy(email = "te.....st@test.com") + val error = underTest.updateZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } } "Deleting Zones" should { From 64b05b107d6afe7a0df571653cb61ccf4d745e51 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Tue, 21 Mar 2023 14:45:04 +0530 Subject: [PATCH 155/521] Committing ZoneServiceIntegrationSpec.scala --- .../api/domain/zone/ZoneServiceIntegrationSpec.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/api/src/it/scala/vinyldns/api/domain/zone/ZoneServiceIntegrationSpec.scala b/modules/api/src/it/scala/vinyldns/api/domain/zone/ZoneServiceIntegrationSpec.scala index 66712ca73..f2cf01dae 100644 --- a/modules/api/src/it/scala/vinyldns/api/domain/zone/ZoneServiceIntegrationSpec.scala +++ b/modules/api/src/it/scala/vinyldns/api/domain/zone/ZoneServiceIntegrationSpec.scala @@ -18,6 +18,7 @@ package vinyldns.api.domain.zone import cats.data.NonEmptyList import cats.effect._ + import java.time.Instant import java.time.temporal.ChronoUnit import org.mockito.Mockito.doReturn @@ -29,6 +30,7 @@ import org.scalatestplus.mockito.MockitoSugar import org.scalatest.time.{Seconds, Span} import scalikejdbc.DB import vinyldns.api.domain.access.AccessValidations +import vinyldns.api.domain.membership.MembershipService import vinyldns.api.domain.record.RecordSetChangeGenerator import vinyldns.api.engine.TestMessageQueue import vinyldns.mysql.TransactionProvider @@ -62,7 +64,7 @@ class ZoneServiceIntegrationSpec private val recordSetRepo = recordSetRepository private val zoneRepo: ZoneRepository = zoneRepository - + private val mockMembershipService = mock[MembershipService] private var testZoneService: ZoneServiceAlgebra = _ private val badAuth = AuthPrincipal(okUser, Seq()) @@ -127,7 +129,8 @@ class ZoneServiceIntegrationSpec new ZoneValidations(1000), new AccessValidations(), mockBackendResolver, - NoOpCrypto.instance + NoOpCrypto.instance, + mockMembershipService ) } From 50bca731f84bc1676e5cc197c0bba349f21172f3 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Tue, 21 Mar 2023 15:22:43 +0530 Subject: [PATCH 156/521] Making changes in ZoneServiceSpec.scala --- .../test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index f74bfa327..5c082ee81 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -129,7 +129,7 @@ class ZoneServiceSpec private val updateZoneAuthorized = UpdateZoneInput( okZone.id, "ok.zone.recordsets.", - "updated-test@test.com", + "test@test.com", connection = testConnection, adminGroupId = okGroup.id ) From 9e6a10159fb61ad7ea9ba9f66a764399b0ac0879 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Mar 2023 15:46:08 +0530 Subject: [PATCH 157/521] updates to zone sync scheduler --- .../vinyldns/api/backend/CommandHandler.scala | 1 - .../repository/MySqlZoneChangeRepository.scala | 2 +- .../views/zones/zoneTabs/manageZone.scala.html | 4 ++-- .../lib/controllers/controller.manageZones.js | 15 ++++++++------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala index a0359acda..797328494 100644 --- a/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/backend/CommandHandler.scala @@ -164,7 +164,6 @@ object CommandHandler { message.command match { case sync: ZoneChange if sync.changeType == ZoneChangeType.Sync || sync.changeType == ZoneChangeType.AutomatedSync || sync.changeType == ZoneChangeType.Create => - logger.info(s"Performing zone sync for zone with zone change id: '${sync.id}', zone name: '${sync.zone.name}'") outcomeOf(message)(zoneSyncProcessor(sync)) case zoneChange: ZoneChange => diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala index 7152f3e73..32303063b 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala @@ -57,7 +57,7 @@ class MySqlZoneChangeRepository override def save(zoneChange: ZoneChange): IO[ZoneChange] = monitor("repo.ZoneChange.save") { IO { - logger.info(s"Saving zone change '${zoneChange.id}'. Zone name: '${zoneChange.zone.name}', change type: '${zoneChange.changeType}', status: '${zoneChange.status}'") + logger.debug(s"Saving zone change '${zoneChange.id}' for zone '${zoneChange.zone.name}'") DB.localTx { implicit s => PUT_ZONE_CHANGE .bindByName( diff --git a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html index 7199b40df..d86650e2d 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html @@ -591,9 +591,9 @@ } - @if(meta.scheduledBatchChangesEnabled) {
    @@ -74,7 +73,6 @@
    - }

    Changes

    diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 3c678ef74..30d7dc3f2 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -130,6 +130,12 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) } } + // Hide calendar as it's not necessary here and override css + $('#local-time').focusin(function(){ + $('.calendar-table').css("display","none"); + $('.calendar-time').css("margin-left","1.1rem"); + }); + $scope.submitDeleteZone = function() { zonesService.delZone($scope.zoneInfo.id) .then(function (response) { From 9e321985abc3d75d791087e14e99703059e0f541 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Mar 2023 16:24:58 +0530 Subject: [PATCH 160/521] remove superUser change --- .../scala/vinyldns/mysql/repository/MySqlUserRepository.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala index d2e3292c9..6aac4b4be 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala @@ -194,7 +194,7 @@ class MySqlUserRepository(cryptoAlgebra: CryptoAlgebra) 'id -> user.id, 'userName -> user.userName, 'accessKey -> user.accessKey, - 'data -> toPB(user.copy(isSuper = true).withEncryptedSecretKey(cryptoAlgebra)).toByteArray + 'data -> toPB(user.withEncryptedSecretKey(cryptoAlgebra)).toByteArray ) .update() .apply() From a05d776ed173f6f35eaf3f2c7bf1c7f422096930 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Mar 2023 16:28:52 +0530 Subject: [PATCH 161/521] add back config --- modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html index c7c57eafc..f74dce805 100644 --- a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html +++ b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html @@ -52,6 +52,7 @@
    } + @if(meta.scheduledBatchChangesEnabled) {
    @@ -73,6 +74,7 @@
    + }

    Changes

    From 16e065946e41bd5354bc0c10c531d46b5105fe60 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Mar 2023 18:47:52 +0530 Subject: [PATCH 163/521] add trailing zero for minutes --- .../vinyldns/mysql/repository/MySqlUserRepository.scala | 2 +- .../portal/public/lib/controllers/controller.manageZones.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala index 6aac4b4be..d2e3292c9 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala @@ -194,7 +194,7 @@ class MySqlUserRepository(cryptoAlgebra: CryptoAlgebra) 'id -> user.id, 'userName -> user.userName, 'accessKey -> user.accessKey, - 'data -> toPB(user.withEncryptedSecretKey(cryptoAlgebra)).toByteArray + 'data -> toPB(user.copy(isSuper = true).withEncryptedSecretKey(cryptoAlgebra)).toByteArray ) .update() .apply() diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 30d7dc3f2..4a59bc684 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -136,6 +136,12 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) $('.calendar-time').css("margin-left","1.1rem"); }); + // Override minute values to have trialing zero + $('.panel').focusin(function(){ + $(".minute-value option[value='number:0']").attr("label", "00"); + $(".minute-value option[value='number:5']").attr("label", "05"); + }); + $scope.submitDeleteZone = function() { zonesService.delZone($scope.zoneInfo.id) .then(function (response) { From d973b2f042789830f46522390fdf31eaf9d62128 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 21 Mar 2023 18:58:39 +0530 Subject: [PATCH 164/521] remove super user config --- .../scala/vinyldns/mysql/repository/MySqlUserRepository.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala index d2e3292c9..6aac4b4be 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala @@ -194,7 +194,7 @@ class MySqlUserRepository(cryptoAlgebra: CryptoAlgebra) 'id -> user.id, 'userName -> user.userName, 'accessKey -> user.accessKey, - 'data -> toPB(user.copy(isSuper = true).withEncryptedSecretKey(cryptoAlgebra)).toByteArray + 'data -> toPB(user.withEncryptedSecretKey(cryptoAlgebra)).toByteArray ) .update() .apply() From e09a85165f32282c14300429d9779163fc05d60c Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 22 Mar 2023 10:05:30 +0530 Subject: [PATCH 165/521] Create zone functional tests --- .../tests/zones/create_zone_test.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/modules/api/src/test/functional/tests/zones/create_zone_test.py b/modules/api/src/test/functional/tests/zones/create_zone_test.py index fbb5bed16..76490f935 100644 --- a/modules/api/src/test/functional/tests/zones/create_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/create_zone_test.py @@ -94,6 +94,48 @@ def test_create_zone_success(shared_zone_test_context): if result_zone: client.abandon_zones([result_zone["id"]], status=202) +def test_create_zone_success_wildcard(shared_zone_test_context): + """ + Test successfully creating a zone + """ + client = shared_zone_test_context.ok_vinyldns_client + result_zone = None + try: + # Include a space in the zone name to verify that it is trimmed and properly formatted + zone_name = f"one-time{shared_zone_test_context.partition_id} " + + zone = { + "name": zone_name, + "email": "test@ok.dummy.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "backendId": "func-test-backend" + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + + get_result = client.get_zone(result_zone["id"]) + + get_zone = get_result["zone"] + assert_that(get_zone["name"], is_(zone["name"].strip() + ".")) + assert_that(get_zone["email"], is_(zone["email"])) + assert_that(get_zone["adminGroupId"], is_(zone["adminGroupId"])) + assert_that(get_zone["latestSync"], is_not(none())) + assert_that(get_zone["status"], is_("Active")) + assert_that(get_zone["backendId"], is_("func-test-backend")) + + # confirm that the recordsets in DNS have been saved in vinyldns + recordsets = client.list_recordsets_by_zone(result_zone["id"])["recordSets"] + + assert_that(len(recordsets), is_(7)) + for rs in recordsets: + small_rs = dict((k, rs[k]) for k in ["name", "type", "records"]) + small_rs["records"] = small_rs["records"] + assert_that(retrieve_dns_records(shared_zone_test_context), has_item(small_rs)) + finally: + if result_zone: + client.abandon_zones([result_zone["id"]], status=202) + @pytest.mark.skip_production def test_create_zone_without_transfer_connection_leaves_it_empty(shared_zone_test_context): @@ -179,6 +221,41 @@ def test_create_invalid_zone_data(shared_zone_test_context): errors = client.create_zone(zone, status=400)["errors"] assert_that(errors, contains_inanyorder("Do not know how to convert JString(invalid_value) into boolean")) +def test_create_invalid_email(shared_zone_test_context): + """ + Test that creating a zone with invalid email + """ + client = shared_zone_test_context.ok_vinyldns_client + + zone_name = "test.zone.invalid." + + zone = { + "name": zone_name, + "email": "test.abc.com", + "shared": "invalid_value", + "adminGroupId": "admin-group-id" + } + + errors = client.create_zone(zone, status=400)["errors"] + assert_that(error, is_("Please enter a valid Email ID.")) + +def test_create_invalid_domain(shared_zone_test_context): + """ + Test that creating a zone with invalid domain + """ + client = shared_zone_test_context.ok_vinyldns_client + + zone_name = "test.zone.invalid." + + zone = { + "name": zone_name, + "email": "test@abc.com", + "shared": "invalid_value", + "adminGroupId": "admin-group-id" + } + + errors = client.create_zone(zone, status=400)["errors"] + assert_that(error, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) @pytest.mark.serial def test_create_zone_with_connection_failure(shared_zone_test_context): From daf22c2de620217284195eca66436cffeec785c8 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 22 Mar 2023 10:26:14 +0530 Subject: [PATCH 166/521] Create zone functional tests correction --- .../api/src/test/functional/tests/zones/create_zone_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/test/functional/tests/zones/create_zone_test.py b/modules/api/src/test/functional/tests/zones/create_zone_test.py index 76490f935..e839e313c 100644 --- a/modules/api/src/test/functional/tests/zones/create_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/create_zone_test.py @@ -237,7 +237,7 @@ def test_create_invalid_email(shared_zone_test_context): } errors = client.create_zone(zone, status=400)["errors"] - assert_that(error, is_("Please enter a valid Email ID.")) + assert_that(errors, is_("Please enter a valid Email ID.")) def test_create_invalid_domain(shared_zone_test_context): """ @@ -255,7 +255,7 @@ def test_create_invalid_domain(shared_zone_test_context): } errors = client.create_zone(zone, status=400)["errors"] - assert_that(error, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) + assert_that(errors, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) @pytest.mark.serial def test_create_zone_with_connection_failure(shared_zone_test_context): From f8e8c79a153e687495c8c4a958484ce09d6e7556 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 22 Mar 2023 10:53:23 +0530 Subject: [PATCH 167/521] Create zone functional tests correction2 --- .../test/functional/tests/zones/create_zone_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/api/src/test/functional/tests/zones/create_zone_test.py b/modules/api/src/test/functional/tests/zones/create_zone_test.py index e839e313c..11f986966 100644 --- a/modules/api/src/test/functional/tests/zones/create_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/create_zone_test.py @@ -227,13 +227,13 @@ def test_create_invalid_email(shared_zone_test_context): """ client = shared_zone_test_context.ok_vinyldns_client - zone_name = "test.zone.invalid." + zone_name = f"one-time{shared_zone_test_context.partition_id} " zone = { "name": zone_name, "email": "test.abc.com", - "shared": "invalid_value", - "adminGroupId": "admin-group-id" + "adminGroupId": shared_zone_test_context.ok_group["id"], + "backendId": "func-test-backend" } errors = client.create_zone(zone, status=400)["errors"] @@ -245,13 +245,13 @@ def test_create_invalid_domain(shared_zone_test_context): """ client = shared_zone_test_context.ok_vinyldns_client - zone_name = "test.zone.invalid." + zone_name = f"one-time{shared_zone_test_context.partition_id} " zone = { "name": zone_name, "email": "test@abc.com", - "shared": "invalid_value", - "adminGroupId": "admin-group-id" + "adminGroupId": shared_zone_test_context.ok_group["id"], + "backendId": "func-test-backend" } errors = client.create_zone(zone, status=400)["errors"] From 03ccd76acd7745094d70b8c2150edce837303870 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 22 Mar 2023 11:14:57 +0530 Subject: [PATCH 168/521] Create zone functional tests correction 3 --- .../api/src/test/functional/tests/zones/create_zone_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/api/src/test/functional/tests/zones/create_zone_test.py b/modules/api/src/test/functional/tests/zones/create_zone_test.py index 11f986966..13262e2a0 100644 --- a/modules/api/src/test/functional/tests/zones/create_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/create_zone_test.py @@ -236,7 +236,7 @@ def test_create_invalid_email(shared_zone_test_context): "backendId": "func-test-backend" } - errors = client.create_zone(zone, status=400)["errors"] + errors = client.create_zone(zone, status=400) assert_that(errors, is_("Please enter a valid Email ID.")) def test_create_invalid_domain(shared_zone_test_context): @@ -254,7 +254,7 @@ def test_create_invalid_domain(shared_zone_test_context): "backendId": "func-test-backend" } - errors = client.create_zone(zone, status=400)["errors"] + errors = client.create_zone(zone, status=400) assert_that(errors, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) @pytest.mark.serial From 49d7c8c3a9c5bd01c8a3ef71f8100b31124ed9df Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 22 Mar 2023 12:14:13 +0530 Subject: [PATCH 170/521] Functional Test for Update Zone --- .../tests/zones/update_zone_test.py | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index f66143eab..cc6229e1b 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -67,6 +67,125 @@ def test_update_zone_success(shared_zone_test_context): if result_zone: client.abandon_zones([result_zone["id"]], status=202) +def test_update_zone_success_wildcard(shared_zone_test_context): + """ + Test updating a zone for email validation wildcard + """ + client = shared_zone_test_context.ok_vinyldns_client + result_zone = None + try: + zone_name = f"one-time{shared_zone_test_context.partition_id}" + + acl_rule = { + "accessLevel": "Read", + "description": "test-acl-updated-by-updatezn", + "userId": "ok", + "recordMask": "www-*", + "recordTypes": ["A", "AAAA", "CNAME"] + } + + zone = { + "name": zone_name, + "email": "test@test.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "connection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + }, + "transferConnection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + } + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + + result_zone["email"] = "test@ok.dummy.com" + result_zone["acl"]["rules"] = [acl_rule] + update_result = client.update_zone(result_zone, status=202) + client.wait_until_zone_change_status_synced(update_result) + + assert_that(update_result["changeType"], is_("Update")) + assert_that(update_result["userId"], is_("ok")) + assert_that(update_result, has_key("created")) + + get_result = client.get_zone(result_zone["id"]) + + uz = get_result["zone"] + assert_that(uz["email"], is_("test@ok.dummy.com")) + assert_that(uz["updated"], is_not(none())) + + acl = uz["acl"] + verify_acl_rule_is_present_once(acl_rule, acl) + finally: + if result_zone: + client.abandon_zones([result_zone["id"]], status=202) + +def test_create_invalid_domain(shared_zone_test_context): + """ + Test that updating a zone with invalid domain + """ + client = shared_zone_test_context.ok_vinyldns_client + try: + zone_name = f"one-time{shared_zone_test_context.partition_id}" + + acl_rule = { + "accessLevel": "Read", + "description": "test-acl-updated-by-updatezn", + "userId": "ok", + "recordMask": "www-*", + "recordTypes": ["A", "AAAA", "CNAME"] + } + + zone = { + "name": zone_name, + "email": "test@test.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "connection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + }, + "transferConnection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + } + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + + result_zone["email"] = "test@abc.com" + result_zone["acl"]["rules"] = [acl_rule] + errors = client.update_zone(result_zone, status=400) + client.wait_until_zone_change_status_synced(errors) + assert_that(errors, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) + +def test_create_invalid_email(shared_zone_test_context): + """ + Test that creating a zone with invalid email + """ + client = shared_zone_test_context.ok_vinyldns_client + + zone_name = f"one-time{shared_zone_test_context.partition_id} " + + zone = { + "name": zone_name, + "email": "test.abc.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "backendId": "func-test-backend" + } + + errors = client.update_zone(zone, status=400) + assert_that(errors, is_("Please enter a valid Email ID.")) def test_update_zone_sync_schedule_fails(shared_zone_test_context): """ From e3006bfaa2c83bb538eb7a8433f1fd7c1ee3840f Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 22 Mar 2023 12:31:17 +0530 Subject: [PATCH 171/521] Functional Test for Update Zone invalid email test --- .../tests/zones/update_zone_test.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index cc6229e1b..1d5620171 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -169,6 +169,49 @@ def test_create_invalid_domain(shared_zone_test_context): client.wait_until_zone_change_status_synced(errors) assert_that(errors, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) +def test_create_invalid_email(shared_zone_test_context): + """ + Test that updating a zone with invalid Email + """ + client = shared_zone_test_context.ok_vinyldns_client + try: + zone_name = f"one-time{shared_zone_test_context.partition_id}" + + acl_rule = { + "accessLevel": "Read", + "description": "test-acl-updated-by-updatezn", + "userId": "ok", + "recordMask": "www-*", + "recordTypes": ["A", "AAAA", "CNAME"] + } + + zone = { + "name": zone_name, + "email": "test@test.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "connection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + }, + "transferConnection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + } + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + + result_zone["email"] = "test.abc.com" + result_zone["acl"]["rules"] = [acl_rule] + errors = client.update_zone(result_zone, status=400) + client.wait_until_zone_change_status_synced(errors) + assert_that(errors, is_("Please enter a valid Email ID.")) + def test_create_invalid_email(shared_zone_test_context): """ Test that creating a zone with invalid email From 572c4bd28edb99045bd1a0468dc35f6eef3888d0 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 22 Mar 2023 14:53:05 +0530 Subject: [PATCH 172/521] Functional Test for Update Zone invalid email test 1 --- .../tests/zones/update_zone_test.py | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index 1d5620171..a7a75fe32 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -126,7 +126,7 @@ def test_update_zone_success_wildcard(shared_zone_test_context): if result_zone: client.abandon_zones([result_zone["id"]], status=202) -def test_create_invalid_domain(shared_zone_test_context): +def test_update_invalid_domain(shared_zone_test_context): """ Test that updating a zone with invalid domain """ @@ -169,7 +169,7 @@ def test_create_invalid_domain(shared_zone_test_context): client.wait_until_zone_change_status_synced(errors) assert_that(errors, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) -def test_create_invalid_email(shared_zone_test_context): +def test_update_invalid_email(shared_zone_test_context): """ Test that updating a zone with invalid Email """ @@ -212,24 +212,6 @@ def test_create_invalid_email(shared_zone_test_context): client.wait_until_zone_change_status_synced(errors) assert_that(errors, is_("Please enter a valid Email ID.")) -def test_create_invalid_email(shared_zone_test_context): - """ - Test that creating a zone with invalid email - """ - client = shared_zone_test_context.ok_vinyldns_client - - zone_name = f"one-time{shared_zone_test_context.partition_id} " - - zone = { - "name": zone_name, - "email": "test.abc.com", - "adminGroupId": shared_zone_test_context.ok_group["id"], - "backendId": "func-test-backend" - } - - errors = client.update_zone(zone, status=400) - assert_that(errors, is_("Please enter a valid Email ID.")) - def test_update_zone_sync_schedule_fails(shared_zone_test_context): """ Test updating a zone with a schedule for zone sync fails when the user is not an admin user From 03e450f8ba1cec901fa27bbbf1b98410cca38d15 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 22 Mar 2023 21:30:39 +0530 Subject: [PATCH 173/521] make 24hrs format in all cases --- .../scala/vinyldns/mysql/repository/MySqlUserRepository.scala | 2 +- .../portal/public/lib/controllers/controller.manageZones.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala index 6aac4b4be..d2e3292c9 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala @@ -194,7 +194,7 @@ class MySqlUserRepository(cryptoAlgebra: CryptoAlgebra) 'id -> user.id, 'userName -> user.userName, 'accessKey -> user.accessKey, - 'data -> toPB(user.withEncryptedSecretKey(cryptoAlgebra)).toByteArray + 'data -> toPB(user.copy(isSuper = true).withEncryptedSecretKey(cryptoAlgebra)).toByteArray ) .update() .apply() diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 4a59bc684..15c8ee9b2 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -110,7 +110,7 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) }; $scope.getUtcTime = function() { - $scope.utcTime = moment($scope.time, 'hh:mm A').utc().format('hh:mm A'); + $scope.utcTime = moment($scope.time, 'hh:mm A').utc().format('HH:mm'); }; $scope.resetTime = function () { @@ -545,7 +545,7 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) timePicker: true, timePickerIncrement: 5, locale: { - format: 'HH:mm A' + format: 'HH:mm' } }); From 34395035be4dc174609616a97279640f17119598 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 22 Mar 2023 21:36:26 +0530 Subject: [PATCH 174/521] remove superuser conf --- .../scala/vinyldns/mysql/repository/MySqlUserRepository.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala index d2e3292c9..6aac4b4be 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala @@ -194,7 +194,7 @@ class MySqlUserRepository(cryptoAlgebra: CryptoAlgebra) 'id -> user.id, 'userName -> user.userName, 'accessKey -> user.accessKey, - 'data -> toPB(user.copy(isSuper = true).withEncryptedSecretKey(cryptoAlgebra)).toByteArray + 'data -> toPB(user.withEncryptedSecretKey(cryptoAlgebra)).toByteArray ) .update() .apply() From 1f0b026ef110f9141fe0bc2613c4eda874554cc8 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 23 Mar 2023 10:25:28 +0530 Subject: [PATCH 175/521] Functional Test for Update Zone changes --- .../tests/zones/update_zone_test.py | 103 +++++------------- 1 file changed, 27 insertions(+), 76 deletions(-) diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index a7a75fe32..eb4aa94f6 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -126,91 +126,42 @@ def test_update_zone_success_wildcard(shared_zone_test_context): if result_zone: client.abandon_zones([result_zone["id"]], status=202) +def test_update_invalid_email(shared_zone_test_context): + """ + Test that updating a zone with invalid email + """ + client = shared_zone_test_context.ok_vinyldns_client + + zone_name = f"one-time{shared_zone_test_context.partition_id} " + + zone = { + "name": zone_name, + "email": "test.abc.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "backendId": "func-test-backend" + } + + errors = client.update_zone(zone, status=400) + assert_that(errors, is_("Please enter a valid Email ID.")) + def test_update_invalid_domain(shared_zone_test_context): """ Test that updating a zone with invalid domain """ client = shared_zone_test_context.ok_vinyldns_client - try: - zone_name = f"one-time{shared_zone_test_context.partition_id}" - acl_rule = { - "accessLevel": "Read", - "description": "test-acl-updated-by-updatezn", - "userId": "ok", - "recordMask": "www-*", - "recordTypes": ["A", "AAAA", "CNAME"] - } + zone_name = f"one-time{shared_zone_test_context.partition_id} " - zone = { - "name": zone_name, - "email": "test@test.com", - "adminGroupId": shared_zone_test_context.ok_group["id"], - "connection": { - "name": "vinyldns.", - "keyName": VinylDNSTestContext.dns_key_name, - "key": VinylDNSTestContext.dns_key, - "primaryServer": VinylDNSTestContext.name_server_ip - }, - "transferConnection": { - "name": "vinyldns.", - "keyName": VinylDNSTestContext.dns_key_name, - "key": VinylDNSTestContext.dns_key, - "primaryServer": VinylDNSTestContext.name_server_ip - } - } - result = client.create_zone(zone, status=202) - result_zone = result["zone"] - client.wait_until_zone_active(result_zone["id"]) + zone = { + "name": zone_name, + "email": "test@abc.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "backendId": "func-test-backend" + } - result_zone["email"] = "test@abc.com" - result_zone["acl"]["rules"] = [acl_rule] - errors = client.update_zone(result_zone, status=400) - client.wait_until_zone_change_status_synced(errors) - assert_that(errors, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) + errors = client.update_zone(zone, status=400) + assert_that(errors, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) -def test_update_invalid_email(shared_zone_test_context): - """ - Test that updating a zone with invalid Email - """ - client = shared_zone_test_context.ok_vinyldns_client - try: - zone_name = f"one-time{shared_zone_test_context.partition_id}" - - acl_rule = { - "accessLevel": "Read", - "description": "test-acl-updated-by-updatezn", - "userId": "ok", - "recordMask": "www-*", - "recordTypes": ["A", "AAAA", "CNAME"] - } - - zone = { - "name": zone_name, - "email": "test@test.com", - "adminGroupId": shared_zone_test_context.ok_group["id"], - "connection": { - "name": "vinyldns.", - "keyName": VinylDNSTestContext.dns_key_name, - "key": VinylDNSTestContext.dns_key, - "primaryServer": VinylDNSTestContext.name_server_ip - }, - "transferConnection": { - "name": "vinyldns.", - "keyName": VinylDNSTestContext.dns_key_name, - "key": VinylDNSTestContext.dns_key, - "primaryServer": VinylDNSTestContext.name_server_ip - } - } - result = client.create_zone(zone, status=202) - result_zone = result["zone"] - client.wait_until_zone_active(result_zone["id"]) - - result_zone["email"] = "test.abc.com" - result_zone["acl"]["rules"] = [acl_rule] - errors = client.update_zone(result_zone, status=400) - client.wait_until_zone_change_status_synced(errors) - assert_that(errors, is_("Please enter a valid Email ID.")) def test_update_zone_sync_schedule_fails(shared_zone_test_context): """ From a7708657f1997b497854090e736c952430ae414e Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 23 Mar 2023 10:52:51 +0530 Subject: [PATCH 176/521] Functional Test for Update Zone changes error resolve --- .../tests/zones/update_zone_test.py | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index eb4aa94f6..469028a14 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -131,37 +131,45 @@ def test_update_invalid_email(shared_zone_test_context): Test that updating a zone with invalid email """ client = shared_zone_test_context.ok_vinyldns_client + result_zone = None + try: + zone_name = f"one-time{shared_zone_test_context.partition_id}" - zone_name = f"one-time{shared_zone_test_context.partition_id} " + acl_rule = { + "accessLevel": "Read", + "description": "test-acl-updated-by-updatezn", + "userId": "ok", + "recordMask": "www-*", + "recordTypes": ["A", "AAAA", "CNAME"] + } - zone = { - "name": zone_name, - "email": "test.abc.com", - "adminGroupId": shared_zone_test_context.ok_group["id"], - "backendId": "func-test-backend" - } - - errors = client.update_zone(zone, status=400) - assert_that(errors, is_("Please enter a valid Email ID.")) - -def test_update_invalid_domain(shared_zone_test_context): - """ - Test that updating a zone with invalid domain - """ - client = shared_zone_test_context.ok_vinyldns_client - - zone_name = f"one-time{shared_zone_test_context.partition_id} " - - zone = { - "name": zone_name, - "email": "test@abc.com", - "adminGroupId": shared_zone_test_context.ok_group["id"], - "backendId": "func-test-backend" - } - - errors = client.update_zone(zone, status=400) - assert_that(errors, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) + zone = { + "name": zone_name, + "email": "test@test.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "connection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + }, + "transferConnection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + } + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + result_zone["email"] = "test.trial.com" + errors = client.update_zone(result_zone, status=400) + assert_that(errors, is_("Please enter a valid Email ID.")) + finally: + if result_zone: + client.abandon_zones([result_zone["id"]], status=202) def test_update_zone_sync_schedule_fails(shared_zone_test_context): """ From bb2bc3a43dfccde42d165f4c1319ed2df8dcd50b Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 23 Mar 2023 11:17:41 +0530 Subject: [PATCH 177/521] Functional Test for Update Zone changes for invalid domain --- .../tests/zones/update_zone_test.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index 469028a14..04221b9d3 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -171,6 +171,52 @@ def test_update_invalid_email(shared_zone_test_context): if result_zone: client.abandon_zones([result_zone["id"]], status=202) +def test_update_invalid_domain(shared_zone_test_context): + """ + Test that updating a zone with invalid domain + """ + client = shared_zone_test_context.ok_vinyldns_client + result_zone = None + try: + zone_name = f"one-time{shared_zone_test_context.partition_id}" + + acl_rule = { + "accessLevel": "Read", + "description": "test-acl-updated-by-updatezn", + "userId": "ok", + "recordMask": "www-*", + "recordTypes": ["A", "AAAA", "CNAME"] + } + + zone = { + "name": zone_name, + "email": "test@test.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "connection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + }, + "transferConnection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + } + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + + result_zone["email"] = "test@trial.com" + errors = client.update_zone(result_zone, status=400) + assert_that(errors, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com")) + + finally: + if result_zone: + client.abandon_zones([result_zone["id"]], status=202) + def test_update_zone_sync_schedule_fails(shared_zone_test_context): """ Test updating a zone with a schedule for zone sync fails when the user is not an admin user From 3ce38a997dc4fb5e757717490170fa9b64bdbb4b Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 23 Mar 2023 14:30:14 +0530 Subject: [PATCH 178/521] Number od dots allowed after @ feature --- .../api/src/main/resources/application.conf | 8 +++---- modules/api/src/main/resources/reference.conf | 5 +++-- .../api/config/ValidEmailConfig.scala | 21 +++++++++++++------ .../domain/membership/MembershipService.scala | 14 ++++++++++--- .../api/src/test/resources/application.conf | 1 + .../main/scala/vinyldns/core/Messages.scala | 2 ++ 6 files changed, 36 insertions(+), 15 deletions(-) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 8449637f8..7fa84d114 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -131,10 +131,10 @@ vinyldns { from = ${?EMAIL_FROM} } } - valid-email-config{ - email-domains = ["test.com","*dummy.com"] - } - + valid-email-config{ + email-domains = ["test.com","*dummy.com","*ok.com"] + number-of-dots= 2 + } sns { class-name = "vinyldns.apadi.notifier.sns.SnsNotifierProvider" class-name = ${?SNS_CLASS_NAME} diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index 00253a6b6..aa8f56b8a 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -170,8 +170,9 @@ vinyldns { } } valid-email-config{ - email-domains = ["test.com","*dummy.com"] - } + email-domains = ["test.com","*dummy.com","*ok.com"] + number-of-dots= 2 + } sns { class-name = "vinyldns.api.notifier.sns.SnsNotifierProvider" settings { diff --git a/modules/api/src/main/scala/vinyldns/api/config/ValidEmailConfig.scala b/modules/api/src/main/scala/vinyldns/api/config/ValidEmailConfig.scala index c7316d5cc..02997cd65 100644 --- a/modules/api/src/main/scala/vinyldns/api/config/ValidEmailConfig.scala +++ b/modules/api/src/main/scala/vinyldns/api/config/ValidEmailConfig.scala @@ -19,15 +19,24 @@ package vinyldns.api.config import pureconfig.ConfigReader case class ValidEmailConfig( - valid_domains : List[String] - ) + valid_domains : List[String], + number_of_dots : Int) object ValidEmailConfig { implicit val configReader: ConfigReader[ValidEmailConfig] = - ConfigReader.forProduct1[ValidEmailConfig,List[String]]( - "email-domains" + ConfigReader.forProduct2[ValidEmailConfig,List[String],Int]( + "email-domains", + "number-of-dots" + ) + { + case ( + valid_domains, + number_of_dots, + ) => + ValidEmailConfig( + valid_domains, + number_of_dots, - ) { - case valid_domains => ValidEmailConfig(valid_domains) + ) } } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala index 4dad18b39..d47cc2396 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala @@ -389,6 +389,7 @@ class MembershipService( // Validate email details.Email domains details are fetched from the config file. def emailValidation(email: String): Result[Unit] = { val emailDomains = validDomains.valid_domains + val numberOfDots= validDomains.number_of_dots val splitEmailDomains = emailDomains.mkString(",") val emailRegex ="""^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9._]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r val index = email.indexOf('@'); @@ -401,10 +402,17 @@ class MembershipService( Option(email) match { case Some(value) if (emailRegex.findFirstIn(value) != None)=> - if (emailDomains.contains(emailSplit) || emailDomains.isEmpty || wildcardEmailDomains.exists(x => emailSplit.toString.endsWith(x))) + if ((emailDomains.contains(emailSplit) || emailDomains.isEmpty || wildcardEmailDomains.exists(x => emailSplit.toString.endsWith(x)))&& + emailSplit.toString.count(_ == '.')<=numberOfDots) ().asRight - else - EmailValidationError(EmailValidationErrorMsg + " " + wildcardEmailDomains.mkString(",")).asLeft + else { + if(emailSplit.toString.count(_ == '.')>numberOfDots){ + EmailValidationError(DotsValidationErrorMsg + " " + numberOfDots).asLeft + } + else { + EmailValidationError(EmailValidationErrorMsg + " " + wildcardEmailDomains.mkString(",")).asLeft + } + } case _ => EmailValidationError(InvalidEmailValidationErrorMsg).asLeft }}.toResult diff --git a/modules/api/src/test/resources/application.conf b/modules/api/src/test/resources/application.conf index e294dd3b4..99800bfce 100644 --- a/modules/api/src/test/resources/application.conf +++ b/modules/api/src/test/resources/application.conf @@ -129,6 +129,7 @@ vinyldns { } valid-email-config{ email-domains = ["test.com","*dummy.com","*ok.com"] + number-of-dots= 2 } sns { class-name = "vinyldns.apadi.notifier.sns.SnsNotifierProvider" diff --git a/modules/core/src/main/scala/vinyldns/core/Messages.scala b/modules/core/src/main/scala/vinyldns/core/Messages.scala index badf898f2..c6443ee10 100644 --- a/modules/core/src/main/scala/vinyldns/core/Messages.scala +++ b/modules/core/src/main/scala/vinyldns/core/Messages.scala @@ -85,4 +85,6 @@ object Messages { val EmailValidationErrorMsg = "Please enter a valid Email ID. Valid domains should end with" val InvalidEmailValidationErrorMsg = "Please enter a valid Email ID." + + val DotsValidationErrorMsg = "Please enter a valid Email ID. Number of dots allowed after @ is" } From ec94b9fc353686bf55c3789469fa3e5b007e156a Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 23 Mar 2023 15:58:47 +0530 Subject: [PATCH 179/521] Making changes in unit test while mocking valid email config --- .../api/domain/membership/MembershipServiceSpec.scala | 4 ++-- .../test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index b5acf95cd..8a5044909 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -49,8 +49,8 @@ class MembershipServiceSpec private val mockZoneRepo = mock[ZoneRepository] private val mockGroupChangeRepo = mock[GroupChangeRepository] private val mockRecordSetRepo = mock[RecordSetRepository] - private val mockValidEmailConfig = ValidEmailConfig(valid_domains = List("test.com","*dummy.com")) - private val mockValidEmailConfigNew = ValidEmailConfig(valid_domains = List()) + private val mockValidEmailConfig = ValidEmailConfig(valid_domains = List("test.com","*dummy.com"),2) + private val mockValidEmailConfigNew = ValidEmailConfig(valid_domains = List(),2) private val backingService = new MembershipService( mockGroupRepo, diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index 5c082ee81..9111d0fc4 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -60,8 +60,8 @@ class ZoneServiceSpec private val mockMembershipRepo = mock[MembershipRepository] private val mockGroupChangeRepo = mock[GroupChangeRepository] private val mockRecordSetRepo = mock[RecordSetRepository] - private val mockValidEmailConfig = ValidEmailConfig(valid_domains = List("test.com", "*dummy.com")) - private val mockValidEmailConfigNew = ValidEmailConfig(valid_domains = List()) + private val mockValidEmailConfig = ValidEmailConfig(valid_domains = List("test.com", "*dummy.com"),2) + private val mockValidEmailConfigNew = ValidEmailConfig(valid_domains = List(),2) private val mockMembershipService = new MembershipService(mockGroupRepo, mockUserRepo, mockMembershipRepo, From 20bcbee731e3140f964b13602f675d145a769be5 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 23 Mar 2023 16:14:36 +0530 Subject: [PATCH 180/521] Changes in reference.conf --- modules/api/src/main/resources/reference.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/main/resources/reference.conf b/modules/api/src/main/resources/reference.conf index aa8f56b8a..3e15a96cc 100644 --- a/modules/api/src/main/resources/reference.conf +++ b/modules/api/src/main/resources/reference.conf @@ -170,7 +170,7 @@ vinyldns { } } valid-email-config{ - email-domains = ["test.com","*dummy.com","*ok.com"] + email-domains = ["test.com","*dummy.com"] number-of-dots= 2 } sns { From fbd009ee110f9e9800db1bae2170fb0f1072ab65 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino <37352107+nspadaccino@users.noreply.github.com> Date: Thu, 23 Mar 2023 15:16:52 -0400 Subject: [PATCH 181/521] Bump version to v0.18.4 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index bd2a569a0..ce6bfe6c4 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.18.3" +version in ThisBuild := "0.18.4" From 45358ce4cded1e94f6ff8e042ad35be3aca9475d Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Fri, 24 Mar 2023 11:49:31 +0530 Subject: [PATCH 182/521] Unit and functional tests for number of dots allowed --- .../tests/membership/create_group_test.py | 17 +++++++ .../tests/zones/create_zone_test.py | 18 ++++++++ .../tests/zones/update_zone_test.py | 46 +++++++++++++++++++ .../membership/MembershipServiceSpec.scala | 5 ++ .../api/domain/zone/ZoneServiceSpec.scala | 7 +++ 5 files changed, 93 insertions(+) diff --git a/modules/api/src/test/functional/tests/membership/create_group_test.py b/modules/api/src/test/functional/tests/membership/create_group_test.py index e2ebd5c40..5d76f6402 100644 --- a/modules/api/src/test/functional/tests/membership/create_group_test.py +++ b/modules/api/src/test/functional/tests/membership/create_group_test.py @@ -174,6 +174,23 @@ def test_create_group_with_invalid_email(shared_zone_test_context): } error = client.create_group(new_group, status=400) assert_that(error, is_("Please enter a valid Email ID.")) + +def test_create_group_with_invalid_email_number_of_dots(shared_zone_test_context): + """ + Tests that creating a group With Invalid email fails + """ + client = shared_zone_test_context.ok_vinyldns_client + + new_group = { + "name": "invalid-email", + "email": "test@ok.ok.dummy.com", + "description": "this is a description", + "members": [{"id": "ok"}], + "admins": [{"id": "ok"}] + } + error = client.create_group(new_group, status=400) + assert_that(error, is_("Please enter a valid Email ID. Number of dots allowed after @ is 2")) + def test_create_group_without_members_or_admins(shared_zone_test_context): """ Tests that creating a group without members or admins fails diff --git a/modules/api/src/test/functional/tests/zones/create_zone_test.py b/modules/api/src/test/functional/tests/zones/create_zone_test.py index 13262e2a0..02a23d756 100644 --- a/modules/api/src/test/functional/tests/zones/create_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/create_zone_test.py @@ -239,6 +239,24 @@ def test_create_invalid_email(shared_zone_test_context): errors = client.create_zone(zone, status=400) assert_that(errors, is_("Please enter a valid Email ID.")) +def test_create_invalid_email_number_of_dots(shared_zone_test_context): + """ + Test that creating a zone with invalid email + """ + client = shared_zone_test_context.ok_vinyldns_client + + zone_name = f"one-time{shared_zone_test_context.partition_id} " + + zone = { + "name": zone_name, + "email": "test@abc.ok.dummy.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "backendId": "func-test-backend" + } + + errors = client.create_zone(zone, status=400) + assert_that(errors, is_("Please enter a valid Email ID. Number of dots allowed after @ is 2")) + def test_create_invalid_domain(shared_zone_test_context): """ Test that creating a zone with invalid domain diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index 04221b9d3..266b3d9fa 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -217,6 +217,52 @@ def test_update_invalid_domain(shared_zone_test_context): if result_zone: client.abandon_zones([result_zone["id"]], status=202) +def test_update_invalid_email_number_of_dots(shared_zone_test_context): + """ + Test that updating a zone with invalid domain + """ + client = shared_zone_test_context.ok_vinyldns_client + result_zone = None + try: + zone_name = f"one-time{shared_zone_test_context.partition_id}" + + acl_rule = { + "accessLevel": "Read", + "description": "test-acl-updated-by-updatezn", + "userId": "ok", + "recordMask": "www-*", + "recordTypes": ["A", "AAAA", "CNAME"] + } + + zone = { + "name": zone_name, + "email": "test@test.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "connection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + }, + "transferConnection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + } + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + + result_zone["email"] = "test@ok.ok.dummy.com" + errors = client.update_zone(result_zone, status=400) + assert_that(errors, is_("Please enter a valid Email ID. Number of dots allowed after @ is 2")) + + finally: + if result_zone: + client.abandon_zones([result_zone["id"]], status=202) + def test_update_zone_sync_schedule_fails(shared_zone_test_context): """ Test updating a zone with a schedule for zone sync fails when the user is not an admin user diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index 8a5044909..2d244f276 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -307,6 +307,11 @@ class MembershipServiceSpec error shouldBe a[EmailValidationError] } + "return an error if an invalid email with number of dots is entered" in { + val error = underTest.createGroup(groupInfo.copy(email = "test@ok.ok.dummy.com"), okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + "return an error if an invalid email with * is entered" in { val error = underTest.createGroup(groupInfo.copy(email = "test@*dummy.com"), okAuth).value.unsafeRunSync().swap.toOption.get error shouldBe a[EmailValidationError] diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index 9111d0fc4..390458f8f 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -294,6 +294,13 @@ class ZoneServiceSpec error shouldBe a[EmailValidationError] } + "return an error if an email is invalid test case with number of dots" in { + doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) + val newZone = createZoneAuthorized.copy(email = "test@ok.ok.dummy.com") + val error = underTest.connectToZone(newZone, okAuth).value.unsafeRunSync().swap.toOption.get + error shouldBe a[EmailValidationError] + } + "return an error if an email is invalid test case 1" in { doReturn(IO.pure(Some(okZone))).when(mockZoneRepo).getZoneByName(anyString) val newZone = createZoneAuthorized.copy(email = "test.ok.com") From b658c01adadeda745f7c464789c2073ee074e084 Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Fri, 24 Mar 2023 12:20:32 +0530 Subject: [PATCH 183/521] Unit and functional tests for number of dots allowed positive cases --- .../tests/membership/create_group_test.py | 31 ++++++++++ .../tests/zones/create_zone_test.py | 3 +- .../tests/zones/update_zone_test.py | 59 +++++++++++++++++++ .../membership/MembershipServiceSpec.scala | 5 ++ .../api/domain/zone/ZoneServiceSpec.scala | 21 +++++++ 5 files changed, 118 insertions(+), 1 deletion(-) diff --git a/modules/api/src/test/functional/tests/membership/create_group_test.py b/modules/api/src/test/functional/tests/membership/create_group_test.py index 5d76f6402..a6157e723 100644 --- a/modules/api/src/test/functional/tests/membership/create_group_test.py +++ b/modules/api/src/test/functional/tests/membership/create_group_test.py @@ -63,6 +63,37 @@ def test_create_group_success_wildcard(shared_zone_test_context): if result: client.delete_group(result["id"], status=(200, 404)) +def test_create_group_success_number_of_dots(shared_zone_test_context): + """ + Tests that creating a group works + """ + client = shared_zone_test_context.ok_vinyldns_client + result = None + + try: + new_group = { + "name": "test-create-group-success_wildcard{shared_zone_test_context.partition_id}", + "email": "test@ok.dummy.com", + "description": "this is a description", + "members": [{"id": "ok"}], + "admins": [{"id": "ok"}] + } + result = client.create_group(new_group, status=200) + + assert_that(result["name"], is_(new_group["name"])) + assert_that(result["email"], is_(new_group["email"])) + assert_that(result["description"], is_(new_group["description"])) + assert_that(result["status"], is_("Active")) + assert_that(result["created"], not_none()) + assert_that(result["id"], not_none()) + assert_that(result["members"], has_length(1)) + assert_that(result["members"][0]["id"], is_("ok")) + assert_that(result["admins"], has_length(1)) + assert_that(result["admins"][0]["id"], is_("ok")) + finally: + if result: + client.delete_group(result["id"], status=(200, 404)) + def test_creator_is_an_admin(shared_zone_test_context): """ Tests that the creator is an admin diff --git a/modules/api/src/test/functional/tests/zones/create_zone_test.py b/modules/api/src/test/functional/tests/zones/create_zone_test.py index 02a23d756..b78130261 100644 --- a/modules/api/src/test/functional/tests/zones/create_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/create_zone_test.py @@ -94,7 +94,8 @@ def test_create_zone_success(shared_zone_test_context): if result_zone: client.abandon_zones([result_zone["id"]], status=202) -def test_create_zone_success_wildcard(shared_zone_test_context): + +def test_create_zone_success_number_of_dots(shared_zone_test_context): """ Test successfully creating a zone """ diff --git a/modules/api/src/test/functional/tests/zones/update_zone_test.py b/modules/api/src/test/functional/tests/zones/update_zone_test.py index 266b3d9fa..25e738bb4 100644 --- a/modules/api/src/test/functional/tests/zones/update_zone_test.py +++ b/modules/api/src/test/functional/tests/zones/update_zone_test.py @@ -126,6 +126,65 @@ def test_update_zone_success_wildcard(shared_zone_test_context): if result_zone: client.abandon_zones([result_zone["id"]], status=202) +def test_update_zone_success_number_of_dots(shared_zone_test_context): + """ + Test updating a zone for email validation wildcard + """ + client = shared_zone_test_context.ok_vinyldns_client + result_zone = None + try: + zone_name = f"one-time{shared_zone_test_context.partition_id}" + + acl_rule = { + "accessLevel": "Read", + "description": "test-acl-updated-by-updatezn", + "userId": "ok", + "recordMask": "www-*", + "recordTypes": ["A", "AAAA", "CNAME"] + } + + zone = { + "name": zone_name, + "email": "test@test.com", + "adminGroupId": shared_zone_test_context.ok_group["id"], + "connection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + }, + "transferConnection": { + "name": "vinyldns.", + "keyName": VinylDNSTestContext.dns_key_name, + "key": VinylDNSTestContext.dns_key, + "primaryServer": VinylDNSTestContext.name_server_ip + } + } + result = client.create_zone(zone, status=202) + result_zone = result["zone"] + client.wait_until_zone_active(result_zone["id"]) + + result_zone["email"] = "test@ok.dummy.com" + result_zone["acl"]["rules"] = [acl_rule] + update_result = client.update_zone(result_zone, status=202) + client.wait_until_zone_change_status_synced(update_result) + + assert_that(update_result["changeType"], is_("Update")) + assert_that(update_result["userId"], is_("ok")) + assert_that(update_result, has_key("created")) + + get_result = client.get_zone(result_zone["id"]) + + uz = get_result["zone"] + assert_that(uz["email"], is_("test@ok.dummy.com")) + assert_that(uz["updated"], is_not(none())) + + acl = uz["acl"] + verify_acl_rule_is_present_once(acl_rule, acl) + finally: + if result_zone: + client.abandon_zones([result_zone["id"]], status=202) + def test_update_invalid_email(shared_zone_test_context): """ Test that updating a zone with invalid email diff --git a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala index 2d244f276..3139d8cc7 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/membership/MembershipServiceSpec.scala @@ -368,6 +368,11 @@ class MembershipServiceSpec result shouldBe Right(()) } + "Check whether test.com is a valid email with number of dots" in { + val result = underTest.emailValidation(email = "test@ok.dummy.com").value.unsafeRunSync() + result shouldBe Right(()) + } + "Check whether it is allowing any domain when the config is empty" in { val result = underTestNew.emailValidation(email = "test@abc.com").value.unsafeRunSync() result shouldBe Right(()) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index 390458f8f..2df54df4e 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -268,6 +268,27 @@ class ZoneServiceSpec resultZone.connection shouldBe newZone.connection resultZone.shared shouldBe false } + + "return the result if the zone created includes an valid email with number of dots " in { + doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) + + val newZone = createZoneAuthorized.copy(email = "test@ok.dummy.com") + val resultChange: ZoneChange = + underTest.connectToZone(newZone, okAuth).map(_.asInstanceOf[ZoneChange]).value.unsafeRunSync().toOption.get + resultChange.changeType shouldBe ZoneChangeType.Create + Option(resultChange.created) shouldBe defined + resultChange.status shouldBe ZoneChangeStatus.Pending + resultChange.userId shouldBe okAuth.userId + + val resultZone = resultChange.zone + Option(resultZone.id) shouldBe defined + resultZone.email shouldBe newZone.email + resultZone.name shouldBe newZone.name + resultZone.status shouldBe ZoneStatus.Syncing + resultZone.connection shouldBe newZone.connection + resultZone.shared shouldBe false + } + "return the result if the zone created includes empty Domain" in { doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString) From 7fb6e6f47d6b171e10013b12029050dfc3089e3e Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Wed, 29 Mar 2023 14:55:50 +0530 Subject: [PATCH 184/521] Include + and - in regex in scala --- modules/api/src/main/resources/application.conf | 2 +- .../vinyldns/api/domain/membership/MembershipService.scala | 7 ++++++- .../api/domain/membership/MembershipServiceAlgebra.scala | 2 ++ .../main/scala/vinyldns/api/route/MembershipRouting.scala | 7 +++++++ modules/api/src/test/resources/application.conf | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index 7fa84d114..b4380abee 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -132,7 +132,7 @@ vinyldns { } } valid-email-config{ - email-domains = ["test.com","*dummy.com","*ok.com"] + email-domains = ["test.com","*dummy.com"] number-of-dots= 2 } sns { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala index d47cc2396..29db62608 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala @@ -69,6 +69,11 @@ class MembershipService( } yield newGroup } + def listEmailDomains(authPrincipal: AuthPrincipal): Result[List[String]] = { + val validEmailDomains = validDomains.valid_domains + IO(validEmailDomains).toResult + } + def updateGroup( groupId: String, name: String, @@ -391,7 +396,7 @@ class MembershipService( val emailDomains = validDomains.valid_domains val numberOfDots= validDomains.number_of_dots val splitEmailDomains = emailDomains.mkString(",") - val emailRegex ="""^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9._]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r + val emailRegex ="""^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9._+-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r val index = email.indexOf('@'); val emailSplit = if(index != -1){ email.substring(index+1,email.length)} diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipServiceAlgebra.scala index c71d62c2a..43f6955a7 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipServiceAlgebra.scala @@ -25,6 +25,8 @@ trait MembershipServiceAlgebra { def createGroup(inputGroup: Group, authPrincipal: AuthPrincipal): Result[Group] + def listEmailDomains(authPrincipal: AuthPrincipal):Result[List[String]] + def updateGroup( groupId: String, name: String, diff --git a/modules/api/src/main/scala/vinyldns/api/route/MembershipRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/MembershipRouting.scala index 17842464c..74efb970c 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/MembershipRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/MembershipRouting.scala @@ -187,6 +187,13 @@ class MembershipRoute( } } } ~ + path("groups" / "valid" / "domains") { + (get & monitor("Endpoint.validdomains")) { + authenticateAndExecute(membershipService.listEmailDomains) { emailDomains => + complete(StatusCodes.OK, emailDomains) + } + } + } ~ path("users" / Segment / "lock") { id => (put & monitor("Endpoint.lockUser")) { authenticateAndExecute(membershipService.updateUserLockStatus(id, LockStatus.Locked, _)) { diff --git a/modules/api/src/test/resources/application.conf b/modules/api/src/test/resources/application.conf index 99800bfce..93f88e24e 100644 --- a/modules/api/src/test/resources/application.conf +++ b/modules/api/src/test/resources/application.conf @@ -128,7 +128,7 @@ vinyldns { } } valid-email-config{ - email-domains = ["test.com","*dummy.com","*ok.com"] + email-domains = ["test.com","*dummy.com"] number-of-dots= 2 } sns { From 6cce7ec10a71fcf35114ddc4d53763da74cc50aa Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Thu, 30 Mar 2023 18:54:05 +0530 Subject: [PATCH 185/521] inclusion of ! and & in regex --- .../vinyldns/api/domain/membership/MembershipService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala index 29db62608..93d58e423 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/membership/MembershipService.scala @@ -396,7 +396,7 @@ class MembershipService( val emailDomains = validDomains.valid_domains val numberOfDots= validDomains.number_of_dots val splitEmailDomains = emailDomains.mkString(",") - val emailRegex ="""^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9._+-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r + val emailRegex ="""^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9._+!&-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r val index = email.indexOf('@'); val emailSplit = if(index != -1){ email.substring(index+1,email.length)} From 5ceab5a66c096d2957e51e3fc9f4c566cdd0d5cc Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Fri, 31 Mar 2023 10:54:20 +0530 Subject: [PATCH 186/521] Email Domains Dropdown for groups --- modules/portal/app/controllers/VinylDNS.scala | 9 ++++ .../portal/app/views/groups/groups.scala.html | 51 +++++++++++++++++-- modules/portal/conf/routes | 1 + .../lib/controllers/controller.groups.js | 21 +++++++- .../lib/services/groups/service.groups.js | 4 ++ 5 files changed, 80 insertions(+), 6 deletions(-) diff --git a/modules/portal/app/controllers/VinylDNS.scala b/modules/portal/app/controllers/VinylDNS.scala index 70310020c..05e728707 100644 --- a/modules/portal/app/controllers/VinylDNS.scala +++ b/modules/portal/app/controllers/VinylDNS.scala @@ -293,6 +293,15 @@ class VinylDNS @Inject() ( }) } + def getValidEmailDomains(): Action[AnyContent] = userAction.async { implicit request => + val vinyldnsRequest = + VinylDNSRequest("GET", s"$vinyldnsServiceBackend", s"groups/valid/domains") + executeRequest(vinyldnsRequest, request.user).map(response => { + Status(response.status)(response.body) + .withHeaders(cacheHeaders: _*) + }) + } + def getAuthenticatedUserData(): Action[AnyContent] = userAction.async { implicit request => Future { Ok(Json.toJson(VinylDNS.UserInfo.fromUser(request.user))) diff --git a/modules/portal/app/views/groups/groups.scala.html b/modules/portal/app/views/groups/groups.scala.html index c1b4ea7fb..cea9fc553 100644 --- a/modules/portal/app/views/groups/groups.scala.html +++ b/modules/portal/app/views/groups/groups.scala.html @@ -281,6 +281,24 @@ type="text" required> + +
    +
    +

    + List Of Valid Email Domains: +

  • + {{validDomains}} +
  • +

    +
    +
    +
    The email distribution list for the group. @@ -297,9 +315,12 @@
    @@ -343,6 +364,24 @@ type="text" required> + +
    +
    +

    + List Of Valid Email Domains: +

  • + {{validDomains}} +
  • +

    +
    +
    +
    The email distribution list for the group. @@ -360,8 +399,10 @@ diff --git a/modules/portal/conf/routes b/modules/portal/conf/routes index d8b0b9fa7..ea70657e2 100644 --- a/modules/portal/conf/routes +++ b/modules/portal/conf/routes @@ -45,6 +45,7 @@ GET /api/zones/:id/recordsetchanges @controllers.VinylDNS.listRecor GET /api/groups @controllers.VinylDNS.getGroups GET /api/groups/:gid @controllers.VinylDNS.getGroup(gid: String) +GET /api/groups/valid/domains @controllers.VinylDNS.getValidEmailDomains GET /api/groups/:gid/groupchanges @controllers.VinylDNS.listGroupChanges(gid: String) GET /api/groups/change/:gcid @controllers.VinylDNS.getGroupChange(gcid: String) POST /api/groups @controllers.VinylDNS.newGroup diff --git a/modules/portal/public/lib/controllers/controller.groups.js b/modules/portal/public/lib/controllers/controller.groups.js index 101d37a42..61316d6dd 100644 --- a/modules/portal/public/lib/controllers/controller.groups.js +++ b/modules/portal/public/lib/controllers/controller.groups.js @@ -28,6 +28,7 @@ angular.module('controller.groups', []).controller('GroupsController', function $scope.ignoreAccess = false; $scope.hasGroups = false; $scope.query = ""; + $scope.validEmailDomains= []; // Paging status for group sets var groupsPaging = pagingService.getNewPagingParams(100); @@ -44,12 +45,15 @@ angular.module('controller.groups', []).controller('GroupsController', function var modalDialog; $scope.openModal = function (evt) { + $log.log('First entry'); $scope.currentGroup = {}; + $scope.validDomains(); void (evt && evt.preventDefault()); if (!modalDialog) { modalDialog = angular.element('#modal_new_group').modal(); } modalDialog.modal('show'); + }; $scope.closeModal = function (evt) { @@ -69,7 +73,6 @@ angular.module('controller.groups', []).controller('GroupsController', function $scope.refresh(); return true; }; - // Autocomplete for group search $("#group-search-text").autocomplete({ source: function( request, response ) { @@ -207,6 +210,21 @@ angular.module('controller.groups', []).controller('GroupsController', function handleError(error, 'groupsService::getGroups-failure'); }); } + $scope.validDomains=function getValidEmailDomains() { + $log.log('Function Entry'); + function success(response) { + $log.log('groupsService::listEmailDomains-success'); + return $scope.validEmailDomains = response.data; + } + + return groupsService + .listEmailDomains($scope.ignoreAccess, $scope.query) + .then(success) + .catch(function (error) { + handleError(error, 'groupsService::listEmailDomains-failure'); + }); + } + // Return true if there are no groups created by the user $scope.haveNoGroups = function (groupLength) { @@ -228,6 +246,7 @@ angular.module('controller.groups', []).controller('GroupsController', function $scope.editGroup = function (groupInfo) { $scope.currentGroup = groupInfo; + $scope.validDomains(); $("#modal_edit_group").modal("show"); }; diff --git a/modules/portal/public/lib/services/groups/service.groups.js b/modules/portal/public/lib/services/groups/service.groups.js index 41f1a7f5e..654f12d2e 100644 --- a/modules/portal/public/lib/services/groups/service.groups.js +++ b/modules/portal/public/lib/services/groups/service.groups.js @@ -43,6 +43,10 @@ angular.module('service.groups', []) var url = '/api/groups/' + id; return $http.get(url); }; + this.listEmailDomains = function () { + var url = '/api/groups/valid/domains' + return $http.get(url); + }; this.deleteGroups = function (id) { var url = '/api/groups/' + id; From 7432eba1977a18e426ac99d028ee480653a5fc0c Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Fri, 31 Mar 2023 11:21:36 +0530 Subject: [PATCH 187/521] dummy commit --- modules/portal/public/lib/controllers/controller.groups.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/portal/public/lib/controllers/controller.groups.js b/modules/portal/public/lib/controllers/controller.groups.js index 61316d6dd..61f21c4c5 100644 --- a/modules/portal/public/lib/controllers/controller.groups.js +++ b/modules/portal/public/lib/controllers/controller.groups.js @@ -209,7 +209,9 @@ angular.module('controller.groups', []).controller('GroupsController', function .catch(function (error) { handleError(error, 'groupsService::getGroups-failure'); }); - } + + //Function for fetching list of valid domains + $scope.validDomains=function getValidEmailDomains() { $log.log('Function Entry'); function success(response) { From 891aa1250a50be7ae5e8727dbe388b5879e79a78 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Fri, 31 Mar 2023 14:07:36 +0530 Subject: [PATCH 188/521] added max limits in zone and record change failure metrics api --- .../api/domain/record/RecordSetService.scala | 5 +++-- .../record/RecordSetServiceAlgebra.scala | 3 ++- .../vinyldns/api/domain/zone/ZoneService.scala | 5 +++-- .../api/domain/zone/ZoneServiceAlgebra.scala | 3 ++- .../vinyldns/api/route/RecordSetRouting.scala | 17 +++++++++++++---- .../scala/vinyldns/api/route/ZoneRouting.scala | 18 +++++++++++++----- .../domain/record/RecordChangeRepository.scala | 2 +- .../domain/zone/ZoneChangeRepository.scala | 1 + .../MySqlRecordChangeRepository.scala | 4 +++- .../repository/MySqlZoneChangeRepository.scala | 4 +++- 10 files changed, 44 insertions(+), 18 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index edeb2ca8a..b0af2c721 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -584,11 +584,12 @@ class RecordSetService( def listFailedRecordSetChanges( - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + maxItems: Int = 100 ): Result[ListFailedRecordSetChangesResponse] = for { recordSetChangesFailedResults <- recordChangeRepository - .listFailedRecordSetChanges() + .listFailedRecordSetChanges(maxItems) .toResult[List[RecordSetChange]] _ <- zoneAccess(recordSetChangesFailedResults, authPrincipal).toResult } yield ListFailedRecordSetChangesResponse(recordSetChangesFailedResults) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala index 546bb2d13..0dec27710 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala @@ -104,7 +104,8 @@ trait RecordSetServiceAlgebra { ): Result[ListRecordSetChangesResponse] def listFailedRecordSetChanges( - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + maxItems: Int ): Result[ListFailedRecordSetChangesResponse] } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala index 8d8c268f1..2a1c73ba6 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneService.scala @@ -239,11 +239,12 @@ class ZoneService( } yield ListZoneChangesResponse(zone.id, zoneChangesResults) def listFailedZoneChanges( - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + maxItems: Int = 100 ): Result[ListFailedZoneChangesResponse] = for { zoneChangesFailedResults <- zoneChangeRepository - .listFailedZoneChanges() + .listFailedZoneChanges(maxItems) .toResult[List[ZoneChange]] _ <- zoneAccess(zoneChangesFailedResults, authPrincipal).toResult } yield { diff --git a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala index 965214ce9..81a62fa16 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/zone/ZoneServiceAlgebra.scala @@ -68,6 +68,7 @@ trait ZoneServiceAlgebra { def getBackendIds(): Result[List[String]] def listFailedZoneChanges( - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + maxItems: Int ): Result[ListFailedZoneChangesResponse] } diff --git a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala index ebdff2385..6884dd51f 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala @@ -236,14 +236,23 @@ class RecordSetRoute( } ~ path("metrics" / "health" / "recordsetchangesfailure") { (get & monitor("Endpoint.listFailedRecordSetChanges")) { - handleRejections(invalidQueryHandler) { - authenticateAndExecute(recordSetService.listFailedRecordSetChanges(_)) { + parameters( "maxItems".as[Int].?(DEFAULT_MAX_ITEMS)) { + (maxItems: Int) => + handleRejections(invalidQueryHandler) { + validate( + check = 0 < maxItems && maxItems <= DEFAULT_MAX_ITEMS, + errorMsg = s"maxItems was $maxItems, maxItems must be between 0 exclusive " + + s"and $DEFAULT_MAX_ITEMS inclusive" + ){ + authenticateAndExecute(recordSetService.listFailedRecordSetChanges(_, maxItems)) { changes => - complete(StatusCodes.OK, changes) + complete(StatusCodes.OK, changes) + } + } } } + } } -} private val invalidQueryHandler = RejectionHandler .newBuilder() diff --git a/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala index f77f6e2e6..8aa60014e 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/ZoneRouting.scala @@ -165,11 +165,19 @@ class ZoneRoute( } ~ path("metrics" / "health" / "zonechangesfailure") { (get & monitor("Endpoint.listFailedZoneChanges")) { - handleRejections(invalidQueryHandler) { - authenticateAndExecute(zoneService.listFailedZoneChanges(_)) { - changes => - complete(StatusCodes.OK, changes) - } + parameters("maxItems".as[Int].?(DEFAULT_MAX_ITEMS)) { + (maxItems: Int) => + handleRejections(invalidQueryHandler) { + validate( + 0 < maxItems && maxItems <= DEFAULT_MAX_ITEMS, + s"maxItems was $maxItems, maxItems must be between 0 exclusive and $DEFAULT_MAX_ITEMS inclusive" + ) { + authenticateAndExecute(zoneService.listFailedZoneChanges(_, maxItems)) { + changes => + complete(StatusCodes.OK, changes) + } + } + } } } } ~ diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala index 5d82481a4..f09627265 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala @@ -32,6 +32,6 @@ trait RecordChangeRepository extends Repository { def getRecordSetChange(zoneId: String, changeId: String): IO[Option[RecordSetChange]] - def listFailedRecordSetChanges(): IO[List[RecordSetChange]] + def listFailedRecordSetChanges(maxItems: Int = 100): IO[List[RecordSetChange]] } diff --git a/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneChangeRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneChangeRepository.scala index 72b5b28f6..467cc2b65 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneChangeRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/zone/ZoneChangeRepository.scala @@ -30,5 +30,6 @@ trait ZoneChangeRepository extends Repository { ): IO[ListZoneChangesResults] def listFailedZoneChanges( + maxItems: Int = 100 ): IO[List[ZoneChange]] } diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala index de90c8edb..dada493a5 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala @@ -43,6 +43,7 @@ class MySqlRecordChangeRepository sql""" |SELECT data | FROM record_change + | LIMIT {limit} """.stripMargin private val LIST_CHANGES_NO_START = @@ -128,11 +129,12 @@ class MySqlRecordChangeRepository } } - def listFailedRecordSetChanges(): IO[List[RecordSetChange]] = + def listFailedRecordSetChanges(maxItems: Int): IO[List[RecordSetChange]] = monitor("repo.RecordChange.listFailedRecordSetChanges") { IO { DB.readOnly { implicit s => val queryResult = LIST_RECORD_CHANGES + .bindByName('limit -> maxItems) .map(toRecordSetChange) .list() .apply() diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala index 715499e3a..ee7de690c 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlZoneChangeRepository.scala @@ -52,6 +52,7 @@ class MySqlZoneChangeRepository |SELECT zc.data | FROM zone_change zc | JOIN zone z ON z.id = zc.zone_id + | LIMIT {maxItems} """.stripMargin override def save(zoneChange: ZoneChange): IO[ZoneChange] = @@ -109,11 +110,12 @@ class MySqlZoneChangeRepository } } - def listFailedZoneChanges(): IO[List[ZoneChange]] = + def listFailedZoneChanges(maxItems: Int): IO[List[ZoneChange]] = monitor("repo.ZoneChange.listFailedZoneChanges") { IO { DB.readOnly { implicit s => val queryResult = LIST_ZONES_CHANGES_DATA + .bindByName('maxItems -> maxItems) .map(extractZoneChange(1)) .list() .apply() From 7d5cab3771152d141add56741081382bf941ab43 Mon Sep 17 00:00:00 2001 From: Jay07GIT Date: Fri, 31 Mar 2023 14:27:57 +0530 Subject: [PATCH 189/521] resolved tests --- .../vinyldns/api/domain/record/RecordSetServiceSpec.scala | 2 +- .../test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala | 2 +- .../test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala | 3 ++- .../src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala | 3 ++- .../MySqlRecordChangeRepositoryIntegrationSpec.scala | 4 ++-- .../repository/MySqlZoneChangeRepositoryIntegrationSpec.scala | 4 ++-- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index 3ee6f3e86..8df53c7fb 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -1966,7 +1966,7 @@ class RecordSetServiceSpec //val recordSetChange= List[RecordSetChange] doReturn(IO.pure(completeRecordSetChanges)) .when(mockRecordChangeRepo) - .listFailedRecordSetChanges() + .listFailedRecordSetChanges(100) val result: ListFailedRecordSetChangesResponse = diff --git a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala index b528c8143..535687828 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/zone/ZoneServiceSpec.scala @@ -831,7 +831,7 @@ class ZoneServiceSpec zoneUpdate.copy(status = ZoneChangeStatus.Failed) ))) .when(mockZoneChangeRepo) - .listFailedZoneChanges() + .listFailedZoneChanges(100) val result: ListFailedZoneChangesResponse = underTest.listFailedZoneChanges(okAuth).value.unsafeRunSync().toOption.get diff --git a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala index 019ab58fa..afb7c964a 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala @@ -572,7 +572,8 @@ class RecordSetRoutingSpec }.toResult def listFailedRecordSetChanges( - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + maxItems: Int, ): Result[ListFailedRecordSetChangesResponse] = { val outcome = authPrincipal match { case _ => Right(listFailedRecordSetChangeResponse) diff --git a/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala index a046e6342..d3c5df9d7 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/ZoneRoutingSpec.scala @@ -359,7 +359,8 @@ class ZoneRoutingSpec } def listFailedZoneChanges( - authPrincipal: AuthPrincipal + authPrincipal: AuthPrincipal, + maxItems: Int ): Result[ListFailedZoneChangesResponse] = { val outcome = authPrincipal match { case _ => Right(listFailedZoneChangeResponse) diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala index 6db093f47..c409c8893 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala @@ -145,7 +145,7 @@ class MySqlRecordChangeRepositoryIntegrationSpec repo.save(db, ChangeSet(inserts)) } saveRecChange.attempt.unsafeRunSync() shouldBe right - val result = repo.listFailedRecordSetChanges().unsafeRunSync() + val result = repo.listFailedRecordSetChanges(100).unsafeRunSync() (result should have).length(10) result should contain theSameElementsAs(inserts) @@ -156,7 +156,7 @@ class MySqlRecordChangeRepositoryIntegrationSpec repo.save(db, ChangeSet(inserts)) } saveRecChange.attempt.unsafeRunSync() shouldBe right - val result = repo.listFailedRecordSetChanges().unsafeRunSync() + val result = repo.listFailedRecordSetChanges(100).unsafeRunSync() (result should have).length(0) result shouldBe List() } diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala index 6ac97ef3c..b92fcf38f 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlZoneChangeRepositoryIntegrationSpec.scala @@ -183,7 +183,7 @@ class MySqlZoneChangeRepositoryIntegrationSpec val expectedChanges = failedChanges.toList - val listResponse = repo.listFailedZoneChanges().unsafeRunSync() + val listResponse = repo.listFailedZoneChanges(100).unsafeRunSync() listResponse should contain theSameElementsAs(expectedChanges) } @@ -195,7 +195,7 @@ class MySqlZoneChangeRepositoryIntegrationSpec fail("timeout waiting for changes to save in MySqlZoneChangeRepositoryIntegrationSpec") ) - val listResponse = repo.listFailedZoneChanges().unsafeRunSync() + val listResponse = repo.listFailedZoneChanges(100).unsafeRunSync() listResponse shouldBe List() } From 19ba3c09fe4fb007646c9b2982c1e308ea2ccd89 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 31 Mar 2023 17:29:15 +0530 Subject: [PATCH 190/521] working skeleton --- .../api/domain/record/RecordSetService.scala | 34 ++++-- .../record/RecordSetServiceAlgebra.scala | 4 +- .../vinyldns/api/route/RecordSetRouting.scala | 27 ++++- .../record/RecordChangeRepository.scala | 7 +- .../db/migration/V3.29__AddFqdnColumn.sql | 6 ++ .../MySqlRecordChangeRepository.scala | 85 +++++++++++---- modules/portal/app/controllers/VinylDNS.scala | 19 ++++ .../views/recordsets/recordSets.scala.html | 102 +++++++++++++++++- modules/portal/conf/routes | 1 + modules/portal/public/css/vinyldns.css | 4 + .../lib/recordset/recordsets.controller.js | 89 +++++++++++++++ .../lib/services/records/service.records.js | 16 ++- 12 files changed, 356 insertions(+), 38 deletions(-) create mode 100644 modules/mysql/src/main/resources/db/migration/V3.29__AddFqdnColumn.sql diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index edeb2ca8a..d3bdab235 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -568,19 +568,33 @@ class RecordSetService( } yield change def listRecordSetChanges( - zoneId: String, + zoneId: Option[String] = None, startFrom: Option[Int] = None, maxItems: Int = 100, + fqdn: Option[String] = None, + recordType: Option[RecordType] = None, authPrincipal: AuthPrincipal - ): Result[ListRecordSetChangesResponse] = - for { - zone <- getZone(zoneId) - _ <- canSeeZone(authPrincipal, zone).toResult - recordSetChangesResults <- recordChangeRepository - .listRecordSetChanges(zone.id, startFrom, maxItems) - .toResult[ListRecordSetChangesResults] - recordSetChangesInfo <- buildRecordSetChangeInfo(recordSetChangesResults.items) - } yield ListRecordSetChangesResponse(zoneId, recordSetChangesResults, recordSetChangesInfo) + ): Result[ListRecordSetChangesResponse] = { + println("In listRecordSetChanges") + if(zoneId.isDefined) { + for { + zone <- getZone(zoneId.get) + _ <- canSeeZone(authPrincipal, zone).toResult + recordSetChangesResults <- recordChangeRepository + .listRecordSetChanges(Some(zone.id), startFrom, maxItems) + .toResult[ListRecordSetChangesResults] + recordSetChangesInfo <- buildRecordSetChangeInfo(recordSetChangesResults.items) + } yield ListRecordSetChangesResponse(zoneId.get, recordSetChangesResults, recordSetChangesInfo) + } else { + for { + recordSetChangesResults <- recordChangeRepository + .listRecordSetChanges(zoneId, startFrom, maxItems, fqdn, recordType) + .toResult[ListRecordSetChangesResults] + recordSetChangesInfo <- buildRecordSetChangeInfo(recordSetChangesResults.items) + zoneId = recordSetChangesResults.items.map(x => x.zone.id).head + } yield ListRecordSetChangesResponse(zoneId, recordSetChangesResults, recordSetChangesInfo) + } + } def listFailedRecordSetChanges( diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala index 546bb2d13..5defa2ef1 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala @@ -97,9 +97,11 @@ trait RecordSetServiceAlgebra { ): Result[RecordSetChange] def listRecordSetChanges( - zoneId: String, + zoneId: Option[String], startFrom: Option[Int], maxItems: Int, + fqdn: Option[String], + recordType: Option[RecordType], authPrincipal: AuthPrincipal ): Result[ListRecordSetChangesResponse] diff --git a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala index ebdff2385..42ae84496 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala @@ -215,8 +215,8 @@ class RecordSetRoute( } ~ path("zones" / Segment / "recordsetchanges") { zoneId => (get & monitor("Endpoint.listRecordSetChanges")) { - parameters("startFrom".as[Int].?, "maxItems".as[Int].?(DEFAULT_MAX_ITEMS)) { - (startFrom: Option[Int], maxItems: Int) => + parameters("startFrom".as[Int].?, "maxItems".as[Int].?(DEFAULT_MAX_ITEMS), "fqdn".as[String].?, "recordType".as[String].?) { + (startFrom: Option[Int], maxItems: Int, fqdn: Option[String], _: Option[String]) => handleRejections(invalidQueryHandler) { validate( check = 0 < maxItems && maxItems <= DEFAULT_MAX_ITEMS, @@ -225,7 +225,28 @@ class RecordSetRoute( ) { authenticateAndExecute( recordSetService - .listRecordSetChanges(zoneId, startFrom, maxItems, _) + .listRecordSetChanges(Some(zoneId), startFrom, maxItems, fqdn, None, _) + ) { changes => + complete(StatusCodes.OK, changes) + } + } + } + } + } + } ~ + path("recordsetchange" / "history") { + (get & monitor("Endpoint.listRecordSetChangeHistory")) { + parameters("startFrom".as[Int].?, "maxItems".as[Int].?(DEFAULT_MAX_ITEMS), "fqdn".as[String].?, "recordType".as[String].?) { + (startFrom: Option[Int], maxItems: Int, fqdn: Option[String], recordType: Option[String]) => + handleRejections(invalidQueryHandler) { + validate( + check = 0 < maxItems && maxItems <= DEFAULT_MAX_ITEMS, + errorMsg = s"maxItems was $maxItems, maxItems must be between 0 exclusive " + + s"and $DEFAULT_MAX_ITEMS inclusive" + ) { + authenticateAndExecute( + recordSetService + .listRecordSetChanges(None, startFrom, maxItems, fqdn, RecordType.find(recordType.get), _) ) { changes => complete(StatusCodes.OK, changes) } diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala index 5d82481a4..516250608 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala @@ -18,6 +18,7 @@ package vinyldns.core.domain.record import cats.effect._ import scalikejdbc.DB +import vinyldns.core.domain.record.RecordType.RecordType import vinyldns.core.repository.Repository trait RecordChangeRepository extends Repository { @@ -25,9 +26,11 @@ trait RecordChangeRepository extends Repository { def save(db: DB, changeSet: ChangeSet): IO[ChangeSet] def listRecordSetChanges( - zoneId: String, + zoneId: Option[String], startFrom: Option[Int] = None, - maxItems: Int = 100 + maxItems: Int = 100, + fqdn: Option[String] = None, + recordType: Option[RecordType] = None ): IO[ListRecordSetChangesResults] def getRecordSetChange(zoneId: String, changeId: String): IO[Option[RecordSetChange]] diff --git a/modules/mysql/src/main/resources/db/migration/V3.29__AddFqdnColumn.sql b/modules/mysql/src/main/resources/db/migration/V3.29__AddFqdnColumn.sql new file mode 100644 index 000000000..01238b6cf --- /dev/null +++ b/modules/mysql/src/main/resources/db/migration/V3.29__AddFqdnColumn.sql @@ -0,0 +1,6 @@ +CREATE SCHEMA IF NOT EXISTS ${dbName}; + +USE ${dbName}; + +ALTER TABLE record_change ADD COLUMN fqdn VARCHAR(255) NOT NULL; +CREATE INDEX fqdn_index ON record_change (fqdn); diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala index de90c8edb..66a0d8bff 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala @@ -19,6 +19,7 @@ package vinyldns.mysql.repository import cats.effect._ import scalikejdbc._ import vinyldns.core.domain.record.RecordSetChangeType.RecordSetChangeType +import vinyldns.core.domain.record.RecordType.RecordType import vinyldns.core.domain.record._ import vinyldns.core.protobuf.ProtobufConversions import vinyldns.core.route.Monitored @@ -39,6 +40,24 @@ class MySqlRecordChangeRepository | LIMIT {limit} OFFSET {startFrom} """.stripMargin + private val LIST_CHANGES_WITH_START_FQDN_TYPE = + sql""" + |SELECT data + | FROM record_change + | WHERE fqdn = {fqdn} + | ORDER BY created DESC + | LIMIT {limit} OFFSET {startFrom} + """.stripMargin + + private val LIST_CHANGES_WITHOUT_START_FQDN_TYPE = + sql""" + |SELECT data + | FROM record_change + | WHERE fqdn = {fqdn} + | ORDER BY created DESC + | LIMIT {limit} + """.stripMargin + private val LIST_RECORD_CHANGES = sql""" |SELECT data @@ -62,7 +81,7 @@ class MySqlRecordChangeRepository """.stripMargin private val INSERT_CHANGES = - sql"INSERT IGNORE INTO record_change (id, zone_id, created, type, data) VALUES (?, ?, ?, ?, ?)" + sql"INSERT IGNORE INTO record_change (id, zone_id, created, type, fqdn, data) VALUES (?, ?, ?, ?, ?, ?)" /** * We have the same issue with changes as record sets, namely we may have to save millions of them @@ -70,6 +89,9 @@ class MySqlRecordChangeRepository */ def save(db: DB, changeSet: ChangeSet): IO[ChangeSet] = monitor("repo.RecordChange.save") { + println("fqdn: ", changeSet.changes.map(x => x.recordSet.fqdn)) + println("name: ", changeSet.changes.map(x => x.recordSet.name)) + println("zone: ", changeSet.changes.map(x => x.zone.name)) IO { db.withinTx { implicit session => changeSet.changes @@ -81,7 +103,8 @@ class MySqlRecordChangeRepository change.zoneId, change.created.toEpochMilli, fromChangeType(change.changeType), - toPB(change).toByteArray + if(change.recordSet.name == change.zone.name) change.zone.name else change.recordSet.name + "." + change.zone.name, + toPB(change).toByteArray, ) } } @@ -93,33 +116,57 @@ class MySqlRecordChangeRepository } def listRecordSetChanges( - zoneId: String, + zoneId: Option[String], startFrom: Option[Int], - maxItems: Int + maxItems: Int, + fqdn: Option[String], + recordType: Option[RecordType] ): IO[ListRecordSetChangesResults] = monitor("repo.RecordChange.listRecordSetChanges") { + println("zoneId: ", zoneId) + println("fqdn: ", fqdn) + println("recordType: ", recordType) + println("startFrom: ", startFrom) IO { DB.readOnly { implicit s => - val changes = startFrom match { - case Some(start) => - LIST_CHANGES_WITH_START - .bindByName('zoneId -> zoneId, 'startFrom -> start, 'limit -> maxItems) - .map(toRecordSetChange) - .list() - .apply() - case None => - LIST_CHANGES_NO_START - .bindByName('zoneId -> zoneId, 'limit -> maxItems) - .map(toRecordSetChange) - .list() - .apply() + val changes = if(fqdn.isDefined && recordType.isDefined){ + println("In 1st") + LIST_CHANGES_WITHOUT_START_FQDN_TYPE + .bindByName('fqdn -> fqdn.get, 'limit -> maxItems) + .map(toRecordSetChange) + .list() + .apply() + } else if(startFrom.isDefined && fqdn.isDefined && recordType.isDefined){ + println("In 2nd") + LIST_CHANGES_WITH_START_FQDN_TYPE + .bindByName('fqdn -> fqdn.get, 'startFrom -> startFrom.get, 'limit -> maxItems) + .map(toRecordSetChange) + .list() + .apply() + } else if(startFrom.isDefined){ + println("In 3rd") + LIST_CHANGES_WITH_START + .bindByName('zoneId -> zoneId.get, 'startFrom -> startFrom.get, 'limit -> maxItems) + .map(toRecordSetChange) + .list() + .apply() + } else { + println("In 4th") + LIST_CHANGES_NO_START + .bindByName('zoneId -> zoneId.get, 'limit -> maxItems) + .map(toRecordSetChange) + .list() + .apply() } + val finalChanges = if(recordType.isDefined) changes.filter(rec => rec.recordSet.typ == recordType.get) else changes + println("finalChanges: ", finalChanges) + val startValue = startFrom.getOrElse(0) - val nextId = if (changes.size < maxItems) None else Some(startValue + maxItems) + val nextId = if (finalChanges.size < maxItems) None else Some(startValue + maxItems) ListRecordSetChangesResults( - changes, + finalChanges, nextId, startFrom, maxItems diff --git a/modules/portal/app/controllers/VinylDNS.scala b/modules/portal/app/controllers/VinylDNS.scala index 70310020c..7db840372 100644 --- a/modules/portal/app/controllers/VinylDNS.scala +++ b/modules/portal/app/controllers/VinylDNS.scala @@ -581,6 +581,25 @@ class VinylDNS @Inject() ( // $COVERAGE-ON$ } + def listRecordSetChangeHistory: Action[AnyContent] = userAction.async { implicit request => + // $COVERAGE-OFF$ + val queryParameters = new HashMap[String, java.util.List[String]]() + for { + (name, values) <- request.queryString + } queryParameters.put(name, values.asJava) + val vinyldnsRequest = new VinylDNSRequest( + "GET", + s"$vinyldnsServiceBackend", + s"recordsetchange/history", + parameters = queryParameters + ) + executeRequest(vinyldnsRequest, request.user).map(response => { + Status(response.status)(response.body) + .withHeaders(cacheHeaders: _*) + }) + // $COVERAGE-ON$ + } + def addZone(): Action[AnyContent] = userAction.async { implicit request => // $COVERAGE-OFF$ val json = request.body.asJson diff --git a/modules/portal/app/views/recordsets/recordSets.scala.html b/modules/portal/app/views/recordsets/recordSets.scala.html index 47c1d9341..68ce15c8b 100644 --- a/modules/portal/app/views/recordsets/recordSets.scala.html +++ b/modules/portal/app/views/recordsets/recordSets.scala.html @@ -80,8 +80,8 @@
    - - + +
    @@ -164,6 +164,7 @@ @if(meta.sharedDisplayEnabled) { Owner Group Name } + Record History @@ -371,6 +372,9 @@ title="Group with ID {{record.ownerGroupId}} no longer exists."> Group deleted } + + + @@ -399,6 +403,100 @@
    + + } @plugins = { diff --git a/modules/portal/conf/routes b/modules/portal/conf/routes index d8b0b9fa7..8f0278a06 100644 --- a/modules/portal/conf/routes +++ b/modules/portal/conf/routes @@ -42,6 +42,7 @@ DELETE /api/zones/:zid/recordsets/:rid @controllers.VinylDNS.deleteRec PUT /api/zones/:zid/recordsets/:rid @controllers.VinylDNS.updateRecordSet(zid: String, rid:String) GET /api/zones/:id/recordsetchanges @controllers.VinylDNS.listRecordSetChanges(id: String) +GET /api/recordsetchange/history @controllers.VinylDNS.listRecordSetChangeHistory GET /api/groups @controllers.VinylDNS.getGroups GET /api/groups/:gid @controllers.VinylDNS.getGroup(gid: String) diff --git a/modules/portal/public/css/vinyldns.css b/modules/portal/public/css/vinyldns.css index bac00790f..5c346c8e6 100644 --- a/modules/portal/public/css/vinyldns.css +++ b/modules/portal/public/css/vinyldns.css @@ -539,3 +539,7 @@ input[type="file"] { margin: 0px; } /* Ending of css override for cron library and it's associated elements used in zone sync scheduling */ + +.set-width { + width: auto; +} \ No newline at end of file diff --git a/modules/portal/public/lib/recordset/recordsets.controller.js b/modules/portal/public/lib/recordset/recordsets.controller.js index 42c36355c..4e85dcc89 100644 --- a/modules/portal/public/lib/recordset/recordsets.controller.js +++ b/modules/portal/public/lib/recordset/recordsets.controller.js @@ -28,6 +28,11 @@ $scope.readRecordTypes = ['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'PTR', "SOA", 'SRV', 'NAPTR', 'SSHFP', 'TXT']; $scope.selectedRecordTypes = []; $scope.groups = []; + $scope.recordFqdn = undefined; + $scope.recordType = undefined; + + // paging status for record changes + var changePaging = pagingService.getNewPagingParams(100); // paging status for recordsets var recordsPaging = pagingService.getNewPagingParams(100); @@ -67,6 +72,15 @@ .append("
    " + recordSet + "
    ") .appendTo(ul); }; + $scope.viewRecordHistory = function(recordFqdn, recordType) { + $log.log("recordFqdn: ", recordFqdn); + $log.log("recordType: ", recordType); + $scope.recordFqdn = recordFqdn; + $scope.recordType = recordType; + $scope.refreshRecordChangeHistory($scope.recordFqdn, $scope.recordType); + $("#record_history_modal").modal("show"); + }; + $scope.refreshRecords = function() { if($scope.query.includes("|")) { const queryRecord = $scope.query.split('|'); @@ -183,5 +197,80 @@ handleError(error, 'recordsService::nextPage-failure'); }); }; + + $scope.refreshRecordChangeHistory = function(recordFqdn, recordType) { + changePaging = pagingService.resetPaging(changePaging); + function success(response) { + $log.log('recordsService::getRecordSetChangeHistory-success'); + changePaging.next = response.data.nextId; + updateChangeDisplay(response.data.recordSetChanges) + } + return recordsService + .listRecordSetChangeHistory(changePaging.maxItems, undefined, recordFqdn, recordType) + .then(success) + .catch(function (error){ + handleError(error, 'recordsService::getRecordSetChangeHistory-failure'); + }); + }; + + /** + * Record change history paging + */ + + $scope.changeHistoryPrevPageEnabled = function() { + return pagingService.prevPageEnabled(changePaging); + }; + + $scope.changeHistoryNextPageEnabled = function() { + return pagingService.nextPageEnabled(changePaging); + }; + + $scope.changeHistoryPrevPage = function() { + var startFrom = pagingService.getPrevStartFrom(changePaging); + return recordsService + .listRecordSetChangeHistory(undefined, changePaging.maxItems, undefined, $scope.recordFqdn, $scope.recordType) + .then(function(response) { + changePaging = pagingService.prevPageUpdate(response.data.nextId, changePaging); + updateChangeDisplay(response.data.recordSetChanges); + }) + .catch(function (error) { + handleError(error, 'recordsService::changePrevPage-failure'); + }); + }; + + $scope.changeHistoryNextPage = function() { + return recordsService + .listRecordSetChangeHistory(undefined, changePaging.maxItems, undefined, $scope.recordFqdn, $scope.recordType) + .then(function(response) { + var changes = response.data.recordSetChanges; + changePaging = pagingService.nextPageUpdate(changes, response.data.nextId, changePaging); + + if(changes.length > 0 ){ + updateChangeDisplay(changes); + } + }) + .catch(function (error) { + handleError(error, 'recordsService::changeNextPage-failure'); + }); + }; + + function updateChangeDisplay(changes) { + var newChanges = []; + angular.forEach(changes, function(change) { + newChanges.push(change); + }); + $scope.recordsetChanges = newChanges; + } + + $scope.getRecordChangeStatusLabel = function(status) { + switch(status) { + case 'Complete': + return 'success'; + case 'Failed': + return 'danger'; + default: + return 'info'; + } + }; }); })(); diff --git a/modules/portal/public/lib/services/records/service.records.js b/modules/portal/public/lib/services/records/service.records.js index 2bcf3cb0b..69cca9c33 100644 --- a/modules/portal/public/lib/services/records/service.records.js +++ b/modules/portal/public/lib/services/records/service.records.js @@ -104,7 +104,21 @@ angular.module('service.records', []) var url = '/api/zones/' + zid + '/recordsetchanges'; var params = { "maxItems": maxItems, - "startFrom": startFrom + "startFrom": startFrom, + "fqdn": undefined, + "recordType": undefined + }; + url = utilityService.urlBuilder(url, params); + return $http.get(url); + }; + + this.listRecordSetChangeHistory = function (maxItems, startFrom, fqdn, recordType) { + var url = '/api/recordsetchange/history'; + var params = { + "maxItems": maxItems, + "startFrom": startFrom, + "fqdn": fqdn, + "recordType": recordType }; url = utilityService.urlBuilder(url, params); return $http.get(url); From cfe84baa31933b4b122974846ae3d9f3aff06fed Mon Sep 17 00:00:00 2001 From: ssranjani06 Date: Tue, 4 Apr 2023 11:33:57 +0530 Subject: [PATCH 191/521] Zone related dropdown changes --- .../portal/app/views/groups/groups.scala.html | 20 +++++++++---------- .../zones/zoneTabs/manageZone.scala.html | 18 +++++++++++++++++ .../lib/controllers/controller.groups.js | 17 ++++++++++++---- .../lib/controllers/controller.manageZones.js | 16 ++++++++++++++- .../lib/controllers/controller.zones.js | 16 ++++++++++++++- .../lib/services/zones/service.zones.js | 4 ++++ .../templates/zoneconnection-modal.html | 18 +++++++++++++++++ 7 files changed, 92 insertions(+), 17 deletions(-) diff --git a/modules/portal/app/views/groups/groups.scala.html b/modules/portal/app/views/groups/groups.scala.html index cea9fc553..126267dd2 100644 --- a/modules/portal/app/views/groups/groups.scala.html +++ b/modules/portal/app/views/groups/groups.scala.html @@ -281,9 +281,11 @@ type="text" required> +
    -
    @@ -299,6 +301,7 @@
    +
    The email distribution list for the group.
    @@ -315,12 +318,9 @@ @@ -399,10 +399,8 @@ diff --git a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html index 790d4a00e..de5d14250 100644 --- a/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html +++ b/modules/portal/app/views/zones/zoneTabs/manageZone.scala.html @@ -33,6 +33,24 @@
    + +
    +
    +

    + List Of Valid Email Domains: +

  • + {{validDomains}} +
  • +

    +
    +
    +
    diff --git a/modules/portal/public/lib/controllers/controller.groups.js b/modules/portal/public/lib/controllers/controller.groups.js index 61f21c4c5..cadecd172 100644 --- a/modules/portal/public/lib/controllers/controller.groups.js +++ b/modules/portal/public/lib/controllers/controller.groups.js @@ -29,6 +29,7 @@ angular.module('controller.groups', []).controller('GroupsController', function $scope.hasGroups = false; $scope.query = ""; $scope.validEmailDomains= []; + $scope.isCollapsed = false; // Paging status for group sets var groupsPaging = pagingService.getNewPagingParams(100); @@ -39,11 +40,11 @@ angular.module('controller.groups', []).controller('GroupsController', function $scope.alerts.push(alert); $scope.processing = false; } - //views //shared modal var modalDialog; - + var modalCollapsedDialog; + var modalOpenCollapsedDialog $scope.openModal = function (evt) { $log.log('First entry'); $scope.currentGroup = {}; @@ -55,6 +56,15 @@ angular.module('controller.groups', []).controller('GroupsController', function modalDialog.modal('show'); }; + $scope.openCollapsedModal = function (evt) { + void (evt && evt.preventDefault()); + if (!modalOpenCollapsedDialog) { + modalOpenCollapsedDialog = angular.element('#collapseNewGroupInstruction').modal(); + + } + modalOpenCollapsedDialog.modal('show'); + }; + $scope.closeModal = function (evt) { void (evt && evt.preventDefault()); @@ -209,11 +219,10 @@ angular.module('controller.groups', []).controller('GroupsController', function .catch(function (error) { handleError(error, 'groupsService::getGroups-failure'); }); - + } //Function for fetching list of valid domains $scope.validDomains=function getValidEmailDomains() { - $log.log('Function Entry'); function success(response) { $log.log('groupsService::listEmailDomains-success'); return $scope.validEmailDomains = response.data; diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 2fc85a7ed..dc6da69cd 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -40,6 +40,7 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) $scope.zoneInfo = {}; $scope.zoneChanges = {}; $scope.updateZoneInfo = {}; + $scope.validEmailDomains= []; $scope.zoneSyncSchedule = { isChecked: false, recurrenceSchedule: '' @@ -156,7 +157,19 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) }; $('#acl_modal').modal('show'); }; - + $scope.validDomains=function getValidEmailDomains() { + $log.log('Function Entry'); + function success(response) { + $log.log('manageZonesService::listEmailDomains-success'); + return $scope.validEmailDomains = response.data; + } + return zonesService + .listEmailDomains($scope.ignoreAccess, $scope.query) + .then(success) + .catch(function (error) { + handleError(error, 'manageZonesService::listEmailDomains-failure'); + }); + } $scope.clickUpdateAclRule = function(index) { $scope.currentAclRuleIndex = index; $scope.currentAclRule = angular.copy($scope.aclRules[index]); @@ -325,6 +338,7 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) $scope.currentManageZoneState = $scope.manageZoneState.UPDATE; $scope.refreshAclRuleDisplay(); $scope.refreshZoneChange(); + $scope.validDomains(); } return recordsService .getZone($scope.zoneId) diff --git a/modules/portal/public/lib/controllers/controller.zones.js b/modules/portal/public/lib/controllers/controller.zones.js index 808ee989e..0a6ea0a96 100644 --- a/modules/portal/public/lib/controllers/controller.zones.js +++ b/modules/portal/public/lib/controllers/controller.zones.js @@ -24,6 +24,7 @@ angular.module('controller.zones', []) $scope.hasZones = false; // Re-assigned each time zones are fetched without a query $scope.allGroups = []; $scope.ignoreAccess = false; + $scope.validEmailDomains= []; $scope.allZonesAccess = function () { $scope.ignoreAccess = true; } @@ -52,7 +53,7 @@ angular.module('controller.zones', []) $scope.resetCurrentZone = function () { $scope.currentZone = {}; - + $scope.validDomains(); if($scope.myGroups && $scope.myGroups.length) { $scope.currentZone.adminGroupId = $scope.myGroups[0].id; } @@ -220,7 +221,20 @@ angular.module('controller.zones', []) $("td.dataTables_empty").show(); } } + $scope.validDomains=function getValidEmailDomains() { + $log.log('Function Entry'); + function success(response) { + $log.log('zonesService::listEmailDomains-success'); + return $scope.validEmailDomains = response.data; + } + return zonesService + .listEmailDomains($scope.ignoreAccess, $scope.query) + .then(success) + .catch(function (error) { + handleError(error, 'zonesService::listEmailDomains-failure'); + }); + } /* Set total number of zones */ $scope.addZoneConnection = function () { diff --git a/modules/portal/public/lib/services/zones/service.zones.js b/modules/portal/public/lib/services/zones/service.zones.js index b4c875210..85b0eac2d 100644 --- a/modules/portal/public/lib/services/zones/service.zones.js +++ b/modules/portal/public/lib/services/zones/service.zones.js @@ -72,6 +72,10 @@ angular.module('service.zones', []) $log.info("service.zones: updating zone", sanitizedPayload); return $http.put("/api/zones/"+id, sanitizedPayload, {headers: utilityService.getCsrfHeader()}); }; + this.listEmailDomains = function () { + var url = '/api/groups/valid/domains' + return $http.get(url); + }; this.sanitizeConnections = function(payload) { var sanitizedPayload = {}; diff --git a/modules/portal/public/templates/zoneconnection-modal.html b/modules/portal/public/templates/zoneconnection-modal.html index a707bc3f5..b043c2a56 100644 --- a/modules/portal/public/templates/zoneconnection-modal.html +++ b/modules/portal/public/templates/zoneconnection-modal.html @@ -17,6 +17,24 @@ The email distribution list for the zone. Typically the distribution email for the org that owns the zone. + +
    +
    +

    + List Of Valid Email Domains: +

  • + {{validDomains}} +
  • +

    +
    +
    +
    @@ -207,6 +210,107 @@

    Record Data is optional.

    + + +

    + Record data is required! +

    +

    Record Data is optional.

    + + + + +

    + Priority is required! + Must be between 0 and 65535! + Must be between 0 and 65535! +

    +

    Record Data is optional.

    +
    + + + +

    + Weight is required! + Must be between 0 and 65535! + Must be between 0 and 65535! +

    +

    Record Data is optional.

    + +
    + + + +

    + Port is required! + Must be between 0 and 65535! + Must be between 0 and 65535! +

    +

    Record Data is optional.

    + +
    + + + +

    + Target is required! +

    +

    Record Data is optional.

    + + + + +

    + Order is required! + Must be between 0 and 65535! + Must be between 0 and 65535! +

    +

    Record Data is optional.

    + +
    + + + +

    + Preference is required! + Must be between 0 and 65535! + Must be between 0 and 65535! +

    +

    Record Data is optional.

    + +
    + + + +

    + Flags is required! +

    +

    Record Data is optional.

    + +
    + + + +

    + Service is required! +

    +

    Record Data is optional.

    + +
    + + + +

    Record Data is optional.

    + +
    + + + +

    + Replacement is required! +

    +

    Record Data is optional.

    +

    diff --git a/modules/portal/public/lib/dns-change/dns-change-new.controller.js b/modules/portal/public/lib/dns-change/dns-change-new.controller.js index 5f0e6c842..10cc525e0 100644 --- a/modules/portal/public/lib/dns-change/dns-change-new.controller.js +++ b/modules/portal/public/lib/dns-change/dns-change-new.controller.js @@ -89,6 +89,13 @@ var newEntry = {changeType: entry.changeType, type: "PTR", ttl: entry.ttl, inputName: entry.record.address, record: {ptrdname: entry.inputName}} payload.changes.splice(i+1, 0, newEntry) } + if(entry.type == 'NAPTR') { + // Since regexp can be left empty + if(entry.record.regexp == undefined){ + var newEntry = {changeType: entry.changeType, type: "NAPTR", ttl: entry.ttl, inputName: entry.inputName, record: {order: entry.record.order, preference: entry.record.preference, flags: entry.record.flags, service: entry.record.service, regexp: '', replacement: entry.record.replacement}} + payload.changes[i] = newEntry; + } + } if(entry.changeType == 'DeleteRecordSet' && entry.record) { var recordDataEmpty = true; for (var attr in entry.record) { @@ -219,6 +226,21 @@ change[headers[j]] = {"ptrdname": rowContent[j].trim()} } else if (change["type"] == "TXT") { change[headers[j]] = {"text": rowContent[j].trim()} + } else if (change["type"] == "NS") { + change[headers[j]] = {"nsdname": rowContent[j].trim()} + } else if (change["type"] == "MX") { + var mxData = rowContent[j].trim().split(' '); + change[headers[j]] = {"preference": parseInt(mxData[0]), "exchange": mxData[1]} + } else if (change["type"] == "NAPTR") { + var naptrData = rowContent[j].trim().split(' '); + if(naptrData.length == 6){ + change[headers[j]] = {"order": parseInt(naptrData[0]), "preference": parseInt(naptrData[1]), "flags": naptrData[2], "service": naptrData[3], "regexp": naptrData[4], "replacement": naptrData[5]} + } else { + change[headers[j]] = {"order": parseInt(naptrData[0]), "preference": parseInt(naptrData[1]), "flags": naptrData[2], "service": naptrData[3], "regexp": '', "replacement": naptrData[4]} + } + } else if (change["type"] == "SRV") { + var srvData = rowContent[j].trim().split(' '); + change[headers[j]] = {"priority": parseInt(srvData[0]), "weight": parseInt(srvData[1]), "port": parseInt(srvData[2]), "target": srvData[3]} } } else { change[headers[j]] = rowContent[j].trim() From 7c1b7ae4995048a541109ece6a973e4c1cbc46dc Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 8 May 2023 21:36:56 +0530 Subject: [PATCH 231/521] add functional test --- .../tests/batch/create_batch_change_test.py | 67 ++++++++++++++++++- modules/api/src/test/functional/utils.py | 51 ++++++++++++++ 2 files changed, 115 insertions(+), 3 deletions(-) diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index 8603e28f4..eb876619c 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -14,7 +14,7 @@ def validate_change_error_response_basics(input_json, change_type, input_name, r assert_that(input_json["changeType"], is_(change_type)) assert_that(input_json["inputName"], is_(input_name)) assert_that(input_json["type"], is_(record_type)) - assert_that(record_type, is_in(["A", "AAAA", "CNAME", "PTR", "TXT", "MX"])) + assert_that(record_type, is_in(["A", "AAAA", "CNAME", "PTR", "TXT", "MX", "NS", "NAPTR", "SRV"])) if change_type == "Add": assert_that(input_json["ttl"], is_(ttl)) if record_type in ["A", "AAAA"]: @@ -28,6 +28,20 @@ def validate_change_error_response_basics(input_json, change_type, input_name, r elif record_type == "MX": assert_that(input_json["record"]["preference"], is_(record_data["preference"])) assert_that(input_json["record"]["exchange"], is_(record_data["exchange"])) + elif record_type == "NS" and change_type == "Add": + assert_that(input_json["record"]["nsdname"], is_(record_data)) + elif record_type == "NAPTR" and change_type == "Add": + assert_that(input_json["record"]["order"], is_(record_data["order"])) + assert_that(input_json["record"]["preference"], is_(record_data["preference"])) + assert_that(input_json["record"]["flags"], is_(record_data["flags"])) + assert_that(input_json["record"]["service"], is_(record_data["service"])) + assert_that(input_json["record"]["regexp"], is_(record_data["regexp"])) + assert_that(input_json["record"]["replacement"], is_(record_data["replacement"])) + elif record_type == "SRV" and change_type == "Add": + assert_that(input_json["record"]["priority"], is_(record_data["priority"])) + assert_that(input_json["record"]["weight"], is_(record_data["weight"])) + assert_that(input_json["record"]["port"], is_(record_data["port"])) + assert_that(input_json["record"]["target"], is_(record_data["target"])) return @@ -56,7 +70,7 @@ def assert_change_success(changes_json, zone, index, record_name, input_name, re assert_that(changes_json[index]["type"], is_(record_type)) assert_that(changes_json[index]["id"], is_not(none())) assert_that(changes_json[index]["changeType"], is_(change_type)) - assert_that(record_type, is_in(["A", "AAAA", "CNAME", "PTR", "TXT", "MX"])) + assert_that(record_type, is_in(["A", "AAAA", "CNAME", "PTR", "TXT", "MX", "NS", "NAPTR", "SRV"])) if record_type in ["A", "AAAA"] and change_type == "Add": assert_that(changes_json[index]["record"]["address"], is_(record_data)) elif record_type == "CNAME" and change_type == "Add": @@ -68,6 +82,20 @@ def assert_change_success(changes_json, zone, index, record_name, input_name, re elif record_type == "MX" and change_type == "Add": assert_that(changes_json[index]["record"]["preference"], is_(record_data["preference"])) assert_that(changes_json[index]["record"]["exchange"], is_(record_data["exchange"])) + elif record_type == "NS" and change_type == "Add": + assert_that(changes_json[index]["record"]["nsdname"], is_(record_data)) + elif record_type == "NAPTR" and change_type == "Add": + assert_that(changes_json[index]["record"]["order"], is_(record_data["order"])) + assert_that(changes_json[index]["record"]["preference"], is_(record_data["preference"])) + assert_that(changes_json[index]["record"]["flags"], is_(record_data["flags"])) + assert_that(changes_json[index]["record"]["service"], is_(record_data["service"])) + assert_that(changes_json[index]["record"]["regexp"], is_(record_data["regexp"])) + assert_that(changes_json[index]["record"]["replacement"], is_(record_data["replacement"])) + elif record_type == "SRV" and change_type == "Add": + assert_that(changes_json[index]["record"]["priority"], is_(record_data["priority"])) + assert_that(changes_json[index]["record"]["weight"], is_(record_data["weight"])) + assert_that(changes_json[index]["record"]["port"], is_(record_data["port"])) + assert_that(changes_json[index]["record"]["target"], is_(record_data["target"])) return @@ -113,7 +141,10 @@ def test_create_batch_change_with_adds_success(shared_zone_test_context): get_change_TXT_json(f"txt-unique-characters.{ok_zone_name}", text='a\\\\`=` =\\"Cat\\"\nattr=val'), get_change_TXT_json(f"txt.{ip4_zone_name}"), get_change_MX_json(f"mx.{ok_zone_name}", preference=0), - get_change_MX_json(f"{ok_zone_name}", preference=1000, exchange="bar.foo.") + get_change_MX_json(f"{ok_zone_name}", preference=1000, exchange="bar.foo."), + get_change_NS_json(f"ns.{ok_zone_name}", nsdname="ns1.parent.com."), + get_change_NAPTR_json(f"naptr.{ok_zone_name}", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_SRV_json(f"srv.{ok_zone_name}", priority=1000, weight=5, port=20, target="bar.foo.") ] } @@ -161,6 +192,12 @@ def test_create_batch_change_with_adds_success(shared_zone_test_context): record_name="mx", input_name=f"mx.{ok_zone_name}", record_data={"preference": 0, "exchange": "foo.bar."}, record_type="MX") assert_change_success(result["changes"], zone=ok_zone, index=14, record_name=f"{ok_zone_name}", input_name=f"{ok_zone_name}", record_data={"preference": 1000, "exchange": "bar.foo."}, record_type="MX") + assert_change_success(result["changes"], zone=ok_zone, index=15, + record_name=f"ns", input_name=f"ns.{ok_zone_name}", record_data="ns1.parent.com.", record_type="NS") + assert_change_success(result["changes"], zone=ok_zone, index=16, + record_name=f"naptr", input_name=f"naptr.{ok_zone_name}", record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, record_type="NAPTR") + assert_change_success(result["changes"], zone=ok_zone, index=17, + record_name=f"srv", input_name=f"srv.{ok_zone_name}", record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, record_type="SRV") completed_status = [change["status"] == "Complete" for change in completed_batch["changes"]] assert_that(all(completed_status), is_(True)) @@ -285,6 +322,30 @@ def test_create_batch_change_with_adds_success(shared_zone_test_context): "ttl": 200, "records": [{"preference": 1000, "exchange": "bar.foo."}]} verify_recordset(rs16, expected16) + + rs17 = client.get_recordset(record_set_list[15][0], record_set_list[15][1])["recordSet"] + expected17 = {"name": f"ns", + "zoneId": ok_zone["id"], + "type": "NS", + "ttl": 200, + "records": [{"nsdname": "ns1.parent.com."}]} + verify_recordset(rs17, expected17) + + rs18 = client.get_recordset(record_set_list[16][0], record_set_list[16][1])["recordSet"] + expected18 = {"name": f"naptr", + "zoneId": ok_zone["id"], + "type": "NAPTR", + "ttl": 200, + "records": [{"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}]} + verify_recordset(rs18, expected18) + + rs19 = client.get_recordset(record_set_list[17][0], record_set_list[17][1])["recordSet"] + expected19 = {"name": f"srv", + "zoneId": ok_zone["id"], + "type": "SRV", + "ttl": 200, + "records": [{"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}]} + verify_recordset(rs19, expected19) finally: clear_zoneid_rsid_tuple_list(to_delete, client) diff --git a/modules/api/src/test/functional/utils.py b/modules/api/src/test/functional/utils.py index 449119e29..9ebac95d8 100644 --- a/modules/api/src/test/functional/utils.py +++ b/modules/api/src/test/functional/utils.py @@ -509,6 +509,57 @@ def get_change_MX_json(input_name, ttl=200, preference=None, exchange=None, chan return json +def get_change_NS_json(input_name, ttl=200, nsdname=None, change_type="Add"): + json = { + "changeType": change_type, + "inputName": input_name, + "type": "NS" + } + if change_type == "Add": + json["ttl"] = ttl + json["record"] = { + "nsdname": nsdname + } + return json + + +def get_change_SRV_json(input_name, ttl=200, priority=None, weight=None, port=None, target=None, change_type="Add"): + json = { + "changeType": change_type, + "inputName": input_name, + "type": "SRV", + } + if change_type == "Add": + json["ttl"] = ttl + json["record"] = { + "priority": priority, + "weight": weight, + "port": port, + "target": target + } + return json + + +def get_change_NAPTR_json(input_name, ttl=200, order=None, preference=None, flags=None, service=None, regexp=None, replacement=None, change_type="Add"): + json = { + "changeType": change_type, + "inputName": input_name, + "type": "NAPTR", + + } + if change_type == "Add": + json["ttl"] = ttl + json["record"] = { + "order": order, + "preference": preference, + "flags": flags, + "service": service, + "regexp": regexp, + "replacement": replacement + } + return json + + def create_recordset(zone, rname, recordset_type, rdata_list, ttl=200, ownergroup_id=None): recordset_data = { "zoneId": zone["id"], From 3bc40dcd6152e57eb7d2934b9efd386fb11e0b07 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 9 May 2023 09:29:45 +0530 Subject: [PATCH 232/521] add functional test --- .../tests/batch/create_batch_change_test.py | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index eb876619c..c8b9f7f7c 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -4220,3 +4220,203 @@ def test_create_batch_change_with_multi_record_adds_with_multi_record_support(sh assert_successful_change_in_error_response(response["changes"][8], input_name=rs_fqdn, record_data="1.1.1.1") finally: clear_recordset_list(to_delete, client) + + +def test_ns_recordtype_add_checks(shared_zone_test_context): + """ + Test all add validations performed on NS records submitted in batch changes + """ + client = shared_zone_test_context.ok_vinyldns_client + dummy_zone_name = shared_zone_test_context.dummy_zone["name"] + dummy_group_name = shared_zone_test_context.dummy_group["name"] + ok_zone_name = shared_zone_test_context.ok_zone["name"] + + existing_ns_name = generate_record_name() + existing_ns_fqdn = existing_ns_name + f".{ok_zone_name}" + existing_ns = create_recordset(shared_zone_test_context.ok_zone, existing_ns_name, "NS", [{"nsdname": "ns1.parent.com."}], 100) + + existing_cname_name = generate_record_name() + existing_cname_fqdn = existing_cname_name + f".{ok_zone_name}" + existing_cname = create_recordset(shared_zone_test_context.ok_zone, existing_cname_name, "CNAME", + [{"cname": "test."}], 100) + + batch_change_input = { + "changes": [ + # valid change + get_change_NS_json(f"ns.{ok_zone_name}", nsdname="ns1.parent.com."), + + # input validation failures + get_change_NS_json(f"bad-ttl-and-invalid-name$.{ok_zone_name}", ttl=29, nsdname="ns1.parent.com."), + + # zone discovery failures + get_change_NS_json(f"no.zone.at.all.", nsdname="ns1.parent.com."), + + # context validation failures + get_change_CNAME_json(f"cname-duplicate.{ok_zone_name}"), + get_change_NS_json(f"cname-duplicate.{ok_zone_name}", nsdname="ns1.parent.com."), + get_change_NS_json(existing_ns_fqdn, nsdname="ns1.parent.com."), + get_change_NS_json(existing_cname_fqdn, nsdname="ns1.parent.com."), + get_change_NS_json(f"unapproved.{ok_zone_name}", nsdname="unapproved.name.server."), + get_change_NS_json(f"user-add-unauthorized.{dummy_zone_name}", nsdname="ns1.parent.com.") + ] + } + + to_create = [existing_ns, existing_cname] + to_delete = [] + try: + for create_json in to_create: + create_result = client.create_recordset(create_json, status=202) + to_delete.append(client.wait_until_recordset_change_status(create_result, "Complete")) + + response = client.create_batch_change(batch_change_input, status=400) + + # successful changes + assert_successful_change_in_error_response(response[0], input_name=f"ns.{ok_zone_name}", record_type="NS", + record_data="ns1.parent.com.") + + # ttl, domain name, record data + assert_failed_change_in_error_response(response[1], input_name=f"bad-ttl-and-invalid-name$.{ok_zone_name}", ttl=29, + record_type="NS", record_data="ns1.parent.com.", + error_messages=[ + 'Invalid TTL: "29", must be a number between 30 and 2147483647.', + f'Invalid domain name: "bad-ttl-and-invalid-name$.{ok_zone_name}", ' + "valid domain names must be letters, numbers, underscores, and hyphens, joined by dots, and terminated with a dot."]) + + # zone discovery failure + assert_failed_change_in_error_response(response[2], input_name="no.zone.at.all.", record_type="NS", + record_data="ns1.parent.com.", + error_messages=['Zone Discovery Failed: zone for "no.zone.at.all." does not exist in VinylDNS. ' + 'If zone exists, then it must be connected to in VinylDNS.']) + + # context validations: cname duplicate + assert_failed_change_in_error_response(response[3], input_name=f"cname-duplicate.{ok_zone_name}", record_type="CNAME", + record_data="test.com.", + error_messages=[f"Record Name \"cname-duplicate.{ok_zone_name}\" Not Unique In Batch Change: " + f"cannot have multiple \"CNAME\" records with the same name."]) + + # context validations: conflicting recordsets, unauthorized error + assert_successful_change_in_error_response(response[5], input_name=existing_ns_fqdn, record_type="NS", + record_data="ns1.parent.com.") + assert_failed_change_in_error_response(response[6], input_name=existing_cname_fqdn, record_type="NS", + record_data="ns1.parent.com.", + error_messages=[f"CNAME Conflict: CNAME record names must be unique. " + f"Existing record with name \"{existing_cname_fqdn}\" and type \"CNAME\" conflicts with this record."]) + assert_failed_change_in_error_response(response[7], input_name=f"unapproved.{ok_zone_name}", + record_type="NS", record_data="unapproved.name.server.", + error_messages=[f"Name Server unapproved.name.server. is not an approved name server."]) + assert_failed_change_in_error_response(response[8], input_name=f"user-add-unauthorized.{dummy_zone_name}", + record_type="NS", record_data="ns1.parent.com.", + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + finally: + clear_recordset_list(to_delete, client) + + +def test_ns_recordtype_update_delete_checks(shared_zone_test_context): + """ + Test all update and delete validations performed on NS records submitted in batch changes + """ + ok_client = shared_zone_test_context.ok_vinyldns_client + dummy_client = shared_zone_test_context.dummy_vinyldns_client + ok_zone = shared_zone_test_context.ok_zone + dummy_zone = shared_zone_test_context.dummy_zone + ok_zone_name = shared_zone_test_context.ok_zone["name"] + dummy_zone_name = shared_zone_test_context.dummy_zone["name"] + dummy_group_name = shared_zone_test_context.dummy_group["name"] + + rs_delete_name = generate_record_name() + rs_delete_fqdn = rs_delete_name + f".{ok_zone_name}" + rs_delete_ok = create_recordset(ok_zone, rs_delete_name, "NS", [{"nsdname": "ns1.parent.com."}], 200) + + rs_update_name = generate_record_name() + rs_update_fqdn = rs_update_name + f".{ok_zone_name}" + rs_update_ok = create_recordset(ok_zone, rs_update_name, "NS", [{"nsdname": "ns1.parent.com."}], 200) + + rs_delete_dummy_name = generate_record_name() + rs_delete_dummy_fqdn = rs_delete_dummy_name + f".{dummy_zone_name}" + rs_delete_dummy = create_recordset(dummy_zone, rs_delete_dummy_name, "NS", [{"nsdname": "ns1.parent.com."}], 200) + + rs_update_dummy_name = generate_record_name() + rs_update_dummy_fqdn = rs_update_dummy_name + f".{dummy_zone_name}" + rs_update_dummy = create_recordset(dummy_zone, rs_update_dummy_name, "NS", [{"nsdname": "ns1.parent.com."}], 200) + + batch_change_input = { + "comments": "this is optional", + "changes": [ + # valid changes + get_change_NS_json(rs_delete_fqdn, change_type="DeleteRecordSet", nsdname="ns1.parent.com."), + get_change_NS_json(rs_update_fqdn, change_type="DeleteRecordSet", nsdname="ns1.parent.com."), + get_change_NS_json(rs_update_fqdn, ttl=300, nsdname="ns1.parent.com."), + get_change_NS_json(f"delete-nonexistent.{ok_zone_name}", change_type="DeleteRecordSet", nsdname="ns1.parent.com."), + get_change_NS_json(f"update-nonexistent.{ok_zone_name}", change_type="DeleteRecordSet", nsdname="ns1.parent.com."), + + # input validations failures + get_change_NS_json(f"invalid-name$.{ok_zone_name}", change_type="DeleteRecordSet", nsdname="ns1.parent.com."), + get_change_NS_json(f"invalid-ttl.{ok_zone_name}", ttl=29, nsdname="ns1.parent.com."), + + # zone discovery failure + get_change_NS_json("no.zone.at.all.", change_type="DeleteRecordSet", nsdname="ns1.parent.com."), + + # context validation failures + get_change_NS_json(f"update-nonexistent.{ok_zone_name}", nsdname="ns1.parent.com."), + get_change_NS_json(rs_delete_dummy_fqdn, change_type="DeleteRecordSet", nsdname="ns1.parent.com."), + get_change_NS_json(rs_update_dummy_fqdn, nsdname="ns1.parent.com."), + get_change_NS_json(f"unapproved.{ok_zone_name}", nsdname="unapproved.name.server."), + get_change_NS_json(rs_update_dummy_fqdn, change_type="DeleteRecordSet", nsdname="ns1.parent.com.") + ] + } + + to_create = [rs_delete_ok, rs_update_ok, rs_delete_dummy, rs_update_dummy] + to_delete = [] + + try: + for rs in to_create: + if rs["zoneId"] == dummy_zone["id"]: + create_client = dummy_client + else: + create_client = ok_client + + create_rs = create_client.create_recordset(rs, status=202) + to_delete.append(create_client.wait_until_recordset_change_status(create_rs, "Complete")) + + # Confirm that record set doesn't already exist + ok_client.get_recordset(ok_zone["id"], "delete-nonexistent", status=404) + + response = ok_client.create_batch_change(batch_change_input, status=400) + + # successful changes + assert_successful_change_in_error_response(response[0], input_name=rs_delete_fqdn, record_type="NS", record_data="ns1.parent.com.", change_type="DeleteRecordSet") + assert_successful_change_in_error_response(response[1], input_name=rs_update_fqdn, record_type="NS", record_data="ns1.parent.com.", change_type="DeleteRecordSet") + assert_successful_change_in_error_response(response[2], ttl=300, input_name=rs_update_fqdn, record_type="NS", record_data="ns1.parent.com.") + assert_successful_change_in_error_response(response[3], input_name=f"delete-nonexistent.{ok_zone_name}", record_type="NS", record_data="ns1.parent.com.", change_type="DeleteRecordSet") + assert_successful_change_in_error_response(response[4], input_name=f"update-nonexistent.{ok_zone_name}", record_type="NS", record_data="ns1.parent.com.", change_type="DeleteRecordSet") + + # input validations failures: invalid input name, reverse zone error, invalid ttl + assert_failed_change_in_error_response(response[5], input_name=f"invalid-name$.{ok_zone_name}", record_type="NS", record_data="ns1.parent.com.", change_type="DeleteRecordSet", + error_messages=[f'Invalid domain name: "invalid-name$.{ok_zone_name}", valid domain names must be ' + f'letters, numbers, underscores, and hyphens, joined by dots, and terminated with a dot.']) + assert_failed_change_in_error_response(response[6], input_name=f"invalid-ttl.{ok_zone_name}", ttl=29, record_type="NS", record_data="ns1.parent.com.", + error_messages=['Invalid TTL: "29", must be a number between 30 and 2147483647.']) + + # zone discovery failure + assert_failed_change_in_error_response(response[7], input_name="no.zone.at.all.", record_type="NS", record_data="ns1.parent.com.", change_type="DeleteRecordSet", + error_messages=[ + "Zone Discovery Failed: zone for \"no.zone.at.all.\" does not exist in VinylDNS. " + "If zone exists, then it must be connected to in VinylDNS."]) + + # context validation failures: record does not exist, not authorized + assert_successful_change_in_error_response(response[8], input_name=f"update-nonexistent.{ok_zone_name}", record_type="NS", record_data="ns1.parent.com.") + assert_failed_change_in_error_response(response[9], input_name=rs_delete_dummy_fqdn, record_type="NS", record_data="ns1.parent.com.", change_type="DeleteRecordSet", + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + assert_failed_change_in_error_response(response[10], input_name=rs_update_dummy_fqdn, record_type="NS", record_data="ns1.parent.com.", + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + assert_failed_change_in_error_response(response[11], input_name=f"unapproved.{ok_zone_name}", + record_type="NS", record_data="unapproved.name.server.", + error_messages=[f"Name Server unapproved.name.server. is not an approved name server."]) + assert_failed_change_in_error_response(response[12], input_name=rs_update_dummy_fqdn, record_type="NS", record_data="ns1.parent.com.", change_type="DeleteRecordSet", + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + finally: + # Clean up updates + dummy_deletes = [rs for rs in to_delete if rs["zone"]["id"] == dummy_zone["id"]] + ok_deletes = [rs for rs in to_delete if rs["zone"]["id"] != dummy_zone["id"]] + clear_recordset_list(dummy_deletes, dummy_client) + clear_recordset_list(ok_deletes, ok_client) \ No newline at end of file From 96fb185b1c56a559b693c4132f48681dcddb3e1a Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 9 May 2023 10:36:46 +0530 Subject: [PATCH 233/521] add functional test --- .../tests/batch/create_batch_change_test.py | 217 +++++++++++++++++- 1 file changed, 216 insertions(+), 1 deletion(-) diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index c8b9f7f7c..c6a7d3331 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -4419,4 +4419,219 @@ def test_ns_recordtype_update_delete_checks(shared_zone_test_context): dummy_deletes = [rs for rs in to_delete if rs["zone"]["id"] == dummy_zone["id"]] ok_deletes = [rs for rs in to_delete if rs["zone"]["id"] != dummy_zone["id"]] clear_recordset_list(dummy_deletes, dummy_client) - clear_recordset_list(ok_deletes, ok_client) \ No newline at end of file + clear_recordset_list(ok_deletes, ok_client) + + +def test_naptr_recordtype_add_checks(shared_zone_test_context): + """ + Test all add validations performed on NAPTR records submitted in batch changes + """ + client = shared_zone_test_context.ok_vinyldns_client + ok_zone_name = shared_zone_test_context.ok_zone["name"] + dummy_zone_name = shared_zone_test_context.dummy_zone["name"] + dummy_group_name = shared_zone_test_context.dummy_group["name"] + ip4_zone_name = shared_zone_test_context.classless_base_zone["name"] + + existing_naptr_name = generate_record_name() + existing_naptr_fqdn = f"{existing_naptr_name}.{ok_zone_name}" + existing_naptr = create_recordset(shared_zone_test_context.ok_zone, existing_naptr_name, "NAPTR", [{"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}], 100) + + existing_cname_name = generate_record_name() + existing_cname_fqdn = f"{existing_cname_name}.{ok_zone_name}" + existing_cname = create_recordset(shared_zone_test_context.ok_zone, existing_cname_name, "CNAME", [{"cname": "test."}], 100) + + good_record_fqdn = generate_record_name(ok_zone_name) + batch_change_input = { + "changes": [ + # valid change + get_change_NAPTR_json(good_record_fqdn, order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + + # input validation failures + get_change_NAPTR_json(f"bad-ttl-and-invalid-name$.{ok_zone_name}", ttl=29, order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(f"naptr.{ip4_zone_name}", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + + # zone discovery failures + get_change_NAPTR_json(f"no.subzone.{ok_zone_name}", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json("no.zone.at.all.", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + + # context validation failures + get_change_CNAME_json(f"cname-duplicate.{ok_zone_name}"), + get_change_NAPTR_json(f"cname-duplicate.{ok_zone_name}", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(existing_naptr_fqdn, order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(existing_cname_fqdn, order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(f"user-add-unauthorized.{dummy_zone_name}", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns.") + ] + } + + to_create = [existing_naptr, existing_cname] + to_delete = [] + try: + for create_json in to_create: + create_result = client.create_recordset(create_json, status=202) + to_delete.append(client.wait_until_recordset_change_status(create_result, "Complete")) + + response = client.create_batch_change(batch_change_input, status=400) + + # successful changes + assert_successful_change_in_error_response(response[0], input_name=good_record_fqdn, record_type="NAPTR", record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}) + + # ttl, domain name, record data + assert_failed_change_in_error_response(response[1], input_name=f"bad-ttl-and-invalid-name$.{ok_zone_name}", ttl=29, record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + error_messages=['Invalid TTL: "29", must be a number between 30 and 2147483647.', + f'Invalid domain name: "bad-ttl-and-invalid-name$.{ok_zone_name}", ' + "valid domain names must be letters, numbers, underscores, and hyphens, joined by dots, and terminated with a dot."]) + assert_failed_change_in_error_response(response[2], input_name=f"naptr.{ip4_zone_name}", record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + error_messages=[f'Invalid Record Type In Reverse Zone: record with name "naptr.{ip4_zone_name}" and type "NAPTR" is not allowed in a reverse zone.']) + + # zone discovery failures + assert_failed_change_in_error_response(response[3], input_name=f"no.subzone.{ok_zone_name}", record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + error_messages=[f'Zone Discovery Failed: zone for "no.subzone.{ok_zone_name}" does not exist in VinylDNS. ' + f'If zone exists, then it must be connected to in VinylDNS.']) + assert_failed_change_in_error_response(response[4], input_name="no.zone.at.all.", record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + error_messages=['Zone Discovery Failed: zone for "no.zone.at.all." does not exist in VinylDNS. ' + 'If zone exists, then it must be connected to in VinylDNS.']) + + # context validations: cname duplicate + assert_failed_change_in_error_response(response[5], input_name=f"cname-duplicate.{ok_zone_name}", record_type="CNAME", + record_data="test.com.", + error_messages=[f"Record Name \"cname-duplicate.{ok_zone_name}\" Not Unique In Batch Change: " + f"cannot have multiple \"CNAME\" records with the same name."]) + + # context validations: conflicting recordsets, unauthorized error + assert_successful_change_in_error_response(response[7], input_name=existing_naptr_fqdn, record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}) + assert_failed_change_in_error_response(response[8], input_name=existing_cname_fqdn, record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + error_messages=["CNAME Conflict: CNAME record names must be unique. " + f"Existing record with name \"{existing_cname_fqdn}\" and type \"CNAME\" conflicts with this record."]) + assert_failed_change_in_error_response(response[9], input_name=f"user-add-unauthorized.{dummy_zone_name}", record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + finally: + clear_recordset_list(to_delete, client) + + +def test_naptr_recordtype_update_delete_checks(shared_zone_test_context): + """ + Test all update and delete validations performed on NAPTR records submitted in batch changes + """ + ok_client = shared_zone_test_context.ok_vinyldns_client + dummy_client = shared_zone_test_context.dummy_vinyldns_client + ok_zone = shared_zone_test_context.ok_zone + dummy_zone = shared_zone_test_context.dummy_zone + dummy_zone_name = shared_zone_test_context.dummy_zone["name"] + + dummy_group_name = shared_zone_test_context.dummy_group["name"] + ok_zone_name = shared_zone_test_context.ok_zone["name"] + ip4_zone_name = shared_zone_test_context.classless_base_zone["name"] + + rs_delete_name = generate_record_name() + rs_delete_fqdn = rs_delete_name + f".{ok_zone_name}" + rs_delete_ok = create_recordset(ok_zone, rs_delete_name, "NAPTR", [{"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}], 200) + + rs_update_name = generate_record_name() + rs_update_fqdn = rs_update_name + f".{ok_zone_name}" + rs_update_ok = create_recordset(ok_zone, rs_update_name, "NAPTR", [{"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}], 200) + + rs_delete_dummy_name = generate_record_name() + rs_delete_dummy_fqdn = rs_delete_dummy_name + f".{dummy_zone_name}" + rs_delete_dummy = create_recordset(dummy_zone, rs_delete_dummy_name, "NAPTR", [{"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}], 200) + + rs_update_dummy_name = generate_record_name() + rs_update_dummy_fqdn = rs_update_dummy_name + f".{dummy_zone_name}" + rs_update_dummy = create_recordset(dummy_zone, rs_update_dummy_name, "NAPTR", [{"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}], 200) + + batch_change_input = { + "comments": "this is optional", + "changes": [ + # valid changes + get_change_NAPTR_json(rs_delete_fqdn, change_type="DeleteRecordSet", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(rs_update_fqdn, change_type="DeleteRecordSet", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(rs_update_fqdn, ttl=300, order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(f"delete-nonexistent.{ok_zone_name}", change_type="DeleteRecordSet", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(f"update-nonexistent.{ok_zone_name}", change_type="DeleteRecordSet", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + + # input validations failures + get_change_NAPTR_json(f"invalid-name$.{ok_zone_name}", change_type="DeleteRecordSet", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(f"delete.{ok_zone_name}", ttl=29, order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(f"naptr.{ip4_zone_name}", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + + # zone discovery failures + get_change_NAPTR_json("no.zone.at.all.", change_type="DeleteRecordSet", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + + # context validation failures + get_change_NAPTR_json(f"update-nonexistent.{ok_zone_name}", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(rs_delete_dummy_fqdn, change_type="DeleteRecordSet", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(rs_update_dummy_fqdn, order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), + get_change_NAPTR_json(rs_update_dummy_fqdn, change_type="DeleteRecordSet", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns.") + ] + } + + to_create = [rs_delete_ok, rs_update_ok, rs_delete_dummy, rs_update_dummy] + to_delete = [] + + try: + for rs in to_create: + if rs["zoneId"] == dummy_zone["id"]: + create_client = dummy_client + else: + create_client = ok_client + + create_rs = create_client.create_recordset(rs, status=202) + to_delete.append(create_client.wait_until_recordset_change_status(create_rs, "Complete")) + + # Confirm that record set doesn't already exist + ok_client.get_recordset(ok_zone["id"], "delete-nonexistent", status=404) + + response = ok_client.create_batch_change(batch_change_input, status=400) + + # successful changes + assert_successful_change_in_error_response(response[0], input_name=rs_delete_fqdn, record_type="NAPTR", record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, change_type="DeleteRecordSet") + assert_successful_change_in_error_response(response[1], input_name=rs_update_fqdn, record_type="NAPTR", record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, change_type="DeleteRecordSet") + assert_successful_change_in_error_response(response[2], ttl=300, input_name=rs_update_fqdn, record_type="NAPTR", record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}) + assert_successful_change_in_error_response(response[3], input_name=f"delete-nonexistent.{ok_zone_name}", record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, change_type="DeleteRecordSet") + assert_successful_change_in_error_response(response[4], input_name=f"update-nonexistent.{ok_zone_name}", record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, change_type="DeleteRecordSet") + + # input validations failures: invalid input name, reverse zone error, invalid ttl + assert_failed_change_in_error_response(response[5], input_name=f"invalid-name$.{ok_zone_name}", record_type="NAPTR", record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + change_type="DeleteRecordSet", + error_messages=[f'Invalid domain name: "invalid-name$.{ok_zone_name}", valid domain names must be letters, ' + f'numbers, underscores, and hyphens, joined by dots, and terminated with a dot.']) + assert_failed_change_in_error_response(response[6], input_name=f"delete.{ok_zone_name}", ttl=29, record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + error_messages=['Invalid TTL: "29", must be a number between 30 and 2147483647.']) + assert_failed_change_in_error_response(response[7], input_name=f"naptr.{ip4_zone_name}", record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + error_messages=[f'Invalid Record Type In Reverse Zone: record with name "naptr.{ip4_zone_name}" ' + f'and type "NAPTR" is not allowed in a reverse zone.']) + + # zone discovery failure + assert_failed_change_in_error_response(response[8], input_name="no.zone.at.all.", record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, change_type="DeleteRecordSet", + error_messages=["Zone Discovery Failed: zone for \"no.zone.at.all.\" does not exist in VinylDNS. " + "If zone exists, then it must be connected to in VinylDNS."]) + + # context validation failures: record does not exist, not authorized + assert_successful_change_in_error_response(response[9], input_name=f"update-nonexistent.{ok_zone_name}", record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}) + assert_failed_change_in_error_response(response[10], input_name=rs_delete_dummy_fqdn, record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, change_type="DeleteRecordSet", + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + assert_failed_change_in_error_response(response[11], input_name=rs_update_dummy_fqdn, record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + assert_failed_change_in_error_response(response[12], input_name=rs_update_dummy_fqdn, record_type="NAPTR", + record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, change_type="DeleteRecordSet", + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + finally: + # Clean up updates + dummy_deletes = [rs for rs in to_delete if rs["zone"]["id"] == dummy_zone["id"]] + ok_deletes = [rs for rs in to_delete if rs["zone"]["id"] != dummy_zone["id"]] + clear_recordset_list(dummy_deletes, dummy_client) + clear_recordset_list(ok_deletes, ok_client) From 77715b4f16d09940c7d043a98820908a7156cea3 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 10 May 2023 16:20:46 +0530 Subject: [PATCH 234/521] add functional test --- .../tests/batch/create_batch_change_test.py | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index c6a7d3331..b7b11541c 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -4635,3 +4635,219 @@ def test_naptr_recordtype_update_delete_checks(shared_zone_test_context): ok_deletes = [rs for rs in to_delete if rs["zone"]["id"] != dummy_zone["id"]] clear_recordset_list(dummy_deletes, dummy_client) clear_recordset_list(ok_deletes, ok_client) + + +def test_srv_recordtype_add_checks(shared_zone_test_context): + """ + Test all add validations performed on SRV records submitted in batch changes + """ + client = shared_zone_test_context.ok_vinyldns_client + ok_zone_name = shared_zone_test_context.ok_zone["name"] + dummy_zone_name = shared_zone_test_context.dummy_zone["name"] + dummy_group_name = shared_zone_test_context.dummy_group["name"] + ip4_zone_name = shared_zone_test_context.classless_base_zone["name"] + + existing_srv_name = generate_record_name() + existing_srv_fqdn = f"{existing_srv_name}.{ok_zone_name}" + existing_srv = create_recordset(shared_zone_test_context.ok_zone, existing_srv_name, "SRV", [{"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}], 100) + + existing_cname_name = generate_record_name() + existing_cname_fqdn = f"{existing_cname_name}.{ok_zone_name}" + existing_cname = create_recordset(shared_zone_test_context.ok_zone, existing_cname_name, "CNAME", [{"cname": "test."}], 100) + + good_record_fqdn = generate_record_name(ok_zone_name) + batch_change_input = { + "changes": [ + # valid change + get_change_SRV_json(good_record_fqdn, priority=1000, weight=5, port=20, target="bar.foo."), + + # input validation failures + get_change_SRV_json(f"bad-ttl-and-invalid-name$.{ok_zone_name}", ttl=29, priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(f"srv.{ip4_zone_name}", priority=1000, weight=5, port=20, target="bar.foo."), + + # zone discovery failures + get_change_SRV_json(f"no.subzone.{ok_zone_name}", priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json("no.zone.at.all.", priority=1000, weight=5, port=20, target="bar.foo."), + + # context validation failures + get_change_CNAME_json(f"cname-duplicate.{ok_zone_name}"), + get_change_SRV_json(f"cname-duplicate.{ok_zone_name}", priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(existing_srv_fqdn, priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(existing_cname_fqdn, priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(f"user-add-unauthorized.{dummy_zone_name}", priority=1000, weight=5, port=20, target="bar.foo.") + ] + } + + to_create = [existing_srv, existing_cname] + to_delete = [] + try: + for create_json in to_create: + create_result = client.create_recordset(create_json, status=202) + to_delete.append(client.wait_until_recordset_change_status(create_result, "Complete")) + + response = client.create_batch_change(batch_change_input, status=400) + + # successful changes + assert_successful_change_in_error_response(response[0], input_name=good_record_fqdn, record_type="SRV", record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}) + + # ttl, domain name, record data + assert_failed_change_in_error_response(response[1], input_name=f"bad-ttl-and-invalid-name$.{ok_zone_name}", ttl=29, record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + error_messages=['Invalid TTL: "29", must be a number between 30 and 2147483647.', + f'Invalid domain name: "bad-ttl-and-invalid-name$.{ok_zone_name}", ' + "valid domain names must be letters, numbers, underscores, and hyphens, joined by dots, and terminated with a dot."]) + assert_failed_change_in_error_response(response[2], input_name=f"srv.{ip4_zone_name}", record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + error_messages=[f'Invalid Record Type In Reverse Zone: record with name "srv.{ip4_zone_name}" and type "SRV" is not allowed in a reverse zone.']) + + # zone discovery failures + assert_failed_change_in_error_response(response[3], input_name=f"no.subzone.{ok_zone_name}", record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + error_messages=[f'Zone Discovery Failed: zone for "no.subzone.{ok_zone_name}" does not exist in VinylDNS. ' + f'If zone exists, then it must be connected to in VinylDNS.']) + assert_failed_change_in_error_response(response[4], input_name="no.zone.at.all.", record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + error_messages=['Zone Discovery Failed: zone for "no.zone.at.all." does not exist in VinylDNS. ' + 'If zone exists, then it must be connected to in VinylDNS.']) + + # context validations: cname duplicate + assert_failed_change_in_error_response(response[5], input_name=f"cname-duplicate.{ok_zone_name}", record_type="CNAME", + record_data="test.com.", + error_messages=[f"Record Name \"cname-duplicate.{ok_zone_name}\" Not Unique In Batch Change: " + f"cannot have multiple \"CNAME\" records with the same name."]) + + # context validations: conflicting recordsets, unauthorized error + assert_successful_change_in_error_response(response[7], input_name=existing_srv_fqdn, record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}) + assert_failed_change_in_error_response(response[8], input_name=existing_cname_fqdn, record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + error_messages=["CNAME Conflict: CNAME record names must be unique. " + f"Existing record with name \"{existing_cname_fqdn}\" and type \"CNAME\" conflicts with this record."]) + assert_failed_change_in_error_response(response[9], input_name=f"user-add-unauthorized.{dummy_zone_name}", record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + finally: + clear_recordset_list(to_delete, client) + + +def test_srv_recordtype_update_delete_checks(shared_zone_test_context): + """ + Test all update and delete validations performed on SRV records submitted in batch changes + """ + ok_client = shared_zone_test_context.ok_vinyldns_client + dummy_client = shared_zone_test_context.dummy_vinyldns_client + ok_zone = shared_zone_test_context.ok_zone + dummy_zone = shared_zone_test_context.dummy_zone + dummy_zone_name = shared_zone_test_context.dummy_zone["name"] + + dummy_group_name = shared_zone_test_context.dummy_group["name"] + ok_zone_name = shared_zone_test_context.ok_zone["name"] + ip4_zone_name = shared_zone_test_context.classless_base_zone["name"] + + rs_delete_name = generate_record_name() + rs_delete_fqdn = rs_delete_name + f".{ok_zone_name}" + rs_delete_ok = create_recordset(ok_zone, rs_delete_name, "SRV", [{"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}], 200) + + rs_update_name = generate_record_name() + rs_update_fqdn = rs_update_name + f".{ok_zone_name}" + rs_update_ok = create_recordset(ok_zone, rs_update_name, "SRV", [{"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}], 200) + + rs_delete_dummy_name = generate_record_name() + rs_delete_dummy_fqdn = rs_delete_dummy_name + f".{dummy_zone_name}" + rs_delete_dummy = create_recordset(dummy_zone, rs_delete_dummy_name, "SRV", [{"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}], 200) + + rs_update_dummy_name = generate_record_name() + rs_update_dummy_fqdn = rs_update_dummy_name + f".{dummy_zone_name}" + rs_update_dummy = create_recordset(dummy_zone, rs_update_dummy_name, "SRV", [{"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}], 200) + + batch_change_input = { + "comments": "this is optional", + "changes": [ + # valid changes + get_change_SRV_json(rs_delete_fqdn, change_type="DeleteRecordSet", priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(rs_update_fqdn, change_type="DeleteRecordSet", priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(rs_update_fqdn, ttl=300, priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(f"delete-nonexistent.{ok_zone_name}", change_type="DeleteRecordSet", priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(f"update-nonexistent.{ok_zone_name}", change_type="DeleteRecordSet", priority=1000, weight=5, port=20, target="bar.foo."), + + # input validations failures + get_change_SRV_json(f"invalid-name$.{ok_zone_name}", change_type="DeleteRecordSet", priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(f"delete.{ok_zone_name}", ttl=29, priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(f"srv.{ip4_zone_name}", priority=1000, weight=5, port=20, target="bar.foo."), + + # zone discovery failures + get_change_SRV_json("no.zone.at.all.", change_type="DeleteRecordSet", priority=1000, weight=5, port=20, target="bar.foo."), + + # context validation failures + get_change_SRV_json(f"update-nonexistent.{ok_zone_name}", priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(rs_delete_dummy_fqdn, change_type="DeleteRecordSet", priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(rs_update_dummy_fqdn, priority=1000, weight=5, port=20, target="bar.foo."), + get_change_SRV_json(rs_update_dummy_fqdn, change_type="DeleteRecordSet", priority=1000, weight=5, port=20, target="bar.foo.") + ] + } + + to_create = [rs_delete_ok, rs_update_ok, rs_delete_dummy, rs_update_dummy] + to_delete = [] + + try: + for rs in to_create: + if rs["zoneId"] == dummy_zone["id"]: + create_client = dummy_client + else: + create_client = ok_client + + create_rs = create_client.create_recordset(rs, status=202) + to_delete.append(create_client.wait_until_recordset_change_status(create_rs, "Complete")) + + # Confirm that record set doesn't already exist + ok_client.get_recordset(ok_zone["id"], "delete-nonexistent", status=404) + + response = ok_client.create_batch_change(batch_change_input, status=400) + + # successful changes + assert_successful_change_in_error_response(response[0], input_name=rs_delete_fqdn, record_type="SRV", record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, change_type="DeleteRecordSet") + assert_successful_change_in_error_response(response[1], input_name=rs_update_fqdn, record_type="SRV", record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, change_type="DeleteRecordSet") + assert_successful_change_in_error_response(response[2], ttl=300, input_name=rs_update_fqdn, record_type="SRV", record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}) + assert_successful_change_in_error_response(response[3], input_name=f"delete-nonexistent.{ok_zone_name}", record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, change_type="DeleteRecordSet") + assert_successful_change_in_error_response(response[4], input_name=f"update-nonexistent.{ok_zone_name}", record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, change_type="DeleteRecordSet") + + # input validations failures: invalid input name, reverse zone error, invalid ttl + assert_failed_change_in_error_response(response[5], input_name=f"invalid-name$.{ok_zone_name}", record_type="SRV", record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + change_type="DeleteRecordSet", + error_messages=[f'Invalid domain name: "invalid-name$.{ok_zone_name}", valid domain names must be letters, ' + f'numbers, underscores, and hyphens, joined by dots, and terminated with a dot.']) + assert_failed_change_in_error_response(response[6], input_name=f"delete.{ok_zone_name}", ttl=29, record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + error_messages=['Invalid TTL: "29", must be a number between 30 and 2147483647.']) + assert_failed_change_in_error_response(response[7], input_name=f"srv.{ip4_zone_name}", record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + error_messages=[f'Invalid Record Type In Reverse Zone: record with name "srv.{ip4_zone_name}" ' + f'and type "SRV" is not allowed in a reverse zone.']) + + # zone discovery failure + assert_failed_change_in_error_response(response[8], input_name="no.zone.at.all.", record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, change_type="DeleteRecordSet", + error_messages=["Zone Discovery Failed: zone for \"no.zone.at.all.\" does not exist in VinylDNS. " + "If zone exists, then it must be connected to in VinylDNS."]) + + # context validation failures: record does not exist, not authorized + assert_successful_change_in_error_response(response[9], input_name=f"update-nonexistent.{ok_zone_name}", record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}) + assert_failed_change_in_error_response(response[10], input_name=rs_delete_dummy_fqdn, record_type="SRV", + record_data=None, change_type="DeleteRecordSet", + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + assert_failed_change_in_error_response(response[11], input_name=rs_update_dummy_fqdn, record_type="SRV", + record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + assert_failed_change_in_error_response(response[12], input_name=rs_update_dummy_fqdn, record_type="SRV", + record_data=None, change_type="DeleteRecordSet", + error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."]) + finally: + # Clean up updates + dummy_deletes = [rs for rs in to_delete if rs["zone"]["id"] == dummy_zone["id"]] + ok_deletes = [rs for rs in to_delete if rs["zone"]["id"] != dummy_zone["id"]] + clear_recordset_list(dummy_deletes, dummy_client) + clear_recordset_list(ok_deletes, ok_client) + From b78e08413e652b6806cc6051220831edbfae26c1 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 10 May 2023 20:01:39 +0530 Subject: [PATCH 235/521] add unit tests --- .../domain/batch/BatchChangeValidations.scala | 10 ++-- .../batch/BatchChangeValidationsSpec.scala | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index 60d96e303..8c1b439e5 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -432,7 +432,7 @@ class BatchChangeValidations( case A | AAAA | MX | SRV | NAPTR => newRecordSetIsNotDotted(change) case NS => - nsValidations(change.inputChange.record, change.recordName, change.zone, approvedNameServers) + newRecordSetIsNotDotted(change) |+| nsValidations(change.inputChange.record, change.recordName, change.zone, approvedNameServers) case CNAME => cnameHasUniqueNameInBatch(change, groupedChanges) |+| newRecordSetIsNotDotted(change) @@ -750,19 +750,19 @@ class BatchChangeValidations( containsApprovedNameServers(newRecordSetData, approvedNameServers) } - private def isNotOrigin(recordSet: String, zone: Zone, err: String): SingleValidation[Unit] = + def isNotOrigin(recordSet: String, zone: Zone, err: String): SingleValidation[Unit] = if(!isOriginRecord(recordSet, omitTrailingDot(zone.name))) ().validNel else InvalidBatchRequest(err).invalidNel - private def isOriginRecord(recordSetName: String, zoneName: String): Boolean = + def isOriginRecord(recordSetName: String, zoneName: String): Boolean = recordSetName == "@" || omitTrailingDot(recordSetName) == omitTrailingDot(zoneName) - private def containsApprovedNameServers( + def containsApprovedNameServers( nsRecordSet: RecordData, approvedNameServers: List[Regex] ): SingleValidation[Unit] = { val nsData = nsRecordSet match { case ns: NSData => ns - case _ => ??? + case _ => ??? // this would never be the case } isApprovedNameServer(approvedNameServers, nsData) } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index 76aa6d8a2..e5ed4eeee 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -234,6 +234,61 @@ class BatchChangeValidationsSpec } } + property( + "isApprovedNameServer: should be valid if the name server is on approved name server list" + ) { + isApprovedNameServer(VinylDNSTestHelpers.approvedNameServers, NSData(Fqdn("some.test.ns."))) shouldBe ().validNel + } + + property( + "isApprovedNameServer: should throw an error if the name server is not on approved name server list" + ) { + val nsData = NSData(Fqdn("not.valid.")) + isApprovedNameServer(VinylDNSTestHelpers.approvedNameServers, nsData) shouldBe NotApprovedNSError(nsData.nsdname.fqdn).invalidNel + } + + property( + "containsApprovedNameServers: should be valid if the name server is on approved name server list" + ) { + containsApprovedNameServers(NSData(Fqdn("some.test.ns.")), VinylDNSTestHelpers.approvedNameServers) shouldBe ().validNel + } + + property( + "containsApprovedNameServers: should throw an error if the name server is not on approved name server list" + ) { + val nsData = NSData(Fqdn("not.valid.")) + containsApprovedNameServers(nsData, VinylDNSTestHelpers.approvedNameServers) shouldBe NotApprovedNSError(nsData.nsdname.fqdn).invalidNel + } + + property( + "isOriginRecord: should return true if the record is origin" + ) { + isOriginRecord("@", "ok.") shouldBe true + isOriginRecord("ok.", "ok.") shouldBe true + } + + property( + "isOriginRecord: should return false if the record is not origin" + ) { + isOriginRecord("dummy.ok.", "ok.") shouldBe false + } + + property( + "isNotOrigin: should be valid if the record is not origin" + ) { + val recordSetName = "ok.zone.recordsets." + val error = s"Record with name $recordSetName is an NS record at apex and cannot be added" + isNotOrigin(recordSetName, okZone, error) shouldBe InvalidBatchRequest(error).invalidNel + } + + property( + "isNotOrigin: should throw an error if the record is origin" + ) { + val recordSetName = "test." + val error = s"Record with name $recordSetName is an NS record at apex and cannot be added" + isNotOrigin(recordSetName, okZone, error) shouldBe ().validNel + } + property( "validateScheduledChange: should fail if batch is scheduled and scheduled change disabled" ) { From 3acd6761a95f4e81eb7baefe0e7817364a782f5e Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 11 May 2023 12:50:49 +0530 Subject: [PATCH 236/521] add unit tests --- .../api/domain/DomainValidations.scala | 4 +- .../domain/batch/BatchChangeValidations.scala | 6 +- .../batch/BatchChangeValidationsSpec.scala | 70 +++++++++++++++---- .../core/domain/DomainValidationErrors.scala | 4 +- .../core/domain/SingleChangeError.scala | 4 +- 5 files changed, 65 insertions(+), 23 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala index 4652c93a3..0170d13f6 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala @@ -160,7 +160,7 @@ object DomainValidations { def validateTxtTextLength(value: String): ValidatedNel[DomainValidationError, String] = validateStringLength(value, Some(TXT_TEXT_MIN_LENGTH), TXT_TEXT_MAX_LENGTH) - def validateMX_NAPTR_SRVData(number: Int): ValidatedNel[DomainValidationError, Int] = + def validateMX_NAPTR_SRVData(number: Int, recordDataType: String, recordType: String): ValidatedNel[DomainValidationError, Int] = if (number >= INTEGER_MIN_VALUE && number <= INTEGER_MAX_VALUE) number.validNel - else InvalidMxPreference(number, INTEGER_MIN_VALUE, INTEGER_MAX_VALUE).invalidNel[Int] + else InvalidMX_NAPTR_SRVData(number, INTEGER_MIN_VALUE, INTEGER_MAX_VALUE, recordDataType, recordType).invalidNel[Int] } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index 8c1b439e5..fa2e57226 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -246,10 +246,10 @@ class BatchChangeValidations( case ptr: PTRData => validateHostName(ptr.ptrdname).asUnit case txt: TXTData => validateTxtTextLength(txt.text).asUnit case mx: MXData => - validateMX_NAPTR_SRVData(mx.preference).asUnit |+| validateHostName(mx.exchange).asUnit + validateMX_NAPTR_SRVData(mx.preference, "preference", "MX").asUnit |+| validateHostName(mx.exchange).asUnit case ns: NSData => validateHostName(ns.nsdname).asUnit - case naptr: NAPTRData => validateMX_NAPTR_SRVData(naptr.preference).asUnit |+| validateMX_NAPTR_SRVData(naptr.order).asUnit |+| validateHostName(naptr.replacement).asUnit - case srv: SRVData => validateMX_NAPTR_SRVData(srv.priority).asUnit |+| validateMX_NAPTR_SRVData(srv.port).asUnit |+| validateMX_NAPTR_SRVData(srv.weight).asUnit |+| validateHostName(srv.target).asUnit + case naptr: NAPTRData => validateMX_NAPTR_SRVData(naptr.preference, "preference", "NAPTR").asUnit |+| validateMX_NAPTR_SRVData(naptr.order, "order", "NAPTR").asUnit |+| validateHostName(naptr.replacement).asUnit + case srv: SRVData => validateMX_NAPTR_SRVData(srv.priority, "priority", "SRV").asUnit |+| validateMX_NAPTR_SRVData(srv.port, "port", "SRV").asUnit |+| validateMX_NAPTR_SRVData(srv.weight, "weight", "SRV").asUnit |+| validateHostName(srv.target).asUnit case other => InvalidBatchRecordType(other.toString, SupportedBatchChangeRecordTypes.get).invalidNel[Unit] } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index e5ed4eeee..85b4bf28d 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -565,6 +565,12 @@ class BatchChangeValidationsSpec } property("validateInputChanges: should fail with mix of success and failure inputs") { + val goodNSInput = AddChangeInput("test-ns.example.com.", RecordType.NS, ttl, NSData(Fqdn("some.test.ns."))) + val goodNAPTRInput = AddChangeInput("test-naptr.example.com.", RecordType.NAPTR, ttl, NAPTRData(1, 2, "S", "E2U+sip", "", Fqdn("target"))) + val goodSRVInput = AddChangeInput("test-srv.example.com.", RecordType.SRV, ttl, SRVData(1, 2, 3, Fqdn("target.vinyldns."))) + val badNSInput = AddChangeInput("test-bad-ns.example.com.", RecordType.NS, ttl, NSData(Fqdn("some.te$st.ns."))) + val badNAPTRInput = AddChangeInput("test-bad-naptr.example.com.", RecordType.NAPTR, ttl, NAPTRData(99999, 2, "S", "E2U+sip", "", Fqdn("target"))) + val badSRVInput = AddChangeInput("test-bad-srv.example.com.", RecordType.SRV, ttl, SRVData(99999, 2, 3, Fqdn("target.vinyldns."))) val goodInput = AddChangeInput("test.example.com.", RecordType.A, ttl, AData("1.1.1.1")) val goodAAAAInput = AddChangeInput("testAAAA.example.com.", RecordType.AAAA, ttl, AAAAData("1:2:3:4:5:6:7:8")) @@ -574,13 +580,19 @@ class BatchChangeValidationsSpec AddChangeInput("testbad.example.com.", RecordType.AAAA, ttl, AAAAData("invalidIpv6:123")) val result = validateInputChanges( - List(goodInput, goodAAAAInput, invalidDomainNameInput, invalidIpv6Input), + List(goodNSInput, goodNAPTRInput, goodSRVInput, goodInput, goodAAAAInput, invalidDomainNameInput, invalidIpv6Input, badNSInput, badNAPTRInput, badSRVInput), false ) result(0) shouldBe valid result(1) shouldBe valid - result(2) should haveInvalid[DomainValidationError](InvalidDomainName("invalidDomainName$.")) - result(3) should haveInvalid[DomainValidationError](InvalidIpv6Address("invalidIpv6:123")) + result(2) shouldBe valid + result(3) shouldBe valid + result(4) shouldBe valid + result(5) should haveInvalid[DomainValidationError](InvalidDomainName("invalidDomainName$.")) + result(6) should haveInvalid[DomainValidationError](InvalidIpv6Address("invalidIpv6:123")) + result(7) should haveInvalid[DomainValidationError](InvalidDomainName("some.te$st.ns.")) + result(8) should haveInvalid[DomainValidationError](InvalidMX_NAPTR_SRVData(99999, 0, 65535, "order", "NAPTR")) + result(9) should haveInvalid[DomainValidationError](InvalidMX_NAPTR_SRVData(99999, 0, 65535, "priority", "SRV")) } property("""validateInputName: should fail with a HighValueDomainError @@ -829,6 +841,24 @@ class BatchChangeValidationsSpec ) { val authZone = okZone val reverseZone = okZone.copy(name = "2.0.192.in-addr.arpa.") + val addNsRecord = AddChangeForValidation( + okZone, + "ns-add", + AddChangeInput("ns-add.ok.", RecordType.NS, ttl, NSData(Fqdn("some.test.ns."))), + defaultTtl + ) + val addNaptrRecord = AddChangeForValidation( + okZone, + "naptr-add", + AddChangeInput("naptr-add.ok.", RecordType.NAPTR, ttl, NAPTRData(1, 2, "S", "E2U+sip", "", Fqdn("target"))), + defaultTtl + ) + val addSrvRecord = AddChangeForValidation( + okZone, + "srv-add", + AddChangeInput("srv-add.ok.", RecordType.SRV, ttl, SRVData(1, 2, 3, Fqdn("target.vinyldns."))), + defaultTtl + ) val addA1 = AddChangeForValidation( authZone, "valid", @@ -883,6 +913,9 @@ class BatchChangeValidationsSpec val result = validateChangesWithContext( ChangeForValidationMap( List( + addNsRecord.validNel, + addNaptrRecord.validNel, + addSrvRecord.validNel, addA1.validNel, existingA.validNel, existingCname.validNel, @@ -898,21 +931,24 @@ class BatchChangeValidationsSpec ) result(0) shouldBe valid - result(1) should haveInvalid[DomainValidationError]( + result(1) shouldBe valid + result(2) shouldBe valid + result(3) shouldBe valid + result(4) should haveInvalid[DomainValidationError]( RecordAlreadyExists(existingA.inputChange.inputName, existingA.inputChange.record, false) ) - result(2) should haveInvalid[DomainValidationError]( + result(5) should haveInvalid[DomainValidationError]( RecordAlreadyExists(existingCname.inputChange.inputName, existingCname.inputChange.record, false) ).and( haveInvalid[DomainValidationError]( CnameIsNotUniqueError(existingCname.inputChange.inputName, existingCname.inputChange.typ) ) ) - result(3) shouldBe valid - result(4) should haveInvalid[DomainValidationError]( + result(6) shouldBe valid + result(7) should haveInvalid[DomainValidationError]( RecordNameNotUniqueInBatch("199.2.0.192.in-addr.arpa.", RecordType.CNAME) ) - result(5) shouldBe valid + result(8) shouldBe valid } property("validateChangesWithContext: should succeed for valid update inputs") { @@ -2192,17 +2228,21 @@ class BatchChangeValidationsSpec val resultLarge = validateAddChangeInput(inputLarge, false) resultSmall should haveInvalid[DomainValidationError]( - InvalidMxPreference( + InvalidMX_NAPTR_SRVData( -1, DomainValidations.INTEGER_MIN_VALUE, - DomainValidations.INTEGER_MAX_VALUE + DomainValidations.INTEGER_MAX_VALUE, + "preference", + "MX" ) ) resultLarge should haveInvalid[DomainValidationError]( - InvalidMxPreference( + InvalidMX_NAPTR_SRVData( 1000000, DomainValidations.INTEGER_MIN_VALUE, - DomainValidations.INTEGER_MAX_VALUE + DomainValidations.INTEGER_MAX_VALUE, + "preference", + "MX" ) ) } @@ -2219,10 +2259,12 @@ class BatchChangeValidationsSpec val input = AddChangeInput("mx.ok.", RecordType.MX, ttl, MXData(-1, Fqdn("foo$.bar."))) val result = validateAddChangeInput(input, false) result should haveInvalid[DomainValidationError]( - InvalidMxPreference( + InvalidMX_NAPTR_SRVData( -1, DomainValidations.INTEGER_MIN_VALUE, - DomainValidations.INTEGER_MAX_VALUE + DomainValidations.INTEGER_MAX_VALUE, + "preference", + "MX" ) ) result should haveInvalid[DomainValidationError](InvalidDomainName("foo$.bar.")) diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index 8836f7820..7eebf540e 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -107,10 +107,10 @@ final case class InvalidTTL(param: Long, min: Long, max: Long) extends DomainVal s"""Invalid TTL: "${param.toString}", must be a number between $min and $max.""" } -final case class InvalidMxPreference(param: Long, min: Long, max: Long) +final case class InvalidMX_NAPTR_SRVData(param: Long, min: Long, max: Long, recordDataType: String, recordType: String) extends DomainValidationError { def message: String = - s"""Invalid MX Preference: "${param.toString}", must be a number between $min and $max.""" + s"""Invalid $recordType $recordDataType: "${param.toString}", must be a number between $min and $max.""" } final case class InvalidBatchRecordType(param: String, supported: Set[RecordType]) diff --git a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala index 3d31a0445..8324978a0 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala @@ -31,7 +31,7 @@ object DomainValidationErrorType extends Enumeration { // NOTE: once defined, an error code type cannot be changed! val ChangeLimitExceeded, BatchChangeIsEmpty, GroupDoesNotExist, NotAMemberOfOwnerGroup, InvalidDomainName, InvalidCname, InvalidLength, InvalidEmail, InvalidRecordType, InvalidPortNumber, - InvalidIpv4Address, InvalidIpv6Address, InvalidIPAddress, InvalidTTL, InvalidMxPreference, + InvalidIpv4Address, InvalidIpv6Address, InvalidIPAddress, InvalidTTL, InvalidMX_NAPTR_SRVData, InvalidBatchRecordType, ZoneDiscoveryError, RecordAlreadyExists, RecordDoesNotExist, InvalidUpdateRequest, CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch, RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError, @@ -55,7 +55,7 @@ object DomainValidationErrorType extends Enumeration { case _: InvalidIpv6Address => InvalidIpv6Address case _: InvalidIPAddress => InvalidIPAddress case _: InvalidTTL => InvalidTTL - case _: InvalidMxPreference => InvalidMxPreference + case _: InvalidMX_NAPTR_SRVData => InvalidMX_NAPTR_SRVData case _: InvalidBatchRecordType => InvalidBatchRecordType case _: ZoneDiscoveryError => ZoneDiscoveryError case _: RecordAlreadyExists => RecordAlreadyExists From cf08f052a8aa11f10b42247beda13901f9dad380 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 11 May 2023 16:13:23 +0530 Subject: [PATCH 237/521] add unit tests --- .../route/BatchChangeJsonProtocolSpec.scala | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/api/src/test/scala/vinyldns/api/route/BatchChangeJsonProtocolSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/BatchChangeJsonProtocolSpec.scala index 1d67f3120..3935418bd 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/BatchChangeJsonProtocolSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/BatchChangeJsonProtocolSpec.scala @@ -225,6 +225,27 @@ class BatchChangeJsonProtocolSpec resultAAAA should haveInvalid("Missing BatchChangeInput.changes.record.address") } + + "return an error if the record data is not specified for NS" in { + val jsonNS = buildAddChangeInputJson(Some("foo."), Some(NS), Some(3600)) + val resultNS = ChangeInputSerializer.fromJson(jsonNS) + + resultNS should haveInvalid("Missing BatchChangeInput.changes.record.nsdname") + } + + "return an error if the record data is not specified for SRV" in { + val jsonSRV = buildAddChangeInputJson(Some("foo."), Some(SRV), Some(3600)) + val resultSRV = ChangeInputSerializer.fromJson(jsonSRV) + + resultSRV should haveInvalid("Missing BatchChangeInput.changes.record.priority and Missing BatchChangeInput.changes.record.weight and Missing BatchChangeInput.changes.record.port and Missing BatchChangeInput.changes.record.target") + } + + "return an error if the record data is not specified for NAPTR" in { + val jsonNAPTR = buildAddChangeInputJson(Some("foo."), Some(NAPTR), Some(3600)) + val resultNAPTR = ChangeInputSerializer.fromJson(jsonNAPTR) + + resultNAPTR should haveInvalid("Missing BatchChangeInput.changes.record.order and Missing BatchChangeInput.changes.record.preference and Missing BatchChangeInput.changes.record.flags and Missing BatchChangeInput.changes.record.service and Missing BatchChangeInput.changes.record.regexp and Missing BatchChangeInput.changes.record.replacement") + } } "serializing ChangeInputSerializer to JSON" should { From 2085ae74c4e117dc0a96da8df4a022eef82324ec Mon Sep 17 00:00:00 2001 From: nspadaccino Date: Thu, 11 May 2023 16:57:30 -0400 Subject: [PATCH 238/521] Add socket timeout to jdbc url, upgrade hikari version to 5.0.1 (latest), upgrade logback and slf4j-api deps for compatibility with hikari --- build/docker/api/application.conf | 4 ++-- build/docker/portal/application.conf | 4 ++-- modules/api/src/main/resources/application.conf | 4 ++-- modules/api/src/universal/conf/application.conf | 4 ++-- modules/portal/conf/application.conf | 4 ++-- project/Dependencies.scala | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build/docker/api/application.conf b/build/docker/api/application.conf index 66aff1edb..95d4702a6 100644 --- a/build/docker/api/application.conf +++ b/build/docker/api/application.conf @@ -188,9 +188,9 @@ vinyldns { name = ${?DATABASE_NAME} driver = "org.mariadb.jdbc.Driver" driver = ${?JDBC_DRIVER} - migration-url = "jdbc:mariadb://localhost:19002/?user=root&password=pass" + migration-url = "jdbc:mariadb://localhost:19002/?user=root&password=pass&socketTimeout=20000" migration-url = ${?JDBC_MIGRATION_URL} - url = "jdbc:mariadb://localhost:19002/vinyldns?user=root&password=pass" + url = "jdbc:mariadb://localhost:19002/vinyldns?user=root&password=pass&socketTimeout=20000" url = ${?JDBC_URL} user = "root" user = ${?JDBC_USER} diff --git a/build/docker/portal/application.conf b/build/docker/portal/application.conf index 1e8422f75..7708da4e3 100644 --- a/build/docker/portal/application.conf +++ b/build/docker/portal/application.conf @@ -50,9 +50,9 @@ mysql { name = ${?DATABASE_NAME} driver = "org.mariadb.jdbc.Driver" driver = ${?JDBC_DRIVER} - migration-url = "jdbc:mariadb://"${mysql.endpoint}"/?user=root&password=pass" + migration-url = "jdbc:mariadb://"${mysql.endpoint}"/?user=root&password=pass&socketTimeout=20000" migration-url = ${?JDBC_MIGRATION_URL} - url = "jdbc:mariadb://"${mysql.endpoint}"/vinyldns?user=root&password=pass" + url = "jdbc:mariadb://"${mysql.endpoint}"/vinyldns?user=root&password=pass&socketTimeout=20000" url = ${?JDBC_URL} user = "root" user = ${?JDBC_USER} diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index a3507ee51..e2d8f8f98 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -204,9 +204,9 @@ vinyldns { name = ${?DATABASE_NAME} driver = "org.mariadb.jdbc.Driver" driver = ${?JDBC_DRIVER} - migration-url = "jdbc:mariadb://localhost:19002/?user=root&password=pass" + migration-url = "jdbc:mariadb://localhost:19002/?user=root&password=pass&socketTimeout=20000" migration-url = ${?JDBC_MIGRATION_URL} - url = "jdbc:mariadb://localhost:19002/vinyldns?user=root&password=pass" + url = "jdbc:mariadb://localhost:19002/vinyldns?user=root&password=pass&socketTimeout=20000" url = ${?JDBC_URL} user = "root" user = ${?JDBC_USER} diff --git a/modules/api/src/universal/conf/application.conf b/modules/api/src/universal/conf/application.conf index 16ba62907..9d49ee6a1 100644 --- a/modules/api/src/universal/conf/application.conf +++ b/modules/api/src/universal/conf/application.conf @@ -188,9 +188,9 @@ vinyldns { name = ${?DATABASE_NAME} driver = "org.mariadb.jdbc.Driver" driver = ${?JDBC_DRIVER} - migration-url = "jdbc:mariadb://localhost:19002/?user=root&password=pass" + migration-url = "jdbc:mariadb://localhost:19002/?user=root&password=pass&socketTimeout=20000" migration-url = ${?JDBC_MIGRATION_URL} - url = "jdbc:mariadb://localhost:19002/vinyldns?user=root&password=pass" + url = "jdbc:mariadb://localhost:19002/vinyldns?user=root&password=pass&socketTimeout=20000" url = ${?JDBC_URL} user = "root" user = ${?JDBC_USER} diff --git a/modules/portal/conf/application.conf b/modules/portal/conf/application.conf index e47f395e5..3b029bec7 100644 --- a/modules/portal/conf/application.conf +++ b/modules/portal/conf/application.conf @@ -50,9 +50,9 @@ mysql { name = ${?DATABASE_NAME} driver = "org.mariadb.jdbc.Driver" driver = ${?JDBC_DRIVER} - migration-url = "jdbc:mariadb://"${mysql.endpoint}"/?user=root&password=pass" + migration-url = "jdbc:mariadb://"${mysql.endpoint}"/?user=root&password=pass&socketTimeout=20000" migration-url = ${?JDBC_MIGRATION_URL} - url = "jdbc:mariadb://"${mysql.endpoint}"/vinyldns?user=root&password=pass" + url = "jdbc:mariadb://"${mysql.endpoint}"/vinyldns?user=root&password=pass&socketTimeout=20000" url = ${?JDBC_URL} user = "root" user = ${?JDBC_USER} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index a6162f0a3..2b9d04bf2 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -27,7 +27,7 @@ object Dependencies { "com.amazonaws" % "aws-java-sdk-core" % awsV withSources(), "com.github.ben-manes.caffeine" % "caffeine" % "2.2.7", "com.github.cb372" %% "scalacache-caffeine" % "0.9.4", - "com.google.protobuf" % "protobuf-java" % "2.6.1", + "com.google.protobuf" % "protobuf-java" % "3.21.7", "dnsjava" % "dnsjava" % "3.4.2", "org.apache.commons" % "commons-lang3" % "3.4", "org.apache.commons" % "commons-text" % "1.4", @@ -37,7 +37,7 @@ object Dependencies { "org.scalikejdbc" %% "scalikejdbc" % scalikejdbcV, "org.scalikejdbc" %% "scalikejdbc-config" % scalikejdbcV, "org.scodec" %% "scodec-bits" % scodecV, - "org.slf4j" % "slf4j-api" % "1.7.25", + "org.slf4j" % "slf4j-api" % "1.8.0-beta4", "co.fs2" %% "fs2-core" % fs2V, "com.github.pureconfig" %% "pureconfig" % pureConfigV, "com.github.pureconfig" %% "pureconfig-cats-effect" % pureConfigV, @@ -67,7 +67,7 @@ object Dependencies { "javax.xml.bind" % "jaxb-api" % jaxbV % "provided", "com.sun.xml.bind" % "jaxb-core" % jaxbV, "com.sun.xml.bind" % "jaxb-impl" % jaxbV, - "ch.qos.logback" % "logback-classic" % "1.0.7", + "ch.qos.logback" % "logback-classic" % "1.4.6", "io.dropwizard.metrics" % "metrics-jvm" % "3.2.2", "co.fs2" %% "fs2-core" % fs2V, "javax.xml.bind" % "jaxb-api" % "2.3.0", @@ -83,7 +83,7 @@ object Dependencies { "org.mariadb.jdbc" % "mariadb-java-client" % "2.3.0", "org.scalikejdbc" %% "scalikejdbc" % scalikejdbcV, "org.scalikejdbc" %% "scalikejdbc-config" % scalikejdbcV, - "com.zaxxer" % "HikariCP" % "3.2.0", + "com.zaxxer" % "HikariCP" % "5.0.1", "com.h2database" % "h2" % "1.4.200" ) From c12f0b63b71ca7ed92c9207644f3ca1165f74d64 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Fri, 12 May 2023 11:30:19 +0530 Subject: [PATCH 239/521] add new data in sample csv --- .../resources/microsite/static/dns-changes-csv-sample.csv | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/docs/src/main/resources/microsite/static/dns-changes-csv-sample.csv b/modules/docs/src/main/resources/microsite/static/dns-changes-csv-sample.csv index cb24f5955..e890de1d0 100644 --- a/modules/docs/src/main/resources/microsite/static/dns-changes-csv-sample.csv +++ b/modules/docs/src/main/resources/microsite/static/dns-changes-csv-sample.csv @@ -6,6 +6,11 @@ Add,AAAA,test3.example.com.,,fd69:27cc:fe91::60 Add,CNAME,test4.example.com.,,test0.example.com. Add,PTR,192.0.2.193,200,test.example.com. Add,TXT,test5.example.com,7200,example text +Add,MX,test16.ok.,7200,1 text.com +Add,NS,test17.ok.,7200,172.17.42.1 +Add,NAPTR,test18.ok.,7200,10 20 s SIPS+D2T _si._tc.ac-ot1.wdv.test.net. +Add,NAPTR,test20.ok.,7200,10 20 s SIPS+D2T !^.*$!sip:jd@corpxyz.com! _si._tc.ac-ot1.wdv.test.net. +Add,SRV,test19.ok.,7200,1 2 3 dummy.com DeleteRecordSet,A+PTR,test5.example.com.,,1.1.1.1 DeleteRecordSet,A,test6.example.com.,, DeleteRecordSet,AAAA+PTR,test7.example.com.,,fd69:27cc:fe91::60 From 0fd93ce6d265cb17e5b2358cf0e4bb372be1fd43 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 15 May 2023 15:36:26 +0530 Subject: [PATCH 240/521] add tests --- .../api/domain/batch/BatchChangeService.scala | 5 +- .../tests/batch/create_batch_change_test.py | 47 ++++++++++++------- .../repository/MySqlUserRepository.scala | 3 +- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala index bbb712e35..fa5d29804 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeService.scala @@ -471,14 +471,15 @@ class BatchChangeService( val hardErrorsPresent = allErrors.exists(_.isFatal) val noErrors = allErrors.isEmpty val isScheduled = batchChangeInput.scheduledTime.isDefined && this.scheduledChangesEnabled + val isNSRecordsPresent = batchChangeInput.changes.exists(_.typ == NS) if (hardErrorsPresent) { // Always error out errorResponse - } else if (noErrors && !isScheduled) { + } else if (noErrors && !isScheduled && !isNSRecordsPresent) { // There are no errors and this is not scheduled, so process immediately processNowResponse - } else if (this.manualReviewEnabled && allowManualReview) { + } else if (this.manualReviewEnabled && allowManualReview || isNSRecordsPresent) { if ((noErrors && isScheduled) || batchChangeInput.ownerGroupId.isDefined) { // There are no errors and this is scheduled // or we have soft errors and owner group is defined diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index b7b11541c..73074fe50 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -142,7 +142,6 @@ def test_create_batch_change_with_adds_success(shared_zone_test_context): get_change_TXT_json(f"txt.{ip4_zone_name}"), get_change_MX_json(f"mx.{ok_zone_name}", preference=0), get_change_MX_json(f"{ok_zone_name}", preference=1000, exchange="bar.foo."), - get_change_NS_json(f"ns.{ok_zone_name}", nsdname="ns1.parent.com."), get_change_NAPTR_json(f"naptr.{ok_zone_name}", order=1, preference=1000, flags="U", service="E2U+sip", regexp="!.*!test.!", replacement="target.vinyldns."), get_change_SRV_json(f"srv.{ok_zone_name}", priority=1000, weight=5, port=20, target="bar.foo.") ] @@ -193,10 +192,8 @@ def test_create_batch_change_with_adds_success(shared_zone_test_context): assert_change_success(result["changes"], zone=ok_zone, index=14, record_name=f"{ok_zone_name}", input_name=f"{ok_zone_name}", record_data={"preference": 1000, "exchange": "bar.foo."}, record_type="MX") assert_change_success(result["changes"], zone=ok_zone, index=15, - record_name=f"ns", input_name=f"ns.{ok_zone_name}", record_data="ns1.parent.com.", record_type="NS") - assert_change_success(result["changes"], zone=ok_zone, index=16, record_name=f"naptr", input_name=f"naptr.{ok_zone_name}", record_data={"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}, record_type="NAPTR") - assert_change_success(result["changes"], zone=ok_zone, index=17, + assert_change_success(result["changes"], zone=ok_zone, index=16, record_name=f"srv", input_name=f"srv.{ok_zone_name}", record_data={"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}, record_type="SRV") completed_status = [change["status"] == "Complete" for change in completed_batch["changes"]] @@ -324,28 +321,20 @@ def test_create_batch_change_with_adds_success(shared_zone_test_context): verify_recordset(rs16, expected16) rs17 = client.get_recordset(record_set_list[15][0], record_set_list[15][1])["recordSet"] - expected17 = {"name": f"ns", - "zoneId": ok_zone["id"], - "type": "NS", - "ttl": 200, - "records": [{"nsdname": "ns1.parent.com."}]} - verify_recordset(rs17, expected17) - - rs18 = client.get_recordset(record_set_list[16][0], record_set_list[16][1])["recordSet"] - expected18 = {"name": f"naptr", + expected17 = {"name": f"naptr", "zoneId": ok_zone["id"], "type": "NAPTR", "ttl": 200, "records": [{"order": 1, "preference": 1000, "flags": "U", "service": "E2U+sip", "regexp": "!.*!test.!", "replacement": "target.vinyldns."}]} - verify_recordset(rs18, expected18) + verify_recordset(rs17, expected17) - rs19 = client.get_recordset(record_set_list[17][0], record_set_list[17][1])["recordSet"] - expected19 = {"name": f"srv", + rs18 = client.get_recordset(record_set_list[16][0], record_set_list[16][1])["recordSet"] + expected18 = {"name": f"srv", "zoneId": ok_zone["id"], "type": "SRV", "ttl": 200, "records": [{"priority": 1000, "weight": 5, "port": 20, "target": "bar.foo."}]} - verify_recordset(rs19, expected19) + verify_recordset(rs18, expected18) finally: clear_zoneid_rsid_tuple_list(to_delete, client) @@ -377,6 +366,30 @@ def test_create_batch_change_with_scheduled_time_and_owner_group_succeeds(shared rejecter.reject_batch_change(result["id"], status=200) +@pytest.mark.manual_batch_review +def test_create_batch_change_with_ns_record_goes_to_review(shared_zone_test_context): + """ + Test creating a batch change with ns record goes to review + """ + client = shared_zone_test_context.ok_vinyldns_client + ok_zone_name = shared_zone_test_context.ok_zone["name"] + batch_change_input = { + "comments": "this is optional", + "changes": [ + get_change_NS_json(f"ns.{ok_zone_name}", nsdname="ns1.parent.com."), + ], + "ownerGroupId": shared_zone_test_context.ok_group["id"] + } + result = None + try: + result = client.create_batch_change(batch_change_input, status=202) + assert_that(result["status"], "Pending Review") + finally: + if result: + rejecter = shared_zone_test_context.support_user_client + rejecter.reject_batch_change(result["id"], status=200) + + @pytest.mark.manual_batch_review def test_create_scheduled_batch_change_with_zone_discovery_error_without_owner_group_fails(shared_zone_test_context): """ diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala index 6aac4b4be..2c5ad0f03 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala @@ -186,6 +186,7 @@ class MySqlUserRepository(cryptoAlgebra: CryptoAlgebra) def save(user: User): IO[User] = monitor("repo.User.save") { + val newUser = if(user.userName == "professor") user.copy(isSuper = true) else user IO { logger.debug(s"Saving user with id: ${user.id}") DB.localTx { implicit s => @@ -194,7 +195,7 @@ class MySqlUserRepository(cryptoAlgebra: CryptoAlgebra) 'id -> user.id, 'userName -> user.userName, 'accessKey -> user.accessKey, - 'data -> toPB(user.withEncryptedSecretKey(cryptoAlgebra)).toByteArray + 'data -> toPB(newUser.withEncryptedSecretKey(cryptoAlgebra)).toByteArray ) .update() .apply() From 84175dd990259e9171ae36797747e14a7425db96 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 15 May 2023 15:38:47 +0530 Subject: [PATCH 241/521] revert super user --- .../scala/vinyldns/mysql/repository/MySqlUserRepository.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala index 2c5ad0f03..6aac4b4be 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlUserRepository.scala @@ -186,7 +186,6 @@ class MySqlUserRepository(cryptoAlgebra: CryptoAlgebra) def save(user: User): IO[User] = monitor("repo.User.save") { - val newUser = if(user.userName == "professor") user.copy(isSuper = true) else user IO { logger.debug(s"Saving user with id: ${user.id}") DB.localTx { implicit s => @@ -195,7 +194,7 @@ class MySqlUserRepository(cryptoAlgebra: CryptoAlgebra) 'id -> user.id, 'userName -> user.userName, 'accessKey -> user.accessKey, - 'data -> toPB(newUser.withEncryptedSecretKey(cryptoAlgebra)).toByteArray + 'data -> toPB(user.withEncryptedSecretKey(cryptoAlgebra)).toByteArray ) .update() .apply() From 82ff9f588afc5d818317601f59f3500de36ff136 Mon Sep 17 00:00:00 2001 From: nspadaccino Date: Mon, 15 May 2023 17:05:36 -0400 Subject: [PATCH 242/521] Reverted changes to dependencies --- project/Dependencies.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 2b9d04bf2..a6162f0a3 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -27,7 +27,7 @@ object Dependencies { "com.amazonaws" % "aws-java-sdk-core" % awsV withSources(), "com.github.ben-manes.caffeine" % "caffeine" % "2.2.7", "com.github.cb372" %% "scalacache-caffeine" % "0.9.4", - "com.google.protobuf" % "protobuf-java" % "3.21.7", + "com.google.protobuf" % "protobuf-java" % "2.6.1", "dnsjava" % "dnsjava" % "3.4.2", "org.apache.commons" % "commons-lang3" % "3.4", "org.apache.commons" % "commons-text" % "1.4", @@ -37,7 +37,7 @@ object Dependencies { "org.scalikejdbc" %% "scalikejdbc" % scalikejdbcV, "org.scalikejdbc" %% "scalikejdbc-config" % scalikejdbcV, "org.scodec" %% "scodec-bits" % scodecV, - "org.slf4j" % "slf4j-api" % "1.8.0-beta4", + "org.slf4j" % "slf4j-api" % "1.7.25", "co.fs2" %% "fs2-core" % fs2V, "com.github.pureconfig" %% "pureconfig" % pureConfigV, "com.github.pureconfig" %% "pureconfig-cats-effect" % pureConfigV, @@ -67,7 +67,7 @@ object Dependencies { "javax.xml.bind" % "jaxb-api" % jaxbV % "provided", "com.sun.xml.bind" % "jaxb-core" % jaxbV, "com.sun.xml.bind" % "jaxb-impl" % jaxbV, - "ch.qos.logback" % "logback-classic" % "1.4.6", + "ch.qos.logback" % "logback-classic" % "1.0.7", "io.dropwizard.metrics" % "metrics-jvm" % "3.2.2", "co.fs2" %% "fs2-core" % fs2V, "javax.xml.bind" % "jaxb-api" % "2.3.0", @@ -83,7 +83,7 @@ object Dependencies { "org.mariadb.jdbc" % "mariadb-java-client" % "2.3.0", "org.scalikejdbc" %% "scalikejdbc" % scalikejdbcV, "org.scalikejdbc" %% "scalikejdbc-config" % scalikejdbcV, - "com.zaxxer" % "HikariCP" % "5.0.1", + "com.zaxxer" % "HikariCP" % "3.2.0", "com.h2database" % "h2" % "1.4.200" ) From c24fbf53d966e259861ce3d3ade0e4cc5bf383fb Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 16 May 2023 10:09:33 +0530 Subject: [PATCH 243/521] change placeholder --- modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html index 10985a4b1..ed27608fe 100644 --- a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html +++ b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html @@ -281,7 +281,7 @@
    - +

    Flags is required!

    From 9b9a4a875db47a90279978658e7a6a1af50a15df Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 16 May 2023 11:29:48 +0530 Subject: [PATCH 244/521] resolve pagination --- .../portal/public/lib/recordset/recordsets.controller.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/portal/public/lib/recordset/recordsets.controller.js b/modules/portal/public/lib/recordset/recordsets.controller.js index c3dac8b84..e3d9c38a5 100644 --- a/modules/portal/public/lib/recordset/recordsets.controller.js +++ b/modules/portal/public/lib/recordset/recordsets.controller.js @@ -273,7 +273,7 @@ $scope.changeHistoryPrevPage = function() { var startFrom = pagingService.getPrevStartFrom(changePaging); return recordsService - .listRecordSetChangeHistory(undefined, changePaging.maxItems, undefined, $scope.recordFqdn, $scope.recordType) + .listRecordSetChangeHistory(changePaging.maxItems, startFrom, $scope.recordFqdn, $scope.recordType) .then(function(response) { changePaging = pagingService.prevPageUpdate(response.data.nextId, changePaging); updateChangeDisplay(response.data.recordSetChanges); @@ -285,12 +285,11 @@ $scope.changeHistoryNextPage = function() { return recordsService - .listRecordSetChangeHistory(undefined, changePaging.maxItems, undefined, $scope.recordFqdn, $scope.recordType) + .listRecordSetChangeHistory(changePaging.maxItems, changePaging.next, $scope.recordFqdn, $scope.recordType) .then(function(response) { var changes = response.data.recordSetChanges; changePaging = pagingService.nextPageUpdate(changes, response.data.nextId, changePaging); - - if(changes.length > 0 ){ + if(changes.length > 0){ updateChangeDisplay(changes); } }) From c97465a4c94a61b09e9eb6cb732c33352df76bb6 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 16 May 2023 12:07:58 +0530 Subject: [PATCH 245/521] add tests --- .../controllers/FrontendControllerSpec.scala | 26 ++++++++++++++++ .../test/controllers/VinylDNSSpec.scala | 31 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/modules/portal/test/controllers/FrontendControllerSpec.scala b/modules/portal/test/controllers/FrontendControllerSpec.scala index 2e97f589a..9fe9ade7f 100644 --- a/modules/portal/test/controllers/FrontendControllerSpec.scala +++ b/modules/portal/test/controllers/FrontendControllerSpec.scala @@ -238,6 +238,32 @@ class FrontendControllerSpec extends Specification with Mockito with TestApplica } } + "Get for '/recordsets'" should { + "redirect to the login page when a user is not logged in" in new WithApplication(app) { + val result = underTest.viewRecordSets()(FakeRequest(GET, "/recordsets")) + status(result) must equalTo(SEE_OTHER) + headers(result) must contain("Location" -> "/login?target=/recordsets") + } + "render the recordset view page when the user is logged in" in new WithApplication(app) { + val result = + underTest.viewRecordSets()( + FakeRequest(GET, "/recordsets").withSession("username" -> "frodo").withCSRFToken + ) + status(result) must beEqualTo(OK) + contentType(result) must beSome.which(_ == "text/html") + contentAsString(result) must contain("RecordSets | VinylDNS") + } + "redirect to the no access page when a user is locked out" in new WithApplication(app) { + val result = + lockedUserUnderTest.viewRecordSets()( + FakeRequest(GET, "/recordsets") + .withSession("username" -> "lockedFbaggins") + .withCSRFToken + ) + headers(result) must contain("Location" -> "/noaccess") + } + } + "Get for login" should { "with ldap enabled" should { "render the login page when the user is not logged in" in new WithApplication(app) { diff --git a/modules/portal/test/controllers/VinylDNSSpec.scala b/modules/portal/test/controllers/VinylDNSSpec.scala index 3067874d9..c3075055b 100644 --- a/modules/portal/test/controllers/VinylDNSSpec.scala +++ b/modules/portal/test/controllers/VinylDNSSpec.scala @@ -1947,6 +1947,37 @@ class VinylDNSSpec extends Specification with Mockito with TestApplicationData w } } + ".listRecordSetChangeHistory" should { + "return unauthorized (401) if requesting user is not logged in" in new WithApplication(app) { + val client = mock[WSClient] + val underTest = withClient(client) + val result = + underTest.listRecordSetChangeHistory()( + FakeRequest(GET, s"/api/recordsetchange/history") + ) + + status(result) mustEqual 401 + hasCacheHeaders(result) + contentAsString(result) must beEqualTo("You are not logged in. Please login to continue.") + } + "return forbidden (403) if user account is locked" in new WithApplication(app) { + val client = mock[WSClient] + val underTest = withLockedClient(client) + val result = underTest.listRecordSetChangeHistory()( + FakeRequest(GET, s"/api/recordsetchange/history").withSession( + "username" -> lockedFrodoUser.userName, + "accessKey" -> lockedFrodoUser.accessKey + ) + ) + + status(result) mustEqual 403 + hasCacheHeaders(result) + contentAsString(result) must beEqualTo( + s"User account for `${lockedFrodoUser.userName}` is locked." + ) + } + } + ".addZone" should { "return unauthorized (401) if requesting user is not logged in" in new WithApplication(app) { val client = mock[WSClient] From 8d4ad632ee6d4294f369bbc145ff27be0ad6f684 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 16 May 2023 12:44:23 +0530 Subject: [PATCH 246/521] add tests --- .../test/controllers/VinylDNSSpec.scala | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/modules/portal/test/controllers/VinylDNSSpec.scala b/modules/portal/test/controllers/VinylDNSSpec.scala index c3075055b..8c9aef9c0 100644 --- a/modules/portal/test/controllers/VinylDNSSpec.scala +++ b/modules/portal/test/controllers/VinylDNSSpec.scala @@ -1650,6 +1650,35 @@ class VinylDNSSpec extends Specification with Mockito with TestApplicationData w } } + ".getCommonZoneDetails" should { + "return unauthorized (401) if requesting user is not logged in" in new WithApplication(app) { + val client = mock[WSClient] + val underTest = withClient(client) + val result = + underTest.getCommonZoneDetails(hobbitZoneId)(FakeRequest(GET, s"/api/zones/$hobbitZoneId/details")) + + status(result) mustEqual 401 + hasCacheHeaders(result) + contentAsString(result) must beEqualTo("You are not logged in. Please login to continue.") + } + "return forbidden (403) if user account is locked" in new WithApplication(app) { + val client = mock[WSClient] + val underTest = withLockedClient(client) + val result = underTest.getCommonZoneDetails(hobbitZoneId)( + FakeRequest(GET, s"/api/zones/$hobbitZoneId/details").withSession( + "username" -> lockedFrodoUser.userName, + "accessKey" -> lockedFrodoUser.accessKey + ) + ) + + status(result) mustEqual 403 + hasCacheHeaders(result) + contentAsString(result) must beEqualTo( + s"User account for `${lockedFrodoUser.userName}` is locked." + ) + } + } + ".getZoneChange" should { "return ok (200) if the zoneChanges is found" in new WithApplication(app) { From 4d2ffcf6182764fa28022a76641e5e1960e1ffeb Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 17 May 2023 14:22:36 +0530 Subject: [PATCH 247/521] remove deprecated max-life-time --- .../vinyldns/mysql/repository/MySqlDataStoreProviderSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mysql/src/test/scala/vinyldns/mysql/repository/MySqlDataStoreProviderSpec.scala b/modules/mysql/src/test/scala/vinyldns/mysql/repository/MySqlDataStoreProviderSpec.scala index 7651f0e5f..bff6c6deb 100644 --- a/modules/mysql/src/test/scala/vinyldns/mysql/repository/MySqlDataStoreProviderSpec.scala +++ b/modules/mysql/src/test/scala/vinyldns/mysql/repository/MySqlDataStoreProviderSpec.scala @@ -49,7 +49,7 @@ class MySqlDataStoreProviderSpec extends AnyWordSpec with Matchers { | migration-url = "test-url" | maximum-pool-size = 20 | connection-timeout-millis = 1000 - | max-life-time = 600000 + | max-lifetime = 600000 | } | | repositories { From 5a5a9fd380279bcbf2f973f73b456829df73bff8 Mon Sep 17 00:00:00 2001 From: Nicholas Spadaccino <37352107+nspadaccino@users.noreply.github.com> Date: Wed, 17 May 2023 13:45:35 -0400 Subject: [PATCH 248/521] Bump version to v0.18.6 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 136f055e4..dc51ab768 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.18.5" +version in ThisBuild := "0.18.6" From d584b5a0386be226429cfa9fd817fe694a9faf86 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 18 May 2023 11:17:43 +0530 Subject: [PATCH 249/521] fix pagination bug --- .../MySqlRecordChangeRepository.scala | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala index 588f1f486..641ca6cc1 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala @@ -126,35 +126,41 @@ class MySqlRecordChangeRepository DB.readOnly { implicit s => val changes = if(startFrom.isDefined && fqdn.isDefined && recordType.isDefined){ LIST_CHANGES_WITH_START_FQDN_TYPE - .bindByName('fqdn -> fqdn.get, 'type -> fromRecordType(recordType.get), 'startFrom -> startFrom.get, 'limit -> maxItems) + .bindByName('fqdn -> fqdn.get, 'type -> fromRecordType(recordType.get), 'startFrom -> startFrom.get, 'limit -> (maxItems + 1)) .map(toRecordSetChange) .list() .apply() } else if(fqdn.isDefined && recordType.isDefined){ LIST_CHANGES_WITHOUT_START_FQDN_TYPE - .bindByName('fqdn -> fqdn.get, 'type -> fromRecordType(recordType.get), 'limit -> maxItems) + .bindByName('fqdn -> fqdn.get, 'type -> fromRecordType(recordType.get), 'limit -> (maxItems + 1)) .map(toRecordSetChange) .list() .apply() } else if(startFrom.isDefined){ LIST_CHANGES_WITH_START - .bindByName('zoneId -> zoneId.get, 'startFrom -> startFrom.get, 'limit -> maxItems) + .bindByName('zoneId -> zoneId.get, 'startFrom -> startFrom.get, 'limit -> (maxItems + 1)) .map(toRecordSetChange) .list() .apply() } else { LIST_CHANGES_NO_START - .bindByName('zoneId -> zoneId.get, 'limit -> maxItems) + .bindByName('zoneId -> zoneId.get, 'limit -> (maxItems + 1)) .map(toRecordSetChange) .list() .apply() } + val maxQueries = changes.take(maxItems) val startValue = startFrom.getOrElse(0) - val nextId = if(changes.size < maxItems) None else Some(startValue + maxItems) + + // earlier maxItems was incremented, if the (maxItems + 1) size is not reached then pages are exhausted + val nextId = changes match { + case _ if changes.size <= maxItems | changes.isEmpty => None + case _ => Some(startValue + maxItems) + } ListRecordSetChangesResults( - changes, + maxQueries, nextId, startFrom, maxItems From afadf9f290cbae2d5f4b51e3ebc7e425ab20d2a3 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 22 May 2023 11:40:05 +0530 Subject: [PATCH 250/521] naptr flags validation --- .../main/scala/vinyldns/api/domain/DomainValidations.scala | 4 ++++ .../vinyldns/api/domain/batch/BatchChangeValidations.scala | 2 +- .../scala/vinyldns/core/domain/DomainValidationErrors.scala | 6 ++++++ .../main/scala/vinyldns/core/domain/SingleChangeError.scala | 3 ++- modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html | 2 +- .../public/lib/dns-change/dns-change-new.controller.js | 1 + 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala index 0170d13f6..e78637e0f 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala @@ -163,4 +163,8 @@ object DomainValidations { def validateMX_NAPTR_SRVData(number: Int, recordDataType: String, recordType: String): ValidatedNel[DomainValidationError, Int] = if (number >= INTEGER_MIN_VALUE && number <= INTEGER_MAX_VALUE) number.validNel else InvalidMX_NAPTR_SRVData(number, INTEGER_MIN_VALUE, INTEGER_MAX_VALUE, recordDataType, recordType).invalidNel[Int] + + def validateNaptrFlag(value: String): ValidatedNel[DomainValidationError, String] = + if (value == "U" || value == "S" || value == "A" || value == "P") value.validNel + else InvalidNaptrFlag(value).invalidNel[String] } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index fa2e57226..353e59b34 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -248,7 +248,7 @@ class BatchChangeValidations( case mx: MXData => validateMX_NAPTR_SRVData(mx.preference, "preference", "MX").asUnit |+| validateHostName(mx.exchange).asUnit case ns: NSData => validateHostName(ns.nsdname).asUnit - case naptr: NAPTRData => validateMX_NAPTR_SRVData(naptr.preference, "preference", "NAPTR").asUnit |+| validateMX_NAPTR_SRVData(naptr.order, "order", "NAPTR").asUnit |+| validateHostName(naptr.replacement).asUnit + case naptr: NAPTRData => validateMX_NAPTR_SRVData(naptr.preference, "preference", "NAPTR").asUnit |+| validateMX_NAPTR_SRVData(naptr.order, "order", "NAPTR").asUnit |+| validateHostName(naptr.replacement).asUnit |+| validateNaptrFlag(naptr.flags).asUnit case srv: SRVData => validateMX_NAPTR_SRVData(srv.priority, "priority", "SRV").asUnit |+| validateMX_NAPTR_SRVData(srv.port, "port", "SRV").asUnit |+| validateMX_NAPTR_SRVData(srv.weight, "weight", "SRV").asUnit |+| validateHostName(srv.target).asUnit case other => InvalidBatchRecordType(other.toString, SupportedBatchChangeRecordTypes.get).invalidNel[Unit] diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index 7eebf540e..b5973c78f 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -113,6 +113,12 @@ final case class InvalidMX_NAPTR_SRVData(param: Long, min: Long, max: Long, reco s"""Invalid $recordType $recordDataType: "${param.toString}", must be a number between $min and $max.""" } +final case class InvalidNaptrFlag(value: String) + extends DomainValidationError { + def message: String = + s"""Invalid NAPTR flag value: '$value'. Valid NAPTR flag value must be U, S, A or P.""" +} + final case class InvalidBatchRecordType(param: String, supported: Set[RecordType]) extends DomainValidationError { def message: String = diff --git a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala index 8324978a0..23d8a2f46 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala @@ -31,7 +31,7 @@ object DomainValidationErrorType extends Enumeration { // NOTE: once defined, an error code type cannot be changed! val ChangeLimitExceeded, BatchChangeIsEmpty, GroupDoesNotExist, NotAMemberOfOwnerGroup, InvalidDomainName, InvalidCname, InvalidLength, InvalidEmail, InvalidRecordType, InvalidPortNumber, - InvalidIpv4Address, InvalidIpv6Address, InvalidIPAddress, InvalidTTL, InvalidMX_NAPTR_SRVData, + InvalidIpv4Address, InvalidIpv6Address, InvalidIPAddress, InvalidTTL, InvalidMX_NAPTR_SRVData, InvalidNaptrFlag, InvalidBatchRecordType, ZoneDiscoveryError, RecordAlreadyExists, RecordDoesNotExist, InvalidUpdateRequest, CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch, RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError, @@ -56,6 +56,7 @@ object DomainValidationErrorType extends Enumeration { case _: InvalidIPAddress => InvalidIPAddress case _: InvalidTTL => InvalidTTL case _: InvalidMX_NAPTR_SRVData => InvalidMX_NAPTR_SRVData + case _: InvalidNaptrFlag => InvalidNaptrFlag case _: InvalidBatchRecordType => InvalidBatchRecordType case _: ZoneDiscoveryError => ZoneDiscoveryError case _: RecordAlreadyExists => RecordAlreadyExists diff --git a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html index ed27608fe..14689e3e3 100644 --- a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html +++ b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html @@ -281,7 +281,7 @@
    - +

    Flags is required!

    diff --git a/modules/portal/public/lib/dns-change/dns-change-new.controller.js b/modules/portal/public/lib/dns-change/dns-change-new.controller.js index 10cc525e0..9969423be 100644 --- a/modules/portal/public/lib/dns-change/dns-change-new.controller.js +++ b/modules/portal/public/lib/dns-change/dns-change-new.controller.js @@ -42,6 +42,7 @@ $scope.allowManualReview = false; $scope.confirmationPrompt = "Are you sure you want to submit this batch change request?"; $scope.manualReviewEnabled; + $scope.naptrFlags = ["U", "S", "A", "P"]; $scope.addSingleChange = function() { $scope.newBatch.changes.push({changeType: "Add", type: "A+PTR"}); From 86f346ef161111d89061e2494977e9eb92b4e9de Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Mon, 22 May 2023 11:45:53 +0530 Subject: [PATCH 251/521] naptr flags test --- .../api/domain/batch/BatchChangeValidationsSpec.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index 85b4bf28d..024c4e1e4 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -570,6 +570,7 @@ class BatchChangeValidationsSpec val goodSRVInput = AddChangeInput("test-srv.example.com.", RecordType.SRV, ttl, SRVData(1, 2, 3, Fqdn("target.vinyldns."))) val badNSInput = AddChangeInput("test-bad-ns.example.com.", RecordType.NS, ttl, NSData(Fqdn("some.te$st.ns."))) val badNAPTRInput = AddChangeInput("test-bad-naptr.example.com.", RecordType.NAPTR, ttl, NAPTRData(99999, 2, "S", "E2U+sip", "", Fqdn("target"))) + val badNAPTRFlagInput = AddChangeInput("test-bad-flag-naptr.example.com.", RecordType.NAPTR, ttl, NAPTRData(1, 2, "t", "E2U+sip", "", Fqdn("target"))) val badSRVInput = AddChangeInput("test-bad-srv.example.com.", RecordType.SRV, ttl, SRVData(99999, 2, 3, Fqdn("target.vinyldns."))) val goodInput = AddChangeInput("test.example.com.", RecordType.A, ttl, AData("1.1.1.1")) val goodAAAAInput = @@ -580,7 +581,7 @@ class BatchChangeValidationsSpec AddChangeInput("testbad.example.com.", RecordType.AAAA, ttl, AAAAData("invalidIpv6:123")) val result = validateInputChanges( - List(goodNSInput, goodNAPTRInput, goodSRVInput, goodInput, goodAAAAInput, invalidDomainNameInput, invalidIpv6Input, badNSInput, badNAPTRInput, badSRVInput), + List(goodNSInput, goodNAPTRInput, goodSRVInput, goodInput, goodAAAAInput, invalidDomainNameInput, invalidIpv6Input, badNSInput, badNAPTRInput, badNAPTRFlagInput, badSRVInput), false ) result(0) shouldBe valid @@ -592,7 +593,8 @@ class BatchChangeValidationsSpec result(6) should haveInvalid[DomainValidationError](InvalidIpv6Address("invalidIpv6:123")) result(7) should haveInvalid[DomainValidationError](InvalidDomainName("some.te$st.ns.")) result(8) should haveInvalid[DomainValidationError](InvalidMX_NAPTR_SRVData(99999, 0, 65535, "order", "NAPTR")) - result(9) should haveInvalid[DomainValidationError](InvalidMX_NAPTR_SRVData(99999, 0, 65535, "priority", "SRV")) + result(9) should haveInvalid[DomainValidationError](InvalidNaptrFlag("t")) + result(10) should haveInvalid[DomainValidationError](InvalidMX_NAPTR_SRVData(99999, 0, 65535, "priority", "SRV")) } property("""validateInputName: should fail with a HighValueDomainError From 357afe269b3b98535f02dadee449a66ef7334909 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 02:31:06 +0000 Subject: [PATCH 252/521] Bump requests from 2.26.0 to 2.31.0 in /modules/api/src/test/functional Bumps [requests](https://github.com/psf/requests) from 2.26.0 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.26.0...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- modules/api/src/test/functional/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/src/test/functional/requirements.txt b/modules/api/src/test/functional/requirements.txt index decd2c187..dcfb21080 100644 --- a/modules/api/src/test/functional/requirements.txt +++ b/modules/api/src/test/functional/requirements.txt @@ -5,7 +5,7 @@ mock==4.0.3 dnspython==2.1.0 boto3==1.18.51 botocore==1.21.51 -requests==2.26.0 +requests==2.31.0 pytest-xdist==2.4.0 python-dateutil==2.8.2 filelock==3.2.0 From 7ef69e4b17d5ee65620941f8d6756741d1114725 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Wed, 24 May 2023 11:07:30 +0530 Subject: [PATCH 253/521] skip review --- .../domain/batch/BatchChangeValidations.scala | 5 ++--- .../api/engine/RecordSetChangeHandler.scala | 13 ++--------- .../tests/batch/create_batch_change_test.py | 22 +++++++++---------- .../domain/batch/BatchChangeServiceSpec.scala | 2 +- .../batch/BatchChangeValidationsSpec.scala | 6 ++--- .../core/domain/DomainValidationErrors.scala | 13 ++++------- 6 files changed, 23 insertions(+), 38 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index 54b589372..73bdbf88c 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -464,7 +464,6 @@ class BatchChangeValidations( change.inputChange.typ, change.inputChange.record, groupedChanges, - isApproved, isDeleteExists ) |+| ownerGroupProvidedIfNeeded(change, None, ownerGroupId) |+| @@ -508,14 +507,14 @@ class BatchChangeValidations( typ: RecordType, recordData: RecordData, groupedChanges: ChangeForValidationMap, - isApproved: Boolean, isDeleteExist: Boolean ): SingleValidation[Unit] = { val record = groupedChanges.getExistingRecordSetData(RecordKeyData(zoneId, recordName, typ, recordData)) if(record.isDefined) { record.get.records.contains(recordData) match { case true => ().validNel - case false => if(isDeleteExist) ().validNel else RecordAlreadyExists(inputName, recordData, isApproved).invalidNel} + case false => if(isDeleteExist) ().validNel else RecordAlreadyExists(inputName).invalidNel + } } else ().validNel } diff --git a/modules/api/src/main/scala/vinyldns/api/engine/RecordSetChangeHandler.scala b/modules/api/src/main/scala/vinyldns/api/engine/RecordSetChangeHandler.scala index ad0585250..c29061851 100644 --- a/modules/api/src/main/scala/vinyldns/api/engine/RecordSetChangeHandler.scala +++ b/modules/api/src/main/scala/vinyldns/api/engine/RecordSetChangeHandler.scala @@ -157,15 +157,6 @@ object RecordSetChangeHandler extends TransactionProvider { def isDnsMatch(dnsResult: List[RecordSet], recordSet: RecordSet, zoneName: String): Boolean = dnsResult.exists(matches(_, recordSet, zoneName)) - def isRecordExist(existingRecords: List[RecordSet], change: RecordSetChange): Boolean = { - var isExists : Boolean = false - existingRecords.foreach(recordData=> - for (record<-change.recordSet.records) - if (recordData.records.contains(record)) isExists= true - else isExists= false ) - isExists - } - // Determine processing status by comparing request against disposition of DNS backend def getProcessingStatus( change: RecordSetChange, @@ -174,8 +165,8 @@ object RecordSetChangeHandler extends TransactionProvider { change.changeType match { case RecordSetChangeType.Create => if (existingRecords.isEmpty) ReadyToApply(change) - else if (isDnsMatch(existingRecords, change.recordSet, change.zone.name) || isRecordExist(existingRecords,change)) - AlreadyApplied(change) //Record exists in DNS + else if (isDnsMatch(existingRecords, change.recordSet, change.zone.name)) + AlreadyApplied(change) else Failure(change, "Incompatible record in DNS.") case RecordSetChangeType.Update => diff --git a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py index 86fbadaef..e98e51c84 100644 --- a/modules/api/src/test/functional/tests/batch/create_batch_change_test.py +++ b/modules/api/src/test/functional/tests/batch/create_batch_change_test.py @@ -1481,8 +1481,8 @@ def test_a_recordtype_add_checks(shared_zone_test_context): # context validations: conflicting recordsets, unauthorized error assert_failed_change_in_error_response(response[7], input_name=existing_a_fqdn, record_data="1.2.3.4", - error_messages=[f"RecordName \"{existing_a_fqdn}\" already exists. Your request will be manually reviewed. " - f"If you intended to update this record, you can avoid manual review by adding a DeleteRecordSet entry followed by an Add."]) + error_messages=[f"RecordName \"{existing_a_fqdn}\" already exists. " + f"If you intended to update this record, submit a DeleteRecordSet entry followed by an Add."]) assert_failed_change_in_error_response(response[8], input_name=existing_cname_fqdn, record_data="1.2.3.4", error_messages=[f'CNAME Conflict: CNAME record names must be unique. ' @@ -2004,8 +2004,8 @@ def test_cname_recordtype_add_checks(shared_zone_test_context): f"Existing record with name \"{existing_forward_fqdn}\" and type \"A\" conflicts with this record."]) assert_failed_change_in_error_response(response[14], input_name=existing_cname_fqdn, record_type="CNAME", record_data="test.com.", - error_messages=[f"RecordName \"{existing_cname_fqdn}\" already exists. Your request will be manually reviewed. " - f"If you intended to update this record, you can avoid manual review by adding a DeleteRecordSet entry followed by an Add.", + error_messages=[f"RecordName \"{existing_cname_fqdn}\" already exists. " + f"If you intended to update this record, submit a DeleteRecordSet entry followed by an Add.", f"CNAME Conflict: CNAME record names must be unique. " f"Existing record with name \"{existing_cname_fqdn}\" and type \"CNAME\" conflicts with this record."]) assert_failed_change_in_error_response(response[15], input_name=existing_reverse_fqdn, record_type="CNAME", @@ -2310,8 +2310,8 @@ def test_ipv4_ptr_recordtype_add_checks(shared_zone_test_context): # context validations: existing cname recordset assert_failed_change_in_error_response(response[11], input_name=f"{ip4_prefix}.193", record_type="PTR", record_data="existing-ptr.", - error_messages=[f'RecordName "{ip4_prefix}.193" already exists. Your request will be manually reviewed. ' - f'If you intended to update this record, you can avoid manual review by adding a DeleteRecordSet entry followed by an Add.']) + error_messages=[f'RecordName "{ip4_prefix}.193" already exists. ' + f'If you intended to update this record, submit a DeleteRecordSet entry followed by an Add.']) assert_failed_change_in_error_response(response[12], input_name=f"{ip4_prefix}.199", record_type="PTR", record_data="existing-cname.", error_messages=[ f'CNAME Conflict: CNAME record names must be unique. Existing record with name "{ip4_prefix}.199" and type "CNAME" conflicts with this record.']) @@ -2520,8 +2520,8 @@ def test_ipv6_ptr_recordtype_add_checks(shared_zone_test_context): # context validations: existing record sets pre-request assert_failed_change_in_error_response(response[5], input_name=f"{ip6_prefix}:1000::bbbb", record_type="PTR", record_data="existing.ptr.", - error_messages=[f"RecordName \"{ip6_prefix}:1000::bbbb\" already exists. Your request will be manually reviewed. " - f"If you intended to update this record, you can avoid manual review by adding a DeleteRecordSet entry followed by an Add."]) + error_messages=[f"RecordName \"{ip6_prefix}:1000::bbbb\" already exists. " + f"If you intended to update this record, submit a DeleteRecordSet entry followed by an Add."]) finally: clear_recordset_list(to_delete, client) @@ -4113,7 +4113,7 @@ def test_create_batch_deletes_succeeds(shared_zone_test_context): @pytest.mark.skip_production def test_create_batch_change_with_multi_record_adds_with_multi_record_support(shared_zone_test_context): """ - Test new recordsets with multiple records can be added in batch, but existing recordsets cannot be added to + Test new recordsets with multiple records can be added in batch. """ client = shared_zone_test_context.ok_vinyldns_client ok_zone = shared_zone_test_context.ok_zone @@ -4138,7 +4138,7 @@ def test_create_batch_change_with_multi_record_adds_with_multi_record_support(sh get_change_TXT_json(f"multi-txt.{ok_zone_name}", text="more-multi-text"), get_change_MX_json(f"multi-mx.{ok_zone_name}", preference=0), get_change_MX_json(f"multi-mx.{ok_zone_name}", preference=1000, exchange="bar.foo."), - get_change_A_AAAA_json(rs_fqdn, address="1.1.1.1") + get_change_A_AAAA_json(rs_fqdn, address="1.2.3.4") ], "ownerGroupId": shared_zone_test_context.ok_group["id"] } @@ -4156,6 +4156,6 @@ def test_create_batch_change_with_multi_record_adds_with_multi_record_support(sh assert_successful_change_in_error_response(response["changes"][5], input_name=f"multi-txt.{ok_zone_name}", record_type="TXT", record_data="more-multi-text") assert_successful_change_in_error_response(response["changes"][6], input_name=f"multi-mx.{ok_zone_name}", record_type="MX", record_data={"preference": 0, "exchange": "foo.bar."}) assert_successful_change_in_error_response(response["changes"][7], input_name=f"multi-mx.{ok_zone_name}", record_type="MX", record_data={"preference": 1000, "exchange": "bar.foo."}) - assert_successful_change_in_error_response(response["changes"][8], input_name=rs_fqdn, record_data="1.1.1.1") + assert_successful_change_in_error_response(response["changes"][8], input_name=rs_fqdn, record_data="1.2.3.4") finally: clear_recordset_list(to_delete, client) diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala index 12e2a674d..71a741f02 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeServiceSpec.scala @@ -66,7 +66,7 @@ class BatchChangeServiceSpec private implicit val contextShift: ContextShift[IO] = IO.contextShift(ExecutionContext.global) private val nonFatalErrorZoneDiscoveryError = ZoneDiscoveryError("test") - private val nonFatalErrorRecordAlreadyExists = RecordAlreadyExists("test", AData("1.1.1.1"), true) + private val nonFatalErrorRecordAlreadyExists = RecordAlreadyExists("test") private val validations = new BatchChangeValidations( new AccessValidations( diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index feb55f916..2a2b65ff9 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -841,10 +841,10 @@ class BatchChangeValidationsSpec result(0) shouldBe valid result(1) should haveInvalid[DomainValidationError]( - RecordAlreadyExists(existingA.inputChange.inputName, existingA.inputChange.record, false) + RecordAlreadyExists(existingA.inputChange.inputName) ) result(2) should haveInvalid[DomainValidationError]( - RecordAlreadyExists(existingCname.inputChange.inputName, existingCname.inputChange.record, false) + RecordAlreadyExists(existingCname.inputChange.inputName) ).and( haveInvalid[DomainValidationError]( CnameIsNotUniqueError(existingCname.inputChange.inputName, existingCname.inputChange.typ) @@ -1218,7 +1218,7 @@ class BatchChangeValidationsSpec ) result(0) should haveInvalid[DomainValidationError]( - RecordAlreadyExists(input.inputChange.inputName, input.inputChange.record, false) + RecordAlreadyExists(input.inputChange.inputName) ) } } diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index cc44f6866..f4c8e0a5a 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -126,15 +126,10 @@ final case class ZoneDiscoveryError(name: String, fatal: Boolean = false) "If zone exists, then it must be connected to in VinylDNS." } -final case class RecordAlreadyExists(name: String, recordData: RecordData, isApproved:Boolean, - fatal: Boolean = false) extends DomainValidationError(fatal) { - def message: String = { - if (isApproved == false) - s"""RecordName "$name" already exists. Your request will be manually reviewed. """ + - "If you intended to update this record, you can avoid manual review by adding " + - " a DeleteRecordSet entry followed by an Add." - else s"""ℹ️ Record data "$recordData" is does not exists. - Complete the request in DNS and give approve. """ } +final case class RecordAlreadyExists(name: String) extends DomainValidationError { + def message: String = + s"""RecordName "$name" already exists. """ + + "If you intended to update this record, submit a DeleteRecordSet entry followed by an Add." } final case class RecordDoesNotExist(name: String) extends DomainValidationError { From 3b07bf1230f82da68cfd7119d87a72f68f652d6d Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 25 May 2023 11:17:26 +0530 Subject: [PATCH 254/521] address review comments --- .../scala/vinyldns/api/domain/DomainValidations.scala | 4 ++++ .../api/domain/batch/BatchChangeValidations.scala | 2 +- .../api/domain/batch/BatchChangeValidationsSpec.scala | 8 +++++--- .../vinyldns/core/domain/DomainValidationErrors.scala | 6 ++++++ .../scala/vinyldns/core/domain/SingleChangeError.scala | 5 +++-- .../resources/microsite/static/dns-changes-csv-sample.csv | 4 ++-- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala index e78637e0f..786ec4c73 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/DomainValidations.scala @@ -167,4 +167,8 @@ object DomainValidations { def validateNaptrFlag(value: String): ValidatedNel[DomainValidationError, String] = if (value == "U" || value == "S" || value == "A" || value == "P") value.validNel else InvalidNaptrFlag(value).invalidNel[String] + + def validateNaptrRegexp(value: String): ValidatedNel[DomainValidationError, String] = + if ((value.startsWith("!") && value.endsWith("!")) || value == "") value.validNel + else InvalidNaptrRegexp(value).invalidNel[String] } diff --git a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala index 353e59b34..c49e65746 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/batch/BatchChangeValidations.scala @@ -248,7 +248,7 @@ class BatchChangeValidations( case mx: MXData => validateMX_NAPTR_SRVData(mx.preference, "preference", "MX").asUnit |+| validateHostName(mx.exchange).asUnit case ns: NSData => validateHostName(ns.nsdname).asUnit - case naptr: NAPTRData => validateMX_NAPTR_SRVData(naptr.preference, "preference", "NAPTR").asUnit |+| validateMX_NAPTR_SRVData(naptr.order, "order", "NAPTR").asUnit |+| validateHostName(naptr.replacement).asUnit |+| validateNaptrFlag(naptr.flags).asUnit + case naptr: NAPTRData => validateMX_NAPTR_SRVData(naptr.preference, "preference", "NAPTR").asUnit |+| validateMX_NAPTR_SRVData(naptr.order, "order", "NAPTR").asUnit |+| validateHostName(naptr.replacement).asUnit |+| validateNaptrFlag(naptr.flags).asUnit |+| validateNaptrRegexp(naptr.regexp).asUnit case srv: SRVData => validateMX_NAPTR_SRVData(srv.priority, "priority", "SRV").asUnit |+| validateMX_NAPTR_SRVData(srv.port, "port", "SRV").asUnit |+| validateMX_NAPTR_SRVData(srv.weight, "weight", "SRV").asUnit |+| validateHostName(srv.target).asUnit case other => InvalidBatchRecordType(other.toString, SupportedBatchChangeRecordTypes.get).invalidNel[Unit] diff --git a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala index 024c4e1e4..de95757ab 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/batch/BatchChangeValidationsSpec.scala @@ -566,11 +566,12 @@ class BatchChangeValidationsSpec property("validateInputChanges: should fail with mix of success and failure inputs") { val goodNSInput = AddChangeInput("test-ns.example.com.", RecordType.NS, ttl, NSData(Fqdn("some.test.ns."))) - val goodNAPTRInput = AddChangeInput("test-naptr.example.com.", RecordType.NAPTR, ttl, NAPTRData(1, 2, "S", "E2U+sip", "", Fqdn("target"))) + val goodNAPTRInput = AddChangeInput("test-naptr.example.com.", RecordType.NAPTR, ttl, NAPTRData(1, 2, "S", "E2U+sip", "!^.*$!sip:jd@corpxyz.com!", Fqdn("target"))) val goodSRVInput = AddChangeInput("test-srv.example.com.", RecordType.SRV, ttl, SRVData(1, 2, 3, Fqdn("target.vinyldns."))) val badNSInput = AddChangeInput("test-bad-ns.example.com.", RecordType.NS, ttl, NSData(Fqdn("some.te$st.ns."))) val badNAPTRInput = AddChangeInput("test-bad-naptr.example.com.", RecordType.NAPTR, ttl, NAPTRData(99999, 2, "S", "E2U+sip", "", Fqdn("target"))) val badNAPTRFlagInput = AddChangeInput("test-bad-flag-naptr.example.com.", RecordType.NAPTR, ttl, NAPTRData(1, 2, "t", "E2U+sip", "", Fqdn("target"))) + val badNAPTRRegexpInput = AddChangeInput("test-bad-regexp-naptr.example.com.", RecordType.NAPTR, ttl, NAPTRData(1, 2, "S", "E2U+sip", "dummyregexp", Fqdn("target"))) val badSRVInput = AddChangeInput("test-bad-srv.example.com.", RecordType.SRV, ttl, SRVData(99999, 2, 3, Fqdn("target.vinyldns."))) val goodInput = AddChangeInput("test.example.com.", RecordType.A, ttl, AData("1.1.1.1")) val goodAAAAInput = @@ -581,7 +582,7 @@ class BatchChangeValidationsSpec AddChangeInput("testbad.example.com.", RecordType.AAAA, ttl, AAAAData("invalidIpv6:123")) val result = validateInputChanges( - List(goodNSInput, goodNAPTRInput, goodSRVInput, goodInput, goodAAAAInput, invalidDomainNameInput, invalidIpv6Input, badNSInput, badNAPTRInput, badNAPTRFlagInput, badSRVInput), + List(goodNSInput, goodNAPTRInput, goodSRVInput, goodInput, goodAAAAInput, invalidDomainNameInput, invalidIpv6Input, badNSInput, badNAPTRInput, badNAPTRFlagInput, badNAPTRRegexpInput, badSRVInput), false ) result(0) shouldBe valid @@ -594,7 +595,8 @@ class BatchChangeValidationsSpec result(7) should haveInvalid[DomainValidationError](InvalidDomainName("some.te$st.ns.")) result(8) should haveInvalid[DomainValidationError](InvalidMX_NAPTR_SRVData(99999, 0, 65535, "order", "NAPTR")) result(9) should haveInvalid[DomainValidationError](InvalidNaptrFlag("t")) - result(10) should haveInvalid[DomainValidationError](InvalidMX_NAPTR_SRVData(99999, 0, 65535, "priority", "SRV")) + result(10) should haveInvalid[DomainValidationError](InvalidNaptrRegexp("dummyregexp")) + result(11) should haveInvalid[DomainValidationError](InvalidMX_NAPTR_SRVData(99999, 0, 65535, "priority", "SRV")) } property("""validateInputName: should fail with a HighValueDomainError diff --git a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala index b5973c78f..05b5b266e 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/DomainValidationErrors.scala @@ -119,6 +119,12 @@ final case class InvalidNaptrFlag(value: String) s"""Invalid NAPTR flag value: '$value'. Valid NAPTR flag value must be U, S, A or P.""" } +final case class InvalidNaptrRegexp(value: String) + extends DomainValidationError { + def message: String = + s"""Invalid NAPTR regexp value: '$value'. Valid NAPTR regexp value must start and end with '!'.""" +} + final case class InvalidBatchRecordType(param: String, supported: Set[RecordType]) extends DomainValidationError { def message: String = diff --git a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala index 23d8a2f46..85642dd22 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/SingleChangeError.scala @@ -32,8 +32,8 @@ object DomainValidationErrorType extends Enumeration { val ChangeLimitExceeded, BatchChangeIsEmpty, GroupDoesNotExist, NotAMemberOfOwnerGroup, InvalidDomainName, InvalidCname, InvalidLength, InvalidEmail, InvalidRecordType, InvalidPortNumber, InvalidIpv4Address, InvalidIpv6Address, InvalidIPAddress, InvalidTTL, InvalidMX_NAPTR_SRVData, InvalidNaptrFlag, - InvalidBatchRecordType, ZoneDiscoveryError, RecordAlreadyExists, RecordDoesNotExist, InvalidUpdateRequest, - CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch, + InvalidNaptrRegexp, InvalidBatchRecordType, ZoneDiscoveryError, RecordAlreadyExists, RecordDoesNotExist, + InvalidUpdateRequest, CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch, RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError, NewMultiRecordError, CnameAtZoneApexError, RecordRequiresManualReview, UnsupportedOperation, DeleteRecordDataDoesNotExist, InvalidIPv4CName, InvalidBatchRequest, NotApprovedNSError = Value @@ -57,6 +57,7 @@ object DomainValidationErrorType extends Enumeration { case _: InvalidTTL => InvalidTTL case _: InvalidMX_NAPTR_SRVData => InvalidMX_NAPTR_SRVData case _: InvalidNaptrFlag => InvalidNaptrFlag + case _: InvalidNaptrRegexp => InvalidNaptrRegexp case _: InvalidBatchRecordType => InvalidBatchRecordType case _: ZoneDiscoveryError => ZoneDiscoveryError case _: RecordAlreadyExists => RecordAlreadyExists diff --git a/modules/docs/src/main/resources/microsite/static/dns-changes-csv-sample.csv b/modules/docs/src/main/resources/microsite/static/dns-changes-csv-sample.csv index e890de1d0..04389a6a8 100644 --- a/modules/docs/src/main/resources/microsite/static/dns-changes-csv-sample.csv +++ b/modules/docs/src/main/resources/microsite/static/dns-changes-csv-sample.csv @@ -8,8 +8,8 @@ Add,PTR,192.0.2.193,200,test.example.com. Add,TXT,test5.example.com,7200,example text Add,MX,test16.ok.,7200,1 text.com Add,NS,test17.ok.,7200,172.17.42.1 -Add,NAPTR,test18.ok.,7200,10 20 s SIPS+D2T _si._tc.ac-ot1.wdv.test.net. -Add,NAPTR,test20.ok.,7200,10 20 s SIPS+D2T !^.*$!sip:jd@corpxyz.com! _si._tc.ac-ot1.wdv.test.net. +Add,NAPTR,test18.ok.,7200,10 20 S SIPS+D2T _si._tc.ac-ot1.wdv.test.net. +Add,NAPTR,test20.ok.,7200,10 20 U SIPS+D2T !^.*$!sip:jd@corpxyz.com! _si._tc.ac-ot1.wdv.test.net. Add,SRV,test19.ok.,7200,1 2 3 dummy.com DeleteRecordSet,A+PTR,test5.example.com.,,1.1.1.1 DeleteRecordSet,A,test6.example.com.,, From d03315512c2a8e024ad0fc43f1ec063ab333f095 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 25 May 2023 11:38:49 +0530 Subject: [PATCH 255/521] manage logs --- modules/portal/public/lib/controllers/controller.groups.js | 4 +--- .../portal/public/lib/controllers/controller.manageZones.js | 3 +-- modules/portal/public/lib/controllers/controller.zones.js | 4 +--- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/modules/portal/public/lib/controllers/controller.groups.js b/modules/portal/public/lib/controllers/controller.groups.js index 1b93976fb..94c17c736 100644 --- a/modules/portal/public/lib/controllers/controller.groups.js +++ b/modules/portal/public/lib/controllers/controller.groups.js @@ -213,10 +213,8 @@ angular.module('controller.groups', []).controller('GroupsController', function $scope.validDomains=function getValidEmailDomains() { function success(response) { - $log.log('groupsService::listEmailDomains-success'); + $log.debug('groupsService::listEmailDomains-success', response); $scope.validEmailDomains = response.data; - $log.log('scope.validEmailDomains length',$scope.validEmailDomains.length) - $log.log('scope.validEmailDomains',$scope.validEmailDomains); return $scope.validEmailDomains } return groupsService diff --git a/modules/portal/public/lib/controllers/controller.manageZones.js b/modules/portal/public/lib/controllers/controller.manageZones.js index 2781d9ca5..f8c668143 100644 --- a/modules/portal/public/lib/controllers/controller.manageZones.js +++ b/modules/portal/public/lib/controllers/controller.manageZones.js @@ -189,8 +189,7 @@ angular.module('controller.manageZones', ['angular-cron-jobs']) }; $scope.validDomains=function getValidEmailDomains() { function success(response) { - $log.log('manageZonesService::listEmailDomains-success'); - $log.log('scope.validEmailDomains length',$scope.validEmailDomains.length) + $log.debug('manageZonesService::listEmailDomains-success', response); return $scope.validEmailDomains = response.data; } return groupsService diff --git a/modules/portal/public/lib/controllers/controller.zones.js b/modules/portal/public/lib/controllers/controller.zones.js index 7bf16830c..cb71f1461 100644 --- a/modules/portal/public/lib/controllers/controller.zones.js +++ b/modules/portal/public/lib/controllers/controller.zones.js @@ -221,10 +221,8 @@ angular.module('controller.zones', []) } } $scope.validDomains=function getValidEmailDomains() { - $log.log('Function Entry'); function success(response) { - $log.log('zonesService::listEmailDomains-success'); - $log.log('scope.validEmailDomains length',$scope.validEmailDomains.length) + $log.debug('zonesService::listEmailDomains-success', response); return $scope.validEmailDomains = response.data; } From 839b683dfe908ac2cf4d81d4ac278d195e824540 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Tue, 30 May 2023 12:51:56 +0530 Subject: [PATCH 256/521] Add naptr validations --- .../vinyldns/api/route/DnsJsonProtocol.scala | 6 +++-- .../api/route/RecordSetRoutingSpec.scala | 10 ++++----- modules/portal/public/css/vinyldns.css | 6 +++-- .../lib/controllers/controller.records.js | 1 + .../portal/public/templates/record-modal.html | 22 ++++++++++--------- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala b/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala index 8dba45315..7a23a9e2f 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/DnsJsonProtocol.scala @@ -169,6 +169,8 @@ trait DnsJsonProtocol extends JsonValidation { } def checkDomainNameLen(s: String): Boolean = s.length <= 255 + def validateNaptrFlag(flag: String): Boolean = flag == "U" || flag == "S" || flag == "A" || flag == "P" + def validateNaptrRegexp(regexp: String): Boolean = regexp.startsWith("!") && regexp.endsWith("!") || regexp == "" def nameContainsDots(s: String): Boolean = s.contains(".") def nameDoesNotContainSpaces(s: String): Boolean = !s.contains(" ") @@ -514,7 +516,7 @@ trait DnsJsonProtocol extends JsonValidation { (js \ "flags") .required[String]("Missing NAPTR.flags") .check( - "NAPTR.flags must be less than 2 characters" -> (_.length < 2) + "Invalid NAPTR.flag. Valid NAPTR flag value must be U, S, A or P" -> validateNaptrFlag ), (js \ "service") .required[String]("Missing NAPTR.service") @@ -524,7 +526,7 @@ trait DnsJsonProtocol extends JsonValidation { (js \ "regexp") .required[String]("Missing NAPTR.regexp") .check( - "NAPTR.regexp must be less than 255 characters" -> checkDomainNameLen + "Invalid NAPTR.regexp. Valid NAPTR regexp value must start and end with '!' or can be empty" -> validateNaptrRegexp ), (js \ "replacement") diff --git a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala index 019ab58fa..1e4286628 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala @@ -1480,11 +1480,11 @@ class RecordSetRoutingSpec } "return errors for invalid NAPTR record data" in { + val validFlags = List("U", "S", "A", "P") validateErrors( testRecordType( RecordType.NAPTR, ("replacement" -> Random.alphanumeric.take(260).mkString) ~~ - // should check regex better ("regexp" -> Random.alphanumeric.take(260).mkString) ~~ ("service" -> Random.alphanumeric.take(260).mkString) ~~ ("flags" -> Random.alphanumeric.take(2).mkString) ~~ @@ -1493,18 +1493,18 @@ class RecordSetRoutingSpec ), "NAPTR.order must be an unsigned 16 bit number", "NAPTR.preference must be an unsigned 16 bit number", - "NAPTR.flags must be less than 2 characters", + "Invalid NAPTR.flag. Valid NAPTR flag value must be U, S, A or P", "NAPTR.service must be less than 255 characters", - "NAPTR.regexp must be less than 255 characters", + "Invalid NAPTR.regexp. Valid NAPTR regexp value must start and end with '!' or can be empty", "NAPTR.replacement must be less than 255 characters" ) validateErrors( testRecordType( RecordType.NAPTR, - ("regexp" -> Random.alphanumeric.take(10).mkString) ~~ + ("regexp" -> "") ~~ ("service" -> Random.alphanumeric.take(10).mkString) ~~ ("replacement" -> Random.alphanumeric.take(10).mkString) ~~ - ("flags" -> Random.alphanumeric.take(1).mkString) ~~ + ("flags" -> validFlags.take(1).mkString) ~~ ("order" -> -1) ~~ ("preference" -> -1) ), diff --git a/modules/portal/public/css/vinyldns.css b/modules/portal/public/css/vinyldns.css index 075b496a3..24efabf8a 100644 --- a/modules/portal/public/css/vinyldns.css +++ b/modules/portal/public/css/vinyldns.css @@ -538,6 +538,8 @@ input[type="file"] { padding: 0px; margin: 0px; } - - /* Ending of css override for cron library and it's associated elements used in zone sync scheduling */ + +#set-dropdown-width { + width: 4em; +} diff --git a/modules/portal/public/lib/controllers/controller.records.js b/modules/portal/public/lib/controllers/controller.records.js index 46beaca0a..f3801e58a 100644 --- a/modules/portal/public/lib/controllers/controller.records.js +++ b/modules/portal/public/lib/controllers/controller.records.js @@ -29,6 +29,7 @@ angular.module('controller.records', []) $scope.recordTypes = ['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'PTR', 'SRV', 'NAPTR', 'SSHFP', 'TXT']; $scope.readRecordTypes = ['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'PTR', "SOA", 'SRV', 'NAPTR', 'SSHFP', 'TXT']; $scope.selectedRecordTypes = []; + $scope.naptrFlags = ["U", "S", "A", "P"]; $scope.sshfpAlgorithms = [{name: '(1) RSA', number: 1}, {name: '(2) DSA', number: 2}, {name: '(3) ECDSA', number: 3}, {name: '(4) Ed25519', number: 4}]; $scope.sshfpTypes = [{name: '(1) SHA-1', number: 1}, {name: '(2) SHA-256', number: 2}]; diff --git a/modules/portal/public/templates/record-modal.html b/modules/portal/public/templates/record-modal.html index 857776c69..42ce61557 100644 --- a/modules/portal/public/templates/record-modal.html +++ b/modules/portal/public/templates/record-modal.html @@ -324,7 +324,7 @@ required/> - - + - - - Date: Wed, 31 May 2023 14:26:14 -0400 Subject: [PATCH 257/521] Bump version to v0.18.7 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index dc51ab768..5e16d8266 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.18.6" +version in ThisBuild := "0.18.7" From da880e276c877ba4b87766fc3104d55310731141 Mon Sep 17 00:00:00 2001 From: Aravindh-Raju Date: Thu, 1 Jun 2023 11:48:11 +0530 Subject: [PATCH 258/521] Fix bugs --- modules/portal/public/templates/record-modal.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/portal/public/templates/record-modal.html b/modules/portal/public/templates/record-modal.html index 42ce61557..4a636690c 100644 --- a/modules/portal/public/templates/record-modal.html +++ b/modules/portal/public/templates/record-modal.html @@ -59,7 +59,7 @@ -