mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-08-22 02:02:14 +00:00
parent
e4c131af43
commit
ea7c77951c
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,4 +28,4 @@ package-lock.json
|
||||
.bloop
|
||||
.metals
|
||||
tmp.out
|
||||
|
||||
.vscode
|
||||
|
@ -1,3 +1,5 @@
|
||||
version=2.2.1
|
||||
|
||||
style = default
|
||||
|
||||
maxColumn = 100
|
||||
|
@ -57,14 +57,16 @@ class BatchChangeRepositoryIntegrationSpec
|
||||
None,
|
||||
None,
|
||||
None
|
||||
)),
|
||||
)
|
||||
),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
|
||||
val f = for {
|
||||
saveBatchChangeResult <- batchChangeRepository.save(batchChange)
|
||||
getSingleChangesResult <- batchChangeRepository.getSingleChanges(
|
||||
batchChange.changes.map(_.id))
|
||||
batchChange.changes.map(_.id)
|
||||
)
|
||||
} yield (saveBatchChangeResult, getSingleChangesResult)
|
||||
val (saveResponse, singleChanges) = f.unsafeRunSync()
|
||||
|
||||
|
@ -83,7 +83,8 @@ class DnsConversionsIntegrationSpec extends WordSpec with Matchers with ResultHe
|
||||
// deleting the record just added
|
||||
val deleteResult: DnsResponse =
|
||||
rightResultOf(
|
||||
conn.deleteRecord(RecordSetChangeGenerator.forAdd(testRecord, testZone)).value)
|
||||
conn.deleteRecord(RecordSetChangeGenerator.forAdd(testRecord, testZone)).value
|
||||
)
|
||||
|
||||
deleteResult shouldBe a[NoError]
|
||||
|
||||
|
@ -75,7 +75,8 @@ class RecordSetServiceIntegrationSpec
|
||||
"test@test.com",
|
||||
status = ZoneStatus.Active,
|
||||
connection = testConnection,
|
||||
adminGroupId = group.id)
|
||||
adminGroupId = group.id
|
||||
)
|
||||
|
||||
private val apexTestRecordA = RecordSet(
|
||||
zone.id,
|
||||
@ -85,7 +86,8 @@ class RecordSetServiceIntegrationSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("10.1.1.1")))
|
||||
List(AData("10.1.1.1"))
|
||||
)
|
||||
private val apexTestRecordAAAA = RecordSet(
|
||||
zone.id,
|
||||
"live-zone-test",
|
||||
@ -94,7 +96,8 @@ class RecordSetServiceIntegrationSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AAAAData("fd69:27cc:fe91::60")))
|
||||
List(AAAAData("fd69:27cc:fe91::60"))
|
||||
)
|
||||
private val subTestRecordA = RecordSet(
|
||||
zone.id,
|
||||
"a-record",
|
||||
@ -103,7 +106,8 @@ class RecordSetServiceIntegrationSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("10.1.1.1")))
|
||||
List(AData("10.1.1.1"))
|
||||
)
|
||||
private val subTestRecordAAAA = RecordSet(
|
||||
zone.id,
|
||||
"aaaa-record",
|
||||
@ -112,7 +116,8 @@ class RecordSetServiceIntegrationSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AAAAData("fd69:27cc:fe91::60")))
|
||||
List(AAAAData("fd69:27cc:fe91::60"))
|
||||
)
|
||||
private val subTestRecordNS = RecordSet(
|
||||
zone.id,
|
||||
"ns-record",
|
||||
@ -121,14 +126,16 @@ class RecordSetServiceIntegrationSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(NSData("172.17.42.1.")))
|
||||
List(NSData("172.17.42.1."))
|
||||
)
|
||||
|
||||
private val zoneTestNameConflicts = Zone(
|
||||
s"zone-test-name-conflicts.",
|
||||
"test@test.com",
|
||||
status = ZoneStatus.Active,
|
||||
connection = testConnection,
|
||||
adminGroupId = group.id)
|
||||
adminGroupId = group.id
|
||||
)
|
||||
private val apexTestRecordNameConflict = RecordSet(
|
||||
zoneTestNameConflicts.id,
|
||||
"zone-test-name-conflicts.",
|
||||
@ -137,7 +144,8 @@ class RecordSetServiceIntegrationSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("10.1.1.1")))
|
||||
List(AData("10.1.1.1"))
|
||||
)
|
||||
private val subTestRecordNameConflict = RecordSet(
|
||||
zoneTestNameConflicts.id,
|
||||
"relative-name-conflict",
|
||||
@ -146,14 +154,16 @@ class RecordSetServiceIntegrationSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("10.1.1.1")))
|
||||
List(AData("10.1.1.1"))
|
||||
)
|
||||
|
||||
private val zoneTestAddRecords = Zone(
|
||||
s"zone-test-add-records.",
|
||||
"test@test.com",
|
||||
status = ZoneStatus.Active,
|
||||
connection = testConnection,
|
||||
adminGroupId = group.id)
|
||||
adminGroupId = group.id
|
||||
)
|
||||
|
||||
private val highValueDomainRecord = RecordSet(
|
||||
zone.id,
|
||||
@ -172,7 +182,8 @@ class RecordSetServiceIntegrationSpec
|
||||
status = ZoneStatus.Active,
|
||||
connection = testConnection,
|
||||
adminGroupId = group.id,
|
||||
shared = true)
|
||||
shared = true
|
||||
)
|
||||
|
||||
private val sharedTestRecord = RecordSet(
|
||||
sharedZone.id,
|
||||
@ -216,8 +227,9 @@ class RecordSetServiceIntegrationSpec
|
||||
zoneRepo = zoneRepository
|
||||
groupRepo = groupRepository
|
||||
List(group, group2, sharedGroup).map(g => waitForSuccess(groupRepo.save(g)))
|
||||
List(zone, zoneTestNameConflicts, zoneTestAddRecords, sharedZone).map(z =>
|
||||
waitForSuccess(zoneRepo.save(z)))
|
||||
List(zone, zoneTestNameConflicts, zoneTestAddRecords, sharedZone).map(
|
||||
z => waitForSuccess(zoneRepo.save(z))
|
||||
)
|
||||
|
||||
// Seeding records in DB
|
||||
val records = List(
|
||||
@ -242,7 +254,8 @@ class RecordSetServiceIntegrationSpec
|
||||
mock[RecordChangeRepository],
|
||||
mock[UserRepository],
|
||||
TestMessageQueue,
|
||||
new AccessValidations())
|
||||
new AccessValidations()
|
||||
)
|
||||
}
|
||||
|
||||
def tearDown(): Unit = ()
|
||||
@ -270,7 +283,8 @@ class RecordSetServiceIntegrationSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("10.1.1.1")))
|
||||
List(AData("10.1.1.1"))
|
||||
)
|
||||
val result =
|
||||
testRecordSetService
|
||||
.addRecordSet(newRecord, auth)
|
||||
@ -387,7 +401,8 @@ class RecordSetServiceIntegrationSpec
|
||||
.unsafeRunSync()
|
||||
|
||||
leftValue(result) shouldBe InvalidRequest(
|
||||
HighValueDomainError("high-value-domain-new.live-zone-test.").message)
|
||||
HighValueDomainError("high-value-domain-new.live-zone-test.").message
|
||||
)
|
||||
}
|
||||
|
||||
"fail to update a record whose name is a high value domain" in {
|
||||
@ -399,7 +414,8 @@ class RecordSetServiceIntegrationSpec
|
||||
.unsafeRunSync()
|
||||
|
||||
leftValue(result) shouldBe InvalidRequest(
|
||||
HighValueDomainError("high-value-domain-existing.live-zone-test.").message)
|
||||
HighValueDomainError("high-value-domain-existing.live-zone-test.").message
|
||||
)
|
||||
}
|
||||
|
||||
"fail to delete a record whose name is a high value domain" in {
|
||||
@ -409,7 +425,8 @@ class RecordSetServiceIntegrationSpec
|
||||
.unsafeRunSync()
|
||||
|
||||
leftValue(result) shouldBe InvalidRequest(
|
||||
HighValueDomainError("high-value-domain-existing.live-zone-test.").message)
|
||||
HighValueDomainError("high-value-domain-existing.live-zone-test.").message
|
||||
)
|
||||
}
|
||||
|
||||
"get a shared record when user is in assigned ownerGroup" in {
|
||||
@ -518,7 +535,8 @@ class RecordSetServiceIntegrationSpec
|
||||
.deleteRecordSet(
|
||||
testOwnerGroupRecordInNormalZone.id,
|
||||
testOwnerGroupRecordInNormalZone.zoneId,
|
||||
auth2)
|
||||
auth2
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
|
@ -83,7 +83,8 @@ class ZoneServiceIntegrationSpec
|
||||
ttl = 38400,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("10.1.1.1")))
|
||||
records = List(AData("10.1.1.1"))
|
||||
)
|
||||
|
||||
private val changeSetSOA = ChangeSet(RecordSetChangeGenerator.forAdd(testRecordSOA, okZone))
|
||||
private val changeSetNS = ChangeSet(RecordSetChangeGenerator.forAdd(testRecordNS, okZone))
|
||||
@ -159,7 +160,8 @@ class ZoneServiceIntegrationSpec
|
||||
"getBackendIds" should {
|
||||
"return backend ids in config" in {
|
||||
testZoneService.getBackendIds().value.unsafeRunSync() shouldBe Right(
|
||||
List("func-test-backend"))
|
||||
List("func-test-backend")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,17 +32,21 @@ class ZoneViewLoaderIntegrationSpec extends WordSpec with Matchers {
|
||||
assertThrows[IllegalArgumentException](
|
||||
DnsZoneViewLoader(
|
||||
Zone("vinyldns.", "bad@transfer.connection")
|
||||
.copy(transferConnection =
|
||||
Some(ZoneConnection("invalid-connection.", "bad-key", "invalid-key", "10.1.1.1"))))
|
||||
.load()
|
||||
.unsafeRunSync())
|
||||
.copy(
|
||||
transferConnection =
|
||||
Some(ZoneConnection("invalid-connection.", "bad-key", "invalid-key", "10.1.1.1"))
|
||||
)
|
||||
).load()
|
||||
.unsafeRunSync()
|
||||
)
|
||||
}
|
||||
|
||||
"return a failure if the zone doesn't exist in the DNS backend" in {
|
||||
assertThrows[ZoneTransferException](
|
||||
DnsZoneViewLoader(Zone("non-existent-zone", "bad@zone.test"))
|
||||
.load()
|
||||
.unsafeRunSync())
|
||||
.unsafeRunSync()
|
||||
)
|
||||
}
|
||||
|
||||
"return a failure if the zone is larger than the max zone size" in {
|
||||
|
@ -72,7 +72,8 @@ class EmailNotifierIntegrationSpec
|
||||
None,
|
||||
None,
|
||||
None
|
||||
)),
|
||||
)
|
||||
),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
|
||||
|
@ -68,7 +68,8 @@ class SnsNotifierIntegrationSpec
|
||||
None,
|
||||
None,
|
||||
None
|
||||
)),
|
||||
)
|
||||
),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved,
|
||||
id = "a615e2bb-8b35-4a39-8947-1edd0e653afa"
|
||||
)
|
||||
@ -76,12 +77,16 @@ class SnsNotifierIntegrationSpec
|
||||
val credentialsProvider = new AWSStaticCredentialsProvider(
|
||||
new BasicAWSCredentials(
|
||||
snsConfig.getString("access-key"),
|
||||
snsConfig.getString("secret-key")))
|
||||
snsConfig.getString("secret-key")
|
||||
)
|
||||
)
|
||||
val sns = AmazonSNSClientBuilder.standard
|
||||
.withEndpointConfiguration(
|
||||
new EndpointConfiguration(
|
||||
snsConfig.getString("service-endpoint"),
|
||||
snsConfig.getString("signing-region")))
|
||||
snsConfig.getString("signing-region")
|
||||
)
|
||||
)
|
||||
.withCredentials(credentialsProvider)
|
||||
.build()
|
||||
val sqs = AmazonSQSClientBuilder
|
||||
@ -113,7 +118,8 @@ class SnsNotifierIntegrationSpec
|
||||
val notification = parse(messages.get(0).getBody)
|
||||
(notification \ "Message").extract[String] should be(
|
||||
"""{"userId":"ok","userName":"ok","createdTimestamp":"2019-07-22T19:38:23Z",""" +
|
||||
""""status":"Complete","approvalStatus":"AutoApproved","id":"a615e2bb-8b35-4a39-8947-1edd0e653afa"}""")
|
||||
""""status":"Complete","approvalStatus":"AutoApproved","id":"a615e2bb-8b35-4a39-8947-1edd0e653afa"}"""
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -79,7 +79,8 @@ object Boot extends App {
|
||||
repositories.userRepository,
|
||||
repositories.groupRepository,
|
||||
repositories.zoneRepository,
|
||||
repositories.membershipRepository)
|
||||
repositories.membershipRepository
|
||||
)
|
||||
queueConfig <- VinylDNSConfig.messageQueueConfig
|
||||
messageQueue <- MessageQueueLoader.load(queueConfig)
|
||||
processingDisabled <- IO(VinylDNSConfig.vinyldnsConfig.getBoolean("processing-disabled"))
|
||||
@ -128,16 +129,19 @@ object Boot extends App {
|
||||
connectionValidator,
|
||||
messageQueue,
|
||||
zoneValidations,
|
||||
recordAccessValidations)
|
||||
recordAccessValidations
|
||||
)
|
||||
val healthService = new HealthService(
|
||||
messageQueue.healthCheck :: connectionValidator.healthCheck(healthCheckTimeout) ::
|
||||
loaderResponse.healthChecks)
|
||||
loaderResponse.healthChecks
|
||||
)
|
||||
val batchChangeConverter =
|
||||
new BatchChangeConverter(repositories.batchChangeRepository, messageQueue)
|
||||
val authPrincipalProvider =
|
||||
new MembershipAuthPrincipalProvider(
|
||||
repositories.userRepository,
|
||||
repositories.membershipRepository)
|
||||
repositories.membershipRepository
|
||||
)
|
||||
val batchChangeService = BatchChangeService(
|
||||
repositories,
|
||||
batchChangeValidations,
|
||||
|
@ -76,7 +76,8 @@ object VinylDNSConfig {
|
||||
|
||||
lazy val highValueRegexList: List[Regex] =
|
||||
ZoneRecordValidations.toCaseIgnoredRegexList(
|
||||
getOptionalStringList("high-value-domains.regex-list"))
|
||||
getOptionalStringList("high-value-domains.regex-list")
|
||||
)
|
||||
|
||||
lazy val highValueIpList: List[IpAddress] =
|
||||
getOptionalStringList("high-value-domains.ip-list").flatMap(ip => IpAddress(ip))
|
||||
@ -148,13 +149,15 @@ object VinylDNSConfig {
|
||||
|
||||
lazy val domainListRequiringManualReview: List[Regex] =
|
||||
ZoneRecordValidations.toCaseIgnoredRegexList(
|
||||
getOptionalStringList("manual-review-domains.domain-list"))
|
||||
getOptionalStringList("manual-review-domains.domain-list")
|
||||
)
|
||||
|
||||
lazy val ipListRequiringManualReview: List[IpAddress] =
|
||||
getOptionalStringList("manual-review-domains.ip-list").flatMap(ip => IpAddress(ip))
|
||||
|
||||
lazy val zoneNameListRequiringManualReview: Set[String] = {
|
||||
Set() ++ getOptionalStringList("manual-review-domains.zone-name-list").map(zn =>
|
||||
DomainHelpers.ensureTrailingDot(zn.toLowerCase))
|
||||
Set() ++ getOptionalStringList("manual-review-domains.zone-name-list").map(
|
||||
zn => DomainHelpers.ensureTrailingDot(zn.toLowerCase)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,8 @@ object CommandHandler {
|
||||
pollingInterval: FiniteDuration,
|
||||
pauseSignal: SignallingRef[IO, Boolean],
|
||||
connections: ConfiguredDnsConnections,
|
||||
maxOpen: Int = 4)(implicit timer: Timer[IO]): Stream[IO, Unit] = {
|
||||
maxOpen: Int = 4
|
||||
)(implicit timer: Timer[IO]): Stream[IO, Unit] = {
|
||||
|
||||
// Polls queue for message batches, connected to the signal which is toggled in the status endpoint
|
||||
val messageSource = startPolling(mq, count, pollingInterval).pauseWhen(pauseSignal)
|
||||
@ -73,7 +74,8 @@ object CommandHandler {
|
||||
recordChangeHandler,
|
||||
zoneSyncHandler,
|
||||
batchChangeHandler,
|
||||
connections)
|
||||
connections
|
||||
)
|
||||
|
||||
// Delete messages from message queue when complete
|
||||
val updateQueue = messageSink(mq)
|
||||
@ -84,7 +86,8 @@ object CommandHandler {
|
||||
.map(
|
||||
_.observe(increaseTimeoutWhenSyncing)
|
||||
.through(changeRequestProcessor)
|
||||
.through(updateQueue))
|
||||
.through(updateQueue)
|
||||
)
|
||||
.parJoin(maxOpen)
|
||||
.handleErrorWith { error =>
|
||||
logger.error("Encountered unexpected error in main flow", error)
|
||||
@ -98,7 +101,8 @@ object CommandHandler {
|
||||
|
||||
/* Polls Message Queue for messages */
|
||||
def startPolling(mq: MessageQueue, count: MessageCount, pollingInterval: FiniteDuration)(
|
||||
implicit timer: Timer[IO]): Stream[IO, Stream[IO, CommandMessage]] = {
|
||||
implicit timer: Timer[IO]
|
||||
): Stream[IO, Stream[IO, CommandMessage]] = {
|
||||
|
||||
def pollingStream(): Stream[IO, Stream[IO, CommandMessage]] =
|
||||
// every delay duration, we poll
|
||||
@ -145,7 +149,8 @@ object CommandHandler {
|
||||
recordChangeProcessor: (DnsConnection, RecordSetChange) => IO[RecordSetChange],
|
||||
zoneSyncProcessor: ZoneChange => IO[ZoneChange],
|
||||
batchChangeProcessor: BatchChangeCommand => IO[Option[BatchChange]],
|
||||
connections: ConfiguredDnsConnections): Pipe[IO, CommandMessage, MessageOutcome] =
|
||||
connections: ConfiguredDnsConnections
|
||||
): Pipe[IO, CommandMessage, MessageOutcome] =
|
||||
_.evalMap[IO, MessageOutcome] { message =>
|
||||
message.command match {
|
||||
case sync: ZoneChange
|
||||
@ -202,7 +207,8 @@ object CommandHandler {
|
||||
recordChangeRepo: RecordChangeRepository,
|
||||
batchChangeRepo: BatchChangeRepository,
|
||||
notifiers: AllNotifiers,
|
||||
connections: ConfiguredDnsConnections)(implicit timer: Timer[IO]): IO[Unit] = {
|
||||
connections: ConfiguredDnsConnections
|
||||
)(implicit timer: Timer[IO]): IO[Unit] = {
|
||||
// Handlers for each type of change request
|
||||
val zoneChangeHandler =
|
||||
ZoneChangeHandler(zoneRepo, zoneChangeRepo, recordSetRepo)
|
||||
|
@ -95,7 +95,8 @@ object DomainValidations {
|
||||
def validateStringLength(
|
||||
value: Option[String],
|
||||
minInclusive: Option[Int],
|
||||
maxInclusive: Int): ValidatedNel[DomainValidationError, Option[String]] =
|
||||
maxInclusive: Int
|
||||
): ValidatedNel[DomainValidationError, Option[String]] =
|
||||
validateIfDefined(value) { d =>
|
||||
validateStringLength(d, minInclusive, maxInclusive)
|
||||
}
|
||||
@ -103,7 +104,8 @@ object DomainValidations {
|
||||
def validateStringLength(
|
||||
value: String,
|
||||
minInclusive: Option[Int],
|
||||
maxInclusive: Int): ValidatedNel[DomainValidationError, String] =
|
||||
maxInclusive: Int
|
||||
): ValidatedNel[DomainValidationError, String] =
|
||||
if (minInclusive.forall(m => value.length >= m) && value.length <= maxInclusive)
|
||||
value.validNel
|
||||
else InvalidLength(value, minInclusive.getOrElse(0), maxInclusive).invalidNel
|
||||
|
@ -57,7 +57,8 @@ object ReverseZoneHelpers {
|
||||
handleIpv6RecordValidation(zone: Zone, recordName)
|
||||
} else {
|
||||
InvalidRequest(
|
||||
s"RecordSet $recordName does not specify a valid IP address in zone ${zone.name}").asLeft
|
||||
s"RecordSet $recordName does not specify a valid IP address in zone ${zone.name}"
|
||||
).asLeft
|
||||
}
|
||||
|
||||
def convertPTRtoIPv4(zone: Zone, recordName: String): String = {
|
||||
@ -83,7 +84,8 @@ object ReverseZoneHelpers {
|
||||
private def recordsetIsWithinCidrMaskIpv4(
|
||||
mask: String,
|
||||
zone: Zone,
|
||||
recordName: String): Boolean = {
|
||||
recordName: String
|
||||
): Boolean = {
|
||||
val recordIpAddr = convertPTRtoIPv4(zone, recordName)
|
||||
|
||||
Try {
|
||||
@ -125,14 +127,16 @@ object ReverseZoneHelpers {
|
||||
|
||||
private def handleIpv4RecordValidation(
|
||||
zone: Zone,
|
||||
recordName: String): Either[Throwable, Unit] = {
|
||||
recordName: String
|
||||
): Either[Throwable, Unit] = {
|
||||
val isValid = for {
|
||||
cidrMask <- getZoneAsCIDRString(zone)
|
||||
validated <- if (recordsetIsWithinCidrMask(cidrMask, zone, recordName)) {
|
||||
true.asRight
|
||||
} else {
|
||||
InvalidRequest(
|
||||
s"RecordSet $recordName does not specify a valid IP address in zone ${zone.name}").asLeft
|
||||
s"RecordSet $recordName does not specify a valid IP address in zone ${zone.name}"
|
||||
).asLeft
|
||||
}
|
||||
} yield validated
|
||||
|
||||
@ -141,14 +145,16 @@ object ReverseZoneHelpers {
|
||||
|
||||
private def handleIpv6RecordValidation(
|
||||
zone: Zone,
|
||||
recordName: String): Either[Throwable, Unit] = {
|
||||
recordName: String
|
||||
): Either[Throwable, Unit] = {
|
||||
val v6Regex = "([0-9a-f][.]){32}ip6.arpa.".r
|
||||
|
||||
s"$recordName.${zone.name}" match {
|
||||
case v6Regex(_*) => ().asRight
|
||||
case _ =>
|
||||
InvalidRequest(
|
||||
s"RecordSet $recordName does not specify a valid IP address in zone ${zone.name}").asLeft
|
||||
s"RecordSet $recordName does not specify a valid IP address in zone ${zone.name}"
|
||||
).asLeft
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,9 @@ object ValidationImprovements {
|
||||
* If the value is not present, will return None as success
|
||||
*
|
||||
*/
|
||||
def validateIfDefined[E, A](value: => Option[A])(
|
||||
validator: A => ValidatedNel[E, A]): ValidatedNel[E, Option[A]] =
|
||||
def validateIfDefined[E, A](
|
||||
value: => Option[A]
|
||||
)(validator: A => ValidatedNel[E, A]): ValidatedNel[E, Option[A]] =
|
||||
value match {
|
||||
case None => Option.empty[A].validNel[E]
|
||||
case Some(a) => validator(a).map(Some(_))
|
||||
|
@ -31,31 +31,39 @@ class AccessValidations(globalAcls: GlobalAcls = GlobalAcls(List.empty))
|
||||
|
||||
def canSeeZone(auth: AuthPrincipal, zone: Zone): Either[Throwable, Unit] =
|
||||
ensuring(
|
||||
NotAuthorizedError(s"User ${auth.signedInUser.userName} cannot access zone '${zone.name}'"))(
|
||||
NotAuthorizedError(s"User ${auth.signedInUser.userName} cannot access zone '${zone.name}'")
|
||||
)(
|
||||
auth.isSystemAdmin || auth
|
||||
.isGroupMember(zone.adminGroupId) || userHasAclRules(auth, zone))
|
||||
.isGroupMember(zone.adminGroupId) || userHasAclRules(auth, zone)
|
||||
)
|
||||
|
||||
def canChangeZone(
|
||||
auth: AuthPrincipal,
|
||||
zoneName: String,
|
||||
zoneAdminGroupId: String): Either[Throwable, Unit] =
|
||||
zoneAdminGroupId: String
|
||||
): Either[Throwable, Unit] =
|
||||
ensuring(
|
||||
NotAuthorizedError(
|
||||
s"""User '${auth.signedInUser.userName}' cannot create or modify zone '$zoneName' because
|
||||
|they are not in the Zone Admin Group '$zoneAdminGroupId'""".stripMargin
|
||||
.replace("\n", " ")))(auth.isSuper || auth.isGroupMember(zoneAdminGroupId))
|
||||
.replace("\n", " ")
|
||||
)
|
||||
)(auth.isSuper || auth.isGroupMember(zoneAdminGroupId))
|
||||
|
||||
def canAddRecordSet(
|
||||
auth: AuthPrincipal,
|
||||
recordName: String,
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordData: List[RecordData] = List.empty): Either[Throwable, Unit] = {
|
||||
recordData: List[RecordData] = List.empty
|
||||
): Either[Throwable, Unit] = {
|
||||
val accessLevel = getAccessLevel(auth, recordName, recordType, zone, None, recordData)
|
||||
ensuring(
|
||||
NotAuthorizedError(s"User ${auth.signedInUser.userName} does not have access to create " +
|
||||
s"$recordName.${zone.name}"))(
|
||||
accessLevel == AccessLevel.Delete || accessLevel == AccessLevel.Write)
|
||||
NotAuthorizedError(
|
||||
s"User ${auth.signedInUser.userName} does not have access to create " +
|
||||
s"$recordName.${zone.name}"
|
||||
)
|
||||
)(accessLevel == AccessLevel.Delete || accessLevel == AccessLevel.Write)
|
||||
}
|
||||
|
||||
def canUpdateRecordSet(
|
||||
@ -64,13 +72,16 @@ class AccessValidations(globalAcls: GlobalAcls = GlobalAcls(List.empty))
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordOwnerGroupId: Option[String],
|
||||
newRecordData: List[RecordData] = List.empty): Either[Throwable, Unit] = {
|
||||
newRecordData: List[RecordData] = List.empty
|
||||
): Either[Throwable, Unit] = {
|
||||
val accessLevel =
|
||||
getAccessLevel(auth, recordName, recordType, zone, recordOwnerGroupId, newRecordData)
|
||||
ensuring(
|
||||
NotAuthorizedError(s"User ${auth.signedInUser.userName} does not have access to update " +
|
||||
s"$recordName.${zone.name}"))(
|
||||
accessLevel == AccessLevel.Delete || accessLevel == AccessLevel.Write)
|
||||
NotAuthorizedError(
|
||||
s"User ${auth.signedInUser.userName} does not have access to update " +
|
||||
s"$recordName.${zone.name}"
|
||||
)
|
||||
)(accessLevel == AccessLevel.Delete || accessLevel == AccessLevel.Write)
|
||||
}
|
||||
|
||||
def canDeleteRecordSet(
|
||||
@ -79,16 +90,16 @@ class AccessValidations(globalAcls: GlobalAcls = GlobalAcls(List.empty))
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordOwnerGroupId: Option[String],
|
||||
existingRecordData: List[RecordData] = List.empty): Either[Throwable, Unit] =
|
||||
existingRecordData: List[RecordData] = List.empty
|
||||
): Either[Throwable, Unit] =
|
||||
ensuring(
|
||||
NotAuthorizedError(s"User ${auth.signedInUser.userName} does not have access to delete " +
|
||||
s"$recordName.${zone.name}"))(getAccessLevel(
|
||||
auth,
|
||||
recordName,
|
||||
recordType,
|
||||
zone,
|
||||
recordOwnerGroupId,
|
||||
existingRecordData) == AccessLevel.Delete)
|
||||
NotAuthorizedError(
|
||||
s"User ${auth.signedInUser.userName} does not have access to delete " +
|
||||
s"$recordName.${zone.name}"
|
||||
)
|
||||
)(
|
||||
getAccessLevel(auth, recordName, recordType, zone, recordOwnerGroupId, existingRecordData) == AccessLevel.Delete
|
||||
)
|
||||
|
||||
def canViewRecordSet(
|
||||
auth: AuthPrincipal,
|
||||
@ -96,17 +107,22 @@ class AccessValidations(globalAcls: GlobalAcls = GlobalAcls(List.empty))
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordOwnerGroupId: Option[String],
|
||||
recordData: List[RecordData] = List.empty): Either[Throwable, Unit] =
|
||||
recordData: List[RecordData] = List.empty
|
||||
): Either[Throwable, Unit] =
|
||||
ensuring(
|
||||
NotAuthorizedError(s"User ${auth.signedInUser.userName} does not have access to view " +
|
||||
s"$recordName.${zone.name}"))(
|
||||
NotAuthorizedError(
|
||||
s"User ${auth.signedInUser.userName} does not have access to view " +
|
||||
s"$recordName.${zone.name}"
|
||||
)
|
||||
)(
|
||||
getAccessLevel(auth, recordName, recordType, zone, recordOwnerGroupId, recordData) != AccessLevel.NoAccess
|
||||
)
|
||||
|
||||
def getListAccessLevels(
|
||||
auth: AuthPrincipal,
|
||||
recordSets: List[RecordSetInfo],
|
||||
zone: Zone): List[RecordSetListInfo] =
|
||||
zone: Zone
|
||||
): List[RecordSetListInfo] =
|
||||
if (auth.isSuper || auth.isGroupMember(zone.adminGroupId))
|
||||
recordSets.map(RecordSetListInfo(_, AccessLevel.Delete))
|
||||
else {
|
||||
@ -136,13 +152,15 @@ class AccessValidations(globalAcls: GlobalAcls = GlobalAcls(List.empty))
|
||||
auth: AuthPrincipal,
|
||||
recordName: String,
|
||||
recordType: RecordType,
|
||||
zone: Zone): AccessLevel = {
|
||||
zone: Zone
|
||||
): AccessLevel = {
|
||||
val validRules = zone.acl.rules.filter { rule =>
|
||||
ruleAppliesToUser(auth, rule) && ruleAppliesToRecordType(recordType, rule) && ruleAppliesToRecordName(
|
||||
recordName,
|
||||
recordType,
|
||||
zone,
|
||||
rule)
|
||||
rule
|
||||
)
|
||||
}
|
||||
getPrioritizedAccessLevel(recordType, validRules)
|
||||
}
|
||||
@ -164,7 +182,8 @@ class AccessValidations(globalAcls: GlobalAcls = GlobalAcls(List.empty))
|
||||
recordName: String,
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
rule: ACLRule): Boolean =
|
||||
rule: ACLRule
|
||||
): Boolean =
|
||||
rule.recordMask match {
|
||||
case Some(mask) if recordType == RecordType.PTR =>
|
||||
ReverseZoneHelpers.recordsetIsWithinCidrMask(mask, zone, recordName)
|
||||
@ -192,7 +211,8 @@ class AccessValidations(globalAcls: GlobalAcls = GlobalAcls(List.empty))
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordOwnerGroupId: Option[String] = None,
|
||||
recordData: List[RecordData] = List.empty): AccessLevel = auth match {
|
||||
recordData: List[RecordData] = List.empty
|
||||
): AccessLevel = auth match {
|
||||
case testUser if testUser.isTestUser && !zone.isTest => AccessLevel.NoAccess
|
||||
case admin if admin.isGroupMember(zone.adminGroupId) =>
|
||||
AccessLevel.Delete
|
||||
@ -211,7 +231,8 @@ class AccessValidations(globalAcls: GlobalAcls = GlobalAcls(List.empty))
|
||||
def sharedRecordAccess(
|
||||
auth: AuthPrincipal,
|
||||
recordType: RecordType,
|
||||
recordOwnerGroupId: Option[String]): Boolean =
|
||||
recordOwnerGroupId: Option[String]
|
||||
): Boolean =
|
||||
recordOwnerGroupId.exists(auth.isGroupMember) ||
|
||||
(recordOwnerGroupId.isEmpty && VinylDNSConfig.sharedApprovedTypes.contains(recordType))
|
||||
}
|
||||
|
@ -30,14 +30,16 @@ trait AccessValidationsAlgebra {
|
||||
def canChangeZone(
|
||||
auth: AuthPrincipal,
|
||||
zoneName: String,
|
||||
zoneAdminGroupId: String): Either[Throwable, Unit]
|
||||
zoneAdminGroupId: String
|
||||
): Either[Throwable, Unit]
|
||||
|
||||
def canAddRecordSet(
|
||||
auth: AuthPrincipal,
|
||||
recordName: String,
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordData: List[RecordData] = List.empty): Either[Throwable, Unit]
|
||||
recordData: List[RecordData] = List.empty
|
||||
): Either[Throwable, Unit]
|
||||
|
||||
def canUpdateRecordSet(
|
||||
auth: AuthPrincipal,
|
||||
@ -45,7 +47,8 @@ trait AccessValidationsAlgebra {
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordOwnerGroupId: Option[String],
|
||||
newRecordData: List[RecordData] = List.empty): Either[Throwable, Unit]
|
||||
newRecordData: List[RecordData] = List.empty
|
||||
): Either[Throwable, Unit]
|
||||
|
||||
def canDeleteRecordSet(
|
||||
auth: AuthPrincipal,
|
||||
@ -53,7 +56,8 @@ trait AccessValidationsAlgebra {
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordOwnerGroupId: Option[String],
|
||||
existingRecordData: List[RecordData] = List.empty): Either[Throwable, Unit]
|
||||
existingRecordData: List[RecordData] = List.empty
|
||||
): Either[Throwable, Unit]
|
||||
|
||||
def canViewRecordSet(
|
||||
auth: AuthPrincipal,
|
||||
@ -61,12 +65,14 @@ trait AccessValidationsAlgebra {
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordOwnerGroupId: Option[String],
|
||||
recordData: List[RecordData] = List.empty): Either[Throwable, Unit]
|
||||
recordData: List[RecordData] = List.empty
|
||||
): Either[Throwable, Unit]
|
||||
|
||||
def getListAccessLevels(
|
||||
auth: AuthPrincipal,
|
||||
recordSets: List[RecordSetInfo],
|
||||
zone: Zone): List[RecordSetListInfo]
|
||||
zone: Zone
|
||||
): List[RecordSetListInfo]
|
||||
|
||||
def getZoneAccess(auth: AuthPrincipal, zone: Zone): AccessLevel
|
||||
}
|
||||
|
@ -47,7 +47,8 @@ final case class GlobalAcls(acls: List[GlobalAcl]) {
|
||||
recordName: String,
|
||||
recordType: RecordType,
|
||||
zone: Zone,
|
||||
recordData: List[RecordData] = List.empty): Boolean = {
|
||||
recordData: List[RecordData] = List.empty
|
||||
): Boolean = {
|
||||
|
||||
def isAuthorized(authPrincipal: AuthPrincipal, fqdn: String): Boolean = {
|
||||
val regexList = authPrincipal.memberGroupIds.flatMap(aclMap.getOrElse(_, List.empty)).toList
|
||||
|
@ -28,8 +28,8 @@ trait AuthPrincipalProvider extends Monitored {
|
||||
|
||||
class MembershipAuthPrincipalProvider(
|
||||
userRepo: UserRepository,
|
||||
membershipRepo: MembershipRepository)
|
||||
extends AuthPrincipalProvider {
|
||||
membershipRepo: MembershipRepository
|
||||
) extends AuthPrincipalProvider {
|
||||
|
||||
def getAuthPrincipal(accessKey: String): IO[Option[AuthPrincipal]] =
|
||||
getUserByAccessKey(accessKey).flatMap {
|
||||
|
@ -40,16 +40,19 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
||||
batchChange: BatchChange,
|
||||
existingZones: ExistingZones,
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
ownerGroupId: Option[String]): BatchResult[BatchConversionOutput] = {
|
||||
ownerGroupId: Option[String]
|
||||
): BatchResult[BatchConversionOutput] = {
|
||||
logger.info(
|
||||
s"Converting BatchChange [${batchChange.id}] with SingleChanges [${batchChange.changes.map(_.id)}]")
|
||||
s"Converting BatchChange [${batchChange.id}] with SingleChanges [${batchChange.changes.map(_.id)}]"
|
||||
)
|
||||
for {
|
||||
recordSetChanges <- createRecordSetChangesForBatch(
|
||||
batchChange.changes,
|
||||
existingZones,
|
||||
groupedChanges,
|
||||
batchChange.userId,
|
||||
ownerGroupId).toRightBatchResult
|
||||
ownerGroupId
|
||||
).toRightBatchResult
|
||||
_ <- allChangesWereConverted(batchChange.changes, recordSetChanges)
|
||||
_ <- batchChangeRepo
|
||||
.save(batchChange)
|
||||
@ -62,7 +65,8 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
||||
|
||||
def allChangesWereConverted(
|
||||
singleChanges: List[SingleChange],
|
||||
recordSetChanges: List[RecordSetChange]): BatchResult[Unit] = {
|
||||
recordSetChanges: List[RecordSetChange]
|
||||
): BatchResult[Unit] = {
|
||||
val convertedIds = recordSetChanges.flatMap(_.singleBatchChangeIds).toSet
|
||||
|
||||
singleChanges.find(ch => !convertedIds.contains(ch.id)) match {
|
||||
@ -76,7 +80,8 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
||||
|
||||
def putChangesOnQueue(
|
||||
recordSetChanges: List[RecordSetChange],
|
||||
batchChangeId: String): BatchResult[List[RecordSetChange]] =
|
||||
batchChangeId: String
|
||||
): BatchResult[List[RecordSetChange]] =
|
||||
recordSetChanges.toNel match {
|
||||
case None =>
|
||||
recordSetChanges.toRightBatchResult // If list is empty, return normally without queueing
|
||||
@ -94,7 +99,8 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
||||
|
||||
def updateWithQueueingFailures(
|
||||
batchChange: BatchChange,
|
||||
recordSetChanges: List[RecordSetChange]): BatchChange = {
|
||||
recordSetChanges: List[RecordSetChange]
|
||||
): BatchChange = {
|
||||
// idsMap maps batchId to recordSetId
|
||||
val idsMap = recordSetChanges.flatMap { rsChange =>
|
||||
rsChange.singleBatchChangeIds.map(batchId => (batchId, rsChange.id))
|
||||
@ -128,7 +134,8 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
||||
existingZones: ExistingZones,
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
userId: String,
|
||||
ownerGroupId: Option[String]): List[RecordSetChange] = {
|
||||
ownerGroupId: Option[String]
|
||||
): List[RecordSetChange] = {
|
||||
// NOTE: this also assumes we are past approval and know the zone/record split at this point
|
||||
val supportedChangesByKey = changes
|
||||
.filter(sc => SupportedBatchChangeRecordTypes.get.contains(sc.typ))
|
||||
@ -155,7 +162,8 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
||||
proposedRecordData,
|
||||
userId,
|
||||
existingRecordSet,
|
||||
ownerGroupId)
|
||||
ownerGroupId
|
||||
)
|
||||
} yield recordSetChange
|
||||
}
|
||||
.toList
|
||||
@ -170,7 +178,8 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
||||
proposedRecordData: Set[RecordData],
|
||||
userId: String,
|
||||
existingRecordSet: Option[RecordSet],
|
||||
ownerGroupId: Option[String]): Option[RecordSetChange] = {
|
||||
ownerGroupId: Option[String]
|
||||
): Option[RecordSetChange] = {
|
||||
|
||||
val singleChangeIds = singleChangeNel.map(_.id).toList
|
||||
|
||||
|
@ -30,5 +30,6 @@ trait BatchChangeConverterAlgebra {
|
||||
batchChange: BatchChange,
|
||||
existingZones: ExistingZones,
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
ownerGroupId: Option[String]): BatchResult[BatchConversionOutput]
|
||||
ownerGroupId: Option[String]
|
||||
): BatchResult[BatchConversionOutput]
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ final case class InvalidBatchChangeInput(errors: List[DomainValidationError])
|
||||
// This separates error by change requested
|
||||
final case class InvalidBatchChangeResponses(
|
||||
changeRequests: List[ChangeInput],
|
||||
changeRequestResponses: ValidatedBatch[ChangeForValidation])
|
||||
extends BatchChangeErrorResponse
|
||||
changeRequestResponses: ValidatedBatch[ChangeForValidation]
|
||||
) extends BatchChangeErrorResponse
|
||||
|
||||
final case class BatchChangeFailedApproval(batchChange: BatchChange)
|
||||
extends BatchChangeErrorResponse
|
||||
|
@ -29,7 +29,8 @@ final case class BatchChangeInput(
|
||||
comments: Option[String],
|
||||
changes: List[ChangeInput],
|
||||
ownerGroupId: Option[String] = None,
|
||||
scheduledTime: Option[DateTime] = None)
|
||||
scheduledTime: Option[DateTime] = None
|
||||
)
|
||||
|
||||
object BatchChangeInput {
|
||||
def apply(batchChange: BatchChange): BatchChangeInput = {
|
||||
@ -51,8 +52,8 @@ final case class AddChangeInput(
|
||||
inputName: String,
|
||||
typ: RecordType,
|
||||
ttl: Option[Long],
|
||||
record: RecordData)
|
||||
extends ChangeInput {
|
||||
record: RecordData
|
||||
) extends ChangeInput {
|
||||
|
||||
def asNewStoredChange(errors: NonEmptyList[DomainValidationError]): SingleChange = {
|
||||
val knownTtl = ttl.getOrElse(VinylDNSConfig.defaultTtl)
|
||||
@ -76,8 +77,8 @@ final case class AddChangeInput(
|
||||
final case class DeleteRRSetChangeInput(
|
||||
inputName: String,
|
||||
typ: RecordType,
|
||||
record: Option[RecordData])
|
||||
extends ChangeInput {
|
||||
record: Option[RecordData]
|
||||
) extends ChangeInput {
|
||||
def asNewStoredChange(errors: NonEmptyList[DomainValidationError]): SingleChange =
|
||||
SingleDeleteRRSetChange(
|
||||
None,
|
||||
@ -99,7 +100,8 @@ object AddChangeInput {
|
||||
inputName: String,
|
||||
typ: RecordType,
|
||||
ttl: Option[Long],
|
||||
record: RecordData): AddChangeInput = {
|
||||
record: RecordData
|
||||
): AddChangeInput = {
|
||||
val transformName = typ match {
|
||||
case PTR => inputName
|
||||
case _ => ensureTrailingDot(inputName)
|
||||
@ -115,7 +117,8 @@ object DeleteRRSetChangeInput {
|
||||
def apply(
|
||||
inputName: String,
|
||||
typ: RecordType,
|
||||
record: Option[RecordData] = None): DeleteRRSetChangeInput = {
|
||||
record: Option[RecordData] = None
|
||||
): DeleteRRSetChangeInput = {
|
||||
val transformName = typ match {
|
||||
case PTR => inputName
|
||||
case _ => ensureTrailingDot(inputName)
|
||||
|
@ -21,4 +21,5 @@ import org.joda.time.DateTime
|
||||
final case class BatchChangeReviewInfo(
|
||||
reviewerId: String,
|
||||
reviewComment: Option[String],
|
||||
reviewTimestamp: DateTime = DateTime.now())
|
||||
reviewTimestamp: DateTime = DateTime.now()
|
||||
)
|
||||
|
@ -59,7 +59,8 @@ object BatchChangeService {
|
||||
authProvider: AuthPrincipalProvider,
|
||||
notifiers: AllNotifiers,
|
||||
scheduledChangesEnabled: Boolean,
|
||||
v6DiscoveryNibbleBoundaries: V6DiscoveryNibbleBoundaries): BatchChangeService =
|
||||
v6DiscoveryNibbleBoundaries: V6DiscoveryNibbleBoundaries
|
||||
): BatchChangeService =
|
||||
new BatchChangeService(
|
||||
dataAccessor.zoneRepository,
|
||||
dataAccessor.recordSetRepository,
|
||||
@ -88,8 +89,8 @@ class BatchChangeService(
|
||||
authProvider: AuthPrincipalProvider,
|
||||
notifiers: AllNotifiers,
|
||||
scheduledChangesEnabled: Boolean,
|
||||
v6zoneNibbleBoundaries: V6DiscoveryNibbleBoundaries)
|
||||
extends BatchChangeServiceAlgebra {
|
||||
v6zoneNibbleBoundaries: V6DiscoveryNibbleBoundaries
|
||||
) extends BatchChangeServiceAlgebra {
|
||||
|
||||
import batchChangeValidations._
|
||||
|
||||
@ -97,7 +98,8 @@ class BatchChangeService(
|
||||
def applyBatchChange(
|
||||
batchChangeInput: BatchChangeInput,
|
||||
auth: AuthPrincipal,
|
||||
allowManualReview: Boolean): BatchResult[BatchChange] =
|
||||
allowManualReview: Boolean
|
||||
): BatchResult[BatchChange] =
|
||||
for {
|
||||
validationOutput <- applyBatchChangeValidationFlow(batchChangeInput, auth, isApproved = false)
|
||||
changeForConversion <- buildResponse(
|
||||
@ -110,13 +112,15 @@ class BatchChangeService(
|
||||
changeForConversion,
|
||||
validationOutput.existingZones,
|
||||
validationOutput.groupedChanges,
|
||||
batchChangeInput.ownerGroupId)
|
||||
batchChangeInput.ownerGroupId
|
||||
)
|
||||
} yield serviceCompleteBatch
|
||||
|
||||
def applyBatchChangeValidationFlow(
|
||||
batchChangeInput: BatchChangeInput,
|
||||
auth: AuthPrincipal,
|
||||
isApproved: Boolean): BatchResult[BatchValidationFlowOutput] =
|
||||
isApproved: Boolean
|
||||
): BatchResult[BatchValidationFlowOutput] =
|
||||
for {
|
||||
existingGroup <- getOwnerGroup(batchChangeInput.ownerGroupId)
|
||||
_ <- validateBatchChangeInput(batchChangeInput, existingGroup, auth)
|
||||
@ -130,13 +134,15 @@ class BatchChangeService(
|
||||
groupedChanges,
|
||||
auth,
|
||||
isApproved,
|
||||
batchChangeInput.ownerGroupId)
|
||||
batchChangeInput.ownerGroupId
|
||||
)
|
||||
errorGroupIds <- getGroupIdsFromUnauthorizedErrors(validatedSingleChanges)
|
||||
validatedSingleChangesWithGroups = errorGroupMapping(errorGroupIds, validatedSingleChanges)
|
||||
} yield BatchValidationFlowOutput(validatedSingleChangesWithGroups, zoneMap, groupedChanges)
|
||||
|
||||
def getGroupIdsFromUnauthorizedErrors(
|
||||
changes: ValidatedBatch[ChangeForValidation]): BatchResult[Set[Group]] = {
|
||||
changes: ValidatedBatch[ChangeForValidation]
|
||||
): BatchResult[Set[Group]] = {
|
||||
val list = changes.getInvalid.collect {
|
||||
case d: UserIsNotAuthorizedError => d.ownerGroupId
|
||||
}.toSet
|
||||
@ -145,7 +151,8 @@ class BatchChangeService(
|
||||
|
||||
def errorGroupMapping(
|
||||
groups: Set[Group],
|
||||
validations: ValidatedBatch[ChangeForValidation]): ValidatedBatch[ChangeForValidation] =
|
||||
validations: ValidatedBatch[ChangeForValidation]
|
||||
): ValidatedBatch[ChangeForValidation] =
|
||||
validations.map {
|
||||
case Invalid(err) =>
|
||||
err.map {
|
||||
@ -156,7 +163,8 @@ class BatchChangeService(
|
||||
e.ownerGroupId,
|
||||
e.ownerType,
|
||||
group.map(_.email),
|
||||
group.map(_.name))
|
||||
group.map(_.name)
|
||||
)
|
||||
logger.error(updatedError.message)
|
||||
updatedError
|
||||
case e =>
|
||||
@ -169,7 +177,8 @@ class BatchChangeService(
|
||||
def rejectBatchChange(
|
||||
batchChangeId: String,
|
||||
authPrincipal: AuthPrincipal,
|
||||
rejectBatchChangeInput: RejectBatchChangeInput): BatchResult[BatchChange] =
|
||||
rejectBatchChangeInput: RejectBatchChangeInput
|
||||
): BatchResult[BatchChange] =
|
||||
for {
|
||||
batchChange <- getExistingBatchChange(batchChangeId)
|
||||
bypassTestCheck <- getBypassTestCheckForReject(authPrincipal, batchChange)
|
||||
@ -177,14 +186,16 @@ class BatchChangeService(
|
||||
rejectedBatchChange <- rejectBatchChange(
|
||||
batchChange,
|
||||
rejectBatchChangeInput.reviewComment,
|
||||
authPrincipal.signedInUser.id)
|
||||
authPrincipal.signedInUser.id
|
||||
)
|
||||
_ <- notifiers.notify(Notification(rejectedBatchChange)).toBatchResult
|
||||
} yield rejectedBatchChange
|
||||
|
||||
def approveBatchChange(
|
||||
batchChangeId: String,
|
||||
authPrincipal: AuthPrincipal,
|
||||
approveBatchChangeInput: ApproveBatchChangeInput): BatchResult[BatchChange] =
|
||||
approveBatchChangeInput: ApproveBatchChangeInput
|
||||
): BatchResult[BatchChange] =
|
||||
for {
|
||||
batchChange <- getExistingBatchChange(batchChangeId)
|
||||
requesterAuth <- EitherT.fromOptionF(
|
||||
@ -195,23 +206,27 @@ class BatchChangeService(
|
||||
asInput = BatchChangeInput(batchChange)
|
||||
reviewInfo = BatchChangeReviewInfo(
|
||||
authPrincipal.userId,
|
||||
approveBatchChangeInput.reviewComment)
|
||||
approveBatchChangeInput.reviewComment
|
||||
)
|
||||
validationOutput <- applyBatchChangeValidationFlow(asInput, requesterAuth, isApproved = true)
|
||||
changeForConversion = rebuildBatchChangeForUpdate(
|
||||
batchChange,
|
||||
validationOutput.validatedChanges,
|
||||
reviewInfo)
|
||||
reviewInfo
|
||||
)
|
||||
serviceCompleteBatch <- convertOrSave(
|
||||
changeForConversion,
|
||||
validationOutput.existingZones,
|
||||
validationOutput.groupedChanges,
|
||||
batchChange.ownerGroupId)
|
||||
batchChange.ownerGroupId
|
||||
)
|
||||
response <- buildResponseForApprover(serviceCompleteBatch).toBatchResult
|
||||
} yield response
|
||||
|
||||
def cancelBatchChange(
|
||||
batchChangeId: String,
|
||||
authPrincipal: AuthPrincipal): BatchResult[BatchChange] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): BatchResult[BatchChange] =
|
||||
for {
|
||||
batchChange <- getExistingBatchChange(batchChangeId)
|
||||
_ <- validateBatchChangeCancellation(batchChange, authPrincipal).toBatchResult
|
||||
@ -279,7 +294,8 @@ class BatchChangeService(
|
||||
|
||||
def getExistingRecordSets(
|
||||
changes: ValidatedBatch[ChangeForValidation],
|
||||
zoneMap: ExistingZones): IO[ExistingRecordSets] = {
|
||||
zoneMap: ExistingZones
|
||||
): IO[ExistingRecordSets] = {
|
||||
val uniqueGets = changes.getValid.map { change =>
|
||||
change.inputChange.typ match {
|
||||
case PTR => s"${change.recordName}.${change.zone.name}"
|
||||
@ -295,7 +311,8 @@ class BatchChangeService(
|
||||
|
||||
def doTtlMapping(
|
||||
changes: ValidatedBatch[ChangeForValidation],
|
||||
existingRecordSets: ExistingRecordSets): ValidatedBatch[ChangeForValidation] =
|
||||
existingRecordSets: ExistingRecordSets
|
||||
): ValidatedBatch[ChangeForValidation] =
|
||||
changes.mapValid {
|
||||
case add: AddChangeForValidation =>
|
||||
existingRecordSets
|
||||
@ -326,7 +343,8 @@ class BatchChangeService(
|
||||
|
||||
def zoneDiscovery(
|
||||
changes: ValidatedBatch[ChangeInput],
|
||||
zoneMap: ExistingZones): ValidatedBatch[ChangeForValidation] =
|
||||
zoneMap: ExistingZones
|
||||
): ValidatedBatch[ChangeForValidation] =
|
||||
changes.mapValid { change =>
|
||||
change.typ match {
|
||||
case A | AAAA | CNAME | MX | TXT => forwardZoneDiscovery(change, zoneMap)
|
||||
@ -340,7 +358,8 @@ class BatchChangeService(
|
||||
|
||||
def forwardZoneDiscovery(
|
||||
change: ChangeInput,
|
||||
zoneMap: ExistingZones): SingleValidation[ChangeForValidation] = {
|
||||
zoneMap: ExistingZones
|
||||
): SingleValidation[ChangeForValidation] = {
|
||||
|
||||
// getAllPossibleZones is ordered most to least specific, so 1st match is right
|
||||
val zone = getAllPossibleZones(change.inputName).map(zoneMap.getByName).collectFirst {
|
||||
@ -358,7 +377,8 @@ class BatchChangeService(
|
||||
|
||||
def ptrIpv4ZoneDiscovery(
|
||||
change: ChangeInput,
|
||||
zoneMap: ExistingZones): SingleValidation[ChangeForValidation] = {
|
||||
zoneMap: ExistingZones
|
||||
): SingleValidation[ChangeForValidation] = {
|
||||
val recordName = change.inputName.split('.').takeRight(1).mkString
|
||||
val validZones = zoneMap.getipv4PTRMatches(change.inputName)
|
||||
|
||||
@ -374,7 +394,8 @@ class BatchChangeService(
|
||||
|
||||
def ptrIpv6ZoneDiscovery(
|
||||
change: ChangeInput,
|
||||
zoneMap: ExistingZones): SingleValidation[ChangeForValidation] = {
|
||||
zoneMap: ExistingZones
|
||||
): SingleValidation[ChangeForValidation] = {
|
||||
val zones = zoneMap.getipv6PTRMatches(change.inputName)
|
||||
|
||||
if (zones.isEmpty)
|
||||
@ -403,7 +424,8 @@ class BatchChangeService(
|
||||
batchChangeInput: BatchChangeInput,
|
||||
transformed: ValidatedBatch[ChangeForValidation],
|
||||
auth: AuthPrincipal,
|
||||
allowManualReview: Boolean): Either[BatchChangeErrorResponse, BatchChange] = {
|
||||
allowManualReview: Boolean
|
||||
): Either[BatchChangeErrorResponse, BatchChange] = {
|
||||
|
||||
// Respond with a fatal error that kicks the change out to the user
|
||||
def errorResponse =
|
||||
@ -480,7 +502,8 @@ class BatchChangeService(
|
||||
def rebuildBatchChangeForUpdate(
|
||||
existingBatchChange: BatchChange,
|
||||
transformed: ValidatedBatch[ChangeForValidation],
|
||||
reviewInfo: BatchChangeReviewInfo): BatchChange = {
|
||||
reviewInfo: BatchChangeReviewInfo
|
||||
): BatchChange = {
|
||||
val changes = transformed.zip(existingBatchChange.changes).map {
|
||||
case (validated, existing) =>
|
||||
validated match {
|
||||
@ -508,7 +531,8 @@ class BatchChangeService(
|
||||
batchChange: BatchChange,
|
||||
existingZones: ExistingZones,
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
ownerGroupId: Option[String]): BatchResult[BatchChange] = batchChange.approvalStatus match {
|
||||
ownerGroupId: Option[String]
|
||||
): BatchResult[BatchChange] = batchChange.approvalStatus match {
|
||||
case AutoApproved =>
|
||||
// send on to the converter, it will be saved there
|
||||
batchChangeConverter
|
||||
@ -527,12 +551,14 @@ class BatchChangeService(
|
||||
// this should not be called with a rejected change (or if manual review is off)!
|
||||
logger.error(
|
||||
s"convertOrSave called with a rejected batch change; " +
|
||||
s"batchChangeId=${batchChange.id}; manualReviewEnabled=$manualReviewEnabled")
|
||||
s"batchChangeId=${batchChange.id}; manualReviewEnabled=$manualReviewEnabled"
|
||||
)
|
||||
UnknownConversionError("Cannot convert a rejected batch change").toLeftBatchResult
|
||||
}
|
||||
|
||||
def buildResponseForApprover(
|
||||
batchChange: BatchChange): Either[BatchChangeErrorResponse, BatchChange] =
|
||||
batchChange: BatchChange
|
||||
): Either[BatchChangeErrorResponse, BatchChange] =
|
||||
batchChange.approvalStatus match {
|
||||
case ManuallyApproved => batchChange.asRight
|
||||
case _ => BatchChangeFailedApproval(batchChange).asLeft
|
||||
@ -540,7 +566,8 @@ class BatchChangeService(
|
||||
|
||||
def addOwnerGroupNamesToSummaries(
|
||||
summaries: List[BatchChangeSummary],
|
||||
groups: Set[Group]): List[BatchChangeSummary] =
|
||||
groups: Set[Group]
|
||||
): List[BatchChangeSummary] =
|
||||
summaries.map { summary =>
|
||||
val groupName =
|
||||
summary.ownerGroupId.flatMap(groupId => groups.find(_.id == groupId).map(_.name))
|
||||
@ -549,7 +576,8 @@ class BatchChangeService(
|
||||
|
||||
def addReviewerUserNamesToSummaries(
|
||||
summaries: List[BatchChangeSummary],
|
||||
reviewers: ListUsersResults): List[BatchChangeSummary] =
|
||||
reviewers: ListUsersResults
|
||||
): List[BatchChangeSummary] =
|
||||
summaries.map { summary =>
|
||||
val userName =
|
||||
summary.reviewerId.flatMap(userId => reviewers.users.find(_.id == userId).map(_.userName))
|
||||
@ -561,8 +589,8 @@ class BatchChangeService(
|
||||
startFrom: Option[Int] = None,
|
||||
maxItems: Int = 100,
|
||||
ignoreAccess: Boolean = false,
|
||||
approvalStatus: Option[BatchChangeApprovalStatus] = None)
|
||||
: BatchResult[BatchChangeSummaryList] = {
|
||||
approvalStatus: Option[BatchChangeApprovalStatus] = None
|
||||
): BatchResult[BatchChangeSummaryList] = {
|
||||
val userId = if (ignoreAccess && auth.isSystemAdmin) None else Some(auth.userId)
|
||||
for {
|
||||
listResults <- batchChangeRepo
|
||||
@ -572,23 +600,27 @@ class BatchChangeService(
|
||||
rsOwnerGroups <- groupRepository.getGroups(rsOwnerGroupIds).toBatchResult
|
||||
summariesWithGroupNames = addOwnerGroupNamesToSummaries(
|
||||
listResults.batchChanges,
|
||||
rsOwnerGroups)
|
||||
rsOwnerGroups
|
||||
)
|
||||
reviewerIds = listResults.batchChanges.flatMap(_.reviewerId).toSet
|
||||
reviewerUserNames <- userRepository.getUsers(reviewerIds, None, Some(maxItems)).toBatchResult
|
||||
summariesWithReviewerUserNames = addReviewerUserNamesToSummaries(
|
||||
summariesWithGroupNames,
|
||||
reviewerUserNames)
|
||||
reviewerUserNames
|
||||
)
|
||||
listWithGroupNames = listResults.copy(
|
||||
batchChanges = summariesWithReviewerUserNames,
|
||||
ignoreAccess = ignoreAccess,
|
||||
approvalStatus = approvalStatus)
|
||||
approvalStatus = approvalStatus
|
||||
)
|
||||
} yield listWithGroupNames
|
||||
}
|
||||
|
||||
def rejectBatchChange(
|
||||
batchChange: BatchChange,
|
||||
reviewComment: Option[String],
|
||||
reviewerId: String): BatchResult[BatchChange] = {
|
||||
reviewerId: String
|
||||
): BatchResult[BatchChange] = {
|
||||
val rejectedSingleChanges = batchChange.changes.map(_.reject)
|
||||
|
||||
// Update rejection attributes and single changes for batch change
|
||||
@ -618,7 +650,8 @@ class BatchChangeService(
|
||||
|
||||
def getBypassTestCheckForReject(
|
||||
rejecterAuth: AuthPrincipal,
|
||||
batchChange: BatchChange): BatchResult[Boolean] =
|
||||
batchChange: BatchChange
|
||||
): BatchResult[Boolean] =
|
||||
if (!rejecterAuth.isTestUser) {
|
||||
// if the rejecting user isnt a test user, we dont need to get the batch creator's info, can just pass along
|
||||
// true to bypass the test check
|
||||
|
@ -26,7 +26,8 @@ trait BatchChangeServiceAlgebra {
|
||||
def applyBatchChange(
|
||||
batchChangeInput: BatchChangeInput,
|
||||
auth: AuthPrincipal,
|
||||
allowManualReview: Boolean): BatchResult[BatchChange]
|
||||
allowManualReview: Boolean
|
||||
): BatchResult[BatchChange]
|
||||
|
||||
def getBatchChange(id: String, auth: AuthPrincipal): BatchResult[BatchChangeInfo]
|
||||
|
||||
@ -35,17 +36,20 @@ trait BatchChangeServiceAlgebra {
|
||||
startFrom: Option[Int],
|
||||
maxItems: Int,
|
||||
ignoreAccess: Boolean,
|
||||
approvalStatus: Option[BatchChangeApprovalStatus]): BatchResult[BatchChangeSummaryList]
|
||||
approvalStatus: Option[BatchChangeApprovalStatus]
|
||||
): BatchResult[BatchChangeSummaryList]
|
||||
|
||||
def rejectBatchChange(
|
||||
batchChangeId: String,
|
||||
authPrincipal: AuthPrincipal,
|
||||
rejectBatchChangeInput: RejectBatchChangeInput): BatchResult[BatchChange]
|
||||
rejectBatchChangeInput: RejectBatchChangeInput
|
||||
): BatchResult[BatchChange]
|
||||
|
||||
def approveBatchChange(
|
||||
batchChangeId: String,
|
||||
authPrincipal: AuthPrincipal,
|
||||
approveBatchChangeInput: ApproveBatchChangeInput): BatchResult[BatchChange]
|
||||
approveBatchChangeInput: ApproveBatchChangeInput
|
||||
): BatchResult[BatchChange]
|
||||
|
||||
def cancelBatchChange(id: String, auth: AuthPrincipal): BatchResult[BatchChange]
|
||||
}
|
||||
|
@ -35,42 +35,49 @@ trait BatchChangeValidationsAlgebra {
|
||||
def validateBatchChangeInput(
|
||||
input: BatchChangeInput,
|
||||
existingGroup: Option[Group],
|
||||
authPrincipal: AuthPrincipal): BatchResult[Unit]
|
||||
authPrincipal: AuthPrincipal
|
||||
): BatchResult[Unit]
|
||||
|
||||
def validateInputChanges(
|
||||
input: List[ChangeInput],
|
||||
isApproved: Boolean): ValidatedBatch[ChangeInput]
|
||||
isApproved: Boolean
|
||||
): ValidatedBatch[ChangeInput]
|
||||
|
||||
def validateChangesWithContext(
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
auth: AuthPrincipal,
|
||||
isApproved: Boolean,
|
||||
batchOwnerGroupId: Option[String]): ValidatedBatch[ChangeForValidation]
|
||||
batchOwnerGroupId: Option[String]
|
||||
): ValidatedBatch[ChangeForValidation]
|
||||
|
||||
def canGetBatchChange(
|
||||
batchChange: BatchChange,
|
||||
auth: AuthPrincipal): Either[BatchChangeErrorResponse, Unit]
|
||||
auth: AuthPrincipal
|
||||
): Either[BatchChangeErrorResponse, Unit]
|
||||
|
||||
def validateBatchChangeRejection(
|
||||
batchChange: BatchChange,
|
||||
authPrincipal: AuthPrincipal,
|
||||
bypassTestValidation: Boolean): Either[BatchChangeErrorResponse, Unit]
|
||||
bypassTestValidation: Boolean
|
||||
): Either[BatchChangeErrorResponse, Unit]
|
||||
|
||||
def validateBatchChangeApproval(
|
||||
batchChange: BatchChange,
|
||||
authPrincipal: AuthPrincipal,
|
||||
isTestChange: Boolean): Either[BatchChangeErrorResponse, Unit]
|
||||
isTestChange: Boolean
|
||||
): Either[BatchChangeErrorResponse, Unit]
|
||||
|
||||
def validateBatchChangeCancellation(
|
||||
batchChange: BatchChange,
|
||||
authPrincipal: AuthPrincipal): Either[BatchChangeErrorResponse, Unit]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Either[BatchChangeErrorResponse, Unit]
|
||||
}
|
||||
|
||||
class BatchChangeValidations(
|
||||
changeLimit: Int,
|
||||
accessValidation: AccessValidationsAlgebra,
|
||||
scheduledChangesEnabled: Boolean = false)
|
||||
extends BatchChangeValidationsAlgebra {
|
||||
scheduledChangesEnabled: Boolean = false
|
||||
) extends BatchChangeValidationsAlgebra {
|
||||
|
||||
import RecordType._
|
||||
import accessValidation._
|
||||
@ -78,11 +85,13 @@ class BatchChangeValidations(
|
||||
def validateBatchChangeInput(
|
||||
input: BatchChangeInput,
|
||||
existingGroup: Option[Group],
|
||||
authPrincipal: AuthPrincipal): BatchResult[Unit] = {
|
||||
authPrincipal: AuthPrincipal
|
||||
): BatchResult[Unit] = {
|
||||
val validations = validateBatchChangeInputSize(input) |+| validateOwnerGroupId(
|
||||
input.ownerGroupId,
|
||||
existingGroup,
|
||||
authPrincipal)
|
||||
authPrincipal
|
||||
)
|
||||
|
||||
for {
|
||||
_ <- validations
|
||||
@ -105,7 +114,8 @@ class BatchChangeValidations(
|
||||
def validateOwnerGroupId(
|
||||
ownerGroupId: Option[String],
|
||||
existingGroup: Option[Group],
|
||||
authPrincipal: AuthPrincipal): SingleValidation[Unit] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): SingleValidation[Unit] =
|
||||
(ownerGroupId, existingGroup) match {
|
||||
case (None, _) => ().validNel
|
||||
case (Some(groupId), None) => GroupDoesNotExist(groupId).invalidNel
|
||||
@ -117,26 +127,33 @@ class BatchChangeValidations(
|
||||
def validateBatchChangeRejection(
|
||||
batchChange: BatchChange,
|
||||
authPrincipal: AuthPrincipal,
|
||||
bypassTestValidation: Boolean): Either[BatchChangeErrorResponse, Unit] =
|
||||
bypassTestValidation: Boolean
|
||||
): Either[BatchChangeErrorResponse, Unit] =
|
||||
validateAuthorizedReviewer(authPrincipal, batchChange, bypassTestValidation) |+| validateBatchChangePendingReview(
|
||||
batchChange)
|
||||
batchChange
|
||||
)
|
||||
|
||||
def validateBatchChangeApproval(
|
||||
batchChange: BatchChange,
|
||||
authPrincipal: AuthPrincipal,
|
||||
isTestChange: Boolean): Either[BatchChangeErrorResponse, Unit] =
|
||||
isTestChange: Boolean
|
||||
): Either[BatchChangeErrorResponse, Unit] =
|
||||
validateAuthorizedReviewer(authPrincipal, batchChange, isTestChange) |+| validateBatchChangePendingReview(
|
||||
batchChange) |+| validateScheduledApproval(batchChange)
|
||||
batchChange
|
||||
) |+| validateScheduledApproval(batchChange)
|
||||
|
||||
def validateBatchChangeCancellation(
|
||||
batchChange: BatchChange,
|
||||
authPrincipal: AuthPrincipal): Either[BatchChangeErrorResponse, Unit] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Either[BatchChangeErrorResponse, Unit] =
|
||||
validateBatchChangePendingReview(batchChange) |+| validateCreatorCancellation(
|
||||
batchChange,
|
||||
authPrincipal)
|
||||
authPrincipal
|
||||
)
|
||||
|
||||
def validateBatchChangePendingReview(
|
||||
batchChange: BatchChange): Either[BatchChangeErrorResponse, Unit] =
|
||||
batchChange: BatchChange
|
||||
): Either[BatchChangeErrorResponse, Unit] =
|
||||
batchChange.approvalStatus match {
|
||||
case BatchChangeApprovalStatus.PendingReview => ().asRight
|
||||
case _ => BatchChangeNotPendingReview(batchChange.id).asLeft
|
||||
@ -145,7 +162,8 @@ class BatchChangeValidations(
|
||||
def validateAuthorizedReviewer(
|
||||
auth: AuthPrincipal,
|
||||
batchChange: BatchChange,
|
||||
bypassTestValidation: Boolean): Either[BatchChangeErrorResponse, Unit] =
|
||||
bypassTestValidation: Boolean
|
||||
): Either[BatchChangeErrorResponse, Unit] =
|
||||
if (auth.isSystemAdmin && (bypassTestValidation || !auth.isTestUser)) {
|
||||
// bypassTestValidation = true for a test change
|
||||
().asRight
|
||||
@ -161,7 +179,8 @@ class BatchChangeValidations(
|
||||
|
||||
def validateCreatorCancellation(
|
||||
batchChange: BatchChange,
|
||||
auth: AuthPrincipal): Either[BatchChangeErrorResponse, Unit] =
|
||||
auth: AuthPrincipal
|
||||
): Either[BatchChangeErrorResponse, Unit] =
|
||||
if (batchChange.userId == auth.userId) {
|
||||
().asRight
|
||||
} else {
|
||||
@ -171,7 +190,8 @@ class BatchChangeValidations(
|
||||
|
||||
def validateInputChanges(
|
||||
input: List[ChangeInput],
|
||||
isApproved: Boolean): ValidatedBatch[ChangeInput] =
|
||||
isApproved: Boolean
|
||||
): ValidatedBatch[ChangeInput] =
|
||||
input.map {
|
||||
case a: AddChangeInput => validateAddChangeInput(a, isApproved).map(_ => a)
|
||||
case d: DeleteRRSetChangeInput => validateDeleteRRSetChangeInput(d, isApproved).map(_ => d)
|
||||
@ -179,7 +199,8 @@ class BatchChangeValidations(
|
||||
|
||||
def validateAddChangeInput(
|
||||
addChangeInput: AddChangeInput,
|
||||
isApproved: Boolean): SingleValidation[Unit] = {
|
||||
isApproved: Boolean
|
||||
): SingleValidation[Unit] = {
|
||||
val validTTL = addChangeInput.ttl.map(validateTTL(_).asUnit).getOrElse(().valid)
|
||||
val validRecord = validateRecordData(addChangeInput.record)
|
||||
val validInput = validateInputName(addChangeInput, isApproved)
|
||||
@ -189,7 +210,8 @@ class BatchChangeValidations(
|
||||
|
||||
def validateDeleteRRSetChangeInput(
|
||||
deleteRRSetChangeInput: DeleteRRSetChangeInput,
|
||||
isApproved: Boolean): SingleValidation[Unit] = {
|
||||
isApproved: Boolean
|
||||
): SingleValidation[Unit] = {
|
||||
val validRecord = deleteRRSetChangeInput.record match {
|
||||
case Some(recordData) => validateRecordData(recordData)
|
||||
case None => ().validNel
|
||||
@ -244,7 +266,8 @@ class BatchChangeValidations(
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
auth: AuthPrincipal,
|
||||
isApproved: Boolean,
|
||||
batchOwnerGroupId: Option[String]): ValidatedBatch[ChangeForValidation] =
|
||||
batchOwnerGroupId: Option[String]
|
||||
): ValidatedBatch[ChangeForValidation] =
|
||||
// Updates are a combination of an add and delete for a record with the same name and type in a zone.
|
||||
groupedChanges.changes.mapValid {
|
||||
case add: AddChangeForValidation
|
||||
@ -274,13 +297,15 @@ class BatchChangeValidations(
|
||||
|
||||
def ensureRecordExists(
|
||||
change: ChangeForValidation,
|
||||
groupedChanges: ChangeForValidationMap): SingleValidation[Unit] =
|
||||
groupedChanges: ChangeForValidationMap
|
||||
): SingleValidation[Unit] =
|
||||
change match {
|
||||
// For DeleteRecord inputs, need to verify that the record data actually exists
|
||||
case DeleteRRSetChangeForValidation(
|
||||
_,
|
||||
_,
|
||||
DeleteRRSetChangeInput(inputName, _, Some(recordData)))
|
||||
DeleteRRSetChangeInput(inputName, _, Some(recordData))
|
||||
)
|
||||
if !groupedChanges
|
||||
.getExistingRecordSet(change.recordKey)
|
||||
.exists(_.records.contains(recordData)) =>
|
||||
@ -293,7 +318,8 @@ class BatchChangeValidations(
|
||||
change: ChangeForValidation,
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
auth: AuthPrincipal,
|
||||
isApproved: Boolean): SingleValidation[ChangeForValidation] = {
|
||||
isApproved: Boolean
|
||||
): SingleValidation[ChangeForValidation] = {
|
||||
|
||||
val validations =
|
||||
groupedChanges.getExistingRecordSet(change.recordKey) match {
|
||||
@ -311,7 +337,8 @@ class BatchChangeValidations(
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
auth: AuthPrincipal,
|
||||
isApproved: Boolean,
|
||||
batchOwnerGroupId: Option[String]): SingleValidation[ChangeForValidation] = {
|
||||
batchOwnerGroupId: Option[String]
|
||||
): SingleValidation[ChangeForValidation] = {
|
||||
// Updates require checking against other batch changes since multiple adds
|
||||
// could potentially be grouped with a single delete
|
||||
val typedValidations = change.inputChange.typ match {
|
||||
@ -327,7 +354,8 @@ class BatchChangeValidations(
|
||||
change,
|
||||
groupedChanges.existingRecordSets
|
||||
.get(change.zone.id, change.recordName, change.inputChange.typ),
|
||||
batchOwnerGroupId) |+|
|
||||
batchOwnerGroupId
|
||||
) |+|
|
||||
zoneDoesNotRequireManualReview(change, isApproved)
|
||||
case None =>
|
||||
RecordDoesNotExist(change.inputChange.inputName).invalidNel
|
||||
@ -343,7 +371,8 @@ class BatchChangeValidations(
|
||||
change: ChangeForValidation,
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
auth: AuthPrincipal,
|
||||
isApproved: Boolean): SingleValidation[ChangeForValidation] = {
|
||||
isApproved: Boolean
|
||||
): SingleValidation[ChangeForValidation] = {
|
||||
val validations =
|
||||
groupedChanges.getExistingRecordSet(change.recordKey) match {
|
||||
case Some(rs) =>
|
||||
@ -363,7 +392,8 @@ class BatchChangeValidations(
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
auth: AuthPrincipal,
|
||||
isApproved: Boolean,
|
||||
ownerGroupId: Option[String]): SingleValidation[ChangeForValidation] = {
|
||||
ownerGroupId: Option[String]
|
||||
): SingleValidation[ChangeForValidation] = {
|
||||
val typedValidations = change.inputChange.typ match {
|
||||
case A | AAAA | MX =>
|
||||
newRecordSetIsNotDotted(change)
|
||||
@ -385,7 +415,8 @@ class BatchChangeValidations(
|
||||
change.recordName,
|
||||
change.inputChange.inputName,
|
||||
change.inputChange.typ,
|
||||
groupedChanges) |+|
|
||||
groupedChanges
|
||||
) |+|
|
||||
ownerGroupProvidedIfNeeded(change, None, ownerGroupId) |+|
|
||||
zoneDoesNotRequireManualReview(change, isApproved)
|
||||
|
||||
@ -394,7 +425,8 @@ class BatchChangeValidations(
|
||||
|
||||
def cnameHasUniqueNameInBatch(
|
||||
cnameChange: AddChangeForValidation,
|
||||
groupedChanges: ChangeForValidationMap): SingleValidation[Unit] = {
|
||||
groupedChanges: ChangeForValidationMap
|
||||
): SingleValidation[Unit] = {
|
||||
|
||||
val duplicateNameChangeInBatch = RecordType.values.toList.exists { recordType =>
|
||||
val recordKey = RecordKey(cnameChange.zone.id, cnameChange.recordName, recordType)
|
||||
@ -410,7 +442,8 @@ class BatchChangeValidations(
|
||||
|
||||
def recordIsUniqueInBatch(
|
||||
change: AddChangeForValidation,
|
||||
groupedChanges: ChangeForValidationMap): SingleValidation[Unit] = {
|
||||
groupedChanges: ChangeForValidationMap
|
||||
): SingleValidation[Unit] = {
|
||||
// Ignore true duplicates, but identify multi-records
|
||||
val proposedAdds = groupedChanges.getProposedAdds(change.recordKey)
|
||||
|
||||
@ -424,7 +457,8 @@ class BatchChangeValidations(
|
||||
recordName: String,
|
||||
inputName: String,
|
||||
typ: RecordType,
|
||||
groupedChanges: ChangeForValidationMap): SingleValidation[Unit] =
|
||||
groupedChanges: ChangeForValidationMap
|
||||
): SingleValidation[Unit] =
|
||||
groupedChanges.getExistingRecordSet(RecordKey(zoneId, recordName, typ)) match {
|
||||
case Some(_) => RecordAlreadyExists(inputName).invalidNel
|
||||
case None => ().validNel
|
||||
@ -432,7 +466,8 @@ class BatchChangeValidations(
|
||||
|
||||
def noIncompatibleRecordExists(
|
||||
change: AddChangeForValidation,
|
||||
groupedChanges: ChangeForValidationMap): SingleValidation[Unit] = {
|
||||
groupedChanges: ChangeForValidationMap
|
||||
): SingleValidation[Unit] = {
|
||||
// find conflicting types in existing records
|
||||
val conflictingExistingTypes = change.inputChange.typ match {
|
||||
case CNAME =>
|
||||
@ -462,13 +497,15 @@ class BatchChangeValidations(
|
||||
|
||||
def userCanAddRecordSet(
|
||||
input: AddChangeForValidation,
|
||||
authPrincipal: AuthPrincipal): SingleValidation[Unit] = {
|
||||
authPrincipal: AuthPrincipal
|
||||
): SingleValidation[Unit] = {
|
||||
val result = canAddRecordSet(
|
||||
authPrincipal,
|
||||
input.recordName,
|
||||
input.inputChange.typ,
|
||||
input.zone,
|
||||
List(input.inputChange.record))
|
||||
List(input.inputChange.record)
|
||||
)
|
||||
result
|
||||
.leftMap(
|
||||
_ =>
|
||||
@ -476,7 +513,9 @@ class BatchChangeValidations(
|
||||
authPrincipal.signedInUser.userName,
|
||||
input.zone.adminGroupId,
|
||||
OwnerType.Zone,
|
||||
Some(input.zone.email)))
|
||||
Some(input.zone.email)
|
||||
)
|
||||
)
|
||||
.toValidatedNel
|
||||
}
|
||||
|
||||
@ -484,7 +523,8 @@ class BatchChangeValidations(
|
||||
input: ChangeForValidation,
|
||||
authPrincipal: AuthPrincipal,
|
||||
ownerGroupId: Option[String],
|
||||
addRecords: List[RecordData]): SingleValidation[Unit] = {
|
||||
addRecords: List[RecordData]
|
||||
): SingleValidation[Unit] = {
|
||||
val result =
|
||||
canUpdateRecordSet(
|
||||
authPrincipal,
|
||||
@ -495,17 +535,20 @@ class BatchChangeValidations(
|
||||
addRecords
|
||||
)
|
||||
result
|
||||
.leftMap(_ =>
|
||||
ownerGroupId match {
|
||||
case Some(id) if input.zone.shared =>
|
||||
UserIsNotAuthorizedError(authPrincipal.signedInUser.userName, id, OwnerType.Record)
|
||||
case _ =>
|
||||
UserIsNotAuthorizedError(
|
||||
authPrincipal.signedInUser.userName,
|
||||
input.zone.adminGroupId,
|
||||
OwnerType.Zone,
|
||||
Some(input.zone.email))
|
||||
})
|
||||
.leftMap(
|
||||
_ =>
|
||||
ownerGroupId match {
|
||||
case Some(id) if input.zone.shared =>
|
||||
UserIsNotAuthorizedError(authPrincipal.signedInUser.userName, id, OwnerType.Record)
|
||||
case _ =>
|
||||
UserIsNotAuthorizedError(
|
||||
authPrincipal.signedInUser.userName,
|
||||
input.zone.adminGroupId,
|
||||
OwnerType.Zone,
|
||||
Some(input.zone.email)
|
||||
)
|
||||
}
|
||||
)
|
||||
.toValidatedNel
|
||||
}
|
||||
|
||||
@ -513,7 +556,8 @@ class BatchChangeValidations(
|
||||
input: ChangeForValidation,
|
||||
authPrincipal: AuthPrincipal,
|
||||
ownerGroupId: Option[String],
|
||||
existingRecords: List[RecordData]): SingleValidation[Unit] = {
|
||||
existingRecords: List[RecordData]
|
||||
): SingleValidation[Unit] = {
|
||||
val result =
|
||||
canDeleteRecordSet(
|
||||
authPrincipal,
|
||||
@ -524,23 +568,27 @@ class BatchChangeValidations(
|
||||
existingRecords
|
||||
)
|
||||
result
|
||||
.leftMap(_ =>
|
||||
ownerGroupId match {
|
||||
case Some(id) if input.zone.shared =>
|
||||
UserIsNotAuthorizedError(authPrincipal.signedInUser.userName, id, OwnerType.Record)
|
||||
case _ =>
|
||||
UserIsNotAuthorizedError(
|
||||
authPrincipal.signedInUser.userName,
|
||||
input.zone.adminGroupId,
|
||||
OwnerType.Zone,
|
||||
Some(input.zone.email))
|
||||
})
|
||||
.leftMap(
|
||||
_ =>
|
||||
ownerGroupId match {
|
||||
case Some(id) if input.zone.shared =>
|
||||
UserIsNotAuthorizedError(authPrincipal.signedInUser.userName, id, OwnerType.Record)
|
||||
case _ =>
|
||||
UserIsNotAuthorizedError(
|
||||
authPrincipal.signedInUser.userName,
|
||||
input.zone.adminGroupId,
|
||||
OwnerType.Zone,
|
||||
Some(input.zone.email)
|
||||
)
|
||||
}
|
||||
)
|
||||
.toValidatedNel
|
||||
}
|
||||
|
||||
def canGetBatchChange(
|
||||
batchChange: BatchChange,
|
||||
auth: AuthPrincipal): Either[BatchChangeErrorResponse, Unit] =
|
||||
auth: AuthPrincipal
|
||||
): Either[BatchChangeErrorResponse, Unit] =
|
||||
if (auth.isSystemAdmin || auth.userId == batchChange.userId) {
|
||||
().asRight
|
||||
} else {
|
||||
@ -554,7 +602,8 @@ class BatchChangeValidations(
|
||||
case _ =>
|
||||
ZoneRecordValidations.isNotHighValueFqdn(
|
||||
VinylDNSConfig.highValueRegexList,
|
||||
change.inputName)
|
||||
change.inputName
|
||||
)
|
||||
}
|
||||
|
||||
def doesNotRequireManualReview(change: ChangeInput, isApproved: Boolean): SingleValidation[Unit] =
|
||||
@ -566,18 +615,21 @@ class BatchChangeValidations(
|
||||
case RecordType.PTR =>
|
||||
ZoneRecordValidations.ipDoesNotRequireManualReview(
|
||||
VinylDNSConfig.ipListRequiringManualReview,
|
||||
change.inputName)
|
||||
change.inputName
|
||||
)
|
||||
case _ =>
|
||||
ZoneRecordValidations.domainDoesNotRequireManualReview(
|
||||
VinylDNSConfig.domainListRequiringManualReview,
|
||||
change.inputName)
|
||||
change.inputName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def ownerGroupProvidedIfNeeded(
|
||||
change: AddChangeForValidation,
|
||||
existingRecord: Option[RecordSet],
|
||||
ownerGroupId: Option[String]): SingleValidation[Unit] =
|
||||
ownerGroupId: Option[String]
|
||||
): SingleValidation[Unit] =
|
||||
if (!change.zone.shared || ownerGroupId.isDefined) {
|
||||
().validNel
|
||||
} else {
|
||||
@ -591,7 +643,8 @@ class BatchChangeValidations(
|
||||
|
||||
def validateScheduledChange(
|
||||
input: BatchChangeInput,
|
||||
scheduledChangesEnabled: Boolean): Either[BatchChangeErrorResponse, Unit] =
|
||||
scheduledChangesEnabled: Boolean
|
||||
): Either[BatchChangeErrorResponse, Unit] =
|
||||
(scheduledChangesEnabled, input.scheduledTime) match {
|
||||
case (_, None) => Right(())
|
||||
case (true, Some(scheduledTime)) if scheduledTime.isAfterNow => Right(())
|
||||
@ -601,7 +654,8 @@ class BatchChangeValidations(
|
||||
|
||||
def zoneDoesNotRequireManualReview(
|
||||
change: ChangeForValidation,
|
||||
isApproved: Boolean): SingleValidation[Unit] =
|
||||
isApproved: Boolean
|
||||
): SingleValidation[Unit] =
|
||||
if (isApproved) {
|
||||
().validNel
|
||||
} else {
|
||||
|
@ -94,8 +94,8 @@ object BatchTransformations {
|
||||
zone: Zone,
|
||||
recordName: String,
|
||||
inputChange: AddChangeInput,
|
||||
existingRecordTtl: Option[Long] = None)
|
||||
extends ChangeForValidation {
|
||||
existingRecordTtl: Option[Long] = None
|
||||
) extends ChangeForValidation {
|
||||
def asStoredChange(changeId: Option[String] = None): SingleChange = {
|
||||
|
||||
val ttl = inputChange.ttl.orElse(existingRecordTtl).getOrElse(VinylDNSConfig.defaultTtl)
|
||||
@ -123,8 +123,8 @@ object BatchTransformations {
|
||||
final case class DeleteRRSetChangeForValidation(
|
||||
zone: Zone,
|
||||
recordName: String,
|
||||
inputChange: DeleteRRSetChangeInput)
|
||||
extends ChangeForValidation {
|
||||
inputChange: DeleteRRSetChangeInput
|
||||
) extends ChangeForValidation {
|
||||
def asStoredChange(changeId: Option[String] = None): SingleChange =
|
||||
SingleDeleteRRSetChange(
|
||||
Some(zone.id),
|
||||
@ -146,11 +146,13 @@ object BatchTransformations {
|
||||
|
||||
final case class BatchConversionOutput(
|
||||
batchChange: BatchChange,
|
||||
recordSetChanges: List[RecordSetChange])
|
||||
recordSetChanges: List[RecordSetChange]
|
||||
)
|
||||
|
||||
final case class ChangeForValidationMap(
|
||||
changes: ValidatedBatch[ChangeForValidation],
|
||||
existingRecordSets: ExistingRecordSets) {
|
||||
existingRecordSets: ExistingRecordSets
|
||||
) {
|
||||
import BatchChangeInterfaces._
|
||||
|
||||
val innerMap: Map[RecordKey, ValidationChanges] = {
|
||||
@ -182,7 +184,8 @@ object BatchTransformations {
|
||||
object ValidationChanges {
|
||||
def apply(
|
||||
changes: List[ChangeForValidation],
|
||||
existingRecordSet: Option[RecordSet]): ValidationChanges = {
|
||||
existingRecordSet: Option[RecordSet]
|
||||
): ValidationChanges = {
|
||||
// Collect add DNS entries
|
||||
val addChangeRecordDataSet = changes.collect {
|
||||
case add: AddChangeForValidation => add.inputChange.record
|
||||
@ -197,7 +200,8 @@ object BatchTransformations {
|
||||
case DeleteRRSetChangeForValidation(
|
||||
_,
|
||||
_,
|
||||
DeleteRRSetChangeInput(_, _, Some(recordData))) =>
|
||||
DeleteRRSetChangeInput(_, _, Some(recordData))
|
||||
) =>
|
||||
Set(recordData)
|
||||
case _: DeleteRRSetChangeForValidation => existingRecords
|
||||
}
|
||||
@ -228,7 +232,8 @@ object BatchTransformations {
|
||||
final case class ValidationChanges(
|
||||
proposedAdds: Set[RecordData],
|
||||
proposedRecordData: Set[RecordData],
|
||||
logicalChangeType: LogicalChangeType)
|
||||
logicalChangeType: LogicalChangeType
|
||||
)
|
||||
|
||||
final case class BatchValidationFlowOutput(
|
||||
validatedChanges: ValidatedBatch[ChangeForValidation],
|
||||
|
@ -107,7 +107,8 @@ class DnsConnection(val resolver: DNS.SimpleResolver) extends DnsConversions {
|
||||
private[dns] def toQuery(
|
||||
name: String,
|
||||
zoneName: String,
|
||||
typ: RecordType): Either[Throwable, DnsQuery] = {
|
||||
typ: RecordType
|
||||
): Either[Throwable, DnsQuery] = {
|
||||
val dnsName = recordDnsName(name, zoneName)
|
||||
logger.info(s"Querying for dns dnsRecordName='${dnsName.toString}'; recordType='$typ'")
|
||||
val lookup = new DNS.Lookup(dnsName, toDnsRecordType(typ))
|
||||
@ -160,7 +161,8 @@ class DnsConnection(val resolver: DNS.SimpleResolver) extends DnsConversions {
|
||||
} yield resp
|
||||
|
||||
logger.info(
|
||||
s"DnsConnection.send - Sending DNS Message ${obscuredDnsMessage(msg).toString}\n...received response $result")
|
||||
s"DnsConnection.send - Sending DNS Message ${obscuredDnsMessage(msg).toString}\n...received response $result"
|
||||
)
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -177,7 +177,8 @@ trait DnsConversions {
|
||||
def toFlattenedRecordSets(
|
||||
records: List[DNS.Record],
|
||||
zoneName: DNS.Name,
|
||||
zoneId: String = "unknown"): List[RecordSet] = {
|
||||
zoneId: String = "unknown"
|
||||
): List[RecordSet] = {
|
||||
|
||||
/* Combines record sets into a list of one or Nil in case there are no record sets in the list provided */
|
||||
def combineRecordSets(lst: List[RecordSet]): RecordSet =
|
||||
@ -194,7 +195,8 @@ trait DnsConversions {
|
||||
// Do a "relativize" using the zoneName, this removes the zone name from the record itself
|
||||
// For example "test-01.vinyldns." becomes "test-01"...this is necessary as we want to run comparisons upstream
|
||||
def fromDnsRecord[A <: DNS.Record](r: A, zoneName: DNS.Name, zoneId: String)(
|
||||
f: A => List[RecordData]): RecordSet =
|
||||
f: A => List[RecordData]
|
||||
): RecordSet =
|
||||
record.RecordSet(
|
||||
zoneId = zoneId,
|
||||
name = relativize(r.getName, zoneName),
|
||||
@ -240,7 +242,8 @@ trait DnsConversions {
|
||||
DnsSecAlgorithm(data.getAlgorithm),
|
||||
DigestType(data.getDigestID),
|
||||
ByteVector(data.getDigest)
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def fromMXRecord(r: DNS.MXRecord, zoneName: DNS.Name, zoneId: String): RecordSet =
|
||||
@ -268,7 +271,9 @@ trait DnsConversions {
|
||||
data.getRefresh,
|
||||
data.getRetry,
|
||||
data.getExpire,
|
||||
data.getMinimum))
|
||||
data.getMinimum
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def fromSPFRecord(r: DNS.SPFRecord, zoneName: DNS.Name, zoneId: String): RecordSet =
|
||||
@ -290,7 +295,9 @@ trait DnsConversions {
|
||||
data.getFlags,
|
||||
data.getService,
|
||||
data.getRegexp,
|
||||
data.getReplacement.toString))
|
||||
data.getReplacement.toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def fromSSHFPRecord(r: DNS.SSHFPRecord, zoneName: DNS.Name, zoneId: String): RecordSet =
|
||||
@ -326,7 +333,8 @@ trait DnsConversions {
|
||||
keyTag,
|
||||
algorithm.value,
|
||||
digestType.value,
|
||||
digest.toArray)
|
||||
digest.toArray
|
||||
)
|
||||
|
||||
case NSData(nsdname) =>
|
||||
new DNS.NSRecord(recordName, DNS.DClass.IN, ttl, DNS.Name.fromString(nsdname))
|
||||
@ -337,7 +345,8 @@ trait DnsConversions {
|
||||
DNS.DClass.IN,
|
||||
ttl,
|
||||
preference,
|
||||
DNS.Name.fromString(exchange))
|
||||
DNS.Name.fromString(exchange)
|
||||
)
|
||||
|
||||
case PTRData(ptrdname) =>
|
||||
new DNS.PTRRecord(recordName, DNS.DClass.IN, ttl, DNS.Name.fromString(ptrdname))
|
||||
@ -353,7 +362,8 @@ trait DnsConversions {
|
||||
refresh,
|
||||
retry,
|
||||
expire,
|
||||
minimum)
|
||||
minimum
|
||||
)
|
||||
|
||||
case SRVData(priority, weight, port, target) =>
|
||||
new DNS.SRVRecord(
|
||||
@ -363,7 +373,8 @@ trait DnsConversions {
|
||||
priority,
|
||||
weight,
|
||||
port,
|
||||
DNS.Name.fromString(target))
|
||||
DNS.Name.fromString(target)
|
||||
)
|
||||
|
||||
case NAPTRData(order, preference, flags, service, regexp, replacement) =>
|
||||
new DNS.NAPTRRecord(
|
||||
@ -375,7 +386,8 @@ trait DnsConversions {
|
||||
flags,
|
||||
service,
|
||||
regexp,
|
||||
DNS.Name.fromString(replacement))
|
||||
DNS.Name.fromString(replacement)
|
||||
)
|
||||
|
||||
case SSHFPData(algorithm, typ, fingerprint) =>
|
||||
new DNS.SSHFPRecord(recordName, DNS.DClass.IN, ttl, algorithm, typ, fingerprint.getBytes)
|
||||
@ -422,7 +434,8 @@ trait DnsConversions {
|
||||
def toUpdateRecordMessage(
|
||||
r: DNS.RRset,
|
||||
old: DNS.RRset,
|
||||
zoneName: String): Either[Throwable, DNS.Update] = {
|
||||
zoneName: String
|
||||
): Either[Throwable, DNS.Update] = {
|
||||
val update = new DNS.Update(zoneDnsName(zoneName))
|
||||
|
||||
if (!r.getName.equals(old.getName) || r.getTTL != old.getTTL) { // Name or TTL has changed
|
||||
|
@ -119,13 +119,15 @@ final case class ListMembersResponse(
|
||||
members: Seq[MemberInfo],
|
||||
startFrom: Option[String] = None,
|
||||
nextId: Option[String] = None,
|
||||
maxItems: Int)
|
||||
maxItems: Int
|
||||
)
|
||||
|
||||
final case class ListUsersResponse(
|
||||
members: Seq[UserInfo],
|
||||
startFrom: Option[String] = None,
|
||||
nextId: Option[String] = None,
|
||||
maxItems: Int)
|
||||
maxItems: Int
|
||||
)
|
||||
|
||||
final case class ListAdminsResponse(admins: Seq[UserInfo])
|
||||
|
||||
@ -133,7 +135,8 @@ final case class ListGroupChangesResponse(
|
||||
changes: Seq[GroupChangeInfo],
|
||||
startFrom: Option[String] = None,
|
||||
nextId: Option[String] = None,
|
||||
maxItems: Int)
|
||||
maxItems: Int
|
||||
)
|
||||
|
||||
final case class ListMyGroupsResponse(
|
||||
groups: Seq[GroupInfo],
|
||||
@ -141,7 +144,8 @@ final case class ListMyGroupsResponse(
|
||||
startFrom: Option[String] = None,
|
||||
nextId: Option[String] = None,
|
||||
maxItems: Int,
|
||||
ignoreAccess: Boolean)
|
||||
ignoreAccess: Boolean
|
||||
)
|
||||
|
||||
final case class GroupNotFoundError(msg: String) extends Throwable(msg)
|
||||
|
||||
|
@ -43,8 +43,8 @@ class MembershipService(
|
||||
membershipRepo: MembershipRepository,
|
||||
zoneRepo: ZoneRepository,
|
||||
groupChangeRepo: GroupChangeRepository,
|
||||
recordSetRepo: RecordSetRepository)
|
||||
extends MembershipServiceAlgebra {
|
||||
recordSetRepo: RecordSetRepository
|
||||
) extends MembershipServiceAlgebra {
|
||||
|
||||
import MembershipValidations._
|
||||
|
||||
@ -67,7 +67,8 @@ class MembershipService(
|
||||
description: Option[String],
|
||||
memberIds: Set[String],
|
||||
adminUserIds: Set[String],
|
||||
authPrincipal: AuthPrincipal): Result[Group] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[Group] =
|
||||
for {
|
||||
existingGroup <- getExistingGroup(groupId)
|
||||
newGroup = existingGroup.withUpdates(name, email, description, memberIds, adminUserIds)
|
||||
@ -112,17 +113,18 @@ class MembershipService(
|
||||
groupId: String,
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
authPrincipal: AuthPrincipal): Result[ListMembersResponse] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ListMembersResponse] =
|
||||
for {
|
||||
group <- getExistingGroup(groupId)
|
||||
_ <- canSeeGroup(groupId, authPrincipal).toResult
|
||||
result <- getUsers(group.memberIds, startFrom, Some(maxItems))
|
||||
} yield
|
||||
ListMembersResponse(
|
||||
result.users.map(MemberInfo(_, group)),
|
||||
startFrom,
|
||||
result.lastEvaluatedId,
|
||||
maxItems)
|
||||
} yield ListMembersResponse(
|
||||
result.users.map(MemberInfo(_, group)),
|
||||
startFrom,
|
||||
result.lastEvaluatedId,
|
||||
maxItems
|
||||
)
|
||||
|
||||
def listAdmins(groupId: String, authPrincipal: AuthPrincipal): Result[ListAdminsResponse] =
|
||||
for {
|
||||
@ -136,7 +138,8 @@ class MembershipService(
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
authPrincipal: AuthPrincipal,
|
||||
ignoreAccess: Boolean): Result[ListMyGroupsResponse] = {
|
||||
ignoreAccess: Boolean
|
||||
): Result[ListMyGroupsResponse] = {
|
||||
val groupsCall =
|
||||
if (authPrincipal.isSystemAdmin || ignoreAccess) {
|
||||
groupRepo.getAllGroups()
|
||||
@ -154,7 +157,8 @@ class MembershipService(
|
||||
groupNameFilter: Option[String],
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
ignoreAccess: Boolean): ListMyGroupsResponse = {
|
||||
ignoreAccess: Boolean
|
||||
): ListMyGroupsResponse = {
|
||||
val allMyGroups = allGroups
|
||||
.filter(_.status == GroupStatus.Active)
|
||||
.sortBy(_.id)
|
||||
@ -174,23 +178,25 @@ class MembershipService(
|
||||
groupId: String,
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
authPrincipal: AuthPrincipal): Result[ListGroupChangesResponse] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ListGroupChangesResponse] =
|
||||
for {
|
||||
_ <- canSeeGroup(groupId, authPrincipal).toResult
|
||||
result <- groupChangeRepo
|
||||
.getGroupChanges(groupId, startFrom, maxItems)
|
||||
.toResult[ListGroupChangesResults]
|
||||
} yield
|
||||
ListGroupChangesResponse(
|
||||
result.changes.map(GroupChangeInfo.apply),
|
||||
startFrom,
|
||||
result.lastEvaluatedTimeStamp,
|
||||
maxItems)
|
||||
} yield ListGroupChangesResponse(
|
||||
result.changes.map(GroupChangeInfo.apply),
|
||||
startFrom,
|
||||
result.lastEvaluatedTimeStamp,
|
||||
maxItems
|
||||
)
|
||||
|
||||
def getUsers(
|
||||
userIds: Set[String],
|
||||
startFrom: Option[String] = None,
|
||||
pageSize: Option[Int] = None): Result[ListUsersResults] =
|
||||
pageSize: Option[Int] = None
|
||||
): Result[ListUsersResults] =
|
||||
userRepo
|
||||
.getUsers(userIds, startFrom, pageSize)
|
||||
.toResult[ListUsersResults]
|
||||
@ -245,7 +251,8 @@ class MembershipService(
|
||||
.getZonesByAdminGroupId(group.id)
|
||||
.map { zones =>
|
||||
ensuring(InvalidGroupRequestError(s"${group.name} is the admin of a zone. Cannot delete."))(
|
||||
zones.isEmpty)
|
||||
zones.isEmpty
|
||||
)
|
||||
}
|
||||
.toResult
|
||||
|
||||
@ -255,8 +262,9 @@ class MembershipService(
|
||||
.map { rsId =>
|
||||
ensuring(
|
||||
InvalidGroupRequestError(
|
||||
s"${group.name} is the owner for a record set including $rsId. Cannot delete."))(
|
||||
rsId.isEmpty)
|
||||
s"${group.name} is the owner for a record set including $rsId. Cannot delete."
|
||||
)
|
||||
)(rsId.isEmpty)
|
||||
}
|
||||
.toResult
|
||||
|
||||
@ -264,15 +272,19 @@ class MembershipService(
|
||||
zoneRepo
|
||||
.getFirstOwnedZoneAclGroupId(group.id)
|
||||
.map { zId =>
|
||||
ensuring(InvalidGroupRequestError(
|
||||
s"${group.name} has an ACL rule for a zone including $zId. Cannot delete."))(zId.isEmpty)
|
||||
ensuring(
|
||||
InvalidGroupRequestError(
|
||||
s"${group.name} has an ACL rule for a zone including $zId. Cannot delete."
|
||||
)
|
||||
)(zId.isEmpty)
|
||||
}
|
||||
.toResult
|
||||
|
||||
def updateUserLockStatus(
|
||||
userId: String,
|
||||
lockStatus: LockStatus,
|
||||
authPrincipal: AuthPrincipal): Result[User] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[User] =
|
||||
for {
|
||||
_ <- isSuperAdmin(authPrincipal).toResult
|
||||
existingUser <- getExistingUser(userId)
|
||||
|
@ -32,7 +32,8 @@ trait MembershipServiceAlgebra {
|
||||
description: Option[String],
|
||||
memberIds: Set[String],
|
||||
adminUserIds: Set[String],
|
||||
authPrincipal: AuthPrincipal): Result[Group]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[Group]
|
||||
|
||||
def deleteGroup(groupId: String, authPrincipal: AuthPrincipal): Result[Group]
|
||||
|
||||
@ -43,13 +44,15 @@ trait MembershipServiceAlgebra {
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
authPrincipal: AuthPrincipal,
|
||||
ignoreAccess: Boolean): Result[ListMyGroupsResponse]
|
||||
ignoreAccess: Boolean
|
||||
): Result[ListMyGroupsResponse]
|
||||
|
||||
def listMembers(
|
||||
groupId: String,
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
authPrincipal: AuthPrincipal): Result[ListMembersResponse]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ListMembersResponse]
|
||||
|
||||
def listAdmins(groupId: String, authPrincipal: AuthPrincipal): Result[ListAdminsResponse]
|
||||
|
||||
@ -57,10 +60,12 @@ trait MembershipServiceAlgebra {
|
||||
groupId: String,
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
authPrincipal: AuthPrincipal): Result[ListGroupChangesResponse]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ListGroupChangesResponse]
|
||||
|
||||
def updateUserLockStatus(
|
||||
userId: String,
|
||||
lockStatus: LockStatus,
|
||||
authPrincipal: AuthPrincipal): Result[User]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[User]
|
||||
}
|
||||
|
@ -24,17 +24,20 @@ case class ListRecordSetChangesResponse(
|
||||
recordSetChanges: List[RecordSetChangeInfo] = Nil,
|
||||
nextId: Option[String],
|
||||
startFrom: Option[String],
|
||||
maxItems: Int)
|
||||
maxItems: Int
|
||||
)
|
||||
|
||||
object ListRecordSetChangesResponse {
|
||||
def apply(
|
||||
zoneId: String,
|
||||
listResults: ListRecordSetChangesResults,
|
||||
info: List[RecordSetChangeInfo]): ListRecordSetChangesResponse =
|
||||
info: List[RecordSetChangeInfo]
|
||||
): ListRecordSetChangesResponse =
|
||||
ListRecordSetChangesResponse(
|
||||
zoneId,
|
||||
info,
|
||||
listResults.nextId,
|
||||
listResults.startFrom,
|
||||
listResults.maxItems)
|
||||
listResults.maxItems
|
||||
)
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ object RecordSetChangeGenerator extends DnsConversions {
|
||||
recordSet: RecordSet,
|
||||
zone: Zone,
|
||||
userId: String,
|
||||
singleBatchChangeIds: List[String]): RecordSetChange =
|
||||
singleBatchChangeIds: List[String]
|
||||
): RecordSetChange =
|
||||
RecordSetChange(
|
||||
zone = zone,
|
||||
recordSet = recordSet.copy(
|
||||
@ -48,7 +49,8 @@ object RecordSetChangeGenerator extends DnsConversions {
|
||||
def forAdd(
|
||||
recordSet: RecordSet,
|
||||
zone: Zone,
|
||||
auth: Option[AuthPrincipal] = None): RecordSetChange =
|
||||
auth: Option[AuthPrincipal] = None
|
||||
): RecordSetChange =
|
||||
forAdd(recordSet, zone, auth.map(_.userId).getOrElse("system"), List())
|
||||
|
||||
def forAdd(recordSet: RecordSet, zone: Zone, auth: AuthPrincipal): RecordSetChange =
|
||||
@ -59,14 +61,16 @@ object RecordSetChangeGenerator extends DnsConversions {
|
||||
newRecordSet: RecordSet,
|
||||
zone: Zone,
|
||||
userId: String,
|
||||
singleBatchChangeIds: List[String]): RecordSetChange =
|
||||
singleBatchChangeIds: List[String]
|
||||
): RecordSetChange =
|
||||
RecordSetChange(
|
||||
zone = zone,
|
||||
recordSet = newRecordSet.copy(
|
||||
id = replacing.id,
|
||||
name = relativize(newRecordSet.name, zone.name),
|
||||
status = RecordSetStatus.PendingUpdate,
|
||||
updated = Some(DateTime.now)),
|
||||
updated = Some(DateTime.now)
|
||||
),
|
||||
userId = userId,
|
||||
changeType = RecordSetChangeType.Update,
|
||||
status = RecordSetChangeStatus.Pending,
|
||||
@ -78,21 +82,24 @@ object RecordSetChangeGenerator extends DnsConversions {
|
||||
replacing: RecordSet,
|
||||
newRecordSet: RecordSet,
|
||||
zone: Zone,
|
||||
auth: Option[AuthPrincipal] = None): RecordSetChange =
|
||||
auth: Option[AuthPrincipal] = None
|
||||
): RecordSetChange =
|
||||
forUpdate(replacing, newRecordSet, zone, auth.map(_.userId).getOrElse("system"), List())
|
||||
|
||||
def forUpdate(
|
||||
replacing: RecordSet,
|
||||
newRecordSet: RecordSet,
|
||||
zone: Zone,
|
||||
auth: AuthPrincipal): RecordSetChange =
|
||||
auth: AuthPrincipal
|
||||
): RecordSetChange =
|
||||
forUpdate(replacing, newRecordSet, zone, auth.userId, List())
|
||||
|
||||
def forDelete(
|
||||
recordSet: RecordSet,
|
||||
zone: Zone,
|
||||
userId: String,
|
||||
singleBatchChangeIds: List[String]): RecordSetChange =
|
||||
singleBatchChangeIds: List[String]
|
||||
): RecordSetChange =
|
||||
RecordSetChange(
|
||||
zone = zone,
|
||||
recordSet = recordSet.copy(
|
||||
@ -109,7 +116,8 @@ object RecordSetChangeGenerator extends DnsConversions {
|
||||
def forDelete(
|
||||
recordSet: RecordSet,
|
||||
zone: Zone,
|
||||
auth: Option[AuthPrincipal] = None): RecordSetChange =
|
||||
auth: Option[AuthPrincipal] = None
|
||||
): RecordSetChange =
|
||||
forDelete(recordSet, zone, auth.map(_.userId).getOrElse("system"), List())
|
||||
|
||||
def forDelete(recordSet: RecordSet, zone: Zone, auth: AuthPrincipal): RecordSetChange =
|
||||
|
@ -33,7 +33,8 @@ object RecordSetService {
|
||||
def apply(
|
||||
dataAccessor: ApiDataAccessor,
|
||||
messageQueue: MessageQueue,
|
||||
accessValidation: AccessValidationsAlgebra): RecordSetService =
|
||||
accessValidation: AccessValidationsAlgebra
|
||||
): RecordSetService =
|
||||
new RecordSetService(
|
||||
dataAccessor.zoneRepository,
|
||||
dataAccessor.groupRepository,
|
||||
@ -52,8 +53,8 @@ class RecordSetService(
|
||||
recordChangeRepository: RecordChangeRepository,
|
||||
userRepository: UserRepository,
|
||||
messageQueue: MessageQueue,
|
||||
accessValidation: AccessValidationsAlgebra)
|
||||
extends RecordSetServiceAlgebra {
|
||||
accessValidation: AccessValidationsAlgebra
|
||||
) extends RecordSetServiceAlgebra {
|
||||
|
||||
import RecordSetValidations._
|
||||
import accessValidation._
|
||||
@ -106,7 +107,8 @@ class RecordSetService(
|
||||
def deleteRecordSet(
|
||||
recordSetId: String,
|
||||
zoneId: String,
|
||||
auth: AuthPrincipal): Result[ZoneCommandResult] =
|
||||
auth: AuthPrincipal
|
||||
): Result[ZoneCommandResult] =
|
||||
for {
|
||||
zone <- getZone(zoneId)
|
||||
existing <- getRecordSet(recordSetId, zone)
|
||||
@ -121,7 +123,8 @@ class RecordSetService(
|
||||
def getRecordSet(
|
||||
recordSetId: String,
|
||||
zoneId: String,
|
||||
authPrincipal: AuthPrincipal): Result[RecordSetInfo] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[RecordSetInfo] =
|
||||
for {
|
||||
zone <- getZone(zoneId)
|
||||
recordSet <- getRecordSet(recordSetId, zone)
|
||||
@ -130,7 +133,8 @@ class RecordSetService(
|
||||
recordSet.name,
|
||||
recordSet.typ,
|
||||
zone,
|
||||
recordSet.ownerGroupId).toResult
|
||||
recordSet.ownerGroupId
|
||||
).toResult
|
||||
groupName <- getGroupName(recordSet.ownerGroupId)
|
||||
} yield RecordSetInfo(recordSet, groupName)
|
||||
|
||||
@ -139,7 +143,8 @@ class RecordSetService(
|
||||
startFrom: Option[String],
|
||||
maxItems: Option[Int],
|
||||
recordNameFilter: Option[String],
|
||||
authPrincipal: AuthPrincipal): Result[ListRecordSetsResponse] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ListRecordSetsResponse] =
|
||||
for {
|
||||
zone <- getZone(zoneId)
|
||||
_ <- canSeeZone(authPrincipal, zone).toResult
|
||||
@ -150,39 +155,44 @@ class RecordSetService(
|
||||
rsGroups <- groupRepository.getGroups(rsOwnerGroupIds).toResult[Set[Group]]
|
||||
setsWithGroupName = getListWithGroupNames(recordSetResults.recordSets, rsGroups)
|
||||
setsWithAccess <- getListAccessLevels(authPrincipal, setsWithGroupName, zone).toResult
|
||||
} yield
|
||||
ListRecordSetsResponse(
|
||||
setsWithAccess,
|
||||
recordSetResults.startFrom,
|
||||
recordSetResults.nextId,
|
||||
recordSetResults.maxItems,
|
||||
recordSetResults.recordNameFilter)
|
||||
} yield ListRecordSetsResponse(
|
||||
setsWithAccess,
|
||||
recordSetResults.startFrom,
|
||||
recordSetResults.nextId,
|
||||
recordSetResults.maxItems,
|
||||
recordSetResults.recordNameFilter
|
||||
)
|
||||
|
||||
def getRecordSetChange(
|
||||
zoneId: String,
|
||||
changeId: String,
|
||||
authPrincipal: AuthPrincipal): Result[RecordSetChange] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[RecordSetChange] =
|
||||
for {
|
||||
zone <- getZone(zoneId)
|
||||
change <- recordChangeRepository
|
||||
.getRecordSetChange(zone.id, changeId)
|
||||
.orFail(
|
||||
RecordSetChangeNotFoundError(
|
||||
s"Unable to find record set change with id $changeId in zone ${zone.name}"))
|
||||
s"Unable to find record set change with id $changeId in zone ${zone.name}"
|
||||
)
|
||||
)
|
||||
.toResult[RecordSetChange]
|
||||
_ <- canViewRecordSet(
|
||||
authPrincipal,
|
||||
change.recordSet.name,
|
||||
change.recordSet.typ,
|
||||
zone,
|
||||
change.recordSet.ownerGroupId).toResult
|
||||
change.recordSet.ownerGroupId
|
||||
).toResult
|
||||
} yield change
|
||||
|
||||
def listRecordSetChanges(
|
||||
zoneId: String,
|
||||
startFrom: Option[String] = None,
|
||||
maxItems: Int = 100,
|
||||
authPrincipal: AuthPrincipal): Result[ListRecordSetChangesResponse] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ListRecordSetChangesResponse] =
|
||||
for {
|
||||
zone <- getZone(zoneId)
|
||||
_ <- canSeeZone(authPrincipal, zone).toResult
|
||||
@ -203,7 +213,9 @@ class RecordSetService(
|
||||
.getRecordSet(zone.id, recordsetId)
|
||||
.orFail(
|
||||
RecordSetNotFoundError(
|
||||
s"RecordSet with id $recordsetId does not exist in zone ${zone.name}"))
|
||||
s"RecordSet with id $recordsetId does not exist in zone ${zone.name}"
|
||||
)
|
||||
)
|
||||
.toResult[RecordSet]
|
||||
|
||||
def recordSetDoesNotExist(recordSet: RecordSet, zone: Zone): Result[Unit] =
|
||||
@ -215,18 +227,22 @@ class RecordSetService(
|
||||
Left(
|
||||
RecordSetAlreadyExists(
|
||||
s"RecordSet with name ${recordSet.name} and type ${recordSet.typ} already " +
|
||||
s"exists in zone ${zone.name}"))
|
||||
s"exists in zone ${zone.name}"
|
||||
)
|
||||
)
|
||||
}
|
||||
.toResult
|
||||
|
||||
def buildRecordSetChangeInfo(
|
||||
changes: List[RecordSetChange]): Result[List[RecordSetChangeInfo]] = {
|
||||
changes: List[RecordSetChange]
|
||||
): Result[List[RecordSetChangeInfo]] = {
|
||||
val userIds = changes.map(_.userId).toSet
|
||||
for {
|
||||
users <- userRepository.getUsers(userIds, None, None).map(_.users).toResult[Seq[User]]
|
||||
userMap = users.map(u => (u.id, u.userName)).toMap
|
||||
recordSetChangesInfo = changes.map(change =>
|
||||
RecordSetChangeInfo(change, userMap.get(change.userId)))
|
||||
recordSetChangesInfo = changes.map(
|
||||
change => RecordSetChangeInfo(change, userMap.get(change.userId))
|
||||
)
|
||||
} yield recordSetChangesInfo
|
||||
}
|
||||
|
||||
|
@ -31,29 +31,34 @@ trait RecordSetServiceAlgebra {
|
||||
def deleteRecordSet(
|
||||
recordSetId: String,
|
||||
zoneId: String,
|
||||
auth: AuthPrincipal): Result[ZoneCommandResult]
|
||||
auth: AuthPrincipal
|
||||
): Result[ZoneCommandResult]
|
||||
|
||||
def getRecordSet(
|
||||
recordSetId: String,
|
||||
zoneId: String,
|
||||
authPrincipal: AuthPrincipal): Result[RecordSetInfo]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[RecordSetInfo]
|
||||
|
||||
def listRecordSets(
|
||||
zoneId: String,
|
||||
startFrom: Option[String],
|
||||
maxItems: Option[Int],
|
||||
recordNameFilter: Option[String],
|
||||
authPrincipal: AuthPrincipal): Result[ListRecordSetsResponse]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ListRecordSetsResponse]
|
||||
|
||||
def getRecordSetChange(
|
||||
zoneId: String,
|
||||
changeId: String,
|
||||
authPrincipal: AuthPrincipal): Result[RecordSetChange]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[RecordSetChange]
|
||||
|
||||
def listRecordSetChanges(
|
||||
zoneId: String,
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
authPrincipal: AuthPrincipal): Result[ListRecordSetChangesResponse]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ListRecordSetChangesResponse]
|
||||
|
||||
}
|
||||
|
@ -38,7 +38,8 @@ object RecordSetValidations {
|
||||
ensuring(InvalidRequest("PTR is not valid in forward lookup zone"))(zone.isReverse)
|
||||
case _ =>
|
||||
ensuring(InvalidRequest(s"${recordSet.typ} is not valid in reverse lookup zone."))(
|
||||
!zone.isReverse)
|
||||
!zone.isReverse
|
||||
)
|
||||
}
|
||||
|
||||
def validRecordNameLength(recordSet: RecordSet, zone: Zone): Either[Throwable, Unit] = {
|
||||
@ -52,38 +53,51 @@ object RecordSetValidations {
|
||||
ensuring(
|
||||
PendingUpdateError(
|
||||
s"RecordSet with id ${recordSet.id}, name ${recordSet.name} and type ${recordSet.typ} " +
|
||||
s"currently has a pending change"))(
|
||||
s"currently has a pending change"
|
||||
)
|
||||
)(
|
||||
!recordSet.isPending
|
||||
)
|
||||
|
||||
def noCnameWithNewName(
|
||||
newRecordSet: RecordSet,
|
||||
existingRecordsWithName: List[RecordSet],
|
||||
zone: Zone): Either[Throwable, Unit] =
|
||||
zone: Zone
|
||||
): Either[Throwable, Unit] =
|
||||
ensuring(
|
||||
RecordSetAlreadyExists(s"RecordSet with name ${newRecordSet.name} and type CNAME already " +
|
||||
s"exists in zone ${zone.name}"))(
|
||||
RecordSetAlreadyExists(
|
||||
s"RecordSet with name ${newRecordSet.name} and type CNAME already " +
|
||||
s"exists in zone ${zone.name}"
|
||||
)
|
||||
)(
|
||||
!existingRecordsWithName.exists(rs => rs.id != newRecordSet.id && rs.typ == CNAME)
|
||||
)
|
||||
|
||||
def isUniqueUpdate(
|
||||
newRecordSet: RecordSet,
|
||||
existingRecordsWithName: List[RecordSet],
|
||||
zone: Zone): Either[Throwable, Unit] =
|
||||
zone: Zone
|
||||
): Either[Throwable, Unit] =
|
||||
ensuring(
|
||||
RecordSetAlreadyExists(
|
||||
s"RecordSet with name ${newRecordSet.name} and type ${newRecordSet.typ} already " +
|
||||
s"exists in zone ${zone.name}"))(
|
||||
s"exists in zone ${zone.name}"
|
||||
)
|
||||
)(
|
||||
!existingRecordsWithName.exists(rs => rs.id != newRecordSet.id && rs.typ == newRecordSet.typ)
|
||||
)
|
||||
|
||||
def isNotDotted(
|
||||
newRecordSet: RecordSet,
|
||||
zone: Zone,
|
||||
existingRecordSet: Option[RecordSet] = None): Either[Throwable, Unit] =
|
||||
ensuring(InvalidRequest(
|
||||
s"Record with name ${newRecordSet.name} and type ${newRecordSet.typ} is a dotted host which" +
|
||||
s" is not allowed in zone ${zone.name}"))(
|
||||
existingRecordSet: Option[RecordSet] = None
|
||||
): Either[Throwable, Unit] =
|
||||
ensuring(
|
||||
InvalidRequest(
|
||||
s"Record with name ${newRecordSet.name} and type ${newRecordSet.typ} is a dotted host which" +
|
||||
s" is not allowed in zone ${zone.name}"
|
||||
)
|
||||
)(
|
||||
newRecordSet.name == zone.name || !newRecordSet.name.contains(".") ||
|
||||
existingRecordSet.exists(_.name == newRecordSet.name)
|
||||
)
|
||||
@ -92,7 +106,8 @@ object RecordSetValidations {
|
||||
newRecordSet: RecordSet,
|
||||
existingRecordsWithName: List[RecordSet],
|
||||
zone: Zone,
|
||||
existingRecordSet: Option[RecordSet] = None): Either[Throwable, Unit] =
|
||||
existingRecordSet: Option[RecordSet] = None
|
||||
): Either[Throwable, Unit] =
|
||||
newRecordSet.typ match {
|
||||
case CNAME => cnameValidations(newRecordSet, existingRecordsWithName, zone, existingRecordSet)
|
||||
case NS => nsValidations(newRecordSet, zone, existingRecordSet)
|
||||
@ -110,7 +125,8 @@ object RecordSetValidations {
|
||||
isNotOrigin(
|
||||
recordSet,
|
||||
zone,
|
||||
s"Record with name ${recordSet.name} is an NS record at apex and cannot be edited")
|
||||
s"Record with name ${recordSet.name} is an NS record at apex and cannot be edited"
|
||||
)
|
||||
case SOA => InvalidRequest("SOA records cannot be deleted").asLeft
|
||||
case _ => ().asRight
|
||||
}
|
||||
@ -120,12 +136,16 @@ object RecordSetValidations {
|
||||
newRecordSet: RecordSet,
|
||||
existingRecordsWithName: List[RecordSet],
|
||||
zone: Zone,
|
||||
existingRecordSet: Option[RecordSet] = None): Either[Throwable, Unit] = {
|
||||
existingRecordSet: Option[RecordSet] = None
|
||||
): Either[Throwable, Unit] = {
|
||||
// cannot create a cname record if a record with the same exists
|
||||
val noRecordWithName = {
|
||||
ensuring(
|
||||
RecordSetAlreadyExists(s"RecordSet with name ${newRecordSet.name} already " +
|
||||
s"exists in zone ${zone.name}, CNAME record cannot use duplicate name"))(
|
||||
RecordSetAlreadyExists(
|
||||
s"RecordSet with name ${newRecordSet.name} already " +
|
||||
s"exists in zone ${zone.name}, CNAME record cannot use duplicate name"
|
||||
)
|
||||
)(
|
||||
existingRecordsWithName.forall(_.id == newRecordSet.id)
|
||||
)
|
||||
}
|
||||
@ -134,7 +154,8 @@ object RecordSetValidations {
|
||||
_ <- isNotOrigin(
|
||||
newRecordSet,
|
||||
zone,
|
||||
"CNAME RecordSet cannot have name '@' because it points to zone origin")
|
||||
"CNAME RecordSet cannot have name '@' because it points to zone origin"
|
||||
)
|
||||
_ <- noRecordWithName
|
||||
_ <- isNotDotted(newRecordSet, zone, existingRecordSet)
|
||||
} yield ()
|
||||
@ -144,17 +165,20 @@ object RecordSetValidations {
|
||||
def dsValidations(
|
||||
newRecordSet: RecordSet,
|
||||
existingRecordsWithName: List[RecordSet],
|
||||
zone: Zone): Either[Throwable, Unit] = {
|
||||
zone: Zone
|
||||
): Either[Throwable, Unit] = {
|
||||
// see https://tools.ietf.org/html/rfc4035#section-2.4
|
||||
val nsChecks = existingRecordsWithName.find(_.typ == NS) match {
|
||||
case Some(ns) if ns.ttl == newRecordSet.ttl => ().asRight
|
||||
case Some(ns) =>
|
||||
InvalidRequest(
|
||||
s"DS record [${newRecordSet.name}] must have TTL matching its linked NS (${ns.ttl})").asLeft
|
||||
s"DS record [${newRecordSet.name}] must have TTL matching its linked NS (${ns.ttl})"
|
||||
).asLeft
|
||||
case None =>
|
||||
InvalidRequest(
|
||||
s"DS record [${newRecordSet.name}] is invalid because there is no NS record with that " +
|
||||
s"name in the zone [${zone.name}]").asLeft
|
||||
s"name in the zone [${zone.name}]"
|
||||
).asLeft
|
||||
}
|
||||
|
||||
for {
|
||||
@ -162,7 +186,8 @@ object RecordSetValidations {
|
||||
_ <- isNotOrigin(
|
||||
newRecordSet,
|
||||
zone,
|
||||
s"Record with name [${newRecordSet.name}] is an DS record at apex and cannot be added")
|
||||
s"Record with name [${newRecordSet.name}] is an DS record at apex and cannot be added"
|
||||
)
|
||||
_ <- nsChecks
|
||||
} yield ()
|
||||
}
|
||||
@ -170,7 +195,8 @@ object RecordSetValidations {
|
||||
def nsValidations(
|
||||
newRecordSet: RecordSet,
|
||||
zone: Zone,
|
||||
oldRecordSet: Option[RecordSet] = None): Either[Throwable, Unit] = {
|
||||
oldRecordSet: Option[RecordSet] = None
|
||||
): Either[Throwable, Unit] = {
|
||||
// TODO kept consistency with old validation. Not sure why NS could be dotted in reverse specifically
|
||||
val isNotDottedHost = if (!zone.isReverse) isNotDotted(newRecordSet, zone) else ().asRight
|
||||
|
||||
@ -179,14 +205,16 @@ object RecordSetValidations {
|
||||
_ <- isNotOrigin(
|
||||
newRecordSet,
|
||||
zone,
|
||||
s"Record with name ${newRecordSet.name} is an NS record at apex and cannot be added")
|
||||
s"Record with name ${newRecordSet.name} is an NS record at apex and cannot be added"
|
||||
)
|
||||
_ <- containsApprovedNameServers(newRecordSet)
|
||||
_ <- oldRecordSet
|
||||
.map { rs =>
|
||||
isNotOrigin(
|
||||
rs,
|
||||
zone,
|
||||
s"Record with name ${newRecordSet.name} is an NS record at apex and cannot be edited")
|
||||
s"Record with name ${newRecordSet.name} is an NS record at apex and cannot be edited"
|
||||
)
|
||||
}
|
||||
.getOrElse(().asRight)
|
||||
} yield ()
|
||||
@ -233,7 +261,8 @@ object RecordSetValidations {
|
||||
def canUseOwnerGroup(
|
||||
ownerGroupId: Option[String],
|
||||
group: Option[Group],
|
||||
authPrincipal: AuthPrincipal): Either[Throwable, Unit] =
|
||||
authPrincipal: AuthPrincipal
|
||||
): Either[Throwable, Unit] =
|
||||
(ownerGroupId, group) match {
|
||||
case (None, _) => ().asRight
|
||||
case (Some(groupId), None) =>
|
||||
|
@ -40,11 +40,13 @@ trait ACLRuleOrdering extends Ordering[ACLRule] {
|
||||
sortableUserValue(rule1),
|
||||
sortableRecordMaskValue(rule1),
|
||||
sortableRecordTypeValue(rule1),
|
||||
rule1.accessLevel).compare(
|
||||
rule1.accessLevel
|
||||
).compare(
|
||||
sortableUserValue(rule2),
|
||||
sortableRecordMaskValue(rule2),
|
||||
sortableRecordTypeValue(rule2),
|
||||
rule2.accessLevel)
|
||||
rule2.accessLevel
|
||||
)
|
||||
}
|
||||
|
||||
object ACLRuleOrdering extends ACLRuleOrdering {
|
||||
|
@ -24,7 +24,8 @@ case class ListZoneChangesResponse(
|
||||
zoneChanges: List[ZoneChange] = Nil,
|
||||
nextId: Option[String],
|
||||
startFrom: Option[String],
|
||||
maxItems: Int)
|
||||
maxItems: Int
|
||||
)
|
||||
|
||||
object ListZoneChangesResponse {
|
||||
def apply(zoneId: String, listResults: ListZoneChangesResults): ListZoneChangesResponse =
|
||||
@ -33,5 +34,6 @@ object ListZoneChangesResponse {
|
||||
listResults.items,
|
||||
listResults.nextId,
|
||||
listResults.startFrom,
|
||||
listResults.maxItems)
|
||||
listResults.maxItems
|
||||
)
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ object ZoneChangeGenerator {
|
||||
def forAdd(
|
||||
zone: Zone,
|
||||
authPrincipal: AuthPrincipal,
|
||||
status: ZoneChangeStatus = Pending): ZoneChange =
|
||||
status: ZoneChangeStatus = Pending
|
||||
): ZoneChange =
|
||||
ZoneChange(
|
||||
zone
|
||||
.copy(id = UUID.randomUUID().toString, created = DateTime.now, status = ZoneStatus.Syncing),
|
||||
@ -68,6 +69,7 @@ object ZoneChangeGenerator {
|
||||
val oldConn = oldZ.connection.getOrElse(newConn)
|
||||
newConn.copy(
|
||||
key =
|
||||
if (oldConn.key == newConn.decrypted(Crypto.instance).key) oldConn.key else newConn.key)
|
||||
if (oldConn.key == newConn.decrypted(Crypto.instance).key) oldConn.key else newConn.key
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -43,27 +43,31 @@ object ZoneConnectionValidator {
|
||||
|
||||
def getZoneConnection(
|
||||
zone: Zone,
|
||||
configuredDnsConnections: ConfiguredDnsConnections): ZoneConnection =
|
||||
configuredDnsConnections: ConfiguredDnsConnections
|
||||
): ZoneConnection =
|
||||
zone.connection
|
||||
.orElse(getDnsBackend(zone, configuredDnsConnections).map(_.zoneConnection))
|
||||
.getOrElse(configuredDnsConnections.defaultZoneConnection)
|
||||
|
||||
def getTransferConnection(
|
||||
zone: Zone,
|
||||
configuredDnsConnections: ConfiguredDnsConnections): ZoneConnection =
|
||||
configuredDnsConnections: ConfiguredDnsConnections
|
||||
): ZoneConnection =
|
||||
zone.transferConnection
|
||||
.orElse(getDnsBackend(zone, configuredDnsConnections).map(_.transferConnection))
|
||||
.getOrElse(configuredDnsConnections.defaultTransferConnection)
|
||||
|
||||
def getDnsBackend(
|
||||
zone: Zone,
|
||||
configuredDnsConnections: ConfiguredDnsConnections): Option[DnsBackend] =
|
||||
configuredDnsConnections: ConfiguredDnsConnections
|
||||
): Option[DnsBackend] =
|
||||
zone.backendId
|
||||
.flatMap { bid =>
|
||||
val backend = configuredDnsConnections.dnsBackends.find(_.id == bid)
|
||||
if (backend.isEmpty) {
|
||||
logger.error(
|
||||
s"BackendId [$bid] for zone [${zone.id}: ${zone.name}] is not defined in config")
|
||||
s"BackendId [$bid] for zone [${zone.id}: ${zone.name}] is not defined in config"
|
||||
)
|
||||
}
|
||||
backend
|
||||
}
|
||||
@ -96,7 +100,9 @@ class ZoneConnectionValidator(connections: ConfiguredDnsConnections)
|
||||
ZoneValidationFailed(
|
||||
zoneView.zone,
|
||||
nel.toList,
|
||||
"Zone could not be loaded due to validation errors."))
|
||||
"Zone could not be loaded due to validation errors."
|
||||
)
|
||||
)
|
||||
.toEither
|
||||
.toResult
|
||||
}
|
||||
@ -108,7 +114,8 @@ class ZoneConnectionValidator(connections: ConfiguredDnsConnections)
|
||||
withTimeout(
|
||||
loadDns(zone),
|
||||
opTimeout,
|
||||
ConnectionFailed(zone, "Unable to connect to zone: Transfer connection invalid"))
|
||||
ConnectionFailed(zone, "Unable to connect to zone: Transfer connection invalid")
|
||||
)
|
||||
|
||||
def hasSOA(records: List[RecordSet], zone: Zone): Result[Unit] = {
|
||||
if (records.isEmpty) {
|
||||
@ -138,8 +145,10 @@ class ZoneConnectionValidator(connections: ConfiguredDnsConnections)
|
||||
def healthCheck(timeout: Int): HealthCheck =
|
||||
Resource
|
||||
.fromAutoCloseable(IO(new Socket()))
|
||||
.use(socket =>
|
||||
IO(socket.connect(new InetSocketAddress(healthCheckAddress, healthCheckPort), timeout)))
|
||||
.use(
|
||||
socket =>
|
||||
IO(socket.connect(new InetSocketAddress(healthCheckAddress, healthCheckPort), timeout))
|
||||
)
|
||||
.attempt
|
||||
.asHealthCheck
|
||||
|
||||
|
@ -44,14 +44,16 @@ case class ZoneInfo(
|
||||
adminGroupName: String,
|
||||
latestSync: Option[DateTime],
|
||||
backendId: Option[String],
|
||||
accessLevel: AccessLevel)
|
||||
accessLevel: AccessLevel
|
||||
)
|
||||
|
||||
object ZoneInfo {
|
||||
def apply(
|
||||
zone: Zone,
|
||||
aclInfo: ZoneACLInfo,
|
||||
groupName: String,
|
||||
accessLevel: AccessLevel): ZoneInfo =
|
||||
accessLevel: AccessLevel
|
||||
): ZoneInfo =
|
||||
ZoneInfo(
|
||||
name = zone.name,
|
||||
email = zone.email,
|
||||
@ -88,7 +90,8 @@ case class ZoneSummaryInfo(
|
||||
adminGroupName: String,
|
||||
latestSync: Option[DateTime],
|
||||
backendId: Option[String],
|
||||
accessLevel: AccessLevel)
|
||||
accessLevel: AccessLevel
|
||||
)
|
||||
|
||||
object ZoneSummaryInfo {
|
||||
def apply(zone: Zone, groupName: String, accessLevel: AccessLevel): ZoneSummaryInfo =
|
||||
@ -125,7 +128,8 @@ case class RecordSetListInfo(
|
||||
account: String,
|
||||
accessLevel: AccessLevel,
|
||||
ownerGroupId: Option[String],
|
||||
ownerGroupName: Option[String])
|
||||
ownerGroupName: Option[String]
|
||||
)
|
||||
|
||||
object RecordSetListInfo {
|
||||
def apply(recordSet: RecordSetInfo, accessLevel: AccessLevel): RecordSetListInfo =
|
||||
@ -158,7 +162,8 @@ case class RecordSetInfo(
|
||||
id: String,
|
||||
account: String,
|
||||
ownerGroupId: Option[String],
|
||||
ownerGroupName: Option[String])
|
||||
ownerGroupName: Option[String]
|
||||
)
|
||||
|
||||
object RecordSetInfo {
|
||||
def apply(recordSet: RecordSet, groupName: Option[String]): RecordSetInfo =
|
||||
@ -188,7 +193,8 @@ case class RecordSetChangeInfo(
|
||||
systemMessage: Option[String],
|
||||
updates: Option[RecordSet],
|
||||
id: String,
|
||||
userName: String)
|
||||
userName: String
|
||||
)
|
||||
|
||||
object RecordSetChangeInfo {
|
||||
def apply(recordSetChange: RecordSetChange, name: Option[String]): RecordSetChangeInfo =
|
||||
@ -212,7 +218,8 @@ case class ListZonesResponse(
|
||||
startFrom: Option[String] = None,
|
||||
nextId: Option[String] = None,
|
||||
maxItems: Int = 100,
|
||||
ignoreAccess: Boolean = false)
|
||||
ignoreAccess: Boolean = false
|
||||
)
|
||||
|
||||
// Errors
|
||||
case class InvalidRequest(msg: String) extends Throwable(msg)
|
||||
|
@ -46,7 +46,8 @@ object ZoneRecordValidations {
|
||||
/* Checks to see if an individual ns data is part of the approved server list */
|
||||
def isApprovedNameServer(
|
||||
approvedServerList: List[Regex],
|
||||
nsData: NSData): ValidatedNel[String, NSData] =
|
||||
nsData: NSData
|
||||
): ValidatedNel[String, NSData] =
|
||||
if (isStringInRegexList(approvedServerList, nsData.nsdname)) {
|
||||
nsData.validNel[String]
|
||||
} else {
|
||||
@ -56,7 +57,8 @@ object ZoneRecordValidations {
|
||||
/* Inspects each record in the rdata, returning back the record set itself or all ns records that are not approved */
|
||||
def containsApprovedNameServers(
|
||||
approvedServerList: List[Regex],
|
||||
nsRecordSet: RecordSet): ValidatedNel[String, RecordSet] = {
|
||||
nsRecordSet: RecordSet
|
||||
): ValidatedNel[String, RecordSet] = {
|
||||
val validations: List[ValidatedNel[String, NSData]] = nsRecordSet.records
|
||||
.collect { case ns: NSData => ns }
|
||||
.map(isApprovedNameServer(approvedServerList, _))
|
||||
@ -66,7 +68,8 @@ object ZoneRecordValidations {
|
||||
|
||||
def isNotHighValueFqdn(
|
||||
highValueRegexList: List[Regex],
|
||||
fqdn: String): ValidatedNel[DomainValidationError, Unit] =
|
||||
fqdn: String
|
||||
): ValidatedNel[DomainValidationError, Unit] =
|
||||
if (!isStringInRegexList(highValueRegexList, fqdn)) {
|
||||
().validNel
|
||||
} else {
|
||||
@ -75,7 +78,8 @@ object ZoneRecordValidations {
|
||||
|
||||
def isNotHighValueIp(
|
||||
highValueIpList: List[IpAddress],
|
||||
ip: String): ValidatedNel[DomainValidationError, Unit] =
|
||||
ip: String
|
||||
): ValidatedNel[DomainValidationError, Unit] =
|
||||
if (!isIpInIpList(highValueIpList, ip)) {
|
||||
().validNel
|
||||
} else {
|
||||
@ -84,7 +88,8 @@ object ZoneRecordValidations {
|
||||
|
||||
def domainDoesNotRequireManualReview(
|
||||
regexList: List[Regex],
|
||||
fqdn: String): ValidatedNel[DomainValidationError, Unit] =
|
||||
fqdn: String
|
||||
): ValidatedNel[DomainValidationError, Unit] =
|
||||
if (!isStringInRegexList(regexList, fqdn)) {
|
||||
().validNel
|
||||
} else {
|
||||
@ -93,7 +98,8 @@ object ZoneRecordValidations {
|
||||
|
||||
def ipDoesNotRequireManualReview(
|
||||
regexList: List[IpAddress],
|
||||
ip: String): ValidatedNel[DomainValidationError, Unit] =
|
||||
ip: String
|
||||
): ValidatedNel[DomainValidationError, Unit] =
|
||||
if (!isIpInIpList(regexList, ip)) {
|
||||
().validNel
|
||||
} else {
|
||||
@ -103,7 +109,8 @@ object ZoneRecordValidations {
|
||||
def zoneDoesNotRequireManualReview(
|
||||
zonesRequiringReview: Set[String],
|
||||
zoneName: String,
|
||||
fqdn: String): ValidatedNel[DomainValidationError, Unit] =
|
||||
fqdn: String
|
||||
): ValidatedNel[DomainValidationError, Unit] =
|
||||
if (!zonesRequiringReview.contains(DomainHelpers.ensureTrailingDot(zoneName.toLowerCase))) {
|
||||
().validNel
|
||||
} else {
|
||||
|
@ -32,7 +32,8 @@ object ZoneService {
|
||||
connectionValidator: ZoneConnectionValidatorAlgebra,
|
||||
messageQueue: MessageQueue,
|
||||
zoneValidations: ZoneValidations,
|
||||
accessValidation: AccessValidationsAlgebra): ZoneService =
|
||||
accessValidation: AccessValidationsAlgebra
|
||||
): ZoneService =
|
||||
new ZoneService(
|
||||
dataAccessor.zoneRepository,
|
||||
dataAccessor.groupRepository,
|
||||
@ -53,8 +54,8 @@ class ZoneService(
|
||||
connectionValidator: ZoneConnectionValidatorAlgebra,
|
||||
messageQueue: MessageQueue,
|
||||
zoneValidations: ZoneValidations,
|
||||
accessValidation: AccessValidationsAlgebra)
|
||||
extends ZoneServiceAlgebra {
|
||||
accessValidation: AccessValidationsAlgebra
|
||||
) extends ZoneServiceAlgebra {
|
||||
|
||||
import accessValidation._
|
||||
import zoneValidations._
|
||||
@ -62,7 +63,8 @@ class ZoneService(
|
||||
|
||||
def connectToZone(
|
||||
createZoneInput: CreateZoneInput,
|
||||
auth: AuthPrincipal): Result[ZoneCommandResult] =
|
||||
auth: AuthPrincipal
|
||||
): Result[ZoneCommandResult] =
|
||||
for {
|
||||
_ <- isValidZoneAcl(createZoneInput.acl).toResult
|
||||
_ <- connectionValidator.isValidBackendId(createZoneInput.backendId).toResult
|
||||
@ -84,7 +86,8 @@ class ZoneService(
|
||||
_ <- validateSharedZoneAuthorized(
|
||||
existingZone.shared,
|
||||
updateZoneInput.shared,
|
||||
auth.signedInUser).toResult
|
||||
auth.signedInUser
|
||||
).toResult
|
||||
_ <- canChangeZone(auth, existingZone.name, existingZone.adminGroupId).toResult
|
||||
_ <- adminGroupExists(updateZoneInput.adminGroupId)
|
||||
// if admin group changes, this confirms user has access to new group
|
||||
@ -136,32 +139,35 @@ class ZoneService(
|
||||
nameFilter: Option[String] = None,
|
||||
startFrom: Option[String] = None,
|
||||
maxItems: Int = 100,
|
||||
ignoreAccess: Boolean = false): Result[ListZonesResponse] = {
|
||||
ignoreAccess: Boolean = false
|
||||
): Result[ListZonesResponse] = {
|
||||
for {
|
||||
listZonesResult <- zoneRepository.listZones(
|
||||
authPrincipal,
|
||||
nameFilter,
|
||||
startFrom,
|
||||
maxItems,
|
||||
ignoreAccess)
|
||||
ignoreAccess
|
||||
)
|
||||
zones = listZonesResult.zones
|
||||
groupIds = zones.map(_.adminGroupId).toSet
|
||||
groups <- groupRepository.getGroups(groupIds)
|
||||
zoneSummaryInfos = zoneSummaryInfoMapping(zones, authPrincipal, groups)
|
||||
} yield
|
||||
ListZonesResponse(
|
||||
zoneSummaryInfos,
|
||||
listZonesResult.zonesFilter,
|
||||
listZonesResult.startFrom,
|
||||
listZonesResult.nextId,
|
||||
listZonesResult.maxItems,
|
||||
listZonesResult.ignoreAccess)
|
||||
} yield ListZonesResponse(
|
||||
zoneSummaryInfos,
|
||||
listZonesResult.zonesFilter,
|
||||
listZonesResult.startFrom,
|
||||
listZonesResult.nextId,
|
||||
listZonesResult.maxItems,
|
||||
listZonesResult.ignoreAccess
|
||||
)
|
||||
}.toResult
|
||||
|
||||
def zoneSummaryInfoMapping(
|
||||
zones: List[Zone],
|
||||
auth: AuthPrincipal,
|
||||
groups: Set[Group]): List[ZoneSummaryInfo] =
|
||||
groups: Set[Group]
|
||||
): List[ZoneSummaryInfo] =
|
||||
zones.map { zn =>
|
||||
val groupName = groups.find(_.id == zn.adminGroupId) match {
|
||||
case Some(group) => group.name
|
||||
@ -175,7 +181,8 @@ class ZoneService(
|
||||
zoneId: String,
|
||||
authPrincipal: AuthPrincipal,
|
||||
startFrom: Option[String] = None,
|
||||
maxItems: Int = 100): Result[ListZoneChangesResponse] =
|
||||
maxItems: Int = 100
|
||||
): Result[ListZoneChangesResponse] =
|
||||
for {
|
||||
zone <- getZoneOrFail(zoneId)
|
||||
_ <- canSeeZone(authPrincipal, zone).toResult
|
||||
@ -187,7 +194,8 @@ class ZoneService(
|
||||
def addACLRule(
|
||||
zoneId: String,
|
||||
aclRuleInfo: ACLRuleInfo,
|
||||
authPrincipal: AuthPrincipal): Result[ZoneCommandResult] = {
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ZoneCommandResult] = {
|
||||
val newRule = ACLRule(aclRuleInfo)
|
||||
for {
|
||||
zone <- getZoneOrFail(zoneId)
|
||||
@ -207,7 +215,8 @@ class ZoneService(
|
||||
def deleteACLRule(
|
||||
zoneId: String,
|
||||
aclRuleInfo: ACLRuleInfo,
|
||||
authPrincipal: AuthPrincipal): Result[ZoneCommandResult] = {
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ZoneCommandResult] = {
|
||||
val newRule = ACLRule(aclRuleInfo)
|
||||
for {
|
||||
zone <- getZoneOrFail(zoneId)
|
||||
@ -233,7 +242,8 @@ class ZoneService(
|
||||
case Some(existingZone) if existingZone.status != ZoneStatus.Deleted =>
|
||||
ZoneAlreadyExistsError(
|
||||
s"Zone with name $zoneName already exists. " +
|
||||
s"Please contact ${existingZone.email} to request access to the zone.").asLeft
|
||||
s"Please contact ${existingZone.email} to request access to the zone."
|
||||
).asLeft
|
||||
case _ => ().asRight
|
||||
}
|
||||
.toResult
|
||||
|
@ -24,7 +24,8 @@ trait ZoneServiceAlgebra {
|
||||
|
||||
def connectToZone(
|
||||
createZoneInput: CreateZoneInput,
|
||||
auth: AuthPrincipal): Result[ZoneCommandResult]
|
||||
auth: AuthPrincipal
|
||||
): Result[ZoneCommandResult]
|
||||
|
||||
def updateZone(updateZoneInput: UpdateZoneInput, auth: AuthPrincipal): Result[ZoneCommandResult]
|
||||
|
||||
@ -41,23 +42,27 @@ trait ZoneServiceAlgebra {
|
||||
nameFilter: Option[String],
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
ignoreAccess: Boolean): Result[ListZonesResponse]
|
||||
ignoreAccess: Boolean
|
||||
): Result[ListZonesResponse]
|
||||
|
||||
def listZoneChanges(
|
||||
zoneId: String,
|
||||
authPrincipal: AuthPrincipal,
|
||||
startFrom: Option[String],
|
||||
maxItems: Int): Result[ListZoneChangesResponse]
|
||||
maxItems: Int
|
||||
): Result[ListZoneChangesResponse]
|
||||
|
||||
def addACLRule(
|
||||
zoneId: String,
|
||||
aclRuleInfo: ACLRuleInfo,
|
||||
authPrincipal: AuthPrincipal): Result[ZoneCommandResult]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ZoneCommandResult]
|
||||
|
||||
def deleteACLRule(
|
||||
zoneId: String,
|
||||
aclRuleInfo: ACLRuleInfo,
|
||||
authPrincipal: AuthPrincipal): Result[ZoneCommandResult]
|
||||
authPrincipal: AuthPrincipal
|
||||
): Result[ZoneCommandResult]
|
||||
|
||||
def getBackendIds(): Result[List[String]]
|
||||
|
||||
|
@ -74,15 +74,18 @@ class ZoneValidations(syncDelayMillis: Int) {
|
||||
// Validates that the zone is either not shared or shared and the user is a super or support user
|
||||
def validateSharedZoneAuthorized(zoneShared: Boolean, user: User): Either[Throwable, Unit] =
|
||||
ensuring(NotAuthorizedError("Not authorized to create shared zones."))(
|
||||
!zoneShared || user.isSuper || user.isSupport)
|
||||
!zoneShared || user.isSuper || user.isSupport
|
||||
)
|
||||
|
||||
// Validates that the zone shared status has not been changed, or changed and the user is a super user
|
||||
def validateSharedZoneAuthorized(
|
||||
currentShared: Boolean,
|
||||
updateShared: Boolean,
|
||||
user: User): Either[Throwable, Unit] =
|
||||
user: User
|
||||
): Either[Throwable, Unit] =
|
||||
ensuring(
|
||||
NotAuthorizedError(
|
||||
s"Not authorized to update zone shared status from $currentShared to $updateShared."))(
|
||||
currentShared == updateShared || user.isSuper || user.isSupport)
|
||||
s"Not authorized to update zone shared status from $currentShared to $updateShared."
|
||||
)
|
||||
)(currentShared == updateShared || user.isSuper || user.isSupport)
|
||||
}
|
||||
|
@ -62,8 +62,8 @@ object DnsZoneViewLoader extends DnsConversions {
|
||||
case class DnsZoneViewLoader(
|
||||
zone: Zone,
|
||||
zoneTransfer: Zone => ZoneTransferIn,
|
||||
maxZoneSize: Int = VinylDNSConfig.maxZoneSize)
|
||||
extends ZoneViewLoader
|
||||
maxZoneSize: Int = VinylDNSConfig.maxZoneSize
|
||||
) extends ZoneViewLoader
|
||||
with DnsConversions
|
||||
with Monitored {
|
||||
|
||||
@ -76,17 +76,21 @@ case class DnsZoneViewLoader(
|
||||
xfr.run()
|
||||
xfr.getAXFR.asScala.map(_.asInstanceOf[DNS.Record]).toList.distinct
|
||||
}
|
||||
rawDnsRecords = zoneXfr.filter(record =>
|
||||
fromDnsRecordType(record.getType) != RecordType.UNKNOWN)
|
||||
rawDnsRecords = zoneXfr.filter(
|
||||
record => fromDnsRecordType(record.getType) != RecordType.UNKNOWN
|
||||
)
|
||||
_ <- if (rawDnsRecords.length > maxZoneSize)
|
||||
IO.raiseError(ZoneTooLargeError(zone, rawDnsRecords.length, maxZoneSize))
|
||||
else IO.pure(Unit)
|
||||
dnsZoneName <- IO(zoneDnsName(zone.name))
|
||||
recordSets <- IO(rawDnsRecords.map(toRecordSet(_, dnsZoneName, zone.id)))
|
||||
_ <- IO(DnsZoneViewLoader.logger.info(
|
||||
s"dns.loadDnsView zoneName=${zone.name}; rawRsCount=${zoneXfr.size}; rsCount=${recordSets.size}"))
|
||||
_ <- IO(
|
||||
DnsZoneViewLoader.logger.info(
|
||||
s"dns.loadDnsView zoneName=${zone.name}; rawRsCount=${zoneXfr.size}; rsCount=${recordSets.size}"
|
||||
)
|
||||
)
|
||||
} yield ZoneView(zone, recordSets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object VinylDNSZoneViewLoader {
|
||||
@ -103,11 +107,13 @@ case class VinylDNSZoneViewLoader(zone: Zone, recordSetRepository: RecordSetRepo
|
||||
zoneId = zone.id,
|
||||
startFrom = None,
|
||||
maxItems = None,
|
||||
recordNameFilter = None)
|
||||
recordNameFilter = None
|
||||
)
|
||||
.map { result =>
|
||||
VinylDNSZoneViewLoader.logger.info(
|
||||
s"vinyldns.loadZoneView zoneName=${zone.name}; rsCount=${result.recordSets.size}")
|
||||
s"vinyldns.loadZoneView zoneName=${zone.name}; rsCount=${result.recordSets.size}"
|
||||
)
|
||||
ZoneView(zone, result.recordSets)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ object BatchChangeHandler {
|
||||
|
||||
def apply(
|
||||
batchChangeRepository: BatchChangeRepository,
|
||||
notifiers: AllNotifiers): BatchChangeCommand => IO[Option[BatchChange]] =
|
||||
notifiers: AllNotifiers
|
||||
): BatchChangeCommand => IO[Option[BatchChange]] =
|
||||
batchChangeId => {
|
||||
process(
|
||||
batchChangeRepository: BatchChangeRepository,
|
||||
@ -43,7 +44,8 @@ object BatchChangeHandler {
|
||||
def process(
|
||||
batchChangeRepository: BatchChangeRepository,
|
||||
notifiers: AllNotifiers,
|
||||
batchChangeCommand: BatchChangeCommand): IO[Option[BatchChange]] =
|
||||
batchChangeCommand: BatchChangeCommand
|
||||
): IO[Option[BatchChange]] =
|
||||
for {
|
||||
batchChange <- batchChangeRepository.getBatchChange(batchChangeCommand.id)
|
||||
_ <- notify(notifiers, batchChange, batchChangeCommand)
|
||||
@ -55,15 +57,16 @@ object BatchChangeHandler {
|
||||
|
||||
private final case class Pending(
|
||||
change: Option[BatchChange],
|
||||
batchChangeCommand: BatchChangeCommand)
|
||||
extends BatchChangeProcessorState
|
||||
batchChangeCommand: BatchChangeCommand
|
||||
) extends BatchChangeProcessorState
|
||||
|
||||
private final case class PendingBatchNotificationError(change: BatchChange) extends Throwable
|
||||
|
||||
private def notify(
|
||||
notifiers: AllNotifiers,
|
||||
change: Option[BatchChange],
|
||||
batchChangeCommand: BatchChangeCommand): IO[Unit] =
|
||||
batchChangeCommand: BatchChangeCommand
|
||||
): IO[Unit] =
|
||||
change match {
|
||||
case Some(pendingBatch)
|
||||
if pendingBatch.status == BatchChangeStatus.PendingProcessing ||
|
||||
@ -75,7 +78,8 @@ object BatchChangeHandler {
|
||||
notifiers.notify(Notification(completedBatch))
|
||||
case None =>
|
||||
logger.error(
|
||||
s"Notification not sent since batch change with ID ${batchChangeCommand.id} not found.")
|
||||
s"Notification not sent since batch change with ID ${batchChangeCommand.id} not found."
|
||||
)
|
||||
IO.unit
|
||||
}
|
||||
|
||||
|
@ -38,15 +38,16 @@ object RecordSetChangeHandler {
|
||||
def apply(
|
||||
recordSetRepository: RecordSetRepository,
|
||||
recordChangeRepository: RecordChangeRepository,
|
||||
batchChangeRepository: BatchChangeRepository)(
|
||||
implicit timer: Timer[IO]): (DnsConnection, RecordSetChange) => IO[RecordSetChange] =
|
||||
batchChangeRepository: BatchChangeRepository
|
||||
)(implicit timer: Timer[IO]): (DnsConnection, RecordSetChange) => IO[RecordSetChange] =
|
||||
(conn, recordSetChange) => {
|
||||
process(
|
||||
recordSetRepository,
|
||||
recordChangeRepository,
|
||||
batchChangeRepository,
|
||||
conn,
|
||||
recordSetChange)
|
||||
recordSetChange
|
||||
)
|
||||
}
|
||||
|
||||
def process(
|
||||
@ -54,7 +55,8 @@ object RecordSetChangeHandler {
|
||||
recordChangeRepository: RecordChangeRepository,
|
||||
batchChangeRepository: BatchChangeRepository,
|
||||
conn: DnsConnection,
|
||||
recordSetChange: RecordSetChange)(implicit timer: Timer[IO]): IO[RecordSetChange] =
|
||||
recordSetChange: RecordSetChange
|
||||
)(implicit timer: Timer[IO]): IO[RecordSetChange] =
|
||||
for {
|
||||
wildCardExists <- wildCardExistsForRecord(recordSetChange.recordSet, recordSetRepository)
|
||||
completedState <- fsm(Pending(recordSetChange), conn, wildCardExists)
|
||||
@ -62,14 +64,16 @@ object RecordSetChangeHandler {
|
||||
_ <- recordSetRepository.apply(changeSet)
|
||||
_ <- recordChangeRepository.save(changeSet)
|
||||
singleBatchChanges <- batchChangeRepository.getSingleChanges(
|
||||
recordSetChange.singleBatchChangeIds)
|
||||
recordSetChange.singleBatchChangeIds
|
||||
)
|
||||
singleChangeStatusUpdates = updateBatchStatuses(singleBatchChanges, completedState.change)
|
||||
_ <- batchChangeRepository.updateSingleChanges(singleChangeStatusUpdates)
|
||||
} yield completedState.change
|
||||
|
||||
def updateBatchStatuses(
|
||||
singleChanges: List[SingleChange],
|
||||
recordSetChange: RecordSetChange): List[SingleChange] =
|
||||
recordSetChange: RecordSetChange
|
||||
): List[SingleChange] =
|
||||
recordSetChange.status match {
|
||||
case RecordSetChangeStatus.Complete =>
|
||||
singleChanges.map(_.complete(recordSetChange.id, recordSetChange.recordSet.id))
|
||||
@ -146,7 +150,8 @@ object RecordSetChangeHandler {
|
||||
}
|
||||
|
||||
private def fsm(state: ProcessorState, conn: DnsConnection, wildcardExists: Boolean)(
|
||||
implicit timer: Timer[IO]): IO[ProcessorState] = {
|
||||
implicit timer: Timer[IO]
|
||||
): IO[ProcessorState] = {
|
||||
|
||||
/**
|
||||
* If there is a wildcard record with the same type, then we skip validation and verification steps.
|
||||
@ -161,8 +166,9 @@ object RecordSetChangeHandler {
|
||||
* We also skip verification for NS records. We cannot create delegations for zones hosted on the
|
||||
* same ANS if we attempt to validate NS records
|
||||
*/
|
||||
def bypassValidation(skip: => ProcessorState)(
|
||||
orElse: => IO[ProcessorState]): IO[ProcessorState] = {
|
||||
def bypassValidation(
|
||||
skip: => ProcessorState
|
||||
)(orElse: => IO[ProcessorState]): IO[ProcessorState] = {
|
||||
val toRun =
|
||||
if (wildcardExists || state.change.recordSet.typ == RecordType.NS) IO.pure(skip) else orElse
|
||||
|
||||
@ -199,8 +205,11 @@ object RecordSetChangeHandler {
|
||||
case AlreadyApplied(_) => Completed(change.successful)
|
||||
case ReadyToApply(_) => Validated(change)
|
||||
case Failure(_, message) =>
|
||||
Completed(change.failed(
|
||||
s"Failed validating update to DNS for change ${change.id}:${change.recordSet.name}: " + message))
|
||||
Completed(
|
||||
change.failed(
|
||||
s"Failed validating update to DNS for change ${change.id}:${change.recordSet.name}: " + message
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/* Step 2: Apply the change to the dns backend */
|
||||
@ -209,8 +218,11 @@ object RecordSetChangeHandler {
|
||||
case Right(_: NoError) =>
|
||||
Applied(change)
|
||||
case Left(error) =>
|
||||
Completed(change.failed(
|
||||
s"Failed applying update to DNS for change ${change.id}:${change.recordSet.name}: ${error.getMessage}"))
|
||||
Completed(
|
||||
change.failed(
|
||||
s"Failed applying update to DNS for change ${change.id}:${change.recordSet.name}: ${error.getMessage}"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/* Step 3: Verify the record was created. We attempt 12 times over 6 seconds */
|
||||
@ -219,13 +231,22 @@ object RecordSetChangeHandler {
|
||||
getProcessingStatus(change, dnsConn).flatMap {
|
||||
case AlreadyApplied(_) => IO.pure(Completed(change.successful))
|
||||
case ReadyToApply(_) if retries <= 0 =>
|
||||
IO.pure(Completed(change.failed(s"""Failed verifying update to DNS for
|
||||
|change ${change.id}:${change.recordSet.name}: Verify out of retries.""".stripMargin)))
|
||||
IO.pure(
|
||||
Completed(
|
||||
change.failed(s"""Failed verifying update to DNS for
|
||||
|change ${change.id}:${change.recordSet.name}: Verify out of retries.""".stripMargin)
|
||||
)
|
||||
)
|
||||
case ReadyToApply(_) =>
|
||||
IO.sleep(500.milliseconds) *> loop(retries - 1)
|
||||
case Failure(_, message) =>
|
||||
IO.pure(Completed(change.failed(
|
||||
s"Failed verifying update to DNS for change ${change.id}:${change.recordSet.name}: $message")))
|
||||
IO.pure(
|
||||
Completed(
|
||||
change.failed(
|
||||
s"Failed verifying update to DNS for change ${change.id}:${change.recordSet.name}: $message"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
loop()
|
||||
@ -245,10 +266,11 @@ object RecordSetChangeHandler {
|
||||
|
||||
private def wildCardExistsForRecord(
|
||||
recordSet: RecordSet,
|
||||
recordSetRepository: RecordSetRepository): IO[Boolean] =
|
||||
recordSetRepository: RecordSetRepository
|
||||
): IO[Boolean] =
|
||||
(
|
||||
recordSetRepository.getRecordSets(recordSet.zoneId, "*", recordSet.typ),
|
||||
recordSetRepository.getRecordSets(recordSet.zoneId, "*", RecordType.CNAME))
|
||||
.parMapN(_ ++ _)
|
||||
recordSetRepository.getRecordSets(recordSet.zoneId, "*", RecordType.CNAME)
|
||||
).parMapN(_ ++ _)
|
||||
.map(_.nonEmpty)
|
||||
}
|
||||
|
@ -24,14 +24,16 @@ object ZoneChangeHandler {
|
||||
def apply(
|
||||
zoneRepository: ZoneRepository,
|
||||
zoneChangeRepository: ZoneChangeRepository,
|
||||
recordSetRepository: RecordSetRepository): ZoneChange => IO[ZoneChange] =
|
||||
recordSetRepository: RecordSetRepository
|
||||
): ZoneChange => IO[ZoneChange] =
|
||||
zoneChange =>
|
||||
zoneRepository.save(zoneChange.zone).flatMap {
|
||||
case Left(duplicateZoneError) =>
|
||||
zoneChangeRepository.save(
|
||||
zoneChange.copy(
|
||||
status = ZoneChangeStatus.Failed,
|
||||
systemMessage = Some(duplicateZoneError.message))
|
||||
systemMessage = Some(duplicateZoneError.message)
|
||||
)
|
||||
)
|
||||
case Right(_) if zoneChange.changeType == ZoneChangeType.Delete =>
|
||||
recordSetRepository
|
||||
@ -42,5 +44,5 @@ object ZoneChangeHandler {
|
||||
}
|
||||
case Right(_) =>
|
||||
zoneChangeRepository.save(zoneChange.copy(status = ZoneChangeStatus.Synced))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,8 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
||||
zoneRepository: ZoneRepository,
|
||||
dnsLoader: Zone => DnsZoneViewLoader = DnsZoneViewLoader.apply,
|
||||
vinyldnsLoader: (Zone, RecordSetRepository) => VinylDNSZoneViewLoader =
|
||||
VinylDNSZoneViewLoader.apply): ZoneChange => IO[ZoneChange] =
|
||||
VinylDNSZoneViewLoader.apply
|
||||
): ZoneChange => IO[ZoneChange] =
|
||||
zoneChange =>
|
||||
for {
|
||||
_ <- saveZoneAndChange(zoneRepository, zoneChangeRepository, zoneChange) // initial save to store zone status
|
||||
@ -55,7 +56,8 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
||||
recordChangeRepository,
|
||||
zoneChange,
|
||||
dnsLoader,
|
||||
vinyldnsLoader)
|
||||
vinyldnsLoader
|
||||
)
|
||||
_ <- saveZoneAndChange(zoneRepository, zoneChangeRepository, syncChange) // final save to store zone status
|
||||
// as Active
|
||||
} yield syncChange
|
||||
@ -63,13 +65,15 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
||||
def saveZoneAndChange(
|
||||
zoneRepository: ZoneRepository,
|
||||
zoneChangeRepository: ZoneChangeRepository,
|
||||
zoneChange: ZoneChange): IO[ZoneChange] =
|
||||
zoneChange: ZoneChange
|
||||
): IO[ZoneChange] =
|
||||
zoneRepository.save(zoneChange.zone).flatMap {
|
||||
case Left(duplicateZoneError) =>
|
||||
zoneChangeRepository.save(
|
||||
zoneChange.copy(
|
||||
status = ZoneChangeStatus.Failed,
|
||||
systemMessage = Some(duplicateZoneError.message))
|
||||
systemMessage = Some(duplicateZoneError.message)
|
||||
)
|
||||
)
|
||||
case Right(_) =>
|
||||
zoneChangeRepository.save(zoneChange)
|
||||
@ -81,7 +85,8 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
||||
zoneChange: ZoneChange,
|
||||
dnsLoader: Zone => DnsZoneViewLoader = DnsZoneViewLoader.apply,
|
||||
vinyldnsLoader: (Zone, RecordSetRepository) => VinylDNSZoneViewLoader =
|
||||
VinylDNSZoneViewLoader.apply): IO[ZoneChange] =
|
||||
VinylDNSZoneViewLoader.apply
|
||||
): IO[ZoneChange] =
|
||||
monitor("zone.sync") {
|
||||
time(s"zone.sync; zoneName='${zoneChange.zone.name}'") {
|
||||
val zone = zoneChange.zone
|
||||
@ -91,7 +96,8 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
||||
s"zone.sync.loadDnsView; zoneName='${zone.name}'; zoneChange='${zoneChange.id}'"
|
||||
)(dnsLoader(zone).load())
|
||||
val vinyldnsView = time(s"zone.sync.loadVinylDNSView; zoneName='${zone.name}'")(
|
||||
vinyldnsLoader(zone, recordSetRepository).load())
|
||||
vinyldnsLoader(zone, recordSetRepository).load()
|
||||
)
|
||||
val recordSetChanges = (dnsView, vinyldnsView).parTupled.map {
|
||||
case (dnsZoneView, vinylDnsZoneView) => vinylDnsZoneView.diff(dnsZoneView)
|
||||
}
|
||||
@ -101,11 +107,14 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
||||
|
||||
if (changesWithUserIds.isEmpty) {
|
||||
logger.info(
|
||||
s"zone.sync.changes; zoneName='${zone.name}'; changeCount=0; zoneChange='${zoneChange.id}'")
|
||||
s"zone.sync.changes; zoneName='${zone.name}'; changeCount=0; zoneChange='${zoneChange.id}'"
|
||||
)
|
||||
IO.pure(
|
||||
zoneChange.copy(
|
||||
zone.copy(status = ZoneStatus.Active, latestSync = Some(DateTime.now)),
|
||||
status = ZoneChangeStatus.Synced))
|
||||
status = ZoneChangeStatus.Synced
|
||||
)
|
||||
)
|
||||
} else {
|
||||
changesWithUserIds
|
||||
.filter { chg =>
|
||||
@ -120,29 +129,33 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
||||
logger.info(
|
||||
s"Zone sync for zoneName='${zone.name}'; zoneId='${zone.id}'; " +
|
||||
s"zoneChange='${zoneChange.id}' includes the following ${dottedGroup.length} " +
|
||||
s"dotted host records: [$dottedGroupString]")
|
||||
s"dotted host records: [$dottedGroupString]"
|
||||
)
|
||||
}
|
||||
|
||||
logger.info(
|
||||
s"zone.sync.changes; zoneName='${zone.name}'; " +
|
||||
s"changeCount=${changesWithUserIds.size}; zoneChange='${zoneChange.id}'")
|
||||
s"changeCount=${changesWithUserIds.size}; zoneChange='${zoneChange.id}'"
|
||||
)
|
||||
val changeSet = ChangeSet(changesWithUserIds).copy(status = ChangeSetStatus.Applied)
|
||||
|
||||
// we want to make sure we write to both the change repo and record set repo
|
||||
// at the same time as this can take a while
|
||||
val saveRecordChanges = time(s"zone.sync.saveChanges; zoneName='${zone.name}'")(
|
||||
recordChangeRepository.save(changeSet))
|
||||
recordChangeRepository.save(changeSet)
|
||||
)
|
||||
val saveRecordSets = time(s"zone.sync.saveRecordSets; zoneName='${zone.name}'")(
|
||||
recordSetRepository.apply(changeSet))
|
||||
recordSetRepository.apply(changeSet)
|
||||
)
|
||||
|
||||
// join together the results of saving both the record changes as well as the record sets
|
||||
for {
|
||||
_ <- saveRecordChanges
|
||||
_ <- saveRecordSets
|
||||
} yield
|
||||
zoneChange.copy(
|
||||
zone.copy(status = ZoneStatus.Active, latestSync = Some(DateTime.now)),
|
||||
status = ZoneChangeStatus.Synced)
|
||||
} yield zoneChange.copy(
|
||||
zone.copy(status = ZoneStatus.Active, latestSync = Some(DateTime.now)),
|
||||
status = ZoneChangeStatus.Synced
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,11 +164,13 @@ object ZoneSyncHandler extends DnsConversions with Monitored {
|
||||
case Left(e: Throwable) =>
|
||||
logger.error(
|
||||
s"Encountered error syncing ; zoneName='${zoneChange.zone.name}'; zoneChange='${zoneChange.id}'",
|
||||
e)
|
||||
e
|
||||
)
|
||||
// We want to just move back to an active status, do not update latest sync
|
||||
zoneChange.copy(
|
||||
zone = zoneChange.zone.copy(status = ZoneStatus.Active),
|
||||
status = ZoneChangeStatus.Failed)
|
||||
status = ZoneChangeStatus.Failed
|
||||
)
|
||||
case Right(ok) => ok
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,8 @@ object APIMetrics {
|
||||
|
||||
def initialize(
|
||||
settings: APIMetricsSettings,
|
||||
reporter: ScheduledReporter = logReporter): IO[Unit] = IO {
|
||||
reporter: ScheduledReporter = logReporter
|
||||
): IO[Unit] = IO {
|
||||
if (settings.memory.logEnabled) {
|
||||
reporter.start(settings.memory.logSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
|
@ -77,7 +77,8 @@ class EmailNotifier(config: EmailNotifierConfig, session: Session, userRepositor
|
||||
case Some(user: User) if user.email.isDefined =>
|
||||
IO {
|
||||
logger.warn(
|
||||
s"Unable to properly parse email for ${user.id}: ${user.email.getOrElse("<none>")}")
|
||||
s"Unable to properly parse email for ${user.id}: ${user.email.getOrElse("<none>")}"
|
||||
)
|
||||
}
|
||||
case None => IO { logger.warn(s"Unable to find user: ${bc.userId}") }
|
||||
case _ => IO.unit
|
||||
@ -95,16 +96,23 @@ class EmailNotifier(config: EmailNotifierConfig, session: Session, userRepositor
|
||||
|
||||
// For manually reviewed e-mails, add additional info; e-mails are not sent for pending batch changes
|
||||
if (bc.approvalStatus != AutoApproved) {
|
||||
bc.reviewComment.foreach(reviewComment =>
|
||||
sb.append(s"<b>Review comment:</b> $reviewComment <br/>"))
|
||||
bc.reviewTimestamp.foreach(reviewTimestamp =>
|
||||
sb.append(
|
||||
s"<b>Time reviewed:</b> ${reviewTimestamp.toString(DateTimeFormat.fullDateTime)} <br/>"))
|
||||
bc.reviewComment.foreach(
|
||||
reviewComment => sb.append(s"<b>Review comment:</b> $reviewComment <br/>")
|
||||
)
|
||||
bc.reviewTimestamp.foreach(
|
||||
reviewTimestamp =>
|
||||
sb.append(
|
||||
s"<b>Time reviewed:</b> ${reviewTimestamp.toString(DateTimeFormat.fullDateTime)} <br/>"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
bc.cancelledTimestamp.foreach(cancelledTimestamp =>
|
||||
sb.append(
|
||||
s"<b>Time cancelled:</b> ${cancelledTimestamp.toString(DateTimeFormat.fullDateTime)} <br/>"))
|
||||
bc.cancelledTimestamp.foreach(
|
||||
cancelledTimestamp =>
|
||||
sb.append(
|
||||
s"<b>Time cancelled:</b> ${cancelledTimestamp.toString(DateTimeFormat.fullDateTime)} <br/>"
|
||||
)
|
||||
)
|
||||
|
||||
// Single change data table
|
||||
sb.append(s"""<br/><table border = "1">
|
||||
@ -138,7 +146,8 @@ class EmailNotifier(config: EmailNotifierConfig, session: Session, userRepositor
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
_) =>
|
||||
_
|
||||
) =>
|
||||
s"""<tr><td>${index + 1}</td><td>Add</td><td>$typ</td><td>$inputName</td>
|
||||
| <td>$ttl</td><td>${formatRecordData(recordData)}</td><td>$status</td>
|
||||
| <td>${systemMessage.getOrElse("")}</td></tr>"""
|
||||
@ -154,7 +163,8 @@ class EmailNotifier(config: EmailNotifierConfig, session: Session, userRepositor
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
_) =>
|
||||
_
|
||||
) =>
|
||||
val recordDataValue = recordData.map(_.toString).getOrElse("")
|
||||
s"""<tr><td>${index + 1}</td><td>Delete</td><td>$typ</td><td>$inputName</td>
|
||||
| <td></td><td>$recordDataValue</td><td>$status</td><td>${systemMessage
|
||||
|
@ -48,7 +48,8 @@ class SnsNotifier(config: SnsNotifierConfig, sns: AmazonSNS)
|
||||
val request = new PublishRequest(config.topicArn, message)
|
||||
request.addMessageAttributesEntry(
|
||||
"userName",
|
||||
new MessageAttributeValue().withDataType("String").withStringValue(bc.userName))
|
||||
new MessageAttributeValue().withDataType("String").withStringValue(bc.userName)
|
||||
)
|
||||
sns.publish(request)
|
||||
logger.info(s"Sending batch change success; batchChange='${bc.id}'")
|
||||
}.handleErrorWith { e =>
|
||||
|
@ -42,12 +42,17 @@ class SnsNotifierProvider extends NotifierProvider {
|
||||
"Setting up sns notifier client with settings: " +
|
||||
s"service endpoint: ${config.serviceEndpoint}; " +
|
||||
s"signing region: ${config.signingRegion}; " +
|
||||
s"topic name: ${config.topicArn}")
|
||||
s"topic name: ${config.topicArn}"
|
||||
)
|
||||
AmazonSNSClientBuilder.standard
|
||||
.withEndpointConfiguration(
|
||||
new EndpointConfiguration(config.serviceEndpoint, config.signingRegion))
|
||||
.withCredentials(new AWSStaticCredentialsProvider(
|
||||
new BasicAWSCredentials(config.accessKey, config.secretKey)))
|
||||
new EndpointConfiguration(config.serviceEndpoint, config.signingRegion)
|
||||
)
|
||||
.withCredentials(
|
||||
new AWSStaticCredentialsProvider(
|
||||
new BasicAWSCredentials(config.accessKey, config.secretKey)
|
||||
)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
@ -36,5 +36,5 @@ final case class ApiDataAccessor(
|
||||
recordChangeRepository: RecordChangeRepository,
|
||||
zoneChangeRepository: ZoneChangeRepository,
|
||||
zoneRepository: ZoneRepository,
|
||||
batchChangeRepository: BatchChangeRepository)
|
||||
extends DataAccessor
|
||||
batchChangeRepository: BatchChangeRepository
|
||||
) extends DataAccessor
|
||||
|
@ -37,10 +37,12 @@ object ApiDataAccessorProvider extends DataAccessorProvider[ApiDataAccessor] {
|
||||
recordChange,
|
||||
zoneChange,
|
||||
zone,
|
||||
batchChange)
|
||||
batchChange
|
||||
)
|
||||
|
||||
def create(
|
||||
dataStores: List[(DataStoreConfig, DataStore)]): ValidatedNel[String, ApiDataAccessor] =
|
||||
dataStores: List[(DataStoreConfig, DataStore)]
|
||||
): ValidatedNel[String, ApiDataAccessor] =
|
||||
(
|
||||
getRepoOf[UserRepository](dataStores, user),
|
||||
getRepoOf[GroupRepository](dataStores, group),
|
||||
|
@ -192,7 +192,8 @@ object TestDataLoader {
|
||||
id = "shared-zone-group",
|
||||
email = "email",
|
||||
memberIds = Set(sharedZoneUser.id),
|
||||
adminUserIds = Set(sharedZoneUser.id))
|
||||
adminUserIds = Set(sharedZoneUser.id)
|
||||
)
|
||||
|
||||
final val sharedZone = Zone(
|
||||
name = "shared.",
|
||||
@ -207,14 +208,16 @@ object TestDataLoader {
|
||||
id = "global-acl-group-id",
|
||||
email = "email",
|
||||
memberIds = Set(okUser.id, dummyUser.id),
|
||||
adminUserIds = Set(okUser.id, dummyUser.id))
|
||||
adminUserIds = Set(okUser.id, dummyUser.id)
|
||||
)
|
||||
|
||||
final val anotherGlobalACLGroup = Group(
|
||||
name = "globalACLGroup",
|
||||
id = "another-global-acl-group",
|
||||
email = "email",
|
||||
memberIds = Set(testUser.id),
|
||||
adminUserIds = Set(testUser.id))
|
||||
adminUserIds = Set(testUser.id)
|
||||
)
|
||||
|
||||
final val duGroup = Group(
|
||||
name = "duGroup",
|
||||
@ -237,7 +240,8 @@ object TestDataLoader {
|
||||
userRepo: UserRepository,
|
||||
groupRepo: GroupRepository,
|
||||
zoneRepo: ZoneRepository,
|
||||
membershipRepo: MembershipRepository): IO[Unit] =
|
||||
membershipRepo: MembershipRepository
|
||||
): IO[Unit] =
|
||||
for {
|
||||
_ <- (testUser :: okUser :: dummyUser :: sharedZoneUser :: lockedUser :: listGroupUser :: listZonesUser ::
|
||||
listBatchChangeSummariesUser :: listZeroBatchChangeSummariesUser :: zoneHistoryUser :: supportUser ::
|
||||
@ -258,7 +262,8 @@ object TestDataLoader {
|
||||
IO.raiseError(new RuntimeException(msg))
|
||||
} else {
|
||||
logger.info(
|
||||
s"Deleting existing shared zones on startup: ${toDelete.map(z => (z.name, z.id))}")
|
||||
s"Deleting existing shared zones on startup: ${toDelete.map(z => (z.name, z.id))}"
|
||||
)
|
||||
IO.unit
|
||||
}
|
||||
_ <- toDelete.map(zoneRepo.save).parSequence
|
||||
@ -269,17 +274,21 @@ object TestDataLoader {
|
||||
_ <- groupRepo.save(listBatchChangeSummariesGroup)
|
||||
_ <- membershipRepo.addMembers(
|
||||
groupId = "shared-zone-group",
|
||||
memberUserIds = Set(sharedZoneUser.id))
|
||||
memberUserIds = Set(sharedZoneUser.id)
|
||||
)
|
||||
_ <- membershipRepo.addMembers(
|
||||
groupId = "global-acl-group-id",
|
||||
memberUserIds = Set(okUser.id, dummyUser.id))
|
||||
memberUserIds = Set(okUser.id, dummyUser.id)
|
||||
)
|
||||
_ <- membershipRepo.addMembers(
|
||||
groupId = "another-global-acl-group",
|
||||
memberUserIds = Set(testUser.id))
|
||||
memberUserIds = Set(testUser.id)
|
||||
)
|
||||
_ <- membershipRepo.addMembers(groupId = duGroup.id, memberUserIds = duGroup.memberIds)
|
||||
_ <- membershipRepo.addMembers(
|
||||
groupId = listBatchChangeSummariesGroup.id,
|
||||
memberUserIds = listBatchChangeSummariesGroup.memberIds)
|
||||
memberUserIds = listBatchChangeSummariesGroup.memberIds
|
||||
)
|
||||
_ <- zoneRepo.save(sharedZone)
|
||||
_ <- zoneRepo.save(nonTestSharedZone)
|
||||
} yield ()
|
||||
|
@ -47,7 +47,8 @@ object Aws4Authenticator {
|
||||
"akid",
|
||||
"creds",
|
||||
"shs",
|
||||
"sig")
|
||||
"sig"
|
||||
)
|
||||
|
||||
def parseAuthHeader(auth: String): Option[Regex.Match] = aws4AuthRegex.findPrefixMatchOf(auth)
|
||||
}
|
||||
@ -99,12 +100,14 @@ class Aws4Authenticator {
|
||||
req: HttpRequest,
|
||||
authorization: List[String],
|
||||
secret: String,
|
||||
content: String): Boolean = {
|
||||
content: String
|
||||
): Boolean = {
|
||||
val List(
|
||||
_,
|
||||
signatureScope, // signature scope
|
||||
signatureHeaders, // signed headers
|
||||
signatureReceived) = authorization
|
||||
signatureReceived
|
||||
) = authorization
|
||||
val signedHeaders = Set() ++ signatureHeaders.split(';')
|
||||
// convert Date header to canonical form required by AWS
|
||||
val dateTime = iso8601Format.print(getDate(req).get)
|
||||
@ -133,7 +136,8 @@ class Aws4Authenticator {
|
||||
// XXX - need to canonicalize non-trimmed white space
|
||||
def canonicalHeaders(
|
||||
req: HttpRequest,
|
||||
signedHeaderNames: Set[String]): TreeMap[String, Seq[String]] = {
|
||||
signedHeaderNames: Set[String]
|
||||
): TreeMap[String, Seq[String]] = {
|
||||
def getHeaderValue(name: String): Option[String] = name match {
|
||||
case "content-type" => Some(req.entity.contentType.value)
|
||||
case "content-length" => req.entity.contentLengthOption.map(_.toString)
|
||||
@ -160,7 +164,8 @@ class Aws4Authenticator {
|
||||
val stringToSign = joinStrings('\n', aws4AuthScheme, dateTime, scope, hashString(creq))
|
||||
|
||||
hexString(
|
||||
runHmac(Mac.getInstance(hmacAlgorithm))(signingKey(secret, scope).getEncoded, stringToSign))
|
||||
runHmac(Mac.getInstance(hmacAlgorithm))(signingKey(secret, scope).getEncoded, stringToSign)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -208,7 +213,8 @@ class Aws4Authenticator {
|
||||
req: HttpRequest,
|
||||
hdrs: CanonicalHeaders,
|
||||
signedHeaders: String,
|
||||
content: String): String = {
|
||||
content: String
|
||||
): String = {
|
||||
val lines = MutableList[String](req.method.value) ++ canonicalURI(req)
|
||||
|
||||
// add canonical headers
|
||||
|
@ -88,7 +88,8 @@ trait BatchChangeJsonProtocol extends JsonValidation {
|
||||
(js \ "inputName").required[String]("Missing BatchChangeInput.changes.inputName"),
|
||||
recordType,
|
||||
(js \ "ttl").optional[Long],
|
||||
recordType.andThen(extractRecord(_, js \ "record"))).mapN(AddChangeInput.apply)
|
||||
recordType.andThen(extractRecord(_, js \ "record"))
|
||||
).mapN(AddChangeInput.apply)
|
||||
}
|
||||
|
||||
override def toJson(aci: AddChangeInput): JValue =
|
||||
@ -112,7 +113,8 @@ trait BatchChangeJsonProtocol extends JsonValidation {
|
||||
(
|
||||
(js \ "inputName").required[String]("Missing BatchChangeInput.changes.inputName"),
|
||||
recordType,
|
||||
recordData).mapN(DeleteRRSetChangeInput.apply)
|
||||
recordData
|
||||
).mapN(DeleteRRSetChangeInput.apply)
|
||||
}
|
||||
|
||||
override def toJson(drsci: DeleteRRSetChangeInput): JValue =
|
||||
@ -218,7 +220,8 @@ trait BatchChangeJsonProtocol extends JsonValidation {
|
||||
(js \ "reviewComment")
|
||||
.optional[String]
|
||||
.check(
|
||||
s"Comment length must not exceed $MAX_COMMENT_LENGTH characters." -> checkCommentLength)
|
||||
s"Comment length must not exceed $MAX_COMMENT_LENGTH characters." -> checkCommentLength
|
||||
)
|
||||
.map(RejectBatchChangeInput)
|
||||
}
|
||||
|
||||
@ -228,7 +231,8 @@ trait BatchChangeJsonProtocol extends JsonValidation {
|
||||
(js \ "reviewComment")
|
||||
.optional[String]
|
||||
.check(
|
||||
s"Comment length must not exceed $MAX_COMMENT_LENGTH characters." -> checkCommentLength)
|
||||
s"Comment length must not exceed $MAX_COMMENT_LENGTH characters." -> checkCommentLength
|
||||
)
|
||||
.map(ApproveBatchChangeInput)
|
||||
}
|
||||
|
||||
@ -242,7 +246,8 @@ trait BatchChangeJsonProtocol extends JsonValidation {
|
||||
case TXT => js.required[TXTData]("Missing BatchChangeInput.changes.record.text")
|
||||
case MX =>
|
||||
js.required[MXData](
|
||||
"Missing BatchChangeInput.changes.record.preference and BatchChangeInput.changes.record.exchange")
|
||||
"Missing BatchChangeInput.changes.record.preference and BatchChangeInput.changes.record.exchange"
|
||||
)
|
||||
case _ =>
|
||||
s"Unsupported type $typ, valid types include: A, AAAA, CNAME, PTR, TXT, and MX".invalidNel
|
||||
}
|
||||
|
@ -24,8 +24,8 @@ import vinyldns.api.domain.batch._
|
||||
|
||||
class BatchChangeRoute(
|
||||
batchChangeService: BatchChangeServiceAlgebra,
|
||||
val vinylDNSAuthenticator: VinylDNSAuthenticator)
|
||||
extends VinylDNSJsonProtocol
|
||||
val vinylDNSAuthenticator: VinylDNSAuthenticator
|
||||
) extends VinylDNSJsonProtocol
|
||||
with VinylDNSDirectives[BatchChangeErrorResponse] {
|
||||
|
||||
def getRoutes: Route = batchChangeRoute
|
||||
@ -59,7 +59,8 @@ class BatchChangeRoute(
|
||||
authenticateAndExecuteWithEntity[BatchChange, BatchChangeInput](
|
||||
(authPrincipal, batchChangeInput) =>
|
||||
batchChangeService
|
||||
.applyBatchChange(batchChangeInput, authPrincipal, allowManualReview)) { chg =>
|
||||
.applyBatchChange(batchChangeInput, authPrincipal, allowManualReview)
|
||||
) { chg =>
|
||||
complete(StatusCodes.Accepted, chg)
|
||||
}
|
||||
}
|
||||
@ -69,26 +70,31 @@ class BatchChangeRoute(
|
||||
"startFrom".as[Int].?,
|
||||
"maxItems".as[Int].?(MAX_ITEMS_LIMIT),
|
||||
"ignoreAccess".as[Boolean].?(false),
|
||||
"approvalStatus".as[String].?) {
|
||||
"approvalStatus".as[String].?
|
||||
) {
|
||||
(
|
||||
startFrom: Option[Int],
|
||||
maxItems: Int,
|
||||
ignoreAccess: Boolean,
|
||||
approvalStatus: Option[String]) =>
|
||||
approvalStatus: Option[String]
|
||||
) =>
|
||||
{
|
||||
val convertApprovalStatus = approvalStatus.flatMap(BatchChangeApprovalStatus.find)
|
||||
|
||||
handleRejections(invalidQueryHandler) {
|
||||
validate(
|
||||
0 < maxItems && maxItems <= MAX_ITEMS_LIMIT,
|
||||
s"maxItems was $maxItems, maxItems must be between 1 and $MAX_ITEMS_LIMIT, inclusive.") {
|
||||
s"maxItems was $maxItems, maxItems must be between 1 and $MAX_ITEMS_LIMIT, inclusive."
|
||||
) {
|
||||
authenticateAndExecute(
|
||||
batchChangeService.listBatchChangeSummaries(
|
||||
_,
|
||||
startFrom,
|
||||
maxItems,
|
||||
ignoreAccess,
|
||||
convertApprovalStatus)) { summaries =>
|
||||
convertApprovalStatus
|
||||
)
|
||||
) { summaries =>
|
||||
complete(StatusCodes.OK, summaries)
|
||||
}
|
||||
}
|
||||
@ -111,9 +117,9 @@ class BatchChangeRoute(
|
||||
authenticateAndExecuteWithEntity[BatchChange, Option[RejectBatchChangeInput]](
|
||||
(authPrincipal, input) =>
|
||||
batchChangeService
|
||||
.rejectBatchChange(id, authPrincipal, input.getOrElse(RejectBatchChangeInput()))) {
|
||||
chg =>
|
||||
complete(StatusCodes.OK, chg)
|
||||
.rejectBatchChange(id, authPrincipal, input.getOrElse(RejectBatchChangeInput()))
|
||||
) { chg =>
|
||||
complete(StatusCodes.OK, chg)
|
||||
}
|
||||
// TODO: Update response entity to return modified batch change
|
||||
}
|
||||
@ -123,10 +129,8 @@ class BatchChangeRoute(
|
||||
authenticateAndExecuteWithEntity[BatchChange, Option[ApproveBatchChangeInput]](
|
||||
(authPrincipal, input) =>
|
||||
batchChangeService
|
||||
.approveBatchChange(
|
||||
id,
|
||||
authPrincipal,
|
||||
input.getOrElse(ApproveBatchChangeInput()))) { chg =>
|
||||
.approveBatchChange(id, authPrincipal, input.getOrElse(ApproveBatchChangeInput()))
|
||||
) { chg =>
|
||||
complete(StatusCodes.Accepted, chg)
|
||||
// TODO: Update response entity to return modified batch change
|
||||
}
|
||||
|
@ -208,8 +208,8 @@ trait DnsJsonProtocol extends JsonValidation {
|
||||
override def fromJson(js: JValue): ValidatedNel[String, RecordSetListInfo] =
|
||||
(
|
||||
RecordSetInfoSerializer.fromJson(js),
|
||||
(js \ "accessLevel").required[AccessLevel.AccessLevel]("Missing RecordSet.zoneId"))
|
||||
.mapN(RecordSetListInfo.apply)
|
||||
(js \ "accessLevel").required[AccessLevel.AccessLevel]("Missing RecordSet.zoneId")
|
||||
).mapN(RecordSetListInfo.apply)
|
||||
|
||||
override def toJson(rs: RecordSetListInfo): JValue =
|
||||
("type" -> Extraction.decompose(rs.typ)) ~
|
||||
|
@ -41,8 +41,11 @@ object VinylDateParser {
|
||||
format.dateFormat
|
||||
.parse(s)
|
||||
.map(_.getTime)
|
||||
.getOrElse(throw new MappingException(
|
||||
s"Invalid date format $s; provide the date format as YYYY-MM-DDTHH:MM:SSZ"))
|
||||
.getOrElse(
|
||||
throw new MappingException(
|
||||
s"Invalid date format $s; provide the date format as YYYY-MM-DDTHH:MM:SSZ"
|
||||
)
|
||||
)
|
||||
}
|
||||
case object VinylDateTimeSerializer
|
||||
extends CustomSerializer[DateTime](
|
||||
@ -54,7 +57,8 @@ case object VinylDateTimeSerializer
|
||||
}, {
|
||||
case d: DateTime => JString(format.dateFormat.format(d.toDate))
|
||||
}
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
trait JsonValidationSupport extends Json4sSupport {
|
||||
|
||||
@ -72,7 +76,8 @@ trait JsonValidationSupport extends Json4sSupport {
|
||||
IntervalSerializer(),
|
||||
LocalDateSerializer(),
|
||||
LocalTimeSerializer(),
|
||||
PeriodSerializer)
|
||||
PeriodSerializer
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns an adjusted set of serializers excluding the serializer passed in. This is needed otherwise
|
||||
@ -101,7 +106,8 @@ trait JsonValidation extends JsonValidationSupport {
|
||||
|
||||
def JsonV[A: Manifest](
|
||||
validator: JValue => ValidatedNel[String, A],
|
||||
serializer: A => JValue): ValidationSerializer[A] =
|
||||
serializer: A => JValue
|
||||
): ValidationSerializer[A] =
|
||||
new ValidationSerializer[A] {
|
||||
override def fromJson(jv: JValue) = validator(jv)
|
||||
override def toJson(a: A) = serializer(a)
|
||||
@ -215,8 +221,9 @@ trait JsonValidation extends JsonValidationSupport {
|
||||
}
|
||||
}
|
||||
|
||||
def extractEnum[E <: Enumeration](enum: E)(
|
||||
default: => ValidatedNel[String, E#Value]): ValidatedNel[String, E#Value] = {
|
||||
def extractEnum[E <: Enumeration](
|
||||
enum: E
|
||||
)(default: => ValidatedNel[String, E#Value]): ValidatedNel[String, E#Value] = {
|
||||
lazy val invalidMsg =
|
||||
s"Invalid ${enum.getClass.getSimpleName.replace("$", "")}".invalidNel[E#Value]
|
||||
|
||||
|
@ -31,14 +31,16 @@ object MembershipJsonProtocol {
|
||||
email: String,
|
||||
description: Option[String],
|
||||
members: Set[UserInfo],
|
||||
admins: Set[UserInfo])
|
||||
admins: Set[UserInfo]
|
||||
)
|
||||
final case class UpdateGroupInput(
|
||||
id: String,
|
||||
name: String,
|
||||
email: String,
|
||||
description: Option[String],
|
||||
members: Set[UserInfo],
|
||||
admins: Set[UserInfo])
|
||||
admins: Set[UserInfo]
|
||||
)
|
||||
}
|
||||
|
||||
/* Defines the JSON serialization to support the Membership Routes */
|
||||
|
@ -25,8 +25,8 @@ import vinyldns.core.domain.membership.{Group, LockStatus}
|
||||
|
||||
class MembershipRoute(
|
||||
membershipService: MembershipServiceAlgebra,
|
||||
val vinylDNSAuthenticator: VinylDNSAuthenticator)
|
||||
extends VinylDNSJsonProtocol
|
||||
val vinylDNSAuthenticator: VinylDNSAuthenticator
|
||||
) extends VinylDNSJsonProtocol
|
||||
with VinylDNSDirectives[Throwable] {
|
||||
final private val DEFAULT_MAX_ITEMS: Int = 100
|
||||
final private val MAX_ITEMS_LIMIT: Int = 1000
|
||||
@ -62,7 +62,8 @@ class MembershipRoute(
|
||||
input.email,
|
||||
input.description,
|
||||
memberIds = (input.members ++ input.admins).map(_.id),
|
||||
adminUserIds = input.admins.map(_.id))
|
||||
adminUserIds = input.admins.map(_.id)
|
||||
)
|
||||
membershipService.createGroup(group, authPrincipal)
|
||||
} { group =>
|
||||
complete(StatusCodes.OK, GroupInfo(group))
|
||||
@ -73,12 +74,14 @@ class MembershipRoute(
|
||||
"startFrom".?,
|
||||
"maxItems".as[Int].?(DEFAULT_MAX_ITEMS),
|
||||
"groupNameFilter".?,
|
||||
"ignoreAccess".as[Boolean].?(false)) {
|
||||
"ignoreAccess".as[Boolean].?(false)
|
||||
) {
|
||||
(
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
groupNameFilter: Option[String],
|
||||
ignoreAccess: Boolean) =>
|
||||
ignoreAccess: Boolean
|
||||
) =>
|
||||
{
|
||||
handleRejections(invalidQueryHandler) {
|
||||
validate(
|
||||
@ -88,10 +91,11 @@ class MembershipRoute(
|
||||
| and $MAX_ITEMS_LIMIT inclusive"
|
||||
""".stripMargin
|
||||
) {
|
||||
authenticateAndExecute(membershipService
|
||||
.listMyGroups(groupNameFilter, startFrom, maxItems, _, ignoreAccess)) {
|
||||
groups =>
|
||||
complete(StatusCodes.OK, groups)
|
||||
authenticateAndExecute(
|
||||
membershipService
|
||||
.listMyGroups(groupNameFilter, startFrom, maxItems, _, ignoreAccess)
|
||||
) { groups =>
|
||||
complete(StatusCodes.OK, groups)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,7 +114,9 @@ class MembershipRoute(
|
||||
input.description,
|
||||
(input.members ++ input.admins).map(_.id),
|
||||
input.admins.map(_.id),
|
||||
authPrincipal)) { group =>
|
||||
authPrincipal
|
||||
)
|
||||
) { group =>
|
||||
complete(StatusCodes.OK, GroupInfo(group))
|
||||
}
|
||||
}
|
||||
@ -122,9 +128,12 @@ class MembershipRoute(
|
||||
handleRejections(invalidQueryHandler) {
|
||||
validate(
|
||||
0 < maxItems && maxItems <= MAX_ITEMS_LIMIT,
|
||||
s"maxItems was $maxItems, maxItems must be between 0 exclusive and $MAX_ITEMS_LIMIT inclusive") {
|
||||
authenticateAndExecute(membershipService
|
||||
.listMembers(groupId, startFrom, maxItems, _)) { members =>
|
||||
s"maxItems was $maxItems, maxItems must be between 0 exclusive and $MAX_ITEMS_LIMIT inclusive"
|
||||
) {
|
||||
authenticateAndExecute(
|
||||
membershipService
|
||||
.listMembers(groupId, startFrom, maxItems, _)
|
||||
) { members =>
|
||||
complete(StatusCodes.OK, members)
|
||||
}
|
||||
}
|
||||
@ -146,9 +155,12 @@ class MembershipRoute(
|
||||
handleRejections(invalidQueryHandler) {
|
||||
validate(
|
||||
0 < maxItems && maxItems <= MAX_ITEMS_LIMIT,
|
||||
s"maxItems was $maxItems, maxItems must be between 0 and $MAX_ITEMS_LIMIT") {
|
||||
authenticateAndExecute(membershipService
|
||||
.getGroupActivity(groupId, startFrom, maxItems, _)) { activity =>
|
||||
s"maxItems was $maxItems, maxItems must be between 0 and $MAX_ITEMS_LIMIT"
|
||||
) {
|
||||
authenticateAndExecute(
|
||||
membershipService
|
||||
.getGroupActivity(groupId, startFrom, maxItems, _)
|
||||
) { activity =>
|
||||
complete(StatusCodes.OK, activity)
|
||||
}
|
||||
}
|
||||
|
@ -33,12 +33,13 @@ case class ListRecordSetsResponse(
|
||||
startFrom: Option[String] = None,
|
||||
nextId: Option[String] = None,
|
||||
maxItems: Option[Int] = None,
|
||||
recordNameFilter: Option[String] = None)
|
||||
recordNameFilter: Option[String] = None
|
||||
)
|
||||
|
||||
class RecordSetRoute(
|
||||
recordSetService: RecordSetServiceAlgebra,
|
||||
val vinylDNSAuthenticator: VinylDNSAuthenticator)
|
||||
extends VinylDNSJsonProtocol
|
||||
val vinylDNSAuthenticator: VinylDNSAuthenticator
|
||||
) extends VinylDNSJsonProtocol
|
||||
with VinylDNSDirectives[Throwable] {
|
||||
|
||||
def getRoutes: Route = recordSetRoute
|
||||
@ -63,8 +64,9 @@ class RecordSetRoute(
|
||||
|
||||
val recordSetRoute: Route = path("zones" / Segment / "recordsets") { zoneId =>
|
||||
(post & monitor("Endpoint.addRecordSet")) {
|
||||
authenticateAndExecuteWithEntity[ZoneCommandResult, RecordSet]((authPrincipal, recordSet) =>
|
||||
recordSetService.addRecordSet(recordSet, authPrincipal)) { rc =>
|
||||
authenticateAndExecuteWithEntity[ZoneCommandResult, RecordSet](
|
||||
(authPrincipal, recordSet) => recordSetService.addRecordSet(recordSet, authPrincipal)
|
||||
) { rc =>
|
||||
complete(StatusCodes.Accepted, rc)
|
||||
}
|
||||
} ~
|
||||
@ -74,11 +76,13 @@ class RecordSetRoute(
|
||||
handleRejections(invalidQueryHandler) {
|
||||
validate(
|
||||
0 < maxItems && maxItems <= DEFAULT_MAX_ITEMS,
|
||||
s"maxItems was $maxItems, maxItems must be between 0 and $DEFAULT_MAX_ITEMS") {
|
||||
authenticateAndExecute(recordSetService
|
||||
.listRecordSets(zoneId, startFrom, Some(maxItems), recordNameFilter, _)) {
|
||||
rsResponse =>
|
||||
complete(StatusCodes.OK, rsResponse)
|
||||
s"maxItems was $maxItems, maxItems must be between 0 and $DEFAULT_MAX_ITEMS"
|
||||
) {
|
||||
authenticateAndExecute(
|
||||
recordSetService
|
||||
.listRecordSets(zoneId, startFrom, Some(maxItems), recordNameFilter, _)
|
||||
) { rsResponse =>
|
||||
complete(StatusCodes.OK, rsResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,8 +133,10 @@ class RecordSetRoute(
|
||||
errorMsg = s"maxItems was $maxItems, maxItems must be between 0 exclusive " +
|
||||
s"and $DEFAULT_MAX_ITEMS inclusive"
|
||||
) {
|
||||
authenticateAndExecute(recordSetService
|
||||
.listRecordSetChanges(zoneId, startFrom, maxItems, _)) { changes =>
|
||||
authenticateAndExecute(
|
||||
recordSetService
|
||||
.listRecordSetChanges(zoneId, startFrom, maxItems, _)
|
||||
) { changes =>
|
||||
complete(StatusCodes.OK, changes)
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,8 @@ case class CurrentStatus(
|
||||
processingDisabled: Boolean,
|
||||
color: String,
|
||||
keyName: String,
|
||||
version: String)
|
||||
version: String
|
||||
)
|
||||
|
||||
object CurrentStatus {
|
||||
val color = VinylDNSConfig.vinyldnsConfig.getString("color")
|
||||
@ -50,7 +51,8 @@ trait StatusRoute extends Directives {
|
||||
onSuccess(processingDisabled.get.unsafeToFuture()) { isProcessingDisabled =>
|
||||
complete(
|
||||
StatusCodes.OK,
|
||||
CurrentStatus(isProcessingDisabled, color, vinyldnsKeyName, version))
|
||||
CurrentStatus(isProcessingDisabled, color, vinyldnsKeyName, version)
|
||||
)
|
||||
}
|
||||
} ~
|
||||
(post & path("status")) {
|
||||
@ -58,7 +60,8 @@ trait StatusRoute extends Directives {
|
||||
onSuccess(processingDisabled.set(isProcessingDisabled).unsafeToFuture()) {
|
||||
complete(
|
||||
StatusCodes.OK,
|
||||
CurrentStatus(isProcessingDisabled, color, vinyldnsKeyName, version))
|
||||
CurrentStatus(isProcessingDisabled, color, vinyldnsKeyName, version)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,18 +36,20 @@ final case class AccountLocked(reason: String) extends VinylDNSAuthenticationErr
|
||||
trait VinylDNSAuthenticator {
|
||||
def authenticate(
|
||||
ctx: RequestContext,
|
||||
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]]
|
||||
content: String
|
||||
): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]]
|
||||
}
|
||||
|
||||
class ProductionVinylDNSAuthenticator(
|
||||
val authenticator: Aws4Authenticator,
|
||||
val authPrincipalProvider: AuthPrincipalProvider)
|
||||
extends VinylDNSAuthenticator
|
||||
val authPrincipalProvider: AuthPrincipalProvider
|
||||
) extends VinylDNSAuthenticator
|
||||
with Monitored {
|
||||
|
||||
def authenticate(
|
||||
ctx: RequestContext,
|
||||
content: String): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]] =
|
||||
content: String
|
||||
): IO[Either[VinylDNSAuthenticationError, AuthPrincipal]] =
|
||||
// Need to refactor getAuthPrincipal to be an IO[Either[E, A]] instead of how it is implemented.
|
||||
getAuthPrincipal(ctx, content).attempt.flatMap {
|
||||
case Left(e: VinylDNSAuthenticationError) => IO.pure(Left(e))
|
||||
@ -104,7 +106,8 @@ class ProductionVinylDNSAuthenticator(
|
||||
req: HttpRequest,
|
||||
secretKey: String,
|
||||
authHeaderRegex: Regex.Match,
|
||||
content: String): IO[Unit] =
|
||||
content: String
|
||||
): IO[Unit] =
|
||||
authHeaderRegex match {
|
||||
case auth if authenticator.authenticateReq(req, auth.subgroups, secretKey, content) =>
|
||||
IO.unit
|
||||
@ -138,7 +141,8 @@ class ProductionVinylDNSAuthenticator(
|
||||
ctx.request,
|
||||
decryptSecret(authPrincipal.secretKey),
|
||||
regexMatch,
|
||||
content)
|
||||
content
|
||||
)
|
||||
} yield authPrincipal
|
||||
|
||||
def decryptSecret(str: String, crypto: CryptoAlgebra = Crypto.instance): String =
|
||||
@ -149,7 +153,8 @@ class ProductionVinylDNSAuthenticator(
|
||||
case Some(ok) =>
|
||||
if (ok.signedInUser.lockStatus == LockStatus.Locked) {
|
||||
IO.raiseError(
|
||||
AccountLocked(s"Account with username ${ok.signedInUser.userName} is locked"))
|
||||
AccountLocked(s"Account with username ${ok.signedInUser.userName} is locked")
|
||||
)
|
||||
} else IO.pure(ok)
|
||||
case None =>
|
||||
IO.raiseError(AuthRejected(s"Account with accessKey $accessKey specified was not found"))
|
||||
|
@ -42,8 +42,8 @@ trait VinylDNSDirectives[E] extends Directives {
|
||||
def authenticate: Directive1[AuthPrincipal] = extractRequestContext.flatMap { ctx =>
|
||||
extractStrictEntity(10.seconds).flatMap { strictEntity =>
|
||||
onSuccess(
|
||||
vinylDNSAuthenticator.authenticate(ctx, strictEntity.data.utf8String).unsafeToFuture())
|
||||
.flatMap {
|
||||
vinylDNSAuthenticator.authenticate(ctx, strictEntity.data.utf8String).unsafeToFuture()
|
||||
).flatMap {
|
||||
case Right(authPrincipal) ⇒
|
||||
provide(authPrincipal)
|
||||
case Left(e) ⇒
|
||||
@ -159,8 +159,9 @@ trait VinylDNSDirectives[E] extends Directives {
|
||||
* return error to user.
|
||||
* - Invoke service call, f, and return the response to the user.
|
||||
*/
|
||||
def authenticateAndExecuteWithEntity[A, B](f: (AuthPrincipal, B) => EitherT[IO, E, A])(
|
||||
g: A => Route)(implicit um: FromRequestUnmarshaller[B]): Route =
|
||||
def authenticateAndExecuteWithEntity[A, B](
|
||||
f: (AuthPrincipal, B) => EitherT[IO, E, A]
|
||||
)(g: A => Route)(implicit um: FromRequestUnmarshaller[B]): Route =
|
||||
authenticate { authPrincipal =>
|
||||
entity(as[B]) { deserializedEntity =>
|
||||
onSuccess(f(authPrincipal, deserializedEntity).value.unsafeToFuture()) {
|
||||
|
@ -84,32 +84,34 @@ object VinylDNSService {
|
||||
req: HttpRequest =>
|
||||
{
|
||||
val startTime = System.currentTimeMillis()
|
||||
r: Any =>
|
||||
{
|
||||
val endTime = System.currentTimeMillis()
|
||||
val duration = endTime - startTime
|
||||
doNotLog.contains(req.uri.path) match {
|
||||
case false => {
|
||||
r match {
|
||||
case res: HttpResponse =>
|
||||
Some(LogEntry(VinylDNSService.logMessage(req, Some(res), duration), InfoLevel))
|
||||
case res: Complete =>
|
||||
Some(
|
||||
LogEntry(
|
||||
VinylDNSService.logMessage(req, Some(res.response), duration),
|
||||
InfoLevel))
|
||||
case _: Rejected =>
|
||||
Some(LogEntry(VinylDNSService.logMessage(req, None, duration), ErrorLevel))
|
||||
case x => // this can happen if sealRoute below cannot convert into a response.
|
||||
val res = HttpResponse(
|
||||
status = StatusCodes.InternalServerError,
|
||||
entity = HttpEntity(x.toString))
|
||||
Some(LogEntry(VinylDNSService.logMessage(req, Some(res), duration), ErrorLevel))
|
||||
}
|
||||
r: Any => {
|
||||
val endTime = System.currentTimeMillis()
|
||||
val duration = endTime - startTime
|
||||
doNotLog.contains(req.uri.path) match {
|
||||
case false => {
|
||||
r match {
|
||||
case res: HttpResponse =>
|
||||
Some(LogEntry(VinylDNSService.logMessage(req, Some(res), duration), InfoLevel))
|
||||
case res: Complete =>
|
||||
Some(
|
||||
LogEntry(
|
||||
VinylDNSService.logMessage(req, Some(res.response), duration),
|
||||
InfoLevel
|
||||
)
|
||||
)
|
||||
case _: Rejected =>
|
||||
Some(LogEntry(VinylDNSService.logMessage(req, None, duration), ErrorLevel))
|
||||
case x => // this can happen if sealRoute below cannot convert into a response.
|
||||
val res = HttpResponse(
|
||||
status = StatusCodes.InternalServerError,
|
||||
entity = HttpEntity(x.toString)
|
||||
)
|
||||
Some(LogEntry(VinylDNSService.logMessage(req, Some(res), duration), ErrorLevel))
|
||||
}
|
||||
case true => None
|
||||
}
|
||||
case true => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,7 +124,8 @@ object VinylDNSService {
|
||||
HttpResponse(
|
||||
status = StatusCodes.BadRequest,
|
||||
entity = HttpEntity(ContentTypes.`application/json`, msg)
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
.handleNotFound {
|
||||
extractUnmatchedPath { p =>
|
||||
@ -141,8 +144,8 @@ class VinylDNSService(
|
||||
val recordSetService: RecordSetServiceAlgebra,
|
||||
val batchChangeService: BatchChangeServiceAlgebra,
|
||||
val collectorRegistry: CollectorRegistry,
|
||||
authPrincipalProvider: AuthPrincipalProvider)
|
||||
extends PingRoute
|
||||
authPrincipalProvider: AuthPrincipalProvider
|
||||
) extends PingRoute
|
||||
with HealthCheckRoute
|
||||
with BlueGreenRoute
|
||||
with StatusRoute
|
||||
@ -167,9 +170,10 @@ class VinylDNSService(
|
||||
Uri.Path("/color"),
|
||||
Uri.Path("/ping"),
|
||||
Uri.Path("/status"),
|
||||
Uri.Path("/metrics/prometheus"))
|
||||
Uri.Path("/metrics/prometheus")
|
||||
)
|
||||
val unloggedRoutes
|
||||
: Route = healthCheckRoute ~ pingRoute ~ colorRoute ~ statusRoute ~ prometheusRoute
|
||||
: Route = healthCheckRoute ~ pingRoute ~ colorRoute ~ statusRoute ~ prometheusRoute
|
||||
|
||||
val allRoutes: Route = unloggedRoutes ~
|
||||
batchChangeRoute ~
|
||||
|
@ -60,7 +60,8 @@ class ZoneRoute(zoneService: ZoneServiceAlgebra, val vinylDNSAuthenticator: Viny
|
||||
(post & monitor("Endpoint.createZone")) {
|
||||
authenticateAndExecuteWithEntity[ZoneCommandResult, CreateZoneInput](
|
||||
(authPrincipal, createZoneInput) =>
|
||||
zoneService.connectToZone(encrypt(createZoneInput), authPrincipal)) { chg =>
|
||||
zoneService.connectToZone(encrypt(createZoneInput), authPrincipal)
|
||||
) { chg =>
|
||||
complete(StatusCodes.Accepted, chg)
|
||||
}
|
||||
} ~
|
||||
@ -69,19 +70,24 @@ class ZoneRoute(zoneService: ZoneServiceAlgebra, val vinylDNSAuthenticator: Viny
|
||||
"nameFilter".?,
|
||||
"startFrom".as[String].?,
|
||||
"maxItems".as[Int].?(DEFAULT_MAX_ITEMS),
|
||||
"ignoreAccess".as[Boolean].?(false)) {
|
||||
"ignoreAccess".as[Boolean].?(false)
|
||||
) {
|
||||
(
|
||||
nameFilter: Option[String],
|
||||
startFrom: Option[String],
|
||||
maxItems: Int,
|
||||
ignoreAccess: Boolean) =>
|
||||
ignoreAccess: Boolean
|
||||
) =>
|
||||
{
|
||||
handleRejections(invalidQueryHandler) {
|
||||
validate(
|
||||
0 < maxItems && maxItems <= MAX_ITEMS_LIMIT,
|
||||
s"maxItems was $maxItems, maxItems must be between 0 and $MAX_ITEMS_LIMIT") {
|
||||
authenticateAndExecute(zoneService
|
||||
.listZones(_, nameFilter, startFrom, maxItems, ignoreAccess)) { result =>
|
||||
s"maxItems was $maxItems, maxItems must be between 0 and $MAX_ITEMS_LIMIT"
|
||||
) {
|
||||
authenticateAndExecute(
|
||||
zoneService
|
||||
.listZones(_, nameFilter, startFrom, maxItems, ignoreAccess)
|
||||
) { result =>
|
||||
complete(StatusCodes.OK, result)
|
||||
}
|
||||
}
|
||||
@ -111,7 +117,8 @@ class ZoneRoute(zoneService: ZoneServiceAlgebra, val vinylDNSAuthenticator: Viny
|
||||
(put & monitor("Endpoint.updateZone")) {
|
||||
authenticateAndExecuteWithEntity[ZoneCommandResult, UpdateZoneInput](
|
||||
(authPrincipal, updateZoneInput) =>
|
||||
zoneService.updateZone(encrypt(updateZoneInput), authPrincipal)) { chg =>
|
||||
zoneService.updateZone(encrypt(updateZoneInput), authPrincipal)
|
||||
) { chg =>
|
||||
complete(StatusCodes.Accepted, chg)
|
||||
}
|
||||
} ~
|
||||
@ -135,7 +142,8 @@ class ZoneRoute(zoneService: ZoneServiceAlgebra, val vinylDNSAuthenticator: Viny
|
||||
handleRejections(invalidQueryHandler) {
|
||||
validate(
|
||||
0 < maxItems && maxItems <= DEFAULT_MAX_ITEMS,
|
||||
s"maxItems was $maxItems, maxItems must be between 0 exclusive and $DEFAULT_MAX_ITEMS inclusive") {
|
||||
s"maxItems was $maxItems, maxItems must be between 0 exclusive and $DEFAULT_MAX_ITEMS inclusive"
|
||||
) {
|
||||
authenticateAndExecute(zoneService.listZoneChanges(id, _, startFrom, maxItems)) {
|
||||
changes =>
|
||||
complete(StatusCodes.OK, changes)
|
||||
@ -147,14 +155,16 @@ class ZoneRoute(zoneService: ZoneServiceAlgebra, val vinylDNSAuthenticator: Viny
|
||||
} ~
|
||||
path("zones" / Segment / "acl" / "rules") { id =>
|
||||
(put & monitor("Endpoint.addZoneACLRule")) {
|
||||
authenticateAndExecuteWithEntity[ZoneCommandResult, ACLRuleInfo]((authPrincipal, rule) =>
|
||||
zoneService.addACLRule(id, rule, authPrincipal)) { chg =>
|
||||
authenticateAndExecuteWithEntity[ZoneCommandResult, ACLRuleInfo](
|
||||
(authPrincipal, rule) => zoneService.addACLRule(id, rule, authPrincipal)
|
||||
) { chg =>
|
||||
complete(StatusCodes.Accepted, chg)
|
||||
}
|
||||
} ~
|
||||
(delete & monitor("Endpoint.deleteZoneACLRule")) {
|
||||
authenticateAndExecuteWithEntity[ZoneCommandResult, ACLRuleInfo]((authPrincipal, rule) =>
|
||||
zoneService.deleteACLRule(id, rule, authPrincipal)) { chg =>
|
||||
authenticateAndExecuteWithEntity[ZoneCommandResult, ACLRuleInfo](
|
||||
(authPrincipal, rule) => zoneService.deleteACLRule(id, rule, authPrincipal)
|
||||
) { chg =>
|
||||
complete(StatusCodes.Accepted, chg)
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,8 @@ trait CatsHelpers {
|
||||
// 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 = 1.second): Either[E, T] = {
|
||||
duration: FiniteDuration = 1.second
|
||||
): Either[E, T] = {
|
||||
val timeOut = IO.sleep(duration) *> IO(new RuntimeException("Timed out waiting for result"))
|
||||
IO.race(timeOut, f).unsafeRunSync().toOption.get
|
||||
}
|
||||
@ -76,10 +77,12 @@ trait ValidatedBatchMatcherImprovements {
|
||||
MatchResult(
|
||||
left.contains(expectedChange.validNel),
|
||||
s"ValidatedBatch $left does not contain $expectedChange",
|
||||
s"ValidatedBatch $left contains $expectedChange")
|
||||
s"ValidatedBatch $left contains $expectedChange"
|
||||
)
|
||||
}
|
||||
|
||||
def containChangeForValidation(
|
||||
expectedChange: ChangeForValidation): ValidatedBatchContainsChangeForValidation =
|
||||
expectedChange: ChangeForValidation
|
||||
): ValidatedBatchContainsChangeForValidation =
|
||||
new ValidatedBatchContainsChangeForValidation(expectedChange)
|
||||
}
|
||||
|
@ -40,10 +40,12 @@ trait ResultHelpers {
|
||||
// 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 = 1.second): Either[Throwable, T] = {
|
||||
duration: FiniteDuration = 1.second
|
||||
): Either[Throwable, T] = {
|
||||
|
||||
val timeOut = IO.sleep(duration) *> IO(
|
||||
TimeoutException("Timed out waiting for result").asInstanceOf[Throwable])
|
||||
TimeoutException("Timed out waiting for result").asInstanceOf[Throwable]
|
||||
)
|
||||
|
||||
IO.race(timeOut, f.handleError(e => Left(e))).unsafeRunSync() match {
|
||||
case Left(e) => Left(e)
|
||||
@ -61,7 +63,8 @@ trait ResultHelpers {
|
||||
// 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 = 1.second): Throwable = awaitResultOf(f, duration).swap.toOption.get
|
||||
duration: FiniteDuration = 1.second
|
||||
): Throwable = awaitResultOf(f, duration).swap.toOption.get
|
||||
|
||||
def leftValue[T](t: Either[Throwable, T]): Throwable = t.swap.toOption.get
|
||||
|
||||
@ -71,7 +74,8 @@ trait ResultHelpers {
|
||||
object ValidationTestImprovements extends PropSpec with Matchers with ValidatedMatchers {
|
||||
|
||||
implicit class ValidatedNelTestImprovements[DomainValidationError, A](
|
||||
value: ValidatedNel[DomainValidationError, A]) {
|
||||
value: ValidatedNel[DomainValidationError, A]
|
||||
) {
|
||||
|
||||
def failures: List[DomainValidationError] = value match {
|
||||
case Invalid(e) => e.toList
|
||||
|
@ -42,7 +42,8 @@ class VinylDNSConfigSpec extends WordSpec with Matchers {
|
||||
zone,
|
||||
batchChange,
|
||||
user,
|
||||
recordSet)
|
||||
recordSet
|
||||
)
|
||||
}
|
||||
"assign the correct dynamodb repositories" in {
|
||||
val dynamodbConfig =
|
||||
|
@ -56,8 +56,10 @@ class CommandHandlerSpec
|
||||
private implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)
|
||||
private implicit val cs: ContextShift[IO] =
|
||||
IO.contextShift(scala.concurrent.ExecutionContext.global)
|
||||
private val messages = for { i <- 0 to 10 } yield
|
||||
TestCommandMessage(pendingCreateAAAA, i.toString)
|
||||
private val messages = for { i <- 0 to 10 } yield TestCommandMessage(
|
||||
pendingCreateAAAA,
|
||||
i.toString
|
||||
)
|
||||
private val count = MessageCount(10).right.value
|
||||
|
||||
private val mockZoneChangeProcessor = mock[ZoneChange => IO[ZoneChange]]
|
||||
@ -224,7 +226,8 @@ class CommandHandlerSpec
|
||||
"use the default zone connection when the change zone connection is not defined" in {
|
||||
val noConnChange =
|
||||
pendingCreateAAAA.copy(
|
||||
zone = pendingCreateAAAA.zone.copy(connection = None, transferConnection = None))
|
||||
zone = pendingCreateAAAA.zone.copy(connection = None, transferConnection = None)
|
||||
)
|
||||
val default = defaultConn.copy(primaryServer = "default.conn.test.com")
|
||||
val defaultConnProcessor =
|
||||
CommandHandler.processChangeRequests(
|
||||
|
@ -79,7 +79,8 @@ class ReverseZoneHelpersSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
ReverseZoneHelpers.convertPTRtoIPv6(zn1, rs1.name) shouldBe "2001:0db8:0000:0000:0000:0000:0567:89ab"
|
||||
}
|
||||
@ -92,7 +93,8 @@ class ReverseZoneHelpersSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
ReverseZoneHelpers.convertPTRtoIPv6(zn1, rs1.name) shouldBe "2001:0db8:0000:0000:0000:0000:0567:89ab"
|
||||
}
|
||||
@ -105,7 +107,8 @@ class ReverseZoneHelpersSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
ReverseZoneHelpers.convertPTRtoIPv6(zn1, rs1.name) shouldBe "2001:0db8:0000:0000:0000:0000:0567:89ab"
|
||||
}
|
||||
@ -179,7 +182,8 @@ class ReverseZoneHelpersSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val znFalse = Zone("5.b.e.f.9.d.2.f.9.5.c.c.7.4.a.a.8.ip6.arpa.", "email")
|
||||
val rsFalse = RecordSet(
|
||||
"id",
|
||||
@ -187,7 +191,8 @@ class ReverseZoneHelpersSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
ReverseZoneHelpers.recordsetIsWithinCidrMask(mask, znTrue, rsTrue.name) shouldBe true
|
||||
ReverseZoneHelpers.recordsetIsWithinCidrMask(mask, znFalse, rsFalse.name) shouldBe false
|
||||
@ -202,7 +207,8 @@ class ReverseZoneHelpersSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val znFalse = Zone("5.b.e.f.9.d.2.f.9.5.c.c.7.4.a.a.8.ip6.arpa.", "email")
|
||||
val rsFalse = RecordSet(
|
||||
"id",
|
||||
@ -210,7 +216,8 @@ class ReverseZoneHelpersSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
ReverseZoneHelpers.recordsetIsWithinCidrMask(mask, znTrue, rsTrue.name) shouldBe true
|
||||
ReverseZoneHelpers.recordsetIsWithinCidrMask(mask, znFalse, rsFalse.name) shouldBe false
|
||||
@ -225,7 +232,8 @@ class ReverseZoneHelpersSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val znFalse = Zone("5.b.e.f.9.d.2.f.9.5.c.c.7.4.a.a.8.ip6.arpa.", "email")
|
||||
val rsFalse = RecordSet(
|
||||
"id",
|
||||
@ -233,7 +241,8 @@ class ReverseZoneHelpersSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
ReverseZoneHelpers.recordsetIsWithinCidrMask(mask, znTrue, rsTrue.name) shouldBe true
|
||||
ReverseZoneHelpers.recordsetIsWithinCidrMask(mask, znFalse, rsFalse.name) shouldBe false
|
||||
|
@ -91,7 +91,8 @@ class AccessValidationsSpec
|
||||
"return true if the user is an admin or super user" in {
|
||||
val auth = okAuth.copy(
|
||||
signedInUser = okAuth.signedInUser.copy(isSuper = true),
|
||||
memberGroupIds = Seq.empty)
|
||||
memberGroupIds = Seq.empty
|
||||
)
|
||||
accessValidationTest.canSeeZone(auth, okZone) should be(right)
|
||||
}
|
||||
|
||||
@ -105,7 +106,8 @@ class AccessValidationsSpec
|
||||
"return true if the user is a support admin" in {
|
||||
val supportAuth = okAuth.copy(
|
||||
signedInUser = okAuth.signedInUser.copy(isSupport = true),
|
||||
memberGroupIds = Seq.empty)
|
||||
memberGroupIds = Seq.empty
|
||||
)
|
||||
accessValidationTest.canSeeZone(supportAuth, okZone) should be(right)
|
||||
}
|
||||
|
||||
@ -119,7 +121,8 @@ class AccessValidationsSpec
|
||||
"return a NotAuthorizedError if the user is not admin or super user" in {
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canChangeZone(okAuth, zoneNotAuthorized.name, zoneNotAuthorized.adminGroupId))
|
||||
.canChangeZone(okAuth, zoneNotAuthorized.name, zoneNotAuthorized.adminGroupId)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -130,17 +133,20 @@ class AccessValidationsSpec
|
||||
"return true if the user is an admin and a support user" in {
|
||||
val auth = okAuth.copy(
|
||||
signedInUser = okAuth.signedInUser.copy(isSupport = true),
|
||||
memberGroupIds = Seq(okGroup.id))
|
||||
memberGroupIds = Seq(okGroup.id)
|
||||
)
|
||||
accessValidationTest.canChangeZone(auth, okZone.name, okZone.adminGroupId) should be(right)
|
||||
}
|
||||
|
||||
"return a NotAuthorizedError if the user is a support user only" in {
|
||||
val auth = okAuth.copy(
|
||||
signedInUser = okAuth.signedInUser.copy(isSupport = true),
|
||||
memberGroupIds = Seq.empty)
|
||||
memberGroupIds = Seq.empty
|
||||
)
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canChangeZone(auth, okZone.name, okZone.adminGroupId))
|
||||
.canChangeZone(auth, okZone.name, okZone.adminGroupId)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
}
|
||||
@ -149,25 +155,29 @@ class AccessValidationsSpec
|
||||
"return a NotAuthorizedError if the user has AccessLevel.NoAccess" in {
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canAddRecordSet(userAuthNone, "test", RecordType.A, zoneInNone))
|
||||
.canAddRecordSet(userAuthNone, "test", RecordType.A, zoneInNone)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
"return a NotAuthorizedError if the user has AccessLevel.Read" in {
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canAddRecordSet(userAuthRead, "test", RecordType.A, zoneInRead))
|
||||
.canAddRecordSet(userAuthRead, "test", RecordType.A, zoneInRead)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
"return true if the user has AccessLevel.Write" in {
|
||||
accessValidationTest.canAddRecordSet(userAuthWrite, "test", RecordType.A, zoneInWrite) should be(
|
||||
right)
|
||||
right
|
||||
)
|
||||
}
|
||||
|
||||
"return true if the user has AccessLevel.Delete" in {
|
||||
accessValidationTest.canAddRecordSet(userAuthDelete, "test", RecordType.A, zoneInDelete) should be(
|
||||
right)
|
||||
right
|
||||
)
|
||||
}
|
||||
|
||||
"return a NotAuthorizedError if the user is a test user in a non-test zone" in {
|
||||
@ -175,7 +185,8 @@ class AccessValidationsSpec
|
||||
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canAddRecordSet(auth, "test", RecordType.A, okZone))
|
||||
.canAddRecordSet(auth, "test", RecordType.A, okZone)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -197,7 +208,8 @@ class AccessValidationsSpec
|
||||
userAuthWrite,
|
||||
"someRecordName",
|
||||
RecordType.NS,
|
||||
zoneInWrite) should be(right)
|
||||
zoneInWrite
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return true if recordset matches the global ACL" in {
|
||||
@ -205,7 +217,8 @@ class AccessValidationsSpec
|
||||
userAuthGlobalAcl,
|
||||
"someRecordName",
|
||||
RecordType.A,
|
||||
okZone.copy(name = "foo.comcast.com")) should be(right)
|
||||
okZone.copy(name = "foo.comcast.com")
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return false if the record set does not match the global ACL" in {
|
||||
@ -213,7 +226,8 @@ class AccessValidationsSpec
|
||||
|
||||
val error = leftValue(
|
||||
globalAclTest
|
||||
.canAddRecordSet(auth, "test-foo", RecordType.A, okZone))
|
||||
.canAddRecordSet(auth, "test-foo", RecordType.A, okZone)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -231,14 +245,16 @@ class AccessValidationsSpec
|
||||
"return a NotAuthorizedError if the user has AccessLevel.NoAccess" in {
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canUpdateRecordSet(userAuthNone, "test", RecordType.A, zoneInNone, None))
|
||||
.canUpdateRecordSet(userAuthNone, "test", RecordType.A, zoneInNone, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
"return a NotAuthorizedError if the user has AccessLevel.Read" in {
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canUpdateRecordSet(userAuthRead, "test", RecordType.A, zoneInRead, None))
|
||||
.canUpdateRecordSet(userAuthRead, "test", RecordType.A, zoneInRead, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -248,7 +264,8 @@ class AccessValidationsSpec
|
||||
"test",
|
||||
RecordType.A,
|
||||
zoneInWrite,
|
||||
None) should be(right)
|
||||
None
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return true if the user has AccessLevel.Delete" in {
|
||||
@ -257,7 +274,8 @@ class AccessValidationsSpec
|
||||
val userAcl = ACLRule(AccessLevel.Delete, userId = Some(userAuth.userId), groupId = None)
|
||||
val zoneIn = zoneNotAuthorized.copy(acl = ZoneACL(Set(userAcl)))
|
||||
accessValidationTest.canUpdateRecordSet(userAuth, "test", RecordType.A, zoneIn, None) should be(
|
||||
right)
|
||||
right
|
||||
)
|
||||
}
|
||||
|
||||
"return true if the user is in the owner group and the zone is shared" in {
|
||||
@ -293,7 +311,8 @@ class AccessValidationsSpec
|
||||
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canUpdateRecordSet(auth, "test", RecordType.A, okZone, None))
|
||||
.canUpdateRecordSet(auth, "test", RecordType.A, okZone, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -311,7 +330,8 @@ class AccessValidationsSpec
|
||||
"someRecordName",
|
||||
RecordType.A,
|
||||
okZone.copy(name = "foo.comcast.com"),
|
||||
None) should be(right)
|
||||
None
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return false if the record set does not match the global ACL" in {
|
||||
@ -319,7 +339,8 @@ class AccessValidationsSpec
|
||||
|
||||
val error = leftValue(
|
||||
globalAclTest
|
||||
.canUpdateRecordSet(auth, "test-foo", RecordType.A, okZone, None))
|
||||
.canUpdateRecordSet(auth, "test-foo", RecordType.A, okZone, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -339,21 +360,24 @@ class AccessValidationsSpec
|
||||
"return a NotAuthorizedError if the user has AccessLevel.NoAccess" in {
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canDeleteRecordSet(userAuthNone, "test", RecordType.A, zoneInNone, None))
|
||||
.canDeleteRecordSet(userAuthNone, "test", RecordType.A, zoneInNone, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
"return a NotAuthorizedError if the user has AccessLevel.Read" in {
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canDeleteRecordSet(userAuthRead, "test", RecordType.A, zoneInRead, None))
|
||||
.canDeleteRecordSet(userAuthRead, "test", RecordType.A, zoneInRead, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
"return a NotAuthorizedError if the user has AccessLevel.Write" in {
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canDeleteRecordSet(userAuthWrite, "test", RecordType.A, zoneInWrite, None))
|
||||
.canDeleteRecordSet(userAuthWrite, "test", RecordType.A, zoneInWrite, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -363,7 +387,8 @@ class AccessValidationsSpec
|
||||
"test",
|
||||
RecordType.A,
|
||||
zoneInDelete,
|
||||
None) should be(right)
|
||||
None
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return a NotAuthorizedError if the user is a test user in a non-test zone" in {
|
||||
@ -371,7 +396,8 @@ class AccessValidationsSpec
|
||||
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canDeleteRecordSet(auth, "test", RecordType.A, okZone, None))
|
||||
.canDeleteRecordSet(auth, "test", RecordType.A, okZone, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -389,7 +415,8 @@ class AccessValidationsSpec
|
||||
"someRecordName",
|
||||
RecordType.A,
|
||||
okZone.copy(name = "foo.comcast.com"),
|
||||
None) should be(right)
|
||||
None
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return false if the record set does not match the global ACL" in {
|
||||
@ -397,7 +424,8 @@ class AccessValidationsSpec
|
||||
|
||||
val error = leftValue(
|
||||
globalAclTest
|
||||
.canDeleteRecordSet(auth, "test-foo", RecordType.A, okZone, None))
|
||||
.canDeleteRecordSet(auth, "test-foo", RecordType.A, okZone, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -417,18 +445,21 @@ class AccessValidationsSpec
|
||||
"return a NotAuthorizedError if the user has AccessLevel.NoAccess" in {
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canViewRecordSet(userAuthNone, "test", RecordType.A, zoneInNone, None))
|
||||
.canViewRecordSet(userAuthNone, "test", RecordType.A, zoneInNone, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
"return true if the user has AccessLevel.Read" in {
|
||||
accessValidationTest.canViewRecordSet(userAuthRead, "test", RecordType.A, zoneInRead, None) should be(
|
||||
right)
|
||||
right
|
||||
)
|
||||
}
|
||||
|
||||
"return true if the user has AccessLevel.Write" in {
|
||||
accessValidationTest.canViewRecordSet(userAuthWrite, "test", RecordType.A, zoneInWrite, None) should be(
|
||||
right)
|
||||
right
|
||||
)
|
||||
}
|
||||
|
||||
"return true if the user has AccessLevel.Delete" in {
|
||||
@ -437,7 +468,8 @@ class AccessValidationsSpec
|
||||
"test",
|
||||
RecordType.A,
|
||||
zoneInDelete,
|
||||
None) should be(right)
|
||||
None
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return true if the user is in the recordSet owner group and the recordSet is in a shared zone" in {
|
||||
@ -447,7 +479,8 @@ class AccessValidationsSpec
|
||||
recordSet.name,
|
||||
recordSet.typ,
|
||||
sharedZone,
|
||||
recordSet.ownerGroupId) should be(right)
|
||||
recordSet.ownerGroupId
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return a NotAuthorizedError if the user is in the recordSet owner group but it is not in a shared zone" in {
|
||||
@ -458,7 +491,9 @@ class AccessValidationsSpec
|
||||
recordSet.name,
|
||||
recordSet.typ,
|
||||
zoneNotAuthorized,
|
||||
recordSet.ownerGroupId))
|
||||
recordSet.ownerGroupId
|
||||
)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -467,7 +502,8 @@ class AccessValidationsSpec
|
||||
|
||||
val error = leftValue(
|
||||
accessValidationTest
|
||||
.canViewRecordSet(auth, "test", RecordType.A, okZone, None))
|
||||
.canViewRecordSet(auth, "test", RecordType.A, okZone, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -485,7 +521,8 @@ class AccessValidationsSpec
|
||||
"someRecordName",
|
||||
RecordType.A,
|
||||
okZone.copy(name = "foo.comcast.com"),
|
||||
None) should be(right)
|
||||
None
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return false if the record set does not match the global ACL" in {
|
||||
@ -493,7 +530,8 @@ class AccessValidationsSpec
|
||||
|
||||
val error = leftValue(
|
||||
globalAclTest
|
||||
.canViewRecordSet(auth, "test-foo", RecordType.A, okZone, None))
|
||||
.canViewRecordSet(auth, "test-foo", RecordType.A, okZone, None)
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -518,7 +556,8 @@ class AccessValidationsSpec
|
||||
mockRecordSet.name,
|
||||
mockRecordSet.typ,
|
||||
okZone,
|
||||
None)
|
||||
None
|
||||
)
|
||||
result shouldBe AccessLevel.Delete
|
||||
}
|
||||
|
||||
@ -530,7 +569,8 @@ class AccessValidationsSpec
|
||||
sharedZoneRecord.name,
|
||||
sharedZoneRecord.typ,
|
||||
sharedZone,
|
||||
sharedZoneRecord.ownerGroupId)
|
||||
sharedZoneRecord.ownerGroupId
|
||||
)
|
||||
result shouldBe AccessLevel.Delete
|
||||
}
|
||||
|
||||
@ -541,7 +581,8 @@ class AccessValidationsSpec
|
||||
sharedZoneRecordNoOwnerGroup.name,
|
||||
RecordType.AAAA,
|
||||
sharedZone,
|
||||
None)
|
||||
None
|
||||
)
|
||||
result shouldBe AccessLevel.Delete
|
||||
}
|
||||
|
||||
@ -552,7 +593,8 @@ class AccessValidationsSpec
|
||||
sharedZoneRecordNotApprovedRecordType.name,
|
||||
RecordType.MX,
|
||||
sharedZone,
|
||||
None)
|
||||
None
|
||||
)
|
||||
result shouldBe AccessLevel.NoAccess
|
||||
}
|
||||
|
||||
@ -563,7 +605,8 @@ class AccessValidationsSpec
|
||||
notSharedZoneRecordWithOwnerGroup.name,
|
||||
notSharedZoneRecordWithOwnerGroup.typ,
|
||||
zoneNotAuthorized,
|
||||
notSharedZoneRecordWithOwnerGroup.ownerGroupId)
|
||||
notSharedZoneRecordWithOwnerGroup.ownerGroupId
|
||||
)
|
||||
result shouldBe AccessLevel.NoAccess
|
||||
}
|
||||
|
||||
@ -573,7 +616,8 @@ class AccessValidationsSpec
|
||||
"test",
|
||||
RecordType.A,
|
||||
okZone.copy(adminGroupId = "not-a-real-group"),
|
||||
None)
|
||||
None
|
||||
)
|
||||
result shouldBe AccessLevel.Read
|
||||
}
|
||||
|
||||
@ -583,7 +627,8 @@ class AccessValidationsSpec
|
||||
"test",
|
||||
RecordType.A,
|
||||
okZone.copy(adminGroupId = "not-a-real-group"),
|
||||
None)
|
||||
None
|
||||
)
|
||||
result shouldBe AccessLevel.Read
|
||||
}
|
||||
|
||||
@ -608,7 +653,8 @@ class AccessValidationsSpec
|
||||
mockRecordSet.name,
|
||||
mockRecordSet.typ,
|
||||
zoneIn,
|
||||
None)
|
||||
None
|
||||
)
|
||||
result shouldBe AccessLevel.Write
|
||||
}
|
||||
|
||||
@ -713,7 +759,8 @@ class AccessValidationsSpec
|
||||
RecordType.A,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val aclRuleA =
|
||||
ACLRule(AccessLevel.Read, userId = Some(okAuth.userId), recordTypes = Set(RecordType.A))
|
||||
|
||||
@ -731,7 +778,8 @@ class AccessValidationsSpec
|
||||
RecordType.A,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val aclRuleA =
|
||||
ACLRule(AccessLevel.Read, userId = Some(okAuth.userId), recordTypes = Set(RecordType.AAAA))
|
||||
|
||||
@ -749,13 +797,15 @@ class AccessValidationsSpec
|
||||
RecordType.A,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val aclRuleA =
|
||||
ACLRule(AccessLevel.Write, userId = Some(okAuth.userId), recordTypes = Set(RecordType.A))
|
||||
val aclRuleMany = ACLRule(
|
||||
AccessLevel.Read,
|
||||
userId = Some(okAuth.userId),
|
||||
recordTypes = Set(RecordType.A, RecordType.AAAA))
|
||||
recordTypes = Set(RecordType.A, RecordType.AAAA)
|
||||
)
|
||||
|
||||
val zoneAcl = ZoneACL(Set(aclRuleA, aclRuleMany))
|
||||
val zone = Zone("name", "email", acl = zoneAcl)
|
||||
@ -771,7 +821,8 @@ class AccessValidationsSpec
|
||||
RecordType.A,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val aclRuleA =
|
||||
ACLRule(AccessLevel.Write, userId = Some(okAuth.userId), recordTypes = Set(RecordType.A))
|
||||
val aclRuleAll =
|
||||
@ -791,7 +842,8 @@ class AccessValidationsSpec
|
||||
RecordType.A,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val aclRule = userReadAcl.copy(recordMask = Some("rs.*"))
|
||||
|
||||
val zoneAcl = ZoneACL(Set(aclRule))
|
||||
@ -808,7 +860,8 @@ class AccessValidationsSpec
|
||||
RecordType.A,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val aclRule = userReadAcl.copy(recordMask = Some("bad.*"))
|
||||
|
||||
val zoneAcl = ZoneACL(Set(aclRule))
|
||||
@ -825,7 +878,8 @@ class AccessValidationsSpec
|
||||
RecordType.A,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val aclRuleRM = userReadAcl.copy(recordMask = Some("rs.*"))
|
||||
val aclRuleAll = userWriteAcl.copy(recordMask = None)
|
||||
|
||||
@ -891,7 +945,8 @@ class AccessValidationsSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val aclRule = userReadAcl.copy(recordMask = Some(".*0.*"))
|
||||
|
||||
val zoneAcl = ZoneACL(Set(aclRule))
|
||||
@ -913,7 +968,8 @@ class AccessValidationsSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val znFalse = Zone("5.b.e.f.9.d.2.f.9.5.c.c.7.4.a.a.8.ip6.arpa.", "email")
|
||||
val rsFalse = RecordSet(
|
||||
"id",
|
||||
@ -921,7 +977,8 @@ class AccessValidationsSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
accessValidationTest.ruleAppliesToRecordName(rsTrue.name, rsTrue.typ, znTrue, aclRule) shouldBe true
|
||||
accessValidationTest.ruleAppliesToRecordName(rsFalse.name, rsFalse.typ, znFalse, aclRule) shouldBe false
|
||||
@ -937,7 +994,8 @@ class AccessValidationsSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val znFalse = Zone("5.b.e.f.9.d.2.f.9.5.c.c.7.4.a.a.8.ip6.arpa.", "email")
|
||||
val rsFalse = RecordSet(
|
||||
"id",
|
||||
@ -945,7 +1003,8 @@ class AccessValidationsSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
accessValidationTest.ruleAppliesToRecordName(rsTrue.name, rsTrue.typ, znTrue, aclRule) shouldBe true
|
||||
accessValidationTest.ruleAppliesToRecordName(rsFalse.name, rsFalse.typ, znFalse, aclRule) shouldBe false
|
||||
@ -961,7 +1020,8 @@ class AccessValidationsSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
val znFalse = Zone("5.b.e.f.9.d.2.f.9.5.c.c.7.4.a.a.8.ip6.arpa.", "email")
|
||||
val rsFalse = RecordSet(
|
||||
"id",
|
||||
@ -969,7 +1029,8 @@ class AccessValidationsSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
accessValidationTest.ruleAppliesToRecordName(rsTrue.name, rsTrue.typ, znTrue, aclRule) shouldBe true
|
||||
accessValidationTest.ruleAppliesToRecordName(rsFalse.name, rsFalse.typ, znFalse, aclRule) shouldBe false
|
||||
@ -984,7 +1045,8 @@ class AccessValidationsSpec
|
||||
RecordType.PTR,
|
||||
200,
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now)
|
||||
DateTime.now
|
||||
)
|
||||
|
||||
accessValidationTest.ruleAppliesToRecordName(rs.name, rs.typ, zn, aclRule) shouldBe true
|
||||
}
|
||||
@ -994,7 +1056,8 @@ class AccessValidationsSpec
|
||||
val multiRecordList = List("rs1", "rs2", "rs3").map { name =>
|
||||
RecordSetInfo(
|
||||
RecordSet("zoneId", name, RecordType.A, 100, RecordSetStatus.Active, DateTime.now()),
|
||||
None)
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
"return access level DELETE if the user is admin/super of the zone" in {
|
||||
@ -1020,7 +1083,8 @@ class AccessValidationsSpec
|
||||
"return access level Read if there is no ACL rule for the user and user is a support admin" in {
|
||||
val supportAuth = okAuth.copy(
|
||||
signedInUser = okAuth.signedInUser.copy(isSupport = true),
|
||||
memberGroupIds = Seq.empty)
|
||||
memberGroupIds = Seq.empty
|
||||
)
|
||||
|
||||
val zone = Zone("test", "test")
|
||||
|
||||
@ -1034,7 +1098,8 @@ class AccessValidationsSpec
|
||||
val rs1 =
|
||||
RecordSetInfo(
|
||||
RecordSet("zoneId", "rs1", RecordType.A, 100, RecordSetStatus.Active, DateTime.now()),
|
||||
None)
|
||||
None
|
||||
)
|
||||
val rs2 = rs1.copy(name = "rs2")
|
||||
val rs3 = rs1.copy(name = "rs3")
|
||||
val recordList = List(rs1, rs2, rs3)
|
||||
@ -1051,7 +1116,8 @@ class AccessValidationsSpec
|
||||
val expected = List(
|
||||
RecordSetListInfo(rs1, AccessLevel.Write),
|
||||
RecordSetListInfo(rs2, AccessLevel.NoAccess),
|
||||
RecordSetListInfo(rs3, AccessLevel.Read))
|
||||
RecordSetListInfo(rs3, AccessLevel.Read)
|
||||
)
|
||||
result shouldBe expected
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,8 @@ class GlobalAclSpec
|
||||
import vinyldns.core.TestZoneData._
|
||||
|
||||
private val globalAcls = GlobalAcls(
|
||||
List(GlobalAcl(List(okGroup.id, dummyGroup.id), List(".*foo.*", ".*bar.com."))))
|
||||
List(GlobalAcl(List(okGroup.id, dummyGroup.id), List(".*foo.*", ".*bar.com.")))
|
||||
)
|
||||
|
||||
"isAuthorized" should {
|
||||
"return false if the acl list is empty" in {
|
||||
@ -54,7 +55,8 @@ class GlobalAclSpec
|
||||
"foo",
|
||||
RecordType.PTR,
|
||||
zoneIp4,
|
||||
List(PTRData("foo.com"), PTRData("bar.com"))) shouldBe true
|
||||
List(PTRData("foo.com"), PTRData("bar.com"))
|
||||
) shouldBe true
|
||||
}
|
||||
"return false for a PTR record if one of the PTR records does not match an acl" in {
|
||||
globalAcls.isAuthorized(
|
||||
@ -62,7 +64,8 @@ class GlobalAclSpec
|
||||
"foo",
|
||||
RecordType.PTR,
|
||||
zoneIp4,
|
||||
List(PTRData("foo.com"), PTRData("blah.net"))) shouldBe false
|
||||
List(PTRData("foo.com"), PTRData("blah.net"))
|
||||
) shouldBe false
|
||||
}
|
||||
"return false for a PTR record if the record data is empty" in {
|
||||
globalAcls.isAuthorized(okAuth, "foo", RecordType.PTR, zoneIp4, Nil) shouldBe false
|
||||
|
@ -40,7 +40,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
name: String,
|
||||
recordData: RecordData,
|
||||
typ: RecordType = A,
|
||||
zone: Zone = okZone) = {
|
||||
zone: Zone = okZone
|
||||
) = {
|
||||
val fqdn = s"$name.${zone.name}"
|
||||
SingleAddChange(
|
||||
Some(zone.id),
|
||||
@ -53,7 +54,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
SingleChangeStatus.Pending,
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
private def makeSingleDeleteRRSetChange(name: String, typ: RecordType, zone: Zone = okZone) = {
|
||||
@ -68,25 +70,30 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
SingleChangeStatus.Pending,
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
private def makeAddChangeForValidation(
|
||||
recordName: String,
|
||||
recordData: RecordData,
|
||||
typ: RecordType = RecordType.A): AddChangeForValidation =
|
||||
typ: RecordType = RecordType.A
|
||||
): AddChangeForValidation =
|
||||
AddChangeForValidation(
|
||||
okZone,
|
||||
s"$recordName",
|
||||
AddChangeInput(s"$recordName.ok.", typ, Some(123), recordData))
|
||||
AddChangeInput(s"$recordName.ok.", typ, Some(123), recordData)
|
||||
)
|
||||
|
||||
private def makeDeleteRRSetChangeForValidation(
|
||||
recordName: String,
|
||||
typ: RecordType = RecordType.A): DeleteRRSetChangeForValidation =
|
||||
typ: RecordType = RecordType.A
|
||||
): DeleteRRSetChangeForValidation =
|
||||
DeleteRRSetChangeForValidation(
|
||||
okZone,
|
||||
s"$recordName",
|
||||
DeleteRRSetChangeInput(s"$recordName.ok.", typ))
|
||||
DeleteRRSetChangeInput(s"$recordName.ok.", typ)
|
||||
)
|
||||
|
||||
private val addSingleChangesGood = List(
|
||||
makeSingleAddChange("one", AData("1.1.1.1")),
|
||||
@ -184,7 +191,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("1.1.1.1")))
|
||||
List(AData("1.1.1.1"))
|
||||
)
|
||||
private val cnameToDelete = RecordSet(
|
||||
okZone.id,
|
||||
"cnameToDelete",
|
||||
@ -193,7 +201,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(CNAMEData("old.com.")))
|
||||
List(CNAMEData("old.com."))
|
||||
)
|
||||
private val aToUpdate = RecordSet(
|
||||
okZone.id,
|
||||
"aToUpdate",
|
||||
@ -202,7 +211,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("1.1.1.1")))
|
||||
List(AData("1.1.1.1"))
|
||||
)
|
||||
private val cnameToUpdate = RecordSet(
|
||||
okZone.id,
|
||||
"cnameToUpdate",
|
||||
@ -211,7 +221,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(CNAMEData("old.com.")))
|
||||
List(CNAMEData("old.com."))
|
||||
)
|
||||
private val txtToUpdate = RecordSet(
|
||||
okZone.id,
|
||||
"txtToUpdate",
|
||||
@ -220,7 +231,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(TXTData("old")))
|
||||
List(TXTData("old"))
|
||||
)
|
||||
private val txtToDelete = RecordSet(
|
||||
okZone.id,
|
||||
"txtToDelete",
|
||||
@ -229,7 +241,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(TXTData("test")))
|
||||
List(TXTData("test"))
|
||||
)
|
||||
private val mxToUpdate = RecordSet(
|
||||
okZone.id,
|
||||
"mxToUpdate",
|
||||
@ -238,7 +251,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(MXData(1, "old.com.")))
|
||||
List(MXData(1, "old.com."))
|
||||
)
|
||||
private val mxToDelete = RecordSet(
|
||||
okZone.id,
|
||||
"mxToDelete",
|
||||
@ -247,7 +261,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(MXData(1, "delete.com.")))
|
||||
List(MXData(1, "delete.com."))
|
||||
)
|
||||
private def existingRecordSets =
|
||||
ExistingRecordSets(
|
||||
List(
|
||||
@ -258,7 +273,9 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
txtToUpdate,
|
||||
txtToDelete,
|
||||
mxToUpdate,
|
||||
mxToDelete))
|
||||
mxToDelete
|
||||
)
|
||||
)
|
||||
|
||||
private val batchChangeRepo = new InMemoryBatchChangeRepository
|
||||
private val underTest = new BatchChangeConverter(batchChangeRepo, TestMessageQueue)
|
||||
@ -272,15 +289,18 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
None,
|
||||
DateTime.now,
|
||||
addSingleChangesGood,
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
val result = rightResultOf(
|
||||
underTest
|
||||
.sendBatchForProcessing(
|
||||
batchChange,
|
||||
existingZones,
|
||||
ChangeForValidationMap(addChangeForValidationGood.map(_.validNel), existingRecordSets),
|
||||
None)
|
||||
.value)
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
val rsChanges = result.recordSetChanges
|
||||
|
||||
// validate recordset changes generated from batch
|
||||
@ -303,7 +323,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
None,
|
||||
DateTime.now,
|
||||
deleteSingleChangesGood,
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
val result = rightResultOf(
|
||||
underTest
|
||||
.sendBatchForProcessing(
|
||||
@ -311,9 +332,12 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
existingZones,
|
||||
ChangeForValidationMap(
|
||||
deleteRRSetChangeForValidationGood.map(_.validNel),
|
||||
existingRecordSets),
|
||||
None)
|
||||
.value)
|
||||
existingRecordSets
|
||||
),
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
val rsChanges = result.recordSetChanges
|
||||
|
||||
// validate recordset change basics generated from batch
|
||||
@ -323,7 +347,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
cnameToDelete.name,
|
||||
rsChanges,
|
||||
batchChange,
|
||||
RecordSetChangeType.Delete)
|
||||
RecordSetChangeType.Delete
|
||||
)
|
||||
validateRecordSetChange(txtToDelete.name, rsChanges, batchChange, RecordSetChangeType.Delete)
|
||||
validateRecordSetChange(mxToDelete.name, rsChanges, batchChange, RecordSetChangeType.Delete)
|
||||
|
||||
@ -332,7 +357,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
aToDelete.id,
|
||||
cnameToDelete.id,
|
||||
txtToDelete.id,
|
||||
mxToDelete.id)
|
||||
mxToDelete.id
|
||||
)
|
||||
|
||||
// validate statuses unchanged as returned
|
||||
result.batchChange shouldBe batchChange
|
||||
@ -346,7 +372,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
None,
|
||||
DateTime.now,
|
||||
updateSingleChangesGood,
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
val result = rightResultOf(
|
||||
underTest
|
||||
.sendBatchForProcessing(
|
||||
@ -354,9 +381,12 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
existingZones,
|
||||
ChangeForValidationMap(
|
||||
updateChangeForValidationGood.map(_.validNel),
|
||||
existingRecordSets),
|
||||
None)
|
||||
.value)
|
||||
existingRecordSets
|
||||
),
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
val rsChanges = result.recordSetChanges
|
||||
|
||||
// validate recordset changes generated from batch
|
||||
@ -371,7 +401,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
aToUpdate.id,
|
||||
cnameToUpdate.id,
|
||||
txtToUpdate.id,
|
||||
mxToUpdate.id)
|
||||
mxToUpdate.id
|
||||
)
|
||||
|
||||
// validate statuses unchanged as returned
|
||||
result.batchChange shouldBe batchChange
|
||||
@ -388,15 +419,18 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
None,
|
||||
DateTime.now,
|
||||
changes,
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
val result = rightResultOf(
|
||||
underTest
|
||||
.sendBatchForProcessing(
|
||||
batchChange,
|
||||
existingZones,
|
||||
ChangeForValidationMap(changeForValidation.map(_.validNel), existingRecordSets),
|
||||
None)
|
||||
.value)
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
val rsChanges = result.recordSetChanges
|
||||
|
||||
// validate recordset changes generated from batch
|
||||
@ -436,15 +470,18 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
val result = rightResultOf(
|
||||
underTest
|
||||
.sendBatchForProcessing(
|
||||
batchChange,
|
||||
existingZones,
|
||||
ChangeForValidationMap(List(), existingRecordSets),
|
||||
None)
|
||||
.value)
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
result.batchChange shouldBe batchChange
|
||||
}
|
||||
@ -457,15 +494,18 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
None,
|
||||
DateTime.now,
|
||||
singleChangesOneBad,
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
val result = rightResultOf(
|
||||
underTest
|
||||
.sendBatchForProcessing(
|
||||
batchWithBadChange,
|
||||
existingZones,
|
||||
ChangeForValidationMap(changeForValidationOneBad.map(_.validNel), existingRecordSets),
|
||||
None)
|
||||
.value)
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
val rsChanges = result.recordSetChanges
|
||||
|
||||
rsChanges.length shouldBe 3
|
||||
@ -500,7 +540,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
None,
|
||||
DateTime.now,
|
||||
singleChangesOneUnsupported,
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
val result = leftResultOf(
|
||||
underTest
|
||||
.sendBatchForProcessing(
|
||||
@ -508,9 +549,12 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
existingZones,
|
||||
ChangeForValidationMap(
|
||||
changeForValidationOneUnsupported.map(_.validNel),
|
||||
existingRecordSets),
|
||||
None)
|
||||
.value)
|
||||
existingRecordSets
|
||||
),
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
result shouldBe an[BatchConversionError]
|
||||
|
||||
val notSaved: Option[BatchChange] =
|
||||
@ -533,7 +577,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
Set(singleAddChange.recordData),
|
||||
okUser.id,
|
||||
None,
|
||||
None)
|
||||
None
|
||||
)
|
||||
result shouldBe defined
|
||||
result.foreach(_.recordSet.ownerGroupId shouldBe None)
|
||||
}
|
||||
@ -548,7 +593,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
Set(singleAddChange.recordData),
|
||||
okUser.id,
|
||||
None,
|
||||
ownerGroupId)
|
||||
ownerGroupId
|
||||
)
|
||||
result shouldBe defined
|
||||
result.foreach(_.recordSet.ownerGroupId shouldBe ownerGroupId)
|
||||
}
|
||||
@ -563,7 +609,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
Set(singleAddChange.recordData),
|
||||
okUser.id,
|
||||
None,
|
||||
ownerGroupId)
|
||||
ownerGroupId
|
||||
)
|
||||
result shouldBe defined
|
||||
result.foreach(_.recordSet.ownerGroupId shouldBe None)
|
||||
}
|
||||
@ -630,7 +677,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
name: String,
|
||||
recordSetChanges: List[RecordSetChange],
|
||||
batchChange: BatchChange,
|
||||
typ: RecordSetChangeType) = {
|
||||
typ: RecordSetChangeType
|
||||
) = {
|
||||
val singleChangesOut = batchChange.changes.filter { change =>
|
||||
change.recordName match {
|
||||
case Some(rn) if rn == name => true
|
||||
@ -659,7 +707,8 @@ class BatchChangeConverterSpec extends WordSpec with Matchers with CatsHelpers {
|
||||
private def validateRecordDataCombination(
|
||||
name: String,
|
||||
recordSetChanges: List[RecordSetChange],
|
||||
batchChange: BatchChange) = {
|
||||
batchChange: BatchChange
|
||||
) = {
|
||||
val singleChangesOut = batchChange.changes.filter { change =>
|
||||
change.recordName match {
|
||||
case Some(rn) if rn == name => true
|
||||
|
@ -42,7 +42,8 @@ class BatchChangeInputSpec extends WordSpec with Matchers {
|
||||
val input = BatchChangeInput(
|
||||
None,
|
||||
List(changeA, changeAAAA, changeCname, changeADotted, changeAAAADotted, changeCnameDotted),
|
||||
None)
|
||||
None
|
||||
)
|
||||
|
||||
input.changes(0).inputName shouldBe "apex.test.com."
|
||||
input.changes(1).inputName shouldBe "aaaa.test.com."
|
||||
@ -141,7 +142,8 @@ class BatchChangeInputSpec extends WordSpec with Matchers {
|
||||
BatchChangeInput(
|
||||
Some("comments"),
|
||||
List(expectedAddChange, expectedDelChange),
|
||||
Some("owner"))
|
||||
Some("owner")
|
||||
)
|
||||
|
||||
BatchChangeInput(change) shouldBe expectedInput
|
||||
}
|
||||
|
@ -87,7 +87,8 @@ class BatchChangeServiceSpec
|
||||
"cname.55.144.10.in-addr.arpa",
|
||||
RecordType.CNAME,
|
||||
ttl,
|
||||
CNAMEData("testing.cname.com."))
|
||||
CNAMEData("testing.cname.com.")
|
||||
)
|
||||
private val ptrAdd = AddChangeInput("10.144.55.11", RecordType.PTR, ttl, PTRData("ptr"))
|
||||
private val ptrAdd2 = AddChangeInput("10.144.55.255", RecordType.PTR, ttl, PTRData("ptr"))
|
||||
private val ptrDelegatedAdd = AddChangeInput("192.0.2.193", RecordType.PTR, ttl, PTRData("ptr"))
|
||||
@ -121,7 +122,8 @@ class BatchChangeServiceSpec
|
||||
private val ptrV6AddForVal = AddChangeForValidation(
|
||||
ipv6PTRZone,
|
||||
"9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0",
|
||||
ptrV6Add)
|
||||
ptrV6Add
|
||||
)
|
||||
|
||||
private val defaultv6Discovery = new V6DiscoveryNibbleBoundaries(5, 16)
|
||||
|
||||
@ -136,7 +138,8 @@ class BatchChangeServiceSpec
|
||||
SingleChangeStatus.Pending,
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
None
|
||||
)
|
||||
|
||||
private val singleChangeGood = SingleAddChange(
|
||||
Some(baseZone.id),
|
||||
@ -184,14 +187,16 @@ class BatchChangeServiceSpec
|
||||
batchChange: BatchChange,
|
||||
existingZones: ExistingZones,
|
||||
groupedChanges: ChangeForValidationMap,
|
||||
ownerGroupId: Option[String]): BatchResult[BatchConversionOutput] =
|
||||
ownerGroupId: Option[String]
|
||||
): BatchResult[BatchConversionOutput] =
|
||||
batchChange.comments match {
|
||||
case Some("conversionError") => BatchConversionError(pendingChange).toLeftBatchResult
|
||||
case Some("checkConverter") =>
|
||||
// hacking reviewComment to determine if things were sent to the converter
|
||||
BatchConversionOutput(
|
||||
batchChange.copy(reviewComment = Some("batchSentToConverter")),
|
||||
List()).toRightBatchResult
|
||||
List()
|
||||
).toRightBatchResult
|
||||
case _ => BatchConversionOutput(batchChange, List()).toRightBatchResult
|
||||
}
|
||||
}
|
||||
@ -202,7 +207,8 @@ class BatchChangeServiceSpec
|
||||
zoneId: String,
|
||||
name: String,
|
||||
typ: RecordType,
|
||||
recordData: Option[List[RecordData]] = None): RecordSet = {
|
||||
recordData: Option[List[RecordData]] = None
|
||||
): RecordSet = {
|
||||
val records = recordData.getOrElse(List())
|
||||
RecordSet(zoneId, name, typ, 100, RecordSetStatus.Active, DateTime.now(), records = records)
|
||||
}
|
||||
@ -214,7 +220,8 @@ class BatchChangeServiceSpec
|
||||
nonApexAddForVal.zone.id,
|
||||
nonApexAddForVal.recordName,
|
||||
TXT,
|
||||
recordData = Some(List(TXTData("some data"))))
|
||||
recordData = Some(List(TXTData("some data")))
|
||||
)
|
||||
private val existingPtr: RecordSet =
|
||||
makeRS(ptrAddForVal.zone.id, ptrAddForVal.recordName, PTR)
|
||||
private val existingPtrDelegated: RecordSet =
|
||||
@ -233,7 +240,8 @@ class BatchChangeServiceSpec
|
||||
(existingPtrDelegated, "193.64/25.55.144.10.in-addr.arpa."),
|
||||
(
|
||||
existingPtrV6,
|
||||
"9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.2.ip6.arpa."),
|
||||
"9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.2.ip6.arpa."
|
||||
),
|
||||
(deletedZoneApex, "apex.test.com.")
|
||||
)
|
||||
|
||||
@ -304,7 +312,8 @@ class BatchChangeServiceSpec
|
||||
otherPTRZone,
|
||||
ipv6PTR16Zone,
|
||||
ipv6PTR17Zone,
|
||||
ipv6PTR18Zone)
|
||||
ipv6PTR18Zone
|
||||
)
|
||||
|
||||
override def getZonesByNames(zoneNames: Set[String]): IO[Set[Zone]] =
|
||||
IO.pure(dbZones.filter(zn => zoneNames.contains(zn.name)))
|
||||
@ -338,7 +347,8 @@ class BatchChangeServiceSpec
|
||||
override def getUsers(
|
||||
userIds: Set[String],
|
||||
startFrom: Option[String],
|
||||
maxItems: Option[Int]): IO[ListUsersResults] =
|
||||
maxItems: Option[Int]
|
||||
): IO[ListUsersResults] =
|
||||
IO.pure(ListUsersResults(Seq(superUser), None))
|
||||
}
|
||||
|
||||
@ -415,7 +425,8 @@ class BatchChangeServiceSpec
|
||||
"2001:0000:0000:0001:0000:ff00:0042:8329",
|
||||
RecordType.PTR,
|
||||
ttl,
|
||||
PTRData("ptr"))
|
||||
PTRData("ptr")
|
||||
)
|
||||
|
||||
val input = BatchChangeInput(None, List(ptr), Some(authGrp.id))
|
||||
|
||||
@ -444,7 +455,8 @@ class BatchChangeServiceSpec
|
||||
"2001:0000:0000:0001:0000:ff00:0042:8329",
|
||||
RecordType.PTR,
|
||||
ttl,
|
||||
PTRData("ptr"))
|
||||
PTRData("ptr")
|
||||
)
|
||||
|
||||
val input = BatchChangeInput(None, List(ptr), Some(authGrp.id))
|
||||
|
||||
@ -476,7 +488,8 @@ class BatchChangeServiceSpec
|
||||
|
||||
result shouldBe
|
||||
InvalidBatchChangeInput(
|
||||
List(NotAMemberOfOwnerGroup(ownerGroupId, notAuth.signedInUser.userName)))
|
||||
List(NotAMemberOfOwnerGroup(ownerGroupId, notAuth.signedInUser.userName))
|
||||
)
|
||||
}
|
||||
|
||||
"succeed if owner group ID is provided and user is a member of the group" in {
|
||||
@ -493,7 +506,8 @@ class BatchChangeServiceSpec
|
||||
rightResultOf(
|
||||
underTest
|
||||
.applyBatchChange(input, AuthPrincipal(superUser, Seq(baseZone.adminGroupId)), true)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result.changes.length shouldBe 1
|
||||
}
|
||||
@ -525,7 +539,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(pendingChange),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
doReturn(IO.unit).when(mockNotifier).notify(any[Notification[_]])
|
||||
@ -536,8 +551,10 @@ class BatchChangeServiceSpec
|
||||
.rejectBatchChange(
|
||||
batchChange.id,
|
||||
supportUserAuth,
|
||||
RejectBatchChangeInput(Some("review comment")))
|
||||
.value)
|
||||
RejectBatchChangeInput(Some("review comment"))
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
result.status shouldBe BatchChangeStatus.Rejected
|
||||
result.approvalStatus shouldBe BatchChangeApprovalStatus.ManuallyRejected
|
||||
@ -557,7 +574,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(pendingChange),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
val rejectAuth = AuthPrincipal(supportUser.copy(isTest = true), List())
|
||||
|
||||
@ -565,7 +583,8 @@ class BatchChangeServiceSpec
|
||||
rightResultOf(
|
||||
underTestManualEnabled
|
||||
.rejectBatchChange(batchChange.id, rejectAuth, RejectBatchChangeInput(Some("bad")))
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result.status shouldBe BatchChangeStatus.Rejected
|
||||
}
|
||||
@ -577,7 +596,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(pendingChange),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
val rejectAuth = AuthPrincipal(supportUser.copy(isTest = true), List())
|
||||
|
||||
@ -585,7 +605,8 @@ class BatchChangeServiceSpec
|
||||
leftResultOf(
|
||||
underTestManualEnabled
|
||||
.rejectBatchChange(batchChange.id, rejectAuth, RejectBatchChangeInput(Some("bad")))
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result shouldBe UserNotAuthorizedError(batchChange.id)
|
||||
}
|
||||
@ -597,14 +618,16 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result =
|
||||
leftResultOf(
|
||||
underTest
|
||||
.rejectBatchChange(batchChange.id, supportUserAuth, RejectBatchChangeInput())
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result shouldBe BatchChangeNotPendingReview(batchChange.id)
|
||||
}
|
||||
@ -617,12 +640,14 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result =
|
||||
leftResultOf(
|
||||
underTest.rejectBatchChange(batchChange.id, auth, RejectBatchChangeInput()).value)
|
||||
underTest.rejectBatchChange(batchChange.id, auth, RejectBatchChangeInput()).value
|
||||
)
|
||||
|
||||
result shouldBe UserNotAuthorizedError(batchChange.id)
|
||||
}
|
||||
@ -635,12 +660,14 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result =
|
||||
leftResultOf(
|
||||
underTest.rejectBatchChange(batchChange.id, auth, RejectBatchChangeInput()).value)
|
||||
underTest.rejectBatchChange(batchChange.id, auth, RejectBatchChangeInput()).value
|
||||
)
|
||||
|
||||
result shouldBe UserNotAuthorizedError(batchChange.id)
|
||||
}
|
||||
@ -665,8 +692,10 @@ class BatchChangeServiceSpec
|
||||
.approveBatchChange(
|
||||
batchChangeNeedsApproval.id,
|
||||
supportUserAuth,
|
||||
ApproveBatchChangeInput(Some("reviewed!")))
|
||||
.value)
|
||||
ApproveBatchChangeInput(Some("reviewed!"))
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
result.userId shouldBe batchChangeNeedsApproval.userId
|
||||
result.userName shouldBe batchChangeNeedsApproval.userName
|
||||
@ -691,8 +720,10 @@ class BatchChangeServiceSpec
|
||||
.approveBatchChange(
|
||||
batchChangeNeedsApproval.id,
|
||||
auth,
|
||||
ApproveBatchChangeInput(Some("reviewed!")))
|
||||
.value)
|
||||
ApproveBatchChangeInput(Some("reviewed!"))
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
result shouldBe UserNotAuthorizedError(batchChangeNeedsApproval.id)
|
||||
}
|
||||
@ -705,7 +736,8 @@ class BatchChangeServiceSpec
|
||||
leftResultOf(
|
||||
underTest
|
||||
.approveBatchChange(batchChange.id, supportUserAuth, ApproveBatchChangeInput())
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result shouldBe BatchChangeNotPendingReview(batchChange.id)
|
||||
}
|
||||
@ -717,7 +749,8 @@ class BatchChangeServiceSpec
|
||||
leftResultOf(
|
||||
underTest
|
||||
.approveBatchChange(batchChangeNeedsApproval.id, auth, ApproveBatchChangeInput())
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result shouldBe UserNotAuthorizedError(batchChangeNeedsApproval.id)
|
||||
}
|
||||
@ -729,7 +762,8 @@ class BatchChangeServiceSpec
|
||||
|
||||
val result =
|
||||
leftResultOf(
|
||||
underTest.approveBatchChange(batchChange.id, auth, ApproveBatchChangeInput()).value)
|
||||
underTest.approveBatchChange(batchChange.id, auth, ApproveBatchChangeInput()).value
|
||||
)
|
||||
|
||||
result shouldBe UserNotAuthorizedError(batchChange.id)
|
||||
}
|
||||
@ -742,14 +776,16 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result =
|
||||
leftResultOf(
|
||||
underTest
|
||||
.approveBatchChange(batchChange.id, superUserAuth, ApproveBatchChangeInput())
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result shouldBe BatchRequesterNotFound("someOtherUserId", "someUn")
|
||||
}
|
||||
@ -764,14 +800,16 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(pendingChange),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result =
|
||||
rightResultOf(
|
||||
underTest
|
||||
.cancelBatchChange(batchChange.id, auth)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result.status shouldBe BatchChangeStatus.Cancelled
|
||||
result.approvalStatus shouldBe BatchChangeApprovalStatus.Cancelled
|
||||
@ -787,7 +825,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(pendingChange),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result =
|
||||
@ -804,14 +843,16 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result =
|
||||
leftResultOf(
|
||||
underTest
|
||||
.cancelBatchChange(batchChange.id, auth)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result shouldBe BatchChangeNotPendingReview(batchChange.id)
|
||||
}
|
||||
@ -824,14 +865,16 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result =
|
||||
leftResultOf(
|
||||
underTest
|
||||
.cancelBatchChange(batchChange.id, supportUserAuth)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result shouldBe BatchChangeNotPendingReview(batchChange.id)
|
||||
}
|
||||
@ -846,7 +889,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result = rightResultOf(underTest.getBatchChange(batchChange.id, auth).value)
|
||||
@ -867,7 +911,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result = leftResultOf(underTest.getBatchChange(batchChange.id, notAuth).value)
|
||||
@ -882,7 +927,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val authSuper = notAuth.copy(signedInUser = notAuth.signedInUser.copy(isSuper = true))
|
||||
@ -899,7 +945,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val authSuper = notAuth.copy(signedInUser = notAuth.signedInUser.copy(isSupport = true))
|
||||
@ -918,7 +965,8 @@ class BatchChangeServiceSpec
|
||||
DateTime.now,
|
||||
List(),
|
||||
ownerGroupId = Some(okGroup.id),
|
||||
BatchChangeApprovalStatus.AutoApproved)
|
||||
BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result = rightResultOf(underTest.getBatchChange(batchChange.id, auth).value)
|
||||
@ -934,7 +982,8 @@ class BatchChangeServiceSpec
|
||||
DateTime.now,
|
||||
List(),
|
||||
ownerGroupId = Some("no-existo"),
|
||||
BatchChangeApprovalStatus.AutoApproved)
|
||||
BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result = rightResultOf(underTest.getBatchChange(batchChange.id, auth).value)
|
||||
@ -973,7 +1022,8 @@ class BatchChangeServiceSpec
|
||||
ptrAddForVal.validNel,
|
||||
ptrDelegatedAddForVal.validNel,
|
||||
ptrV6AddForVal.validNel,
|
||||
error)
|
||||
error
|
||||
)
|
||||
val zoneMap = ExistingZones(Set(apexZone, baseZone, ptrZone, delegatedPTRZone, ipv6PTRZone))
|
||||
val result = await(underTest.getExistingRecordSets(in, zoneMap))
|
||||
|
||||
@ -989,7 +1039,8 @@ class BatchChangeServiceSpec
|
||||
ptrAddForVal.validNel,
|
||||
ptrDelegatedAddForVal.validNel,
|
||||
ptrV6AddForVal.validNel,
|
||||
error)
|
||||
error
|
||||
)
|
||||
val zoneMap = ExistingZones(Set(apexZone, baseZone, ptrZone, ipv6PTRZone))
|
||||
val result = await(underTest.getExistingRecordSets(in, zoneMap))
|
||||
|
||||
@ -1184,7 +1235,8 @@ class BatchChangeServiceSpec
|
||||
underTest.zoneDiscovery(List(onlyApexAddA.validNel), ExistingZones(Set(onlyApexZone)))
|
||||
|
||||
result should containChangeForValidation(
|
||||
AddChangeForValidation(onlyApexZone, "only.apex.exists.", onlyApexAddA))
|
||||
AddChangeForValidation(onlyApexZone, "only.apex.exists.", onlyApexAddA)
|
||||
)
|
||||
}
|
||||
|
||||
"map the batch change input to the base zone if only the base zone exists for A records" in {
|
||||
@ -1192,7 +1244,8 @@ class BatchChangeServiceSpec
|
||||
underTest.zoneDiscovery(List(onlyBaseAddAAAA.validNel), ExistingZones(Set(onlyBaseZone)))
|
||||
|
||||
result should containChangeForValidation(
|
||||
AddChangeForValidation(onlyBaseZone, "have", onlyBaseAddAAAA))
|
||||
AddChangeForValidation(onlyBaseZone, "have", onlyBaseAddAAAA)
|
||||
)
|
||||
}
|
||||
|
||||
"map the batch change input to the base zone only for CNAME records" in {
|
||||
@ -1218,7 +1271,8 @@ class BatchChangeServiceSpec
|
||||
|
||||
val discovered = underTest.zoneDiscovery(
|
||||
List(aApex.validNel, aNormal.validNel, aDotted.validNel),
|
||||
ExistingZones(Set(apexZone, baseZone)))
|
||||
ExistingZones(Set(apexZone, baseZone))
|
||||
)
|
||||
|
||||
discovered.getValid shouldBe expected
|
||||
}
|
||||
@ -1239,7 +1293,8 @@ class BatchChangeServiceSpec
|
||||
|
||||
val discovered = underTest.zoneDiscovery(
|
||||
List(txtApex.validNel, txtNormal.validNel, txtDotted.validNel),
|
||||
ExistingZones(Set(apexZone, baseZone)))
|
||||
ExistingZones(Set(apexZone, baseZone))
|
||||
)
|
||||
|
||||
discovered.getValid shouldBe expected
|
||||
}
|
||||
@ -1266,29 +1321,35 @@ class BatchChangeServiceSpec
|
||||
"handle mapping a combination of zone discovery successes and failures" in {
|
||||
val result = underTest.zoneDiscovery(
|
||||
List(apexAddA.validNel, onlyApexAddA.validNel, onlyBaseAddAAAA.validNel, cnameAdd.validNel),
|
||||
ExistingZones(Set(apexZone, baseZone, onlyBaseZone)))
|
||||
ExistingZones(Set(apexZone, baseZone, onlyBaseZone))
|
||||
)
|
||||
|
||||
result.head should beValid[ChangeForValidation](apexAddForVal)
|
||||
result(1) should haveInvalid[DomainValidationError](ZoneDiscoveryError("only.apex.exists."))
|
||||
result(2) should beValid[ChangeForValidation](
|
||||
AddChangeForValidation(onlyBaseZone, "have", onlyBaseAddAAAA))
|
||||
AddChangeForValidation(onlyBaseZone, "have", onlyBaseAddAAAA)
|
||||
)
|
||||
result(3) should beValid[ChangeForValidation](
|
||||
AddChangeForValidation(baseZone, "cname", cnameAdd))
|
||||
AddChangeForValidation(baseZone, "cname", cnameAdd)
|
||||
)
|
||||
}
|
||||
|
||||
"map the batch change input to the delegated PTR zone for PTR records (ipv4)" in {
|
||||
val result = underTest.zoneDiscovery(
|
||||
List(ptrAdd.validNel),
|
||||
ExistingZones(Set(delegatedPTRZone, ptrZone)))
|
||||
ExistingZones(Set(delegatedPTRZone, ptrZone))
|
||||
)
|
||||
|
||||
result should containChangeForValidation(
|
||||
AddChangeForValidation(delegatedPTRZone, "11", ptrAdd))
|
||||
AddChangeForValidation(delegatedPTRZone, "11", ptrAdd)
|
||||
)
|
||||
}
|
||||
|
||||
"map the batch change input to the non delegated PTR zone for PTR records (ipv4)" in {
|
||||
val result = underTest.zoneDiscovery(
|
||||
List(ptrAdd2.validNel),
|
||||
ExistingZones(Set(delegatedPTRZone, ptrZone)))
|
||||
ExistingZones(Set(delegatedPTRZone, ptrZone))
|
||||
)
|
||||
|
||||
result should containChangeForValidation(AddChangeForValidation(ptrZone, "255", ptrAdd2))
|
||||
}
|
||||
@ -1302,7 +1363,8 @@ class BatchChangeServiceSpec
|
||||
"return an error for PTR if there are zone matches for the IP but no match on the record name" in {
|
||||
val result = underTest.zoneDiscovery(
|
||||
List(ptrAdd.validNel),
|
||||
ExistingZones(Set(delegatedPTRZone.copy(name = "192/30.55.144.10.in-addr.arpa."))))
|
||||
ExistingZones(Set(delegatedPTRZone.copy(name = "192/30.55.144.10.in-addr.arpa.")))
|
||||
)
|
||||
|
||||
result.head should haveInvalid[DomainValidationError](ZoneDiscoveryError(ptrAdd.inputName))
|
||||
}
|
||||
@ -1318,41 +1380,52 @@ class BatchChangeServiceSpec
|
||||
"2001:0db8:0111:0000:0000:ff00:0042:8329",
|
||||
RecordType.PTR,
|
||||
ttl,
|
||||
PTRData("ptr"))
|
||||
PTRData("ptr")
|
||||
)
|
||||
val bigZoneAdd = AddChangeInput(
|
||||
"2001:0000:0000:0000:0000:ff00:0042:8329",
|
||||
RecordType.PTR,
|
||||
ttl,
|
||||
PTRData("ptr"))
|
||||
PTRData("ptr")
|
||||
)
|
||||
val notFoundZoneAdd = AddChangeInput("::1", RecordType.PTR, ttl, PTRData("ptr"))
|
||||
|
||||
val ptripv6Adds = List(
|
||||
smallZoneAdd.validNel,
|
||||
medZoneAdd.validNel,
|
||||
bigZoneAdd.validNel,
|
||||
notFoundZoneAdd.validNel)
|
||||
notFoundZoneAdd.validNel
|
||||
)
|
||||
|
||||
val result = underTest.zoneDiscovery(
|
||||
ptripv6Adds,
|
||||
ExistingZones(Set(ptrv6ZoneSmall, ptrv6ZoneMed, ptrv6ZoneBig)))
|
||||
ExistingZones(Set(ptrv6ZoneSmall, ptrv6ZoneMed, ptrv6ZoneBig))
|
||||
)
|
||||
|
||||
result should containChangeForValidation(
|
||||
AddChangeForValidation(
|
||||
ptrv6ZoneSmall,
|
||||
"9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0",
|
||||
smallZoneAdd))
|
||||
smallZoneAdd
|
||||
)
|
||||
)
|
||||
result should containChangeForValidation(
|
||||
AddChangeForValidation(
|
||||
ptrv6ZoneMed,
|
||||
"9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0.1.1.1",
|
||||
medZoneAdd))
|
||||
medZoneAdd
|
||||
)
|
||||
)
|
||||
result should containChangeForValidation(
|
||||
AddChangeForValidation(
|
||||
ptrv6ZoneBig,
|
||||
"9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0",
|
||||
bigZoneAdd))
|
||||
bigZoneAdd
|
||||
)
|
||||
)
|
||||
result(3) should haveInvalid[DomainValidationError](
|
||||
ZoneDiscoveryError(notFoundZoneAdd.inputName))
|
||||
ZoneDiscoveryError(notFoundZoneAdd.inputName)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1490,7 +1563,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
List(apexAddA),
|
||||
Some("owner-group-id"),
|
||||
scheduledTime = Some(DateTime.now.plusMinutes(1))),
|
||||
scheduledTime = Some(DateTime.now.plusMinutes(1))
|
||||
),
|
||||
List(
|
||||
AddChangeForValidation(apexZone, "apex.test.com.", apexAddA).validNel
|
||||
),
|
||||
@ -1526,7 +1600,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
List(apexAddA),
|
||||
None,
|
||||
scheduledTime = Some(DateTime.now.plusMinutes(1))),
|
||||
scheduledTime = Some(DateTime.now.plusMinutes(1))
|
||||
),
|
||||
List(
|
||||
AddChangeForValidation(apexZone, "apex.test.com.", apexAddA).validNel
|
||||
),
|
||||
@ -1625,7 +1700,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
List(apexAddA),
|
||||
Some("owner-group-id"),
|
||||
Some(DateTime.now.plusMinutes(1))),
|
||||
Some(DateTime.now.plusMinutes(1))
|
||||
),
|
||||
List(
|
||||
AddChangeForValidation(apexZone, "apex.test.com.", apexAddA).validNel,
|
||||
nonFatalError.invalidNel,
|
||||
@ -1662,7 +1738,8 @@ class BatchChangeServiceSpec
|
||||
List(
|
||||
ZoneDiscoveryError("no.zone.match.").invalidNel,
|
||||
AddChangeForValidation(baseZone, "non-apex", nonApexAddA).validNel,
|
||||
nonFatalError.invalidNel),
|
||||
nonFatalError.invalidNel
|
||||
),
|
||||
okAuth,
|
||||
true
|
||||
)
|
||||
@ -1672,9 +1749,11 @@ class BatchChangeServiceSpec
|
||||
result shouldBe an[InvalidBatchChangeResponses]
|
||||
val ibcr = result.asInstanceOf[InvalidBatchChangeResponses]
|
||||
ibcr.changeRequestResponses.head should haveInvalid[DomainValidationError](
|
||||
ZoneDiscoveryError("no.zone.match."))
|
||||
ZoneDiscoveryError("no.zone.match.")
|
||||
)
|
||||
ibcr.changeRequestResponses(1) shouldBe Valid(
|
||||
AddChangeForValidation(baseZone, "non-apex", nonApexAddA))
|
||||
AddChangeForValidation(baseZone, "non-apex", nonApexAddA)
|
||||
)
|
||||
ibcr.changeRequestResponses(2) should haveInvalid[DomainValidationError](nonFatalError)
|
||||
}
|
||||
|
||||
@ -1706,7 +1785,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
List(apexAddA, onlyBaseAddAAAA, delete),
|
||||
None,
|
||||
Some(DateTime.now.plusMinutes(1))),
|
||||
Some(DateTime.now.plusMinutes(1))
|
||||
),
|
||||
List(
|
||||
AddChangeForValidation(apexZone, "apex.test.com.", apexAddA).validNel,
|
||||
nonFatalError.invalidNel,
|
||||
@ -1728,7 +1808,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
List(noZoneAddA, nonApexAddA),
|
||||
None,
|
||||
Some(DateTime.now.plusMinutes(1))),
|
||||
Some(DateTime.now.plusMinutes(1))
|
||||
),
|
||||
List(
|
||||
ZoneDiscoveryError("no.zone.match.", fatal = true).invalidNel,
|
||||
AddChangeForValidation(baseZone, "non-apex", nonApexAddA).validNel
|
||||
@ -1770,7 +1851,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
List(apexAddA),
|
||||
Some("owner-group-id"),
|
||||
Some(DateTime.now.plusMinutes(1))),
|
||||
Some(DateTime.now.plusMinutes(1))
|
||||
),
|
||||
List(
|
||||
AddChangeForValidation(apexZone, "apex.test.com.", apexAddA).validNel,
|
||||
nonFatalError.invalidNel,
|
||||
@ -1825,7 +1907,8 @@ class BatchChangeServiceSpec
|
||||
AddChangeForValidation(
|
||||
baseZone,
|
||||
singleChangeGood.inputName.split('.').head,
|
||||
asAdds.head).validNel,
|
||||
asAdds.head
|
||||
).validNel,
|
||||
AddChangeForValidation(baseZone, singleChangeNR.inputName.split('.').head, asAdds(1)).validNel
|
||||
),
|
||||
reviewInfo
|
||||
@ -1844,7 +1927,8 @@ class BatchChangeServiceSpec
|
||||
AddChangeForValidation(
|
||||
baseZone,
|
||||
singleChangeGood.inputName.split('.').head,
|
||||
asAdds.head).validNel,
|
||||
asAdds.head
|
||||
).validNel,
|
||||
fatalError.invalidNel
|
||||
),
|
||||
reviewInfo
|
||||
@ -1894,7 +1978,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeOne)
|
||||
|
||||
val batchChangeTwo = BatchChange(
|
||||
@ -1903,7 +1988,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
new DateTime(DateTime.now.getMillis + 1000),
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeTwo)
|
||||
|
||||
val result = rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 100).value)
|
||||
@ -1926,7 +2012,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeOne)
|
||||
|
||||
val batchChangeTwo = BatchChange(
|
||||
@ -1935,7 +2022,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
new DateTime(DateTime.now.getMillis + 1000),
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeTwo)
|
||||
|
||||
val result = rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 1).value)
|
||||
@ -1957,7 +2045,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
batchChangeRepo.save(batchChangeOne)
|
||||
|
||||
val batchChangeTwo = BatchChange(
|
||||
@ -1966,15 +2055,18 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
new DateTime(DateTime.now.getMillis + 1000),
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeTwo)
|
||||
|
||||
val result = rightResultOf(
|
||||
underTest
|
||||
.listBatchChangeSummaries(
|
||||
auth,
|
||||
approvalStatus = Some(BatchChangeApprovalStatus.PendingReview))
|
||||
.value)
|
||||
approvalStatus = Some(BatchChangeApprovalStatus.PendingReview)
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
result.maxItems shouldBe 100
|
||||
result.nextId shouldBe None
|
||||
@ -1994,7 +2086,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeOne)
|
||||
|
||||
val batchChangeTwo = BatchChange(
|
||||
@ -2003,7 +2096,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
new DateTime(DateTime.now.getMillis + 1000),
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeTwo)
|
||||
|
||||
val result =
|
||||
@ -2026,7 +2120,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeUserOne)
|
||||
|
||||
val batchChangeUserTwo = BatchChange(
|
||||
@ -2035,7 +2130,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
new DateTime(DateTime.now.getMillis + 1000),
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeUserTwo)
|
||||
|
||||
val result =
|
||||
@ -2053,7 +2149,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeUserOne)
|
||||
|
||||
val batchChangeUserTwo = BatchChange(
|
||||
@ -2062,7 +2159,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
new DateTime(DateTime.now.getMillis + 1000),
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeUserTwo)
|
||||
|
||||
val result =
|
||||
@ -2080,7 +2178,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeUserOne)
|
||||
|
||||
val batchChangeUserTwo = BatchChange(
|
||||
@ -2089,7 +2188,8 @@ class BatchChangeServiceSpec
|
||||
None,
|
||||
new DateTime(DateTime.now.getMillis + 1000),
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChangeUserTwo)
|
||||
|
||||
val result =
|
||||
@ -2121,7 +2221,8 @@ class BatchChangeServiceSpec
|
||||
DateTime.now,
|
||||
List(),
|
||||
ownerGroupId = Some(okGroup.id),
|
||||
BatchChangeApprovalStatus.AutoApproved)
|
||||
BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result = rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 100).value)
|
||||
@ -2145,7 +2246,8 @@ class BatchChangeServiceSpec
|
||||
DateTime.now,
|
||||
List(),
|
||||
ownerGroupId = Some("no-existo"),
|
||||
BatchChangeApprovalStatus.AutoApproved)
|
||||
BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
batchChangeRepo.save(batchChange)
|
||||
|
||||
val result = rightResultOf(underTest.listBatchChangeSummaries(auth, maxItems = 100).value)
|
||||
@ -2198,7 +2300,8 @@ class BatchChangeServiceSpec
|
||||
Some("checkConverter"),
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
|
||||
val result = rightResultOf(
|
||||
underTestManualEnabled
|
||||
@ -2206,8 +2309,10 @@ class BatchChangeServiceSpec
|
||||
batchChange,
|
||||
ExistingZones(Set()),
|
||||
ChangeForValidationMap(List(), ExistingRecordSets(List())),
|
||||
None)
|
||||
.value)
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
result.reviewComment shouldBe Some("batchSentToConverter")
|
||||
}
|
||||
"not send to the converter, save the change if PendingReview and MA enabled" in {
|
||||
@ -2218,7 +2323,8 @@ class BatchChangeServiceSpec
|
||||
Some("checkConverter"),
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
|
||||
val result = rightResultOf(
|
||||
underTestManualEnabled
|
||||
@ -2226,8 +2332,10 @@ class BatchChangeServiceSpec
|
||||
batchChange,
|
||||
ExistingZones(Set()),
|
||||
ChangeForValidationMap(List(), ExistingRecordSets(List())),
|
||||
None)
|
||||
.value)
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
// not sent to converter
|
||||
result.reviewComment shouldBe None
|
||||
@ -2242,7 +2350,8 @@ class BatchChangeServiceSpec
|
||||
Some("checkConverter"),
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview)
|
||||
approvalStatus = BatchChangeApprovalStatus.PendingReview
|
||||
)
|
||||
|
||||
val result = leftResultOf(
|
||||
underTest
|
||||
@ -2250,8 +2359,10 @@ class BatchChangeServiceSpec
|
||||
batchChange,
|
||||
ExistingZones(Set()),
|
||||
ChangeForValidationMap(List(), ExistingRecordSets(List())),
|
||||
None)
|
||||
.value)
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
result shouldBe an[UnknownConversionError]
|
||||
}
|
||||
@ -2263,7 +2374,8 @@ class BatchChangeServiceSpec
|
||||
Some("checkConverter"),
|
||||
DateTime.now,
|
||||
List(),
|
||||
approvalStatus = BatchChangeApprovalStatus.ManuallyApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.ManuallyApproved
|
||||
)
|
||||
|
||||
val result = leftResultOf(
|
||||
underTest
|
||||
@ -2271,8 +2383,10 @@ class BatchChangeServiceSpec
|
||||
batchChange,
|
||||
ExistingZones(Set()),
|
||||
ChangeForValidationMap(List(), ExistingRecordSets(List())),
|
||||
None)
|
||||
.value)
|
||||
None
|
||||
)
|
||||
.value
|
||||
)
|
||||
result shouldBe an[UnknownConversionError]
|
||||
}
|
||||
}
|
||||
@ -2357,10 +2471,13 @@ class BatchChangeServiceSpec
|
||||
okGroup.id,
|
||||
OwnerType.Zone,
|
||||
Some(okGroup.email),
|
||||
Some(okGroup.name)))
|
||||
Some(okGroup.name)
|
||||
)
|
||||
)
|
||||
|
||||
result(1) should beValid[ChangeForValidation](
|
||||
AddChangeForValidation(apexZone, "apex.test.com.", apexAddA))
|
||||
AddChangeForValidation(apexZone, "apex.test.com.", apexAddA)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -49,13 +49,16 @@ class BatchTransformationsSpec extends WordSpec with Matchers {
|
||||
ipv6nonMatch1,
|
||||
ipv6nonMatch2,
|
||||
ipv6nonMatch3,
|
||||
forwardMatch1))
|
||||
forwardMatch1
|
||||
)
|
||||
)
|
||||
|
||||
"getipv4PTRMatches" should {
|
||||
"return all possible matches including proper delegations" in {
|
||||
existingZones.getipv4PTRMatches("3.2.1.2") should contain theSameElementsAs List(
|
||||
ip4base1,
|
||||
ip4del1)
|
||||
ip4del1
|
||||
)
|
||||
}
|
||||
"return all possible matches excluding similar delegations" in {
|
||||
existingZones.getipv4PTRMatches("3.2.1.55") should contain theSameElementsAs List(ip4base1)
|
||||
@ -75,7 +78,8 @@ class BatchTransformationsSpec extends WordSpec with Matchers {
|
||||
|
||||
// only matches 1st option
|
||||
existingZones.getipv6PTRMatches("2001:0db0:0000:0000:0000:0000:0000:0000") shouldBe List(
|
||||
ipv6match1)
|
||||
ipv6match1
|
||||
)
|
||||
existingZones.getipv6PTRMatches("2001:db0::ff00:42:8329") shouldBe List(ipv6match1)
|
||||
}
|
||||
"return empty if there are no matches" in {
|
||||
|
@ -55,7 +55,8 @@ class DnsConnectionSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("10.1.1.1")))
|
||||
List(AData("10.1.1.1"))
|
||||
)
|
||||
|
||||
private val testAMultiple = RecordSet(
|
||||
testZone.id,
|
||||
@ -65,13 +66,15 @@ class DnsConnectionSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("1.1.1.1"), AData("2.2.2.2")))
|
||||
List(AData("1.1.1.1"), AData("2.2.2.2"))
|
||||
)
|
||||
|
||||
private val testDnsA = new DNS.ARecord(
|
||||
DNS.Name.fromString("a-record."),
|
||||
DNS.DClass.IN,
|
||||
200L,
|
||||
InetAddress.getByName("10.1.1.1"))
|
||||
InetAddress.getByName("10.1.1.1")
|
||||
)
|
||||
|
||||
private val mockResolver = mock[DNS.SimpleResolver]
|
||||
private val mockMessage = mock[DNS.Message]
|
||||
@ -81,7 +84,8 @@ class DnsConnectionSpec
|
||||
override def toQuery(
|
||||
name: String,
|
||||
zoneName: String,
|
||||
typ: RecordType): Either[Throwable, DnsQuery] =
|
||||
typ: RecordType
|
||||
): Either[Throwable, DnsQuery] =
|
||||
name match {
|
||||
case "try-again" =>
|
||||
Right(new DnsQuery(new Lookup("try-again.vinyldns.", 0, 0), new Name(testZone.name)))
|
||||
@ -165,12 +169,14 @@ class DnsConnectionSpec
|
||||
DNS.Name.fromString("a-record."),
|
||||
DNS.DClass.IN,
|
||||
200L,
|
||||
InetAddress.getByName("1.1.1.1"))
|
||||
InetAddress.getByName("1.1.1.1")
|
||||
)
|
||||
val a2 = new DNS.ARecord(
|
||||
DNS.Name.fromString("a-record."),
|
||||
DNS.DClass.IN,
|
||||
200L,
|
||||
InetAddress.getByName("2.2.2.2"))
|
||||
InetAddress.getByName("2.2.2.2")
|
||||
)
|
||||
doReturn(List(a1, a2)).when(mockDnsQuery).run()
|
||||
|
||||
val records: List[RecordSet] =
|
||||
@ -342,7 +348,8 @@ class DnsConnectionSpec
|
||||
}
|
||||
"send an appropriate replace message to the resolver for multiple records" in {
|
||||
val change = updateRsChange(testZone, testAMultiple).copy(
|
||||
updates = Some(testAMultiple.copy(name = "updated-a-record")))
|
||||
updates = Some(testAMultiple.copy(name = "updated-a-record"))
|
||||
)
|
||||
|
||||
val result: DnsResponse = rightResultOf(underTest.updateRecord(change).value)
|
||||
|
||||
@ -439,7 +446,8 @@ class DnsConnectionSpec
|
||||
}
|
||||
"send an appropriate replace message to the resolver for multiple records" in {
|
||||
val change = updateRsChange(testZone, testAMultiple).copy(
|
||||
updates = Some(testAMultiple.copy(records = List(AData("4.4.4.4"), AData("3.3.3.3")))))
|
||||
updates = Some(testAMultiple.copy(records = List(AData("4.4.4.4"), AData("3.3.3.3"))))
|
||||
)
|
||||
|
||||
val result: DnsResponse = rightResultOf(underTest.updateRecord(change).value)
|
||||
|
||||
@ -535,17 +543,20 @@ class DnsConnectionSpec
|
||||
"applyChange" should {
|
||||
"yield a successful DNS response for a create if there are no errors" in {
|
||||
underTest.applyChange(addRsChange()).value.unsafeRunSync() shouldBe Right(
|
||||
NoError(mockMessage))
|
||||
NoError(mockMessage)
|
||||
)
|
||||
}
|
||||
|
||||
"yield a successful DNS response for an update if there are no errors" in {
|
||||
underTest.applyChange(updateRsChange()).value.unsafeRunSync() shouldBe Right(
|
||||
NoError(mockMessage))
|
||||
NoError(mockMessage)
|
||||
)
|
||||
}
|
||||
|
||||
"yield a successful DNS response for a delete if there are no errors" in {
|
||||
underTest.applyChange(deleteRsChange()).value.unsafeRunSync() shouldBe Right(
|
||||
NoError(mockMessage))
|
||||
NoError(mockMessage)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("10.1.1.1")))
|
||||
List(AData("10.1.1.1"))
|
||||
)
|
||||
private val testAMultiple = RecordSet(
|
||||
testZone.id,
|
||||
"a-record",
|
||||
@ -59,7 +60,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("1.1.1.1"), AData("2.2.2.2")))
|
||||
List(AData("1.1.1.1"), AData("2.2.2.2"))
|
||||
)
|
||||
private val testAAAA = RecordSet(
|
||||
testZone.id,
|
||||
"aaaa-record",
|
||||
@ -68,7 +70,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AAAAData("2001:db8:0:0:0:0:0:3")))
|
||||
List(AAAAData("2001:db8:0:0:0:0:0:3"))
|
||||
)
|
||||
private val testCNAME = RecordSet(
|
||||
testZone.id,
|
||||
"cname-record",
|
||||
@ -77,7 +80,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(CNAMEData("cname.foo.vinyldns.")))
|
||||
List(CNAMEData("cname.foo.vinyldns."))
|
||||
)
|
||||
private val testMX = RecordSet(
|
||||
testZone.id,
|
||||
"mx-record",
|
||||
@ -86,7 +90,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(MXData(100, "exchange.vinyldns.")))
|
||||
List(MXData(100, "exchange.vinyldns."))
|
||||
)
|
||||
private val testNS = RecordSet(
|
||||
testZone.id,
|
||||
"ns-record",
|
||||
@ -95,7 +100,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(NSData("nsdname.vinyldns.")))
|
||||
List(NSData("nsdname.vinyldns."))
|
||||
)
|
||||
private val testPTR = RecordSet(
|
||||
testZone.id,
|
||||
"ptr-record",
|
||||
@ -104,7 +110,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(PTRData("ptr.vinyldns.")))
|
||||
List(PTRData("ptr.vinyldns."))
|
||||
)
|
||||
private val testSOA = RecordSet(
|
||||
testZone.id,
|
||||
"soa-record",
|
||||
@ -123,7 +130,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(SPFData("spf")))
|
||||
List(SPFData("spf"))
|
||||
)
|
||||
private val testLongSPF = RecordSet(
|
||||
testZone.id,
|
||||
"long-spf-record",
|
||||
@ -132,7 +140,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(SPFData("s" * 256)))
|
||||
List(SPFData("s" * 256))
|
||||
)
|
||||
private val testSRV = RecordSet(
|
||||
testZone.id,
|
||||
"srv-record",
|
||||
@ -141,7 +150,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(SRVData(1, 2, 3, "target.vinyldns.")))
|
||||
List(SRVData(1, 2, 3, "target.vinyldns."))
|
||||
)
|
||||
private val testNAPTR = RecordSet(
|
||||
testZone.id,
|
||||
"naptr-record",
|
||||
@ -160,7 +170,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(SSHFPData(1, 2, "fingerprint")))
|
||||
List(SSHFPData(1, 2, "fingerprint"))
|
||||
)
|
||||
private val testTXT = RecordSet(
|
||||
testZone.id,
|
||||
"txt-record",
|
||||
@ -169,7 +180,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(TXTData("text")))
|
||||
List(TXTData("text"))
|
||||
)
|
||||
private val testLongTXT = RecordSet(
|
||||
testZone.id,
|
||||
"long-txt-record",
|
||||
@ -178,7 +190,8 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(TXTData("a" * 64763)))
|
||||
List(TXTData("a" * 64763))
|
||||
)
|
||||
private val testAt = RecordSet(
|
||||
testZone.id,
|
||||
"vinyldns.",
|
||||
@ -187,45 +200,53 @@ class DnsConversionsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(AData("10.1.1.1")))
|
||||
List(AData("10.1.1.1"))
|
||||
)
|
||||
private val testDS = ds.copy(zoneId = testZone.id)
|
||||
|
||||
private val testDnsUnknown = DNS.Record.newRecord(
|
||||
DNS.Name.fromString("unknown-record."),
|
||||
DNS.Type.AFSDB,
|
||||
DNS.DClass.IN,
|
||||
200L)
|
||||
200L
|
||||
)
|
||||
private val testDnsA = new DNS.ARecord(
|
||||
DNS.Name.fromString("a-record."),
|
||||
DNS.DClass.IN,
|
||||
200L,
|
||||
InetAddress.getByName("10.1.1.1"))
|
||||
InetAddress.getByName("10.1.1.1")
|
||||
)
|
||||
private val testDnsA1 = new DNS.ARecord(
|
||||
DNS.Name.fromString("a-record."),
|
||||
DNS.DClass.IN,
|
||||
200L,
|
||||
InetAddress.getByName("1.1.1.1"))
|
||||
InetAddress.getByName("1.1.1.1")
|
||||
)
|
||||
private val testDnsA2 = new DNS.ARecord(
|
||||
DNS.Name.fromString("a-record."),
|
||||
DNS.DClass.IN,
|
||||
200L,
|
||||
InetAddress.getByName("2.2.2.2"))
|
||||
InetAddress.getByName("2.2.2.2")
|
||||
)
|
||||
private val testDnsAReplace = new DNS.ARecord(
|
||||
DNS.Name.fromString("a-record-2."),
|
||||
DNS.DClass.IN,
|
||||
200L,
|
||||
InetAddress.getByName("2.2.2.2"))
|
||||
InetAddress.getByName("2.2.2.2")
|
||||
)
|
||||
private val testDnsAAAA = new DNS.AAAARecord(
|
||||
DNS.Name.fromString("aaaa-record."),
|
||||
DNS.DClass.IN,
|
||||
200L,
|
||||
InetAddress.getByName("2001:db8::3"))
|
||||
InetAddress.getByName("2001:db8::3")
|
||||
)
|
||||
|
||||
private val testDnsAt = new DNS.ARecord(
|
||||
DNS.Name.fromString("@", DNS.Name.fromString("vinyldns.")),
|
||||
DNS.DClass.IN,
|
||||
200L,
|
||||
InetAddress.getByName("10.1.1.1"))
|
||||
InetAddress.getByName("10.1.1.1")
|
||||
)
|
||||
|
||||
private val testDnsARRset = new DNS.RRset()
|
||||
testDnsARRset.addRR(testDnsA1)
|
||||
|
@ -54,7 +54,8 @@ class MembershipServiceSpec
|
||||
mockMembershipRepo,
|
||||
mockZoneRepo,
|
||||
mockGroupChangeRepo,
|
||||
mockRecordSetRepo)
|
||||
mockRecordSetRepo
|
||||
)
|
||||
private val underTest = spy(backingService)
|
||||
|
||||
private val okUserInfo: UserInfo = UserInfo(okUser)
|
||||
@ -74,7 +75,8 @@ class MembershipServiceSpec
|
||||
private val existingGroup = okGroup.copy(
|
||||
id = "id",
|
||||
memberIds = Set("user1", "user2", "user3", "user4"),
|
||||
adminUserIds = Set("user1", "user2", "ok"))
|
||||
adminUserIds = Set("user1", "user2", "ok")
|
||||
)
|
||||
|
||||
// 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(
|
||||
@ -101,7 +103,8 @@ class MembershipServiceSpec
|
||||
mockMembershipRepo,
|
||||
mockGroupChangeRepo,
|
||||
mockRecordSetRepo,
|
||||
underTest)
|
||||
underTest
|
||||
)
|
||||
|
||||
"MembershipService" should {
|
||||
"create a new group" should {
|
||||
@ -157,7 +160,8 @@ class MembershipServiceSpec
|
||||
doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUser("ok")
|
||||
val info = groupInfo.copy(
|
||||
memberIds = Set(okUserInfo.id, dummyUserInfo.id),
|
||||
adminUserIds = Set(okUserInfo.id, dummyUserInfo.id))
|
||||
adminUserIds = Set(okUserInfo.id, dummyUserInfo.id)
|
||||
)
|
||||
val expectedMembersAdded = Set(okUserInfo.id, dummyUserInfo.id)
|
||||
|
||||
doReturn(().toResult).when(underTest).groupWithSameNameDoesNotExist(info.name)
|
||||
@ -275,8 +279,10 @@ class MembershipServiceSpec
|
||||
updatedInfo.description,
|
||||
updatedInfo.memberIds,
|
||||
updatedInfo.adminUserIds,
|
||||
okAuth)
|
||||
.value)
|
||||
okAuth
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
val groupCaptor = ArgumentCaptor.forClass(classOf[Group])
|
||||
val addedMemberCaptor = ArgumentCaptor.forClass(classOf[Set[String]])
|
||||
@ -326,8 +332,10 @@ class MembershipServiceSpec
|
||||
updatedInfo.description,
|
||||
updatedInfo.memberIds,
|
||||
updatedInfo.adminUserIds,
|
||||
dummyAuth)
|
||||
.value)
|
||||
dummyAuth
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
@ -350,8 +358,10 @@ class MembershipServiceSpec
|
||||
updatedInfo.description,
|
||||
updatedInfo.memberIds,
|
||||
updatedInfo.adminUserIds,
|
||||
okAuth)
|
||||
.value)
|
||||
okAuth
|
||||
)
|
||||
.value
|
||||
)
|
||||
error shouldBe a[GroupAlreadyExistsError]
|
||||
}
|
||||
|
||||
@ -367,8 +377,10 @@ class MembershipServiceSpec
|
||||
updatedInfo.description,
|
||||
updatedInfo.memberIds,
|
||||
updatedInfo.adminUserIds,
|
||||
okAuth)
|
||||
.value)
|
||||
okAuth
|
||||
)
|
||||
.value
|
||||
)
|
||||
error shouldBe a[GroupNotFoundError]
|
||||
}
|
||||
|
||||
@ -392,8 +404,10 @@ class MembershipServiceSpec
|
||||
updatedInfo.description,
|
||||
updatedInfo.memberIds,
|
||||
updatedInfo.adminUserIds,
|
||||
okAuth)
|
||||
.value)
|
||||
okAuth
|
||||
)
|
||||
.value
|
||||
)
|
||||
error shouldBe a[UserNotFoundError]
|
||||
}
|
||||
|
||||
@ -411,8 +425,10 @@ class MembershipServiceSpec
|
||||
updatedInfo.description,
|
||||
Set(),
|
||||
Set(),
|
||||
okAuth)
|
||||
.value)
|
||||
okAuth
|
||||
)
|
||||
.value
|
||||
)
|
||||
error shouldBe an[InvalidGroupError]
|
||||
}
|
||||
}
|
||||
@ -537,7 +553,8 @@ class MembershipServiceSpec
|
||||
None,
|
||||
nextId = Some(listOfDummyGroups(99).id),
|
||||
maxItems = 100,
|
||||
ignoreAccess = false)
|
||||
ignoreAccess = false
|
||||
)
|
||||
}
|
||||
"return only return groups whose name matches the filter" in {
|
||||
doReturn(IO.pure(listOfDummyGroups.toSet))
|
||||
@ -550,15 +567,18 @@ class MembershipServiceSpec
|
||||
startFrom = None,
|
||||
maxItems = 100,
|
||||
listOfDummyGroupsAuth,
|
||||
false)
|
||||
.value)
|
||||
false
|
||||
)
|
||||
.value
|
||||
)
|
||||
result shouldBe ListMyGroupsResponse(
|
||||
groups = listOfDummyGroupInfo.slice(10, 20),
|
||||
groupNameFilter = Some("name-dummy01"),
|
||||
startFrom = None,
|
||||
nextId = None,
|
||||
maxItems = 100,
|
||||
ignoreAccess = false)
|
||||
ignoreAccess = false
|
||||
)
|
||||
}
|
||||
"return only return groups after startFrom" in {
|
||||
doReturn(IO.pure(listOfDummyGroups.toSet))
|
||||
@ -571,15 +591,18 @@ class MembershipServiceSpec
|
||||
startFrom = Some(listOfDummyGroups(99).id),
|
||||
maxItems = 100,
|
||||
listOfDummyGroupsAuth,
|
||||
ignoreAccess = false)
|
||||
.value)
|
||||
ignoreAccess = false
|
||||
)
|
||||
.value
|
||||
)
|
||||
result shouldBe ListMyGroupsResponse(
|
||||
groups = listOfDummyGroupInfo.slice(100, 200),
|
||||
groupNameFilter = None,
|
||||
startFrom = Some(listOfDummyGroups(99).id),
|
||||
nextId = None,
|
||||
maxItems = 100,
|
||||
ignoreAccess = false)
|
||||
ignoreAccess = false
|
||||
)
|
||||
}
|
||||
"return only return maxItems groups" in {
|
||||
doReturn(IO.pure(listOfDummyGroups.toSet))
|
||||
@ -592,15 +615,18 @@ class MembershipServiceSpec
|
||||
startFrom = None,
|
||||
maxItems = 10,
|
||||
listOfDummyGroupsAuth,
|
||||
ignoreAccess = false)
|
||||
.value)
|
||||
ignoreAccess = false
|
||||
)
|
||||
.value
|
||||
)
|
||||
result shouldBe ListMyGroupsResponse(
|
||||
groups = listOfDummyGroupInfo.slice(0, 10),
|
||||
groupNameFilter = None,
|
||||
startFrom = None,
|
||||
nextId = Some(listOfDummyGroups(9).id),
|
||||
maxItems = 10,
|
||||
ignoreAccess = false)
|
||||
ignoreAccess = false
|
||||
)
|
||||
}
|
||||
"return an empty set if the user is not a member of any groups" in {
|
||||
doReturn(IO.pure(Set())).when(mockGroupRepo).getGroups(any[Set[String]])
|
||||
@ -615,7 +641,8 @@ class MembershipServiceSpec
|
||||
verify(mockGroupRepo).getAllGroups()
|
||||
result.groups should contain theSameElementsAs Seq(
|
||||
GroupInfo(dummyGroup),
|
||||
GroupInfo(okGroup))
|
||||
GroupInfo(okGroup)
|
||||
)
|
||||
}
|
||||
"return all groups from the database for super users even if ignoreAccess is false" in {
|
||||
doReturn(IO.pure(Set(okGroup, dummyGroup))).when(mockGroupRepo).getAllGroups()
|
||||
@ -624,7 +651,8 @@ class MembershipServiceSpec
|
||||
verify(mockGroupRepo).getAllGroups()
|
||||
result.groups should contain theSameElementsAs Seq(
|
||||
GroupInfo(dummyGroup),
|
||||
GroupInfo(okGroup))
|
||||
GroupInfo(okGroup)
|
||||
)
|
||||
}
|
||||
"return all groups from the database for super users if ignoreAccess is true" in {
|
||||
doReturn(IO.pure(Set(okGroup, dummyGroup))).when(mockGroupRepo).getAllGroups()
|
||||
@ -633,7 +661,8 @@ class MembershipServiceSpec
|
||||
verify(mockGroupRepo).getAllGroups()
|
||||
result.groups should contain theSameElementsAs Seq(
|
||||
GroupInfo(dummyGroup),
|
||||
GroupInfo(okGroup))
|
||||
GroupInfo(okGroup)
|
||||
)
|
||||
}
|
||||
"return all groups from the database for support users even if ignoreAccess is false" in {
|
||||
val supportAuth = AuthPrincipal(okUser.copy(isSupport = true), Seq())
|
||||
@ -643,7 +672,8 @@ class MembershipServiceSpec
|
||||
verify(mockGroupRepo).getAllGroups()
|
||||
result.groups should contain theSameElementsAs Seq(
|
||||
GroupInfo(dummyGroup),
|
||||
GroupInfo(okGroup))
|
||||
GroupInfo(okGroup)
|
||||
)
|
||||
}
|
||||
"return all groups from the database for support users if ignoreAccess is true" in {
|
||||
val supportAuth = AuthPrincipal(okUser.copy(isSupport = true), Seq())
|
||||
@ -653,7 +683,8 @@ class MembershipServiceSpec
|
||||
verify(mockGroupRepo).getAllGroups()
|
||||
result.groups should contain theSameElementsAs Seq(
|
||||
GroupInfo(dummyGroup),
|
||||
GroupInfo(okGroup))
|
||||
GroupInfo(okGroup)
|
||||
)
|
||||
}
|
||||
"do not return deleted groups" in {
|
||||
val deletedGroupAuth: AuthPrincipal = AuthPrincipal(okUser, Seq(deletedGroup.id))
|
||||
@ -670,7 +701,8 @@ class MembershipServiceSpec
|
||||
"return the group activity" in {
|
||||
val groupChangeRepoResponse = ListGroupChangesResults(
|
||||
listOfDummyGroupChanges.take(100),
|
||||
Some(listOfDummyGroupChanges(100).id))
|
||||
Some(listOfDummyGroupChanges(100).id)
|
||||
)
|
||||
doReturn(IO.pure(groupChangeRepoResponse))
|
||||
.when(mockGroupChangeRepo)
|
||||
.getGroupChanges(anyString, any[Option[String]], anyInt)
|
||||
@ -748,7 +780,8 @@ class MembershipServiceSpec
|
||||
val expectedMembers = List(MemberInfo(okUser, okGroup), MemberInfo(dummyUser, dummyGroup))
|
||||
val supportAuth = okAuth.copy(
|
||||
signedInUser = dummyAuth.signedInUser.copy(isSupport = true),
|
||||
memberGroupIds = Seq.empty)
|
||||
memberGroupIds = Seq.empty
|
||||
)
|
||||
|
||||
doReturn(IO.pure(Some(testGroup))).when(mockGroupRepo).getGroup(testGroup.id)
|
||||
doReturn(IO.pure(testListUsersResult))
|
||||
@ -830,7 +863,8 @@ class MembershipServiceSpec
|
||||
doReturn(IO.pure(Some(okGroup))).when(mockGroupRepo).getGroupByName(okGroup.name)
|
||||
|
||||
val result = awaitResultOf(
|
||||
underTest.differentGroupWithSameNameDoesNotExist(okGroup.name, okGroup.id).value)
|
||||
underTest.differentGroupWithSameNameDoesNotExist(okGroup.name, okGroup.id).value
|
||||
)
|
||||
result should be(right)
|
||||
}
|
||||
|
||||
@ -842,7 +876,8 @@ class MembershipServiceSpec
|
||||
.getGroupByName(okGroup.name)
|
||||
|
||||
val result = awaitResultOf(
|
||||
underTest.differentGroupWithSameNameDoesNotExist(okGroup.name, okGroup.id).value)
|
||||
underTest.differentGroupWithSameNameDoesNotExist(okGroup.name, okGroup.id).value
|
||||
)
|
||||
result should be(right)
|
||||
}
|
||||
}
|
||||
@ -942,7 +977,8 @@ class MembershipServiceSpec
|
||||
val error = leftResultOf(
|
||||
underTest
|
||||
.updateUserLockStatus(okUser.id, LockStatus.Locked, dummyAuth)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
@ -950,11 +986,13 @@ class MembershipServiceSpec
|
||||
"return an error if the signed in user is only a support admin" in {
|
||||
val supportAuth = okAuth.copy(
|
||||
signedInUser = dummyAuth.signedInUser.copy(isSupport = true),
|
||||
memberGroupIds = Seq.empty)
|
||||
memberGroupIds = Seq.empty
|
||||
)
|
||||
val error = leftResultOf(
|
||||
underTest
|
||||
.updateUserLockStatus(okUser.id, LockStatus.Locked, supportAuth)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
@ -965,7 +1003,8 @@ class MembershipServiceSpec
|
||||
val error = leftResultOf(
|
||||
underTest
|
||||
.updateUserLockStatus(okUser.id, LockStatus.Locked, superUserAuth)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
error shouldBe a[UserNotFoundError]
|
||||
}
|
||||
|
@ -68,7 +68,8 @@ class RecordSetServiceSpec
|
||||
mockRecordChangeRepo,
|
||||
mockUserRepo,
|
||||
mockMessageQueue,
|
||||
new AccessValidations())
|
||||
new AccessValidations()
|
||||
)
|
||||
|
||||
"addRecordSet" should {
|
||||
"return the recordSet change as the result" in {
|
||||
@ -83,7 +84,8 @@ class RecordSetServiceSpec
|
||||
|
||||
val result: RecordSetChange =
|
||||
rightResultOf(
|
||||
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value)
|
||||
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||
)
|
||||
|
||||
matches(result.recordSet, record, okZone.name) shouldBe true
|
||||
result.changeType shouldBe RecordSetChangeType.Create
|
||||
@ -148,7 +150,8 @@ class RecordSetServiceSpec
|
||||
|
||||
val result = leftResultOf(underTest.addRecordSet(record, okAuth).value)
|
||||
result shouldBe InvalidRequest(
|
||||
HighValueDomainError(s"high-value-domain.${okZone.name}").message)
|
||||
HighValueDomainError(s"high-value-domain.${okZone.name}").message
|
||||
)
|
||||
}
|
||||
"succeed if record is apex with dot" in {
|
||||
val name = okZone.name
|
||||
@ -163,7 +166,8 @@ 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
|
||||
)
|
||||
|
||||
result.recordSet.name shouldBe okZone.name
|
||||
}
|
||||
@ -180,7 +184,8 @@ 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
|
||||
)
|
||||
|
||||
result.recordSet.name shouldBe okZone.name
|
||||
}
|
||||
@ -197,7 +202,8 @@ 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
|
||||
)
|
||||
|
||||
result.recordSet.name shouldBe okZone.name
|
||||
}
|
||||
@ -215,7 +221,8 @@ class RecordSetServiceSpec
|
||||
.getGroup(okGroup.id)
|
||||
|
||||
val result: RecordSetChange = rightResultOf(
|
||||
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value)
|
||||
underTest.addRecordSet(record, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||
)
|
||||
|
||||
result.recordSet.ownerGroupId shouldBe Some(okGroup.id)
|
||||
}
|
||||
@ -268,7 +275,8 @@ class RecordSetServiceSpec
|
||||
.getRecordSetsByName(okZone.id, newRecord.name)
|
||||
|
||||
val result: RecordSetChange = rightResultOf(
|
||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value)
|
||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||
)
|
||||
|
||||
matches(result.recordSet, newRecord, okZone.name) shouldBe true
|
||||
matches(result.updates.get, oldRecord, okZone.name) shouldBe true
|
||||
@ -280,7 +288,8 @@ class RecordSetServiceSpec
|
||||
.when(mockRecordRepo)
|
||||
.getRecordSet(zoneNotAuthorized.id, aaaa.id)
|
||||
val result = leftResultOf(
|
||||
underTest.updateRecordSet(aaaa.copy(zoneId = zoneNotAuthorized.id), okAuth).value)
|
||||
underTest.updateRecordSet(aaaa.copy(zoneId = zoneNotAuthorized.id), okAuth).value
|
||||
)
|
||||
result shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
"fail if the new record name is dotted" in {
|
||||
@ -324,7 +333,8 @@ class RecordSetServiceSpec
|
||||
.getRecordSetsByName(okZone.id, newRecord.name)
|
||||
|
||||
val result: RecordSetChange = rightResultOf(
|
||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value)
|
||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||
)
|
||||
|
||||
result.recordSet.name shouldBe okZone.name
|
||||
}
|
||||
@ -341,7 +351,8 @@ class RecordSetServiceSpec
|
||||
.getRecordSetsByName(okZone.id, newRecord.name)
|
||||
|
||||
val result: RecordSetChange = rightResultOf(
|
||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value)
|
||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||
)
|
||||
|
||||
result.recordSet.name shouldBe okZone.name
|
||||
}
|
||||
@ -358,7 +369,8 @@ class RecordSetServiceSpec
|
||||
.getRecordSetsByName(okZone.id, newRecord.name)
|
||||
|
||||
val result: RecordSetChange = rightResultOf(
|
||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value)
|
||||
underTest.updateRecordSet(newRecord, okAuth).map(_.asInstanceOf[RecordSetChange]).value
|
||||
)
|
||||
|
||||
result.recordSet.name shouldBe okZone.name
|
||||
}
|
||||
@ -370,7 +382,8 @@ class RecordSetServiceSpec
|
||||
|
||||
val result = leftResultOf(underTest.updateRecordSet(newRecord, okAuth).value)
|
||||
result shouldBe InvalidRequest(
|
||||
HighValueDomainError(s"high-value-domain.${okZone.name}").message)
|
||||
HighValueDomainError(s"high-value-domain.${okZone.name}").message
|
||||
)
|
||||
}
|
||||
"fail if user is in owner group but zone is not shared" in {
|
||||
val auth = AuthPrincipal(listOfDummyUsers.head, Seq(oneUserDummyGroup.id))
|
||||
@ -378,7 +391,8 @@ class RecordSetServiceSpec
|
||||
name = "test-owner-group-failure",
|
||||
zoneId = okZone.id,
|
||||
status = RecordSetStatus.Active,
|
||||
ownerGroupId = Some(oneUserDummyGroup.id))
|
||||
ownerGroupId = Some(oneUserDummyGroup.id)
|
||||
)
|
||||
|
||||
val newRecord = oldRecord.copy(ttl = oldRecord.ttl + 1000)
|
||||
|
||||
@ -393,7 +407,8 @@ class RecordSetServiceSpec
|
||||
name = "test-owner-group-failure",
|
||||
zoneId = zone.id,
|
||||
status = RecordSetStatus.Active,
|
||||
ownerGroupId = Some(oneUserDummyGroup.id))
|
||||
ownerGroupId = Some(oneUserDummyGroup.id)
|
||||
)
|
||||
|
||||
val newRecord = oldRecord.copy(ownerGroupId = Some("doesnt-exist"))
|
||||
|
||||
@ -421,7 +436,8 @@ class RecordSetServiceSpec
|
||||
name = "test-owner-group-failure",
|
||||
zoneId = zone.id,
|
||||
status = RecordSetStatus.Active,
|
||||
ownerGroupId = Some(oneUserDummyGroup.id))
|
||||
ownerGroupId = Some(oneUserDummyGroup.id)
|
||||
)
|
||||
|
||||
val newRecord = oldRecord.copy(ownerGroupId = Some(okGroup.id))
|
||||
|
||||
@ -448,7 +464,8 @@ class RecordSetServiceSpec
|
||||
name = "test-owner-group-success",
|
||||
zoneId = zone.id,
|
||||
status = RecordSetStatus.Active,
|
||||
ownerGroupId = Some(oneUserDummyGroup.id))
|
||||
ownerGroupId = Some(oneUserDummyGroup.id)
|
||||
)
|
||||
|
||||
val newRecord = oldRecord.copy(ttl = oldRecord.ttl + 1000)
|
||||
|
||||
@ -466,7 +483,8 @@ class RecordSetServiceSpec
|
||||
.getGroup(oneUserDummyGroup.id)
|
||||
|
||||
val result = rightResultOf(
|
||||
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value)
|
||||
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value
|
||||
)
|
||||
|
||||
result.recordSet.ttl shouldBe newRecord.ttl
|
||||
result.recordSet.ownerGroupId shouldBe Some(oneUserDummyGroup.id)
|
||||
@ -478,7 +496,8 @@ class RecordSetServiceSpec
|
||||
name = "test-owner-group-success",
|
||||
zoneId = zone.id,
|
||||
status = RecordSetStatus.Active,
|
||||
ownerGroupId = Some(oneUserDummyGroup.id))
|
||||
ownerGroupId = Some(oneUserDummyGroup.id)
|
||||
)
|
||||
|
||||
val newRecord = oldRecord.copy(ownerGroupId = None)
|
||||
|
||||
@ -493,7 +512,8 @@ class RecordSetServiceSpec
|
||||
.getRecordSetsByName(zone.id, newRecord.name)
|
||||
|
||||
val result = rightResultOf(
|
||||
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value)
|
||||
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value
|
||||
)
|
||||
|
||||
result.recordSet.ttl shouldBe newRecord.ttl
|
||||
result.recordSet.ownerGroupId shouldBe None
|
||||
@ -527,7 +547,8 @@ class RecordSetServiceSpec
|
||||
underTest
|
||||
.deleteRecordSet(record.id, okZone.id, okAuth)
|
||||
.map(_.asInstanceOf[RecordSetChange])
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
matches(result.recordSet, record, okZone.name) shouldBe true
|
||||
result.changeType shouldBe RecordSetChangeType.Delete
|
||||
@ -552,12 +573,14 @@ class RecordSetServiceSpec
|
||||
val result =
|
||||
leftResultOf(underTest.deleteRecordSet(record.id, okZone.id, okAuth).value)
|
||||
result shouldBe InvalidRequest(
|
||||
HighValueDomainError(s"high-value-domain.${okZone.name}").message)
|
||||
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
|
||||
)
|
||||
|
||||
result shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
@ -568,7 +591,8 @@ class RecordSetServiceSpec
|
||||
|
||||
val result =
|
||||
leftResultOf(
|
||||
underTest.deleteRecordSet(sharedZoneRecord.id, sharedZoneRecord.zoneId, okAuth).value)
|
||||
underTest.deleteRecordSet(sharedZoneRecord.id, sharedZoneRecord.zoneId, okAuth).value
|
||||
)
|
||||
|
||||
result shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
@ -682,7 +706,8 @@ class RecordSetServiceSpec
|
||||
|
||||
val result: RecordSetInfo =
|
||||
rightResultOf(
|
||||
underTest.getRecordSet(sharedZoneRecordNoOwnerGroup.id, sharedZone.id, sharedAuth).value)
|
||||
underTest.getRecordSet(sharedZoneRecordNoOwnerGroup.id, sharedZone.id, sharedAuth).value
|
||||
)
|
||||
result shouldBe expectedRecordSetInfo
|
||||
}
|
||||
|
||||
@ -697,7 +722,8 @@ class RecordSetServiceSpec
|
||||
leftResultOf(
|
||||
underTest
|
||||
.getRecordSet(sharedZoneRecordNotApprovedRecordType.id, sharedZone.id, okAuth)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
result shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -726,7 +752,8 @@ class RecordSetServiceSpec
|
||||
val result = leftResultOf(
|
||||
underTest
|
||||
.getRecordSet(notSharedZoneRecordWithOwnerGroup.id, zoneNotAuthorized.id, okAuth)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
result shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
}
|
||||
@ -752,13 +779,14 @@ class RecordSetServiceSpec
|
||||
.getGroups(Set(okGroup.id, "not-in-backend"))
|
||||
|
||||
doReturn(
|
||||
IO.pure(ListRecordSetResults(List(sharedZoneRecord, sharedZoneRecordNotFoundOwnerGroup))))
|
||||
.when(mockRecordRepo)
|
||||
IO.pure(ListRecordSetResults(List(sharedZoneRecord, sharedZoneRecordNotFoundOwnerGroup)))
|
||||
).when(mockRecordRepo)
|
||||
.listRecordSets(
|
||||
zoneId = sharedZone.id,
|
||||
startFrom = None,
|
||||
maxItems = None,
|
||||
recordNameFilter = None)
|
||||
recordNameFilter = None
|
||||
)
|
||||
|
||||
val result: ListRecordSetsResponse = rightResultOf(
|
||||
underTest
|
||||
@ -767,16 +795,20 @@ class RecordSetServiceSpec
|
||||
startFrom = None,
|
||||
maxItems = None,
|
||||
recordNameFilter = None,
|
||||
authPrincipal = sharedAuth)
|
||||
.value)
|
||||
authPrincipal = sharedAuth
|
||||
)
|
||||
.value
|
||||
)
|
||||
result.recordSets shouldBe
|
||||
List(
|
||||
RecordSetListInfo(
|
||||
RecordSetInfo(sharedZoneRecord, Some(okGroup.name)),
|
||||
AccessLevel.Delete),
|
||||
AccessLevel.Delete
|
||||
),
|
||||
RecordSetListInfo(
|
||||
RecordSetInfo(sharedZoneRecordNotFoundOwnerGroup, None),
|
||||
AccessLevel.Delete)
|
||||
AccessLevel.Delete
|
||||
)
|
||||
)
|
||||
}
|
||||
"return the recordSet for support admin" in {
|
||||
@ -790,7 +822,8 @@ class RecordSetServiceSpec
|
||||
zoneId = okZone.id,
|
||||
startFrom = None,
|
||||
maxItems = None,
|
||||
recordNameFilter = None)
|
||||
recordNameFilter = None
|
||||
)
|
||||
|
||||
val result: ListRecordSetsResponse = rightResultOf(
|
||||
underTest
|
||||
@ -801,9 +834,11 @@ class RecordSetServiceSpec
|
||||
recordNameFilter = None,
|
||||
authPrincipal = AuthPrincipal(okAuth.signedInUser.copy(isSupport = true), Seq.empty)
|
||||
)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
result.recordSets shouldBe List(
|
||||
RecordSetListInfo(RecordSetInfo(aaaa, None), AccessLevel.Read))
|
||||
RecordSetListInfo(RecordSetInfo(aaaa, None), AccessLevel.Read)
|
||||
)
|
||||
}
|
||||
"fails when the account is not authorized" in {
|
||||
val result = leftResultOf(
|
||||
@ -813,8 +848,10 @@ class RecordSetServiceSpec
|
||||
startFrom = None,
|
||||
maxItems = None,
|
||||
recordNameFilter = None,
|
||||
authPrincipal = okAuth)
|
||||
.value)
|
||||
authPrincipal = okAuth
|
||||
)
|
||||
.value
|
||||
)
|
||||
result shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
}
|
||||
@ -840,7 +877,8 @@ class RecordSetServiceSpec
|
||||
recordSetChanges = changesWithName,
|
||||
nextId = None,
|
||||
startFrom = None,
|
||||
maxItems = 100)
|
||||
maxItems = 100
|
||||
)
|
||||
result shouldBe expectedResults
|
||||
}
|
||||
|
||||
@ -856,13 +894,15 @@ class RecordSetServiceSpec
|
||||
recordSetChanges = List(),
|
||||
nextId = None,
|
||||
startFrom = None,
|
||||
maxItems = 100)
|
||||
maxItems = 100
|
||||
)
|
||||
result shouldBe expectedResults
|
||||
}
|
||||
|
||||
"return a NotAuthorizedError" in {
|
||||
val error = leftResultOf(
|
||||
underTest.listRecordSetChanges(zoneNotAuthorized.id, authPrincipal = okAuth).value)
|
||||
underTest.listRecordSetChanges(zoneNotAuthorized.id, authPrincipal = okAuth).value
|
||||
)
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
||||
@ -883,7 +923,8 @@ class RecordSetServiceSpec
|
||||
recordSetChanges = changesWithName,
|
||||
nextId = None,
|
||||
startFrom = None,
|
||||
maxItems = 100)
|
||||
maxItems = 100
|
||||
)
|
||||
result shouldBe expectedResults
|
||||
}
|
||||
}
|
||||
@ -906,7 +947,8 @@ class RecordSetServiceSpec
|
||||
|
||||
val actual: RecordSetChange =
|
||||
rightResultOf(
|
||||
underTest.getRecordSetChange(sharedZone.id, pendingCreateSharedRecord.id, okAuth).value)
|
||||
underTest.getRecordSetChange(sharedZone.id, pendingCreateSharedRecord.id, okAuth).value
|
||||
)
|
||||
actual shouldBe pendingCreateSharedRecord
|
||||
}
|
||||
|
||||
@ -926,7 +968,8 @@ class RecordSetServiceSpec
|
||||
.getRecordSetChange(zoneActive.id, pendingCreateAAAA.id)
|
||||
|
||||
val error = leftResultOf(
|
||||
underTest.getRecordSetChange(zoneActive.id, pendingCreateAAAA.id, dummyAuth).value)
|
||||
underTest.getRecordSetChange(zoneActive.id, pendingCreateAAAA.id, dummyAuth).value
|
||||
)
|
||||
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
@ -937,9 +980,15 @@ class RecordSetServiceSpec
|
||||
.when(mockRecordChangeRepo)
|
||||
.getRecordSetChange(zoneNotAuthorized.id, pendingCreateSharedRecordNotSharedZone.id)
|
||||
|
||||
val error = leftResultOf(underTest
|
||||
.getRecordSetChange(zoneNotAuthorized.id, pendingCreateSharedRecordNotSharedZone.id, okAuth)
|
||||
.value)
|
||||
val error = leftResultOf(
|
||||
underTest
|
||||
.getRecordSetChange(
|
||||
zoneNotAuthorized.id,
|
||||
pendingCreateSharedRecordNotSharedZone.id,
|
||||
okAuth
|
||||
)
|
||||
.value
|
||||
)
|
||||
|
||||
error shouldBe a[NotAuthorizedError]
|
||||
}
|
||||
|
@ -209,7 +209,8 @@ class RecordSetValidationsSpec
|
||||
dottedARecord,
|
||||
List(),
|
||||
okZone,
|
||||
Some(dottedARecord.copy(ttl = 300))) should be(right)
|
||||
Some(dottedARecord.copy(ttl = 300))
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return a success for any existing record with dotted hosts in forward zones (CNAME)" in {
|
||||
@ -218,7 +219,8 @@ class RecordSetValidationsSpec
|
||||
dottedCNAMERecord,
|
||||
List(),
|
||||
okZone,
|
||||
Some(dottedCNAMERecord.copy(ttl = 300))) should be(right)
|
||||
Some(dottedCNAMERecord.copy(ttl = 300))
|
||||
) should be(right)
|
||||
}
|
||||
|
||||
"return a failure for any existing record with dotted hosts in forward zones (NS)" in {
|
||||
@ -228,7 +230,8 @@ class RecordSetValidationsSpec
|
||||
dottedNSRecord,
|
||||
List(),
|
||||
okZone,
|
||||
Some(dottedNSRecord.copy(ttl = 300)))
|
||||
Some(dottedNSRecord.copy(ttl = 300))
|
||||
)
|
||||
) shouldBe an[InvalidRequest]
|
||||
}
|
||||
}
|
||||
@ -311,7 +314,8 @@ class RecordSetValidationsSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(SOAData("something", "other", 1, 2, 3, 5, 6)))
|
||||
List(SOAData("something", "other", 1, 2, 3, 5, 6))
|
||||
)
|
||||
|
||||
typeSpecificValidations(test, List(), zoneIp4) should be(right)
|
||||
}
|
||||
@ -323,7 +327,8 @@ class RecordSetValidationsSpec
|
||||
"return ok if the record is an NS record but not origin" in {
|
||||
val valid = invalidNsApexRs.copy(
|
||||
name = "this-is-not-origin-mate",
|
||||
records = List(NSData("some.test.ns.")))
|
||||
records = List(NSData("some.test.ns."))
|
||||
)
|
||||
|
||||
nsValidations(valid, okZone) should be(right)
|
||||
}
|
||||
@ -406,12 +411,14 @@ class RecordSetValidationsSpec
|
||||
}
|
||||
"return ok if new recordset name does not contain dot" in {
|
||||
cnameValidations(cname, List(), okZone, Some(cname.copy(name = "not-dotted"))) should be(
|
||||
right)
|
||||
right
|
||||
)
|
||||
}
|
||||
"return ok if dotted host name doesn't change" in {
|
||||
val newRecord = cname.copy(name = "dot.ted", ttl = 500)
|
||||
cnameValidations(newRecord, List(), okZone, Some(newRecord.copy(ttl = 300))) should be(
|
||||
right)
|
||||
right
|
||||
)
|
||||
}
|
||||
"return an InvalidRequest if a cname record set name is updated to '@'" in {
|
||||
val error = leftValue(cnameValidations(cname.copy(name = "@"), List(), okZone, Some(cname)))
|
||||
@ -485,7 +492,8 @@ class RecordSetValidationsSpec
|
||||
val ownerGroupIdBad = "bar"
|
||||
val auth = okAuth.copy(
|
||||
memberGroupIds = Seq("foo"),
|
||||
signedInUser = okAuth.signedInUser.copy(isSuper = true))
|
||||
signedInUser = okAuth.signedInUser.copy(isSuper = true)
|
||||
)
|
||||
val ownerGroup = Group(id = ownerGroupIdGood, name = "test", email = "test@test.com")
|
||||
|
||||
canUseOwnerGroup(Some(ownerGroupIdGood), Some(ownerGroup), auth) should be(right)
|
||||
|
@ -103,7 +103,8 @@ class ZoneConnectionValidatorSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(SOAData("something", "other", 1, 2, 3, 5, 6)))
|
||||
List(SOAData("something", "other", 1, 2, 3, 5, 6))
|
||||
)
|
||||
|
||||
private val successNS = RecordSet(
|
||||
testZone.id,
|
||||
@ -113,7 +114,8 @@ class ZoneConnectionValidatorSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(NSData("some.test.ns.")))
|
||||
List(NSData("some.test.ns."))
|
||||
)
|
||||
|
||||
private val failureNs = RecordSet(
|
||||
testZone.id,
|
||||
@ -123,7 +125,8 @@ class ZoneConnectionValidatorSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(NSData("some.test.ns."), NSData("not.approved.")))
|
||||
List(NSData("some.test.ns."), NSData("not.approved."))
|
||||
)
|
||||
|
||||
private val delegatedNS = RecordSet(
|
||||
testZone.id,
|
||||
@ -133,7 +136,8 @@ class ZoneConnectionValidatorSpec
|
||||
RecordSetStatus.Active,
|
||||
DateTime.now,
|
||||
None,
|
||||
List(NSData("sub.some.test.ns.")))
|
||||
List(NSData("sub.some.test.ns."))
|
||||
)
|
||||
|
||||
private val mockRecordSet = mock[RecordSet]
|
||||
|
||||
@ -142,7 +146,8 @@ class ZoneConnectionValidatorSpec
|
||||
val backend = DnsBackend(
|
||||
"some-backend-id",
|
||||
zc.copy(name = "backend-conn"),
|
||||
transfer.copy(name = "backend-transfer"))
|
||||
transfer.copy(name = "backend-transfer")
|
||||
)
|
||||
val connections = ConfiguredDnsConnections(zc, transfer, List(backend))
|
||||
|
||||
"ConnectionValidator" should {
|
||||
@ -172,7 +177,8 @@ class ZoneConnectionValidatorSpec
|
||||
result shouldBe ZoneValidationFailed(
|
||||
testZone,
|
||||
List(s"Name Server not.approved. is not an approved name server."),
|
||||
"Zone could not be loaded due to validation errors.")
|
||||
"Zone could not be loaded due to validation errors."
|
||||
)
|
||||
}
|
||||
|
||||
"respond with a failure if no records are returned from the backend" in {
|
||||
@ -187,7 +193,8 @@ class ZoneConnectionValidatorSpec
|
||||
result shouldBe ZoneValidationFailed(
|
||||
testZone,
|
||||
List("Missing apex NS record"),
|
||||
"Zone could not be loaded due to validation errors.")
|
||||
"Zone could not be loaded due to validation errors."
|
||||
)
|
||||
}
|
||||
|
||||
"respond with a failure if any failure is returned from the backend" in {
|
||||
@ -256,7 +263,8 @@ class ZoneConnectionValidatorSpec
|
||||
val backend = DnsBackend("some-test-backend", testDefaultConnection, testDefaultConnection)
|
||||
val underTest =
|
||||
new ZoneConnectionValidator(
|
||||
ConfiguredDnsConnections(testDefaultConnection, testDefaultConnection, List(backend)))
|
||||
ConfiguredDnsConnections(testDefaultConnection, testDefaultConnection, List(backend))
|
||||
)
|
||||
|
||||
"return success if the backendId exists" in {
|
||||
underTest.isValidBackendId(Some("some-test-backend")) shouldBe right
|
||||
|
@ -77,7 +77,8 @@ class ZoneRecordValidationsSpec extends WordSpec with Matchers with ValidatedMat
|
||||
"return an error if fqdn is in high value list" in {
|
||||
val result = isNotHighValueFqdn(highValueRegexList, "high-value-domain.foo.")
|
||||
result should haveInvalid[DomainValidationError](
|
||||
HighValueDomainError("high-value-domain.foo."))
|
||||
HighValueDomainError("high-value-domain.foo.")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,14 +135,15 @@ class ZoneRecordValidationsSpec extends WordSpec with Matchers with ValidatedMat
|
||||
"return failure if none of the name servers are in the list of approved name servers" in {
|
||||
val test = ns.copy(records = List(NSData("blah1."), NSData("blah2.")))
|
||||
containsApprovedNameServers(approvedNameServers, test) should haveInvalid(
|
||||
"Name Server blah1. is not an approved name server.").and(
|
||||
haveInvalid("Name Server blah2. is not an approved name server."))
|
||||
"Name Server blah1. is not an approved name server."
|
||||
).and(haveInvalid("Name Server blah2. is not an approved name server."))
|
||||
}
|
||||
|
||||
"return a failure if any of the name servers are not in the list of approved name servers" in {
|
||||
val test = ns.copy(records = List(NSData("blah1."), NSData("ns1.test.com")))
|
||||
containsApprovedNameServers(approvedNameServers, test) should haveInvalid(
|
||||
"Name Server blah1. is not an approved name server.")
|
||||
"Name Server blah1. is not an approved name server."
|
||||
)
|
||||
}
|
||||
|
||||
"return success if the name server matches a regular expression" in {
|
||||
@ -160,7 +162,8 @@ class ZoneRecordValidationsSpec extends WordSpec with Matchers with ValidatedMat
|
||||
val test = ns.copy(records = List(NSData("test-foo-ns.")))
|
||||
val approved = List(".*bar.*".r, "www.*".r)
|
||||
containsApprovedNameServers(approved, test) should haveInvalid(
|
||||
"Name Server test-foo-ns. is not an approved name server.")
|
||||
"Name Server test-foo-ns. is not an approved name server."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,20 +74,23 @@ class ZoneServiceSpec
|
||||
TestConnectionValidator,
|
||||
mockMessageQueue,
|
||||
new ZoneValidations(1000),
|
||||
new AccessValidations())
|
||||
new AccessValidations()
|
||||
)
|
||||
|
||||
private val createZoneAuthorized = CreateZoneInput(
|
||||
"ok.zone.recordsets.",
|
||||
"test@test.com",
|
||||
connection = testConnection,
|
||||
adminGroupId = okGroup.id)
|
||||
adminGroupId = okGroup.id
|
||||
)
|
||||
|
||||
private val updateZoneAuthorized = UpdateZoneInput(
|
||||
okZone.id,
|
||||
"ok.zone.recordsets.",
|
||||
"updated-test@test.com",
|
||||
connection = testConnection,
|
||||
adminGroupId = okGroup.id)
|
||||
adminGroupId = okGroup.id
|
||||
)
|
||||
|
||||
override protected def beforeEach(): Unit = {
|
||||
reset(mockGroupRepo, mockZoneRepo, mockUserRepo)
|
||||
@ -100,7 +103,8 @@ class ZoneServiceSpec
|
||||
doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString)
|
||||
|
||||
val resultChange: ZoneChange = rightResultOf(
|
||||
underTest.connectToZone(createZoneAuthorized, okAuth).map(_.asInstanceOf[ZoneChange]).value)
|
||||
underTest.connectToZone(createZoneAuthorized, okAuth).map(_.asInstanceOf[ZoneChange]).value
|
||||
)
|
||||
|
||||
resultChange.changeType shouldBe ZoneChangeType.Create
|
||||
Option(resultChange.created) shouldBe defined
|
||||
@ -124,7 +128,8 @@ class ZoneServiceSpec
|
||||
underTest
|
||||
.connectToZone(createZoneAuthorized, nonTestUser)
|
||||
.map(_.asInstanceOf[ZoneChange])
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
resultChange.zone.isTest shouldBe false
|
||||
}
|
||||
@ -138,7 +143,8 @@ class ZoneServiceSpec
|
||||
underTest
|
||||
.connectToZone(createZoneAuthorized, testUser)
|
||||
.map(_.asInstanceOf[ZoneChange])
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
resultChange.zone.isTest shouldBe true
|
||||
}
|
||||
@ -164,7 +170,8 @@ class ZoneServiceSpec
|
||||
doReturn(IO.pure(Some(zoneDeleted))).when(mockZoneRepo).getZoneByName(anyString)
|
||||
|
||||
val resultChange: ZoneChange = rightResultOf(
|
||||
underTest.connectToZone(createZoneAuthorized, okAuth).map(_.asInstanceOf[ZoneChange]).value)
|
||||
underTest.connectToZone(createZoneAuthorized, okAuth).map(_.asInstanceOf[ZoneChange]).value
|
||||
)
|
||||
resultChange.changeType shouldBe ZoneChangeType.Create
|
||||
}
|
||||
|
||||
@ -181,7 +188,8 @@ class ZoneServiceSpec
|
||||
doReturn(IO.pure(None)).when(mockZoneRepo).getZoneByName(anyString)
|
||||
|
||||
val resultZone = rightResultOf(
|
||||
underTest.connectToZone(newZone, superUserAuth).map(_.asInstanceOf[ZoneChange]).value).zone
|
||||
underTest.connectToZone(newZone, superUserAuth).map(_.asInstanceOf[ZoneChange]).value
|
||||
).zone
|
||||
|
||||
Option(resultZone.id) should not be None
|
||||
resultZone.email shouldBe okZone.email
|
||||
@ -199,7 +207,8 @@ class ZoneServiceSpec
|
||||
underTest
|
||||
.connectToZone(newZone, supportUserAuth)
|
||||
.map(_.asInstanceOf[ZoneChange])
|
||||
.value).zone
|
||||
.value
|
||||
).zone
|
||||
|
||||
Option(resultZone.id) should not be None
|
||||
resultZone.email shouldBe okZone.email
|
||||
@ -237,7 +246,8 @@ class ZoneServiceSpec
|
||||
.updateZone(updateZoneInput, doubleAuth)
|
||||
.map(_.asInstanceOf[ZoneChange])
|
||||
.value,
|
||||
duration = 2.seconds)
|
||||
duration = 2.seconds
|
||||
)
|
||||
|
||||
resultChange.zone.id shouldBe okZone.id
|
||||
resultChange.changeType shouldBe ZoneChangeType.Update
|
||||
@ -259,7 +269,8 @@ class ZoneServiceSpec
|
||||
underTest
|
||||
.updateZone(newZone, doubleAuth)
|
||||
.map(_.asInstanceOf[ZoneChange])
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
resultChange.zone.id shouldBe oldZone.id
|
||||
resultChange.zone.connection shouldBe oldZone.connection
|
||||
}
|
||||
@ -302,7 +313,8 @@ class ZoneServiceSpec
|
||||
val result = rightResultOf(
|
||||
underTest
|
||||
.updateZone(newZone, AuthPrincipal(superUser, List.empty))
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
result shouldBe a[ZoneChange]
|
||||
}
|
||||
|
||||
@ -315,7 +327,8 @@ class ZoneServiceSpec
|
||||
val result = rightResultOf(
|
||||
underTest
|
||||
.updateZone(newZone, supportUserAuth)
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
result shouldBe a[ZoneChange]
|
||||
}
|
||||
|
||||
@ -449,7 +462,8 @@ class ZoneServiceSpec
|
||||
zoneWithRules,
|
||||
ZoneACLInfo(Set(goodUserRuleInfo, goodGroupRuleInfo, goodAllRuleInfo)),
|
||||
goodGroup.name,
|
||||
AccessLevel.Delete)
|
||||
AccessLevel.Delete
|
||||
)
|
||||
val result: ZoneInfo = rightResultOf(underTest.getZone(zoneWithRules.id, abcAuth).value)
|
||||
result shouldBe expectedZoneInfo
|
||||
}
|
||||
@ -564,8 +578,10 @@ class ZoneServiceSpec
|
||||
List(abcZone, xyzZone),
|
||||
maxItems = 2,
|
||||
nextId = Some("zone2."),
|
||||
ignoreAccess = false)))
|
||||
.when(mockZoneRepo)
|
||||
ignoreAccess = false
|
||||
)
|
||||
)
|
||||
).when(mockZoneRepo)
|
||||
.listZones(abcAuth, None, None, 2, false)
|
||||
doReturn(IO.pure(Set(abcGroup, xyzGroup)))
|
||||
.when(mockGroupRepo)
|
||||
@ -588,8 +604,10 @@ class ZoneServiceSpec
|
||||
zonesFilter = Some("foo"),
|
||||
maxItems = 2,
|
||||
nextId = Some("zone2."),
|
||||
ignoreAccess = false)))
|
||||
.when(mockZoneRepo)
|
||||
ignoreAccess = false
|
||||
)
|
||||
)
|
||||
).when(mockZoneRepo)
|
||||
.listZones(abcAuth, Some("foo"), None, 2, false)
|
||||
doReturn(IO.pure(Set(abcGroup, xyzGroup)))
|
||||
.when(mockGroupRepo)
|
||||
@ -610,8 +628,10 @@ class ZoneServiceSpec
|
||||
List(abcZone, xyzZone),
|
||||
startFrom = Some("zone4."),
|
||||
maxItems = 2,
|
||||
ignoreAccess = false)))
|
||||
.when(mockZoneRepo)
|
||||
ignoreAccess = false
|
||||
)
|
||||
)
|
||||
).when(mockZoneRepo)
|
||||
.listZones(abcAuth, None, Some("zone4."), 2, false)
|
||||
doReturn(IO.pure(Set(abcGroup, xyzGroup)))
|
||||
.when(mockGroupRepo)
|
||||
@ -631,8 +651,10 @@ class ZoneServiceSpec
|
||||
startFrom = Some("zone4."),
|
||||
maxItems = 2,
|
||||
nextId = Some("zone6."),
|
||||
ignoreAccess = false)))
|
||||
.when(mockZoneRepo)
|
||||
ignoreAccess = false
|
||||
)
|
||||
)
|
||||
).when(mockZoneRepo)
|
||||
.listZones(abcAuth, None, Some("zone4."), 2, false)
|
||||
doReturn(IO.pure(Set(abcGroup, xyzGroup)))
|
||||
.when(mockGroupRepo)
|
||||
@ -719,7 +741,8 @@ class ZoneServiceSpec
|
||||
underTest
|
||||
.addACLRule(okZone.id, userAclRuleInfo, okAuth)
|
||||
.map(_.asInstanceOf[ZoneChange])
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result.changeType shouldBe ZoneChangeType.Update
|
||||
result.zone.acl.rules.size shouldBe 1
|
||||
@ -754,7 +777,8 @@ class ZoneServiceSpec
|
||||
underTest
|
||||
.deleteACLRule(zone.id, userAclRuleInfo, okAuth)
|
||||
.map(_.asInstanceOf[ZoneChange])
|
||||
.value)
|
||||
.value
|
||||
)
|
||||
|
||||
result.changeType shouldBe ZoneChangeType.Update
|
||||
result.zone.acl.rules.size shouldBe 0
|
||||
|
@ -62,7 +62,8 @@ class ZoneValidationsSpec
|
||||
"fail if given an invalid CIDR rule" in {
|
||||
val invalidPtrAclRuleInfo = baseAclRuleInfo.copy(
|
||||
recordMask = Some("not a cidr rule"),
|
||||
recordTypes = Set(RecordType.PTR))
|
||||
recordTypes = Set(RecordType.PTR)
|
||||
)
|
||||
val error = leftValue(isValidAclRule(ACLRule(invalidPtrAclRuleInfo)))
|
||||
error shouldBe a[InvalidRequest]
|
||||
error.getMessage shouldBe "PTR types must have no mask or a valid CIDR mask: Invalid CIDR block"
|
||||
@ -71,7 +72,8 @@ class ZoneValidationsSpec
|
||||
"fail if there are multiple record types including PTR and mask is regex" in {
|
||||
val invalidMultipleTypeInfo = baseAclRuleInfo.copy(
|
||||
recordMask = Some("regex"),
|
||||
recordTypes = Set(RecordType.A, RecordType.AAAA, RecordType.CNAME, RecordType.PTR))
|
||||
recordTypes = Set(RecordType.A, RecordType.AAAA, RecordType.CNAME, RecordType.PTR)
|
||||
)
|
||||
val error = leftValue(isValidAclRule(ACLRule(invalidMultipleTypeInfo)))
|
||||
error shouldBe a[InvalidRequest]
|
||||
}
|
||||
@ -79,14 +81,16 @@ class ZoneValidationsSpec
|
||||
"fail if there are multiple record types including PTR and mask is cidr" in {
|
||||
val invalidMultipleTypeInfo = baseAclRuleInfo.copy(
|
||||
recordMask = Some("10.10.10.10/5"),
|
||||
recordTypes = Set(RecordType.A, RecordType.AAAA, RecordType.CNAME, RecordType.PTR))
|
||||
recordTypes = Set(RecordType.A, RecordType.AAAA, RecordType.CNAME, RecordType.PTR)
|
||||
)
|
||||
val error = leftValue(isValidAclRule(ACLRule(invalidMultipleTypeInfo)))
|
||||
error shouldBe a[InvalidRequest]
|
||||
}
|
||||
|
||||
"pass if there are multiple record types including PTR and mask is None" in {
|
||||
val validMultipleTypeNoneAclRuleInfo = baseAclRuleInfo.copy(
|
||||
recordTypes = Set(RecordType.A, RecordType.AAAA, RecordType.CNAME, RecordType.PTR))
|
||||
recordTypes = Set(RecordType.A, RecordType.AAAA, RecordType.CNAME, RecordType.PTR)
|
||||
)
|
||||
isValidAclRule(ACLRule(validMultipleTypeNoneAclRuleInfo)) should be(right)
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,8 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
val testZoneName = "vinyldns."
|
||||
|
||||
val testZoneConnection: Option[ZoneConnection] = Some(
|
||||
ZoneConnection(testZoneName, testZoneName, "nzisn+4G2ldMn0q1CV3vsg==", "127.0.0.1:19001"))
|
||||
ZoneConnection(testZoneName, testZoneName, "nzisn+4G2ldMn0q1CV3vsg==", "127.0.0.1:19001")
|
||||
)
|
||||
|
||||
private val testZone = Zone("vinyldns.", "test@test.com")
|
||||
private val records = List(
|
||||
@ -49,7 +50,8 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("1.1.1.1"))),
|
||||
records = List(AData("1.1.1.1"))
|
||||
),
|
||||
RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = "abc",
|
||||
@ -57,7 +59,8 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("2.2.2.2"))),
|
||||
records = List(AData("2.2.2.2"))
|
||||
),
|
||||
RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = "abc",
|
||||
@ -74,7 +77,8 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("3.3.3.3")))
|
||||
records = List(AData("3.3.3.3"))
|
||||
)
|
||||
)
|
||||
|
||||
"VinylDNSZoneViewLoader" should {
|
||||
@ -115,7 +119,8 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
ttl = 38400,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("1.1.1.1"))),
|
||||
records = List(AData("1.1.1.1"))
|
||||
),
|
||||
RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = "abc",
|
||||
@ -123,7 +128,8 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
ttl = 38400,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("2.2.2.2"))),
|
||||
records = List(AData("2.2.2.2"))
|
||||
),
|
||||
RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = "abc",
|
||||
@ -140,7 +146,8 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
ttl = 38400,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("3.3.3.3")))
|
||||
records = List(AData("3.3.3.3"))
|
||||
)
|
||||
)
|
||||
|
||||
val dnsRecords = new mutable.ArrayBuffer[DNS.Record]()
|
||||
@ -149,25 +156,33 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
new Name("abc.vinyldns."),
|
||||
DNS.DClass.IN,
|
||||
38400,
|
||||
InetAddress.getByName("1.1.1.1")))
|
||||
InetAddress.getByName("1.1.1.1")
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.ARecord(
|
||||
new Name("abc.vinyldns."),
|
||||
DNS.DClass.IN,
|
||||
38400,
|
||||
InetAddress.getByName("2.2.2.2")))
|
||||
InetAddress.getByName("2.2.2.2")
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.AAAARecord(
|
||||
new Name("abc.vinyldns."),
|
||||
DNS.DClass.IN,
|
||||
38400,
|
||||
InetAddress.getByName("2001:db8:a0b:12f0::1")))
|
||||
InetAddress.getByName("2001:db8:a0b:12f0::1")
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.ARecord(
|
||||
new Name("def.vinyldns."),
|
||||
DNS.DClass.IN,
|
||||
38400,
|
||||
InetAddress.getByName("3.3.3.3")))
|
||||
InetAddress.getByName("3.3.3.3")
|
||||
)
|
||||
)
|
||||
|
||||
doReturn(dnsRecords.asJava).when(mockTransfer).getAXFR
|
||||
|
||||
@ -228,7 +243,8 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(
|
||||
SOAData("172.17.42.1.", "admin.vinyldns.com.", 1439234395, 10800, 3600, 604800, 38400))
|
||||
SOAData("172.17.42.1.", "admin.vinyldns.com.", 1439234395, 10800, 3600, 604800, 38400)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@ -244,37 +260,49 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
10800,
|
||||
3600,
|
||||
604800,
|
||||
38400))
|
||||
38400
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.ARecord(
|
||||
new Name("abc.vinyldns."),
|
||||
DNS.DClass.IN,
|
||||
38400,
|
||||
InetAddress.getByName("1.1.1.1")))
|
||||
InetAddress.getByName("1.1.1.1")
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.ARecord(
|
||||
new Name("abc.vinyldns."),
|
||||
DNS.DClass.IN,
|
||||
38400,
|
||||
InetAddress.getByName("1.1.1.1")))
|
||||
InetAddress.getByName("1.1.1.1")
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.ARecord(
|
||||
new Name("abc.vinyldns."),
|
||||
DNS.DClass.IN,
|
||||
38400,
|
||||
InetAddress.getByName("1.1.1.1")))
|
||||
InetAddress.getByName("1.1.1.1")
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.ARecord(
|
||||
new Name("abc.vinyldns."),
|
||||
DNS.DClass.IN,
|
||||
38400,
|
||||
InetAddress.getByName("1.1.1.1")))
|
||||
InetAddress.getByName("1.1.1.1")
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.ARecord(
|
||||
new Name("abc.vinyldns."),
|
||||
DNS.DClass.IN,
|
||||
38400,
|
||||
InetAddress.getByName("2.2.2.2")))
|
||||
InetAddress.getByName("2.2.2.2")
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.SOARecord(
|
||||
new Name("vinyldns."),
|
||||
@ -286,7 +314,9 @@ class ZoneViewLoaderSpec extends WordSpec with Matchers with MockitoSugar with D
|
||||
10800,
|
||||
3600,
|
||||
604800,
|
||||
38400))
|
||||
38400
|
||||
)
|
||||
)
|
||||
dnsRecords.append(
|
||||
new DNS.NULLRecord(
|
||||
new Name("some.unsupported.record.type."),
|
||||
|
@ -35,7 +35,8 @@ class ZoneViewSpec extends WordSpec with Matchers with VinylDNSTestHelpers {
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("1.1.1.1"))),
|
||||
records = List(AData("1.1.1.1"))
|
||||
),
|
||||
RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = "abc",
|
||||
@ -43,7 +44,8 @@ class ZoneViewSpec extends WordSpec with Matchers with VinylDNSTestHelpers {
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("2.2.2.2"))),
|
||||
records = List(AData("2.2.2.2"))
|
||||
),
|
||||
RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = "abc",
|
||||
@ -71,7 +73,8 @@ class ZoneViewSpec extends WordSpec with Matchers with VinylDNSTestHelpers {
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("1.1.1.1"))),
|
||||
records = List(AData("1.1.1.1"))
|
||||
),
|
||||
RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = "vinyldns.",
|
||||
@ -93,7 +96,8 @@ class ZoneViewSpec extends WordSpec with Matchers with VinylDNSTestHelpers {
|
||||
case Some(records) =>
|
||||
records.records should contain theSameElementsAs List(
|
||||
AData("1.1.1.1"),
|
||||
AData("2.2.2.2"))
|
||||
AData("2.2.2.2")
|
||||
)
|
||||
case None => fail()
|
||||
}
|
||||
|
||||
@ -193,7 +197,8 @@ class ZoneViewSpec extends WordSpec with Matchers with VinylDNSTestHelpers {
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("4.4.4.4"))) //updated
|
||||
records = List(AData("4.4.4.4"))
|
||||
) //updated
|
||||
)
|
||||
val dnsView = ZoneView(testZone, dnsRecords)
|
||||
|
||||
@ -268,7 +273,8 @@ class ZoneViewSpec extends WordSpec with Matchers with VinylDNSTestHelpers {
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("4.4.4.4"))) //updated
|
||||
records = List(AData("4.4.4.4"))
|
||||
) //updated
|
||||
)
|
||||
val dnsView = ZoneView(testZone, dnsRecords)
|
||||
|
||||
|
@ -66,7 +66,8 @@ class BatchChangeHandlerSpec
|
||||
DateTime.now,
|
||||
List(addChange),
|
||||
Some("ownerGroupId"),
|
||||
BatchChangeApprovalStatus.AutoApproved)
|
||||
BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
|
||||
override protected def beforeEach(): Unit =
|
||||
batchRepo.clear()
|
||||
|
@ -73,7 +73,8 @@ class RecordSetChangeHandlerSpec
|
||||
SingleChangeStatus.Pending,
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
None
|
||||
)
|
||||
}
|
||||
private val notUpdatedChange = SingleAddChange(
|
||||
Some("someId"),
|
||||
@ -86,7 +87,8 @@ class RecordSetChangeHandlerSpec
|
||||
SingleChangeStatus.Pending,
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
None
|
||||
)
|
||||
private val singleChanges = notUpdatedChange :: completeCreateAAAASingleChanges
|
||||
private val batchChange = BatchChange(
|
||||
"userId",
|
||||
@ -94,7 +96,8 @@ class RecordSetChangeHandlerSpec
|
||||
None,
|
||||
DateTime.now,
|
||||
singleChanges,
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved)
|
||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
||||
)
|
||||
|
||||
private val rsChange =
|
||||
completeCreateAAAA.copy(singleBatchChangeIds = completeCreateAAAASingleChanges.map(_.id))
|
||||
@ -147,7 +150,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Complete,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
recordSetId = Some(rsChange.recordSet.id))
|
||||
recordSetId = Some(rsChange.recordSet.id)
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -188,7 +192,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Complete,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
recordSetId = Some(rsChange.recordSet.id))
|
||||
recordSetId = Some(rsChange.recordSet.id)
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -232,7 +237,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Failed,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
systemMessage = savedCs.changes.head.systemMessage)
|
||||
systemMessage = savedCs.changes.head.systemMessage
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -277,7 +283,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Complete,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
recordSetId = Some(rsChange.recordSet.id))
|
||||
recordSetId = Some(rsChange.recordSet.id)
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -318,7 +325,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Failed,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
systemMessage = savedCs.changes.head.systemMessage)
|
||||
systemMessage = savedCs.changes.head.systemMessage
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -361,7 +369,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Failed,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
systemMessage = savedCs.changes.head.systemMessage)
|
||||
systemMessage = savedCs.changes.head.systemMessage
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -400,7 +409,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Failed,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
systemMessage = savedCs.changes.head.systemMessage)
|
||||
systemMessage = savedCs.changes.head.systemMessage
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -438,7 +448,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Failed,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
systemMessage = savedCs.changes.head.systemMessage)
|
||||
systemMessage = savedCs.changes.head.systemMessage
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -491,7 +502,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Complete,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
recordSetId = Some(rsChange.recordSet.id))
|
||||
recordSetId = Some(rsChange.recordSet.id)
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -549,7 +561,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Complete,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
recordSetId = Some(rsChange.recordSet.id))
|
||||
recordSetId = Some(rsChange.recordSet.id)
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -605,7 +618,8 @@ class RecordSetChangeHandlerSpec
|
||||
"complete an update successfully if the requested record set change matches the DNS backend" in {
|
||||
val updateChange = rsChange.copy(
|
||||
changeType = RecordSetChangeType.Update,
|
||||
updates = Some(rsChange.recordSet.copy(ttl = 87)))
|
||||
updates = Some(rsChange.recordSet.copy(ttl = 87))
|
||||
)
|
||||
doReturn(Interfaces.result(Right(List(updateChange.recordSet))))
|
||||
.when(mockConn)
|
||||
.resolve(rsChange.recordSet.name, rsChange.zone.name, rsChange.recordSet.typ)
|
||||
@ -635,7 +649,8 @@ class RecordSetChangeHandlerSpec
|
||||
ch.copy(
|
||||
status = SingleChangeStatus.Complete,
|
||||
recordChangeId = Some(rsChange.id),
|
||||
recordSetId = Some(rsChange.recordSet.id))
|
||||
recordSetId = Some(rsChange.recordSet.id)
|
||||
)
|
||||
}
|
||||
val scExpected = notUpdatedChange :: updatedSingleChanges
|
||||
batchChangeUpdates.get.changes shouldBe scExpected
|
||||
@ -644,7 +659,8 @@ class RecordSetChangeHandlerSpec
|
||||
"fail an update if current record does not match the DNS backend and the change has not already been applied" in {
|
||||
val updateChange = rsChange.copy(
|
||||
changeType = RecordSetChangeType.Update,
|
||||
updates = Some(rsChange.recordSet.copy(ttl = 87)))
|
||||
updates = Some(rsChange.recordSet.copy(ttl = 87))
|
||||
)
|
||||
doReturn(Interfaces.result(Right(List(updateChange.recordSet.copy(ttl = 30)))))
|
||||
.when(mockConn)
|
||||
.resolve(rsChange.recordSet.name, rsChange.zone.name, rsChange.recordSet.typ)
|
||||
@ -669,7 +685,8 @@ class RecordSetChangeHandlerSpec
|
||||
changeSet.systemMessage shouldBe Some(
|
||||
s"Failed validating update to DNS for change ${changeSet.id}:${changeSet.recordSet.name}: " +
|
||||
s"This record set is out of sync with the DNS backend; sync this zone before attempting to " +
|
||||
"update this record set.")
|
||||
"update this record set."
|
||||
)
|
||||
|
||||
val savedCs = changeRepoCaptor.getValue
|
||||
savedCs.status shouldBe ChangeSetStatus.Complete
|
||||
@ -731,7 +748,8 @@ class RecordSetChangeHandlerSpec
|
||||
.getProcessingStatus(
|
||||
rsChange
|
||||
.copy(changeType = RecordSetChangeType.Update, updates = Some(rs.copy(ttl = 300))),
|
||||
mockConn)
|
||||
mockConn
|
||||
)
|
||||
.unsafeRunSync()
|
||||
processorStatus shouldBe a[ReadyToApply]
|
||||
}
|
||||
@ -755,7 +773,8 @@ class RecordSetChangeHandlerSpec
|
||||
val processorStatus = RecordSetChangeHandler
|
||||
.getProcessingStatus(
|
||||
rsChange.copy(changeType = RecordSetChangeType.Update, updates = None),
|
||||
mockConn)
|
||||
mockConn
|
||||
)
|
||||
.unsafeRunSync()
|
||||
processorStatus shouldBe a[Failure]
|
||||
}
|
||||
|
@ -75,7 +75,8 @@ class ZoneSyncHandlerSpec
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("1.1.1.1")))
|
||||
records = List(AData("1.1.1.1"))
|
||||
)
|
||||
private val testRecord2 = RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = "def",
|
||||
@ -83,7 +84,8 @@ class ZoneSyncHandlerSpec
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("2.2.2.2")))
|
||||
records = List(AData("2.2.2.2"))
|
||||
)
|
||||
private val testRecordDotted = RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = "gh.i.",
|
||||
@ -91,7 +93,8 @@ class ZoneSyncHandlerSpec
|
||||
ttl = 100,
|
||||
status = RecordSetStatus.Active,
|
||||
created = DateTime.now,
|
||||
records = List(AData("3.3.3.3")))
|
||||
records = List(AData("3.3.3.3"))
|
||||
)
|
||||
private val testRecordDottedOk = RecordSet(
|
||||
zoneId = testZone.id,
|
||||
name = s"ok-dotted.${testZone.name}",
|
||||
@ -144,14 +147,16 @@ class ZoneSyncHandlerSpec
|
||||
zoneChangeRepo,
|
||||
zoneRepo,
|
||||
_ => mockDNSLoader,
|
||||
(_, _) => mockVinylDNSLoader)
|
||||
(_, _) => mockVinylDNSLoader
|
||||
)
|
||||
|
||||
private val runSync = ZoneSyncHandler.runSync(
|
||||
recordSetRepo,
|
||||
recordChangeRepo,
|
||||
testZoneChange,
|
||||
_ => mockDNSLoader,
|
||||
(_, _) => mockVinylDNSLoader)
|
||||
(_, _) => mockVinylDNSLoader
|
||||
)
|
||||
|
||||
override def beforeEach(): Unit = {
|
||||
reset(recordSetRepo)
|
||||
@ -309,7 +314,8 @@ class ZoneSyncHandlerSpec
|
||||
recordChangeRepo,
|
||||
testZoneChange,
|
||||
dnsLoader,
|
||||
(_, _) => mockVinylDNSLoader)
|
||||
(_, _) => mockVinylDNSLoader
|
||||
)
|
||||
.unsafeRunSync()
|
||||
|
||||
verify(dnsLoader).apply(captor.capture())
|
||||
@ -325,7 +331,8 @@ class ZoneSyncHandlerSpec
|
||||
recordChangeRepo,
|
||||
testZoneChange,
|
||||
_ => mockDNSLoader,
|
||||
(_, _) => mockVinylDNSLoader)
|
||||
(_, _) => mockVinylDNSLoader
|
||||
)
|
||||
.unsafeRunSync()
|
||||
|
||||
verify(mockDNSLoader, times(1)).load
|
||||
@ -344,7 +351,8 @@ class ZoneSyncHandlerSpec
|
||||
recordChangeRepo,
|
||||
testZoneChange,
|
||||
_ => mockDNSLoader,
|
||||
vinyldnsLoader)
|
||||
vinyldnsLoader
|
||||
)
|
||||
.unsafeRunSync()
|
||||
|
||||
verify(vinyldnsLoader).apply(zoneCaptor.capture(), repoCaptor.capture())
|
||||
@ -444,7 +452,8 @@ class ZoneSyncHandlerSpec
|
||||
recordChangeRepo,
|
||||
zoneChange,
|
||||
_ => mockDNSLoader,
|
||||
(_, _) => mockVinylDNSLoader)
|
||||
(_, _) => mockVinylDNSLoader
|
||||
)
|
||||
.unsafeRunSync()
|
||||
|
||||
captor.getValue.changes should contain theSameElementsAs expectedChanges
|
||||
|
@ -40,7 +40,8 @@ class APIMetricsSpec extends WordSpec with Matchers with MockitoSugar with Eithe
|
||||
""".stripMargin
|
||||
)
|
||||
APIMetrics.loadSettings(config).attempt.unsafeRunSync() shouldBe Right(
|
||||
APIMetricsSettings(MemoryMetricsSettings(logEnabled = true, logSeconds = 5)))
|
||||
APIMetricsSettings(MemoryMetricsSettings(logEnabled = true, logSeconds = 5))
|
||||
)
|
||||
}
|
||||
"fail with invalid config" in {
|
||||
val config = ConfigFactory.parseString(
|
||||
@ -70,7 +71,8 @@ class APIMetricsSpec extends WordSpec with Matchers with MockitoSugar with Eithe
|
||||
APIMetrics
|
||||
.initialize(
|
||||
APIMetricsSettings(MemoryMetricsSettings(logEnabled = true, logSeconds = 5)),
|
||||
reporter)
|
||||
reporter
|
||||
)
|
||||
.unsafeRunSync()
|
||||
verify(reporter).start(5, TimeUnit.SECONDS)
|
||||
}
|
||||
@ -79,7 +81,8 @@ class APIMetricsSpec extends WordSpec with Matchers with MockitoSugar with Eithe
|
||||
APIMetrics
|
||||
.initialize(
|
||||
APIMetricsSettings(MemoryMetricsSettings(logEnabled = false, logSeconds = 5)),
|
||||
reporter)
|
||||
reporter
|
||||
)
|
||||
.unsafeRunSync()
|
||||
verifyZeroInteractions(reporter)
|
||||
}
|
||||
|
@ -72,14 +72,17 @@ class EmailNotifierSpec
|
||||
"smtp",
|
||||
"vinyldns.api.notifier.email.MockTransport",
|
||||
"vinyl",
|
||||
"1.0"))
|
||||
"1.0"
|
||||
)
|
||||
)
|
||||
|
||||
override protected def beforeEach(): Unit =
|
||||
reset(mockUserRepository, mockTransport)
|
||||
|
||||
def batchChange(
|
||||
description: Option[String] = None,
|
||||
changes: List[SingleChange] = List.empty): BatchChange =
|
||||
changes: List[SingleChange] = List.empty
|
||||
): BatchChange =
|
||||
BatchChange(
|
||||
"test",
|
||||
"testUser",
|
||||
@ -91,7 +94,8 @@ class EmailNotifierSpec
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"testBatch")
|
||||
"testBatch"
|
||||
)
|
||||
|
||||
"Email Notifier" should {
|
||||
"do nothing for unsupported Notifications" in {
|
||||
@ -100,7 +104,8 @@ class EmailNotifierSpec
|
||||
"from" -> "Testing <test@test.com>",
|
||||
"smtp.host" -> "wouldfail.mail.com",
|
||||
"smtp.auth.mechanisms" -> "PLAIN"
|
||||
).asJava)
|
||||
).asJava
|
||||
)
|
||||
val notifier = new EmailNotifierProvider()
|
||||
.load(NotifierConfig("", emailConfig), mockUserRepository)
|
||||
.unsafeRunSync()
|
||||
@ -149,8 +154,8 @@ class EmailNotifierSpec
|
||||
)
|
||||
|
||||
doReturn(
|
||||
IO.pure(Some(User("testUser", "access", "secret", None, None, Some("testuser@test.com")))))
|
||||
.when(mockUserRepository)
|
||||
IO.pure(Some(User("testUser", "access", "secret", None, None, Some("testuser@test.com"))))
|
||||
).when(mockUserRepository)
|
||||
.getUser("test")
|
||||
|
||||
val expectedAddresses = Array[Address](new InternetAddress("testuser@test.com"))
|
||||
@ -176,7 +181,8 @@ class EmailNotifierSpec
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
List.empty),
|
||||
List.empty
|
||||
),
|
||||
SingleDeleteRRSetChange(
|
||||
Some(""),
|
||||
Some(""),
|
||||
@ -188,7 +194,8 @@ class EmailNotifierSpec
|
||||
Some("message for you"),
|
||||
None,
|
||||
None,
|
||||
List.empty)
|
||||
List.empty
|
||||
)
|
||||
)
|
||||
val change = batchChange(Some(description), singleChanges)
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user