mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-09-02 23:35:18 +00:00
Merge branch 'master' into aravindhr/add-zone-sync-scheduler-config
This commit is contained in:
@@ -137,6 +137,9 @@ vinyldns {
|
|||||||
from = ${?EMAIL_FROM}
|
from = ${?EMAIL_FROM}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
valid-email-config{
|
||||||
|
email-domains = ["test.com","*dummy.com"]
|
||||||
|
}
|
||||||
|
|
||||||
sns {
|
sns {
|
||||||
class-name = "vinyldns.apadi.notifier.sns.SnsNotifierProvider"
|
class-name = "vinyldns.apadi.notifier.sns.SnsNotifierProvider"
|
||||||
|
@@ -169,7 +169,9 @@ vinyldns {
|
|||||||
from = "VinylDNS <do-not-reply@vinyldns.io>"
|
from = "VinylDNS <do-not-reply@vinyldns.io>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
valid-email-config{
|
||||||
|
email-domains = ["test.com","*dummy.com"]
|
||||||
|
}
|
||||||
sns {
|
sns {
|
||||||
class-name = "vinyldns.api.notifier.sns.SnsNotifierProvider"
|
class-name = "vinyldns.api.notifier.sns.SnsNotifierProvider"
|
||||||
settings {
|
settings {
|
||||||
|
@@ -142,7 +142,7 @@ object Boot extends App {
|
|||||||
vinyldnsConfig.batchChangeConfig,
|
vinyldnsConfig.batchChangeConfig,
|
||||||
vinyldnsConfig.scheduledChangesConfig
|
vinyldnsConfig.scheduledChangesConfig
|
||||||
)
|
)
|
||||||
val membershipService = MembershipService(repositories)
|
val membershipService = MembershipService(repositories,vinyldnsConfig.validEmailConfig)
|
||||||
|
|
||||||
val connectionValidator =
|
val connectionValidator =
|
||||||
new ZoneConnectionValidator(
|
new ZoneConnectionValidator(
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package vinyldns.api.config
|
||||||
|
|
||||||
|
import pureconfig.ConfigReader
|
||||||
|
|
||||||
|
case class ValidEmailConfig(
|
||||||
|
valid_domains : List[String]
|
||||||
|
)
|
||||||
|
object ValidEmailConfig {
|
||||||
|
implicit val configReader: ConfigReader[ValidEmailConfig] =
|
||||||
|
ConfigReader.forProduct1[ValidEmailConfig,List[String]](
|
||||||
|
"email-domains"
|
||||||
|
|
||||||
|
) {
|
||||||
|
case valid_domains => ValidEmailConfig(valid_domains)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -38,6 +38,7 @@ import scala.reflect.ClassTag
|
|||||||
final case class VinylDNSConfig(
|
final case class VinylDNSConfig(
|
||||||
serverConfig: ServerConfig,
|
serverConfig: ServerConfig,
|
||||||
limitsconfig: LimitsConfig,
|
limitsconfig: LimitsConfig,
|
||||||
|
validEmailConfig: ValidEmailConfig,
|
||||||
httpConfig: HttpConfig,
|
httpConfig: HttpConfig,
|
||||||
highValueDomainConfig: HighValueDomainConfig,
|
highValueDomainConfig: HighValueDomainConfig,
|
||||||
manualReviewConfig: ManualReviewConfig,
|
manualReviewConfig: ManualReviewConfig,
|
||||||
@@ -83,6 +84,7 @@ object VinylDNSConfig {
|
|||||||
for {
|
for {
|
||||||
config <- IO.delay(ConfigFactory.load())
|
config <- IO.delay(ConfigFactory.load())
|
||||||
limitsconfig <- loadIO[LimitsConfig](config, "vinyldns.api.limits") //Added Limitsconfig to fetch data from the reference.config and pass to LimitsConfig.config
|
limitsconfig <- loadIO[LimitsConfig](config, "vinyldns.api.limits") //Added Limitsconfig to fetch data from the reference.config and pass to LimitsConfig.config
|
||||||
|
validEmailConfig <- loadIO[ValidEmailConfig](config, path="vinyldns.valid-email-config")
|
||||||
serverConfig <- loadIO[ServerConfig](config, "vinyldns")
|
serverConfig <- loadIO[ServerConfig](config, "vinyldns")
|
||||||
batchChangeConfig <- loadIO[BatchChangeConfig](config, "vinyldns")
|
batchChangeConfig <- loadIO[BatchChangeConfig](config, "vinyldns")
|
||||||
backendConfigs <- loadIO[BackendConfigs](config, "vinyldns.backend")
|
backendConfigs <- loadIO[BackendConfigs](config, "vinyldns.backend")
|
||||||
@@ -103,6 +105,7 @@ object VinylDNSConfig {
|
|||||||
} yield VinylDNSConfig(
|
} yield VinylDNSConfig(
|
||||||
serverConfig,
|
serverConfig,
|
||||||
limitsconfig,
|
limitsconfig,
|
||||||
|
validEmailConfig,
|
||||||
httpConfig,
|
httpConfig,
|
||||||
hvdConfig,
|
hvdConfig,
|
||||||
manualReviewConfig,
|
manualReviewConfig,
|
||||||
|
@@ -27,6 +27,7 @@ import scala.util.matching.Regex
|
|||||||
Object to house common domain validations
|
Object to house common domain validations
|
||||||
*/
|
*/
|
||||||
object DomainValidations {
|
object DomainValidations {
|
||||||
|
|
||||||
val validReverseZoneFQDNRegex: Regex =
|
val validReverseZoneFQDNRegex: Regex =
|
||||||
"""^(?:([0-9a-zA-Z\-\/_]{1,63}|[0-9a-zA-Z\-\/_]{1}[0-9a-zA-Z\-\/_]{0,61}[0-9a-zA-Z\-\/_]{1}|[*.]{2}[0-9a-zA-Z\-\/_]{0,60}[0-9a-zA-Z\-\/_]{1})\.)*$""".r
|
"""^(?:([0-9a-zA-Z\-\/_]{1,63}|[0-9a-zA-Z\-\/_]{1}[0-9a-zA-Z\-\/_]{0,61}[0-9a-zA-Z\-\/_]{1}|[*.]{2}[0-9a-zA-Z\-\/_]{0,60}[0-9a-zA-Z\-\/_]{1})\.)*$""".r
|
||||||
val validForwardZoneFQDNRegex: Regex =
|
val validForwardZoneFQDNRegex: Regex =
|
||||||
@@ -61,13 +62,20 @@ object DomainValidations {
|
|||||||
val MX_PREFERENCE_MIN_VALUE: Int = 0
|
val MX_PREFERENCE_MIN_VALUE: Int = 0
|
||||||
val MX_PREFERENCE_MAX_VALUE: Int = 65535
|
val MX_PREFERENCE_MAX_VALUE: Int = 65535
|
||||||
|
|
||||||
|
// Cname check - Cname should not be IP address
|
||||||
|
def validateCname(name: Fqdn, isReverse: Boolean): ValidatedNel[DomainValidationError, Fqdn] =
|
||||||
|
validateIpv4Address(name.fqdn.dropRight(1)).isValid match {
|
||||||
|
case true => InvalidIPv4CName(name.toString).invalidNel
|
||||||
|
case false => validateIsReverseCname(name, isReverse)
|
||||||
|
}
|
||||||
|
|
||||||
def validateHostName(name: Fqdn): ValidatedNel[DomainValidationError, Fqdn] =
|
def validateHostName(name: Fqdn): ValidatedNel[DomainValidationError, Fqdn] =
|
||||||
validateHostName(name.fqdn).map(_ => name)
|
validateHostName(name.fqdn).map(_ => name)
|
||||||
|
|
||||||
def validateCname(name: Fqdn, isReverse: Boolean): ValidatedNel[DomainValidationError, Fqdn] =
|
def validateIsReverseCname(name: Fqdn, isReverse: Boolean): ValidatedNel[DomainValidationError, Fqdn] =
|
||||||
validateCname(name.fqdn, isReverse).map(_ => name)
|
validateIsReverseCname(name.fqdn, isReverse).map(_ => name)
|
||||||
|
|
||||||
def validateCname(name: String, isReverse: Boolean): ValidatedNel[DomainValidationError, String] = {
|
def validateIsReverseCname(name: String, isReverse: Boolean): ValidatedNel[DomainValidationError, String] = {
|
||||||
isReverse match {
|
isReverse match {
|
||||||
case true =>
|
case true =>
|
||||||
val checkRegex = validReverseZoneFQDNRegex
|
val checkRegex = validReverseZoneFQDNRegex
|
||||||
|
@@ -178,6 +178,8 @@ final case class GroupAlreadyExistsError(msg: String) extends Throwable(msg)
|
|||||||
|
|
||||||
final case class GroupValidationError(msg: String) extends Throwable(msg)
|
final case class GroupValidationError(msg: String) extends Throwable(msg)
|
||||||
|
|
||||||
|
final case class EmailValidationError(msg: String) extends Throwable(msg)
|
||||||
|
|
||||||
final case class UserNotFoundError(msg: String) extends Throwable(msg)
|
final case class UserNotFoundError(msg: String) extends Throwable(msg)
|
||||||
|
|
||||||
final case class InvalidGroupError(msg: String) extends Throwable(msg)
|
final case class InvalidGroupError(msg: String) extends Throwable(msg)
|
||||||
|
@@ -20,6 +20,7 @@ import cats.effect.IO
|
|||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import scalikejdbc.DB
|
import scalikejdbc.DB
|
||||||
import vinyldns.api.Interfaces._
|
import vinyldns.api.Interfaces._
|
||||||
|
import vinyldns.api.config.ValidEmailConfig
|
||||||
import vinyldns.api.repository.ApiDataAccessor
|
import vinyldns.api.repository.ApiDataAccessor
|
||||||
import vinyldns.core.domain.auth.AuthPrincipal
|
import vinyldns.core.domain.auth.AuthPrincipal
|
||||||
import vinyldns.core.domain.membership.LockStatus.LockStatus
|
import vinyldns.core.domain.membership.LockStatus.LockStatus
|
||||||
@@ -30,14 +31,15 @@ import vinyldns.core.Messages._
|
|||||||
import vinyldns.mysql.TransactionProvider
|
import vinyldns.mysql.TransactionProvider
|
||||||
|
|
||||||
object MembershipService {
|
object MembershipService {
|
||||||
def apply(dataAccessor: ApiDataAccessor): MembershipService =
|
def apply(dataAccessor: ApiDataAccessor,emailConfig:ValidEmailConfig): MembershipService =
|
||||||
new MembershipService(
|
new MembershipService(
|
||||||
dataAccessor.groupRepository,
|
dataAccessor.groupRepository,
|
||||||
dataAccessor.userRepository,
|
dataAccessor.userRepository,
|
||||||
dataAccessor.membershipRepository,
|
dataAccessor.membershipRepository,
|
||||||
dataAccessor.zoneRepository,
|
dataAccessor.zoneRepository,
|
||||||
dataAccessor.groupChangeRepository,
|
dataAccessor.groupChangeRepository,
|
||||||
dataAccessor.recordSetRepository
|
dataAccessor.recordSetRepository,
|
||||||
|
emailConfig
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +49,8 @@ class MembershipService(
|
|||||||
membershipRepo: MembershipRepository,
|
membershipRepo: MembershipRepository,
|
||||||
zoneRepo: ZoneRepository,
|
zoneRepo: ZoneRepository,
|
||||||
groupChangeRepo: GroupChangeRepository,
|
groupChangeRepo: GroupChangeRepository,
|
||||||
recordSetRepo: RecordSetRepository
|
recordSetRepo: RecordSetRepository,
|
||||||
|
validDomains: ValidEmailConfig
|
||||||
) extends MembershipServiceAlgebra with TransactionProvider {
|
) extends MembershipServiceAlgebra with TransactionProvider {
|
||||||
|
|
||||||
import MembershipValidations._
|
import MembershipValidations._
|
||||||
@@ -58,6 +61,7 @@ class MembershipService(
|
|||||||
val nonAdminMembers = inputGroup.memberIds.diff(adminMembers)
|
val nonAdminMembers = inputGroup.memberIds.diff(adminMembers)
|
||||||
for {
|
for {
|
||||||
_ <- groupValidation(newGroup)
|
_ <- groupValidation(newGroup)
|
||||||
|
_ <- emailValidation(newGroup.email)
|
||||||
_ <- hasMembersAndAdmins(newGroup).toResult
|
_ <- hasMembersAndAdmins(newGroup).toResult
|
||||||
_ <- groupWithSameNameDoesNotExist(newGroup.name)
|
_ <- groupWithSameNameDoesNotExist(newGroup.name)
|
||||||
_ <- usersExist(newGroup.memberIds)
|
_ <- usersExist(newGroup.memberIds)
|
||||||
@@ -78,6 +82,7 @@ class MembershipService(
|
|||||||
existingGroup <- getExistingGroup(groupId)
|
existingGroup <- getExistingGroup(groupId)
|
||||||
newGroup = existingGroup.withUpdates(name, email, description, memberIds, adminUserIds)
|
newGroup = existingGroup.withUpdates(name, email, description, memberIds, adminUserIds)
|
||||||
_ <- groupValidation(newGroup)
|
_ <- groupValidation(newGroup)
|
||||||
|
_ <- emailValidation(newGroup.email)
|
||||||
_ <- canEditGroup(existingGroup, authPrincipal).toResult
|
_ <- canEditGroup(existingGroup, authPrincipal).toResult
|
||||||
addedAdmins = newGroup.adminUserIds.diff(existingGroup.adminUserIds)
|
addedAdmins = newGroup.adminUserIds.diff(existingGroup.adminUserIds)
|
||||||
// new non-admin members ++ admins converted to non-admins
|
// new non-admin members ++ admins converted to non-admins
|
||||||
@@ -381,6 +386,29 @@ class MembershipService(
|
|||||||
().asRight
|
().asRight
|
||||||
}
|
}
|
||||||
}.toResult
|
}.toResult
|
||||||
|
// Validate email details.Email domains details are fetched from the config file.
|
||||||
|
def emailValidation(email: String): Result[Unit] = {
|
||||||
|
val emailDomains = validDomains.valid_domains
|
||||||
|
val splitEmailDomains = emailDomains.mkString(",")
|
||||||
|
val emailRegex ="""^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9._]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r
|
||||||
|
val index = email.indexOf('@');
|
||||||
|
val emailSplit = if(index != -1){
|
||||||
|
email.substring(index+1,email.length)}
|
||||||
|
val wildcardEmailDomains=if(splitEmailDomains.contains("*")){
|
||||||
|
emailDomains.map(x=>x.replaceAllLiterally("*",""))}
|
||||||
|
else emailDomains
|
||||||
|
|
||||||
|
Option(email) match {
|
||||||
|
case Some(value) if (emailRegex.findFirstIn(value) != None)=>
|
||||||
|
|
||||||
|
if (emailDomains.contains(emailSplit) || emailDomains.isEmpty || wildcardEmailDomains.exists(x => emailSplit.toString.endsWith(x)))
|
||||||
|
().asRight
|
||||||
|
else
|
||||||
|
EmailValidationError(EmailValidationErrorMsg + " " + wildcardEmailDomains.mkString(",")).asLeft
|
||||||
|
case _ =>
|
||||||
|
EmailValidationError(InvalidEmailValidationErrorMsg).asLeft
|
||||||
|
}}.toResult
|
||||||
|
|
||||||
|
|
||||||
def groupWithSameNameDoesNotExist(name: String): Result[Unit] =
|
def groupWithSameNameDoesNotExist(name: String): Result[Unit] =
|
||||||
groupRepo
|
groupRepo
|
||||||
|
@@ -20,6 +20,7 @@ import cats.syntax.either._
|
|||||||
import vinyldns.api.Interfaces._
|
import vinyldns.api.Interfaces._
|
||||||
import vinyldns.api.backend.dns.DnsConversions
|
import vinyldns.api.backend.dns.DnsConversions
|
||||||
import vinyldns.api.config.HighValueDomainConfig
|
import vinyldns.api.config.HighValueDomainConfig
|
||||||
|
import vinyldns.api.domain.DomainValidations.validateIpv4Address
|
||||||
import vinyldns.api.domain._
|
import vinyldns.api.domain._
|
||||||
import vinyldns.core.domain.DomainHelpers._
|
import vinyldns.core.domain.DomainHelpers._
|
||||||
import vinyldns.core.domain.record.RecordType._
|
import vinyldns.core.domain.record.RecordType._
|
||||||
@@ -236,6 +237,16 @@ object RecordSetValidations {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isNotIPv4inCname = {
|
||||||
|
ensuring(
|
||||||
|
RecordSetValidation(
|
||||||
|
s"""Invalid CNAME: ${newRecordSet.records.head.toString.dropRight(1)}, valid CNAME record data cannot be an IP address."""
|
||||||
|
)
|
||||||
|
)(
|
||||||
|
validateIpv4Address(newRecordSet.records.head.toString.dropRight(1)).isInvalid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_ <- isNotOrigin(
|
_ <- isNotOrigin(
|
||||||
newRecordSet,
|
newRecordSet,
|
||||||
@@ -243,6 +254,7 @@ object RecordSetValidations {
|
|||||||
"CNAME RecordSet cannot have name '@' because it points to zone origin"
|
"CNAME RecordSet cannot have name '@' because it points to zone origin"
|
||||||
)
|
)
|
||||||
_ <- noRecordWithName
|
_ <- noRecordWithName
|
||||||
|
_ <- isNotIPv4inCname
|
||||||
_ <- RDataWithConsecutiveDots
|
_ <- RDataWithConsecutiveDots
|
||||||
_ <- checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit)
|
_ <- checkForDot(newRecordSet, zone, existingRecordSet, recordFqdnDoesNotExist, dottedHostZoneConfig, isRecordTypeAndUserAllowed, allowedDotsLimit)
|
||||||
} yield ()
|
} yield ()
|
||||||
|
@@ -49,6 +49,7 @@ class MembershipRoute(
|
|||||||
case InvalidGroupError(msg) => complete(StatusCodes.BadRequest, msg)
|
case InvalidGroupError(msg) => complete(StatusCodes.BadRequest, msg)
|
||||||
case UserNotFoundError(msg) => complete(StatusCodes.NotFound, msg)
|
case UserNotFoundError(msg) => complete(StatusCodes.NotFound, msg)
|
||||||
case InvalidGroupRequestError(msg) => complete(StatusCodes.BadRequest, msg)
|
case InvalidGroupRequestError(msg) => complete(StatusCodes.BadRequest, msg)
|
||||||
|
case EmailValidationError(msg) => complete(StatusCodes.BadRequest, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
val membershipRoute: Route = path("groups" / Segment) { groupId =>
|
val membershipRoute: Route = path("groups" / Segment) { groupId =>
|
||||||
|
@@ -1938,7 +1938,8 @@ def test_cname_recordtype_add_checks(shared_zone_test_context):
|
|||||||
get_change_CNAME_json(existing_forward_fqdn),
|
get_change_CNAME_json(existing_forward_fqdn),
|
||||||
get_change_CNAME_json(existing_cname_fqdn),
|
get_change_CNAME_json(existing_cname_fqdn),
|
||||||
get_change_CNAME_json(f"0.{ip4_zone_name}", cname="duplicate.in.db."),
|
get_change_CNAME_json(f"0.{ip4_zone_name}", cname="duplicate.in.db."),
|
||||||
get_change_CNAME_json(f"user-add-unauthorized.{dummy_zone_name}")
|
get_change_CNAME_json(f"user-add-unauthorized.{dummy_zone_name}"),
|
||||||
|
get_change_CNAME_json(f"invalid-ipv4-{parent_zone_name}", cname="1.2.3.4")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2014,6 +2015,9 @@ def test_cname_recordtype_add_checks(shared_zone_test_context):
|
|||||||
assert_failed_change_in_error_response(response[16], input_name=f"user-add-unauthorized.{dummy_zone_name}",
|
assert_failed_change_in_error_response(response[16], input_name=f"user-add-unauthorized.{dummy_zone_name}",
|
||||||
record_type="CNAME", record_data="test.com.",
|
record_type="CNAME", record_data="test.com.",
|
||||||
error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."])
|
error_messages=[f"User \"ok\" is not authorized. Contact zone owner group: {dummy_group_name} at test@test.com to make DNS changes."])
|
||||||
|
assert_failed_change_in_error_response(response[17], input_name=f"invalid-ipv4-{parent_zone_name}", record_type="CNAME", record_data="1.2.3.4.",
|
||||||
|
error_messages=[f'Invalid Cname: "Fqdn(1.2.3.4.)", Valid CNAME record data should not be an IP address'])
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
clear_recordset_list(to_delete, client)
|
clear_recordset_list(to_delete, client)
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ def test_create_group_success(shared_zone_test_context):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
new_group = {
|
new_group = {
|
||||||
"name": f"test-create-group-success{shared_zone_test_context.partition_id}",
|
"name": "test-create-group-success{shared_zone_test_context.partition_id}",
|
||||||
"email": "test@test.com",
|
"email": "test@test.com",
|
||||||
"description": "this is a description",
|
"description": "this is a description",
|
||||||
"members": [{"id": "ok"}],
|
"members": [{"id": "ok"}],
|
||||||
@@ -32,6 +32,36 @@ def test_create_group_success(shared_zone_test_context):
|
|||||||
if result:
|
if result:
|
||||||
client.delete_group(result["id"], status=(200, 404))
|
client.delete_group(result["id"], status=(200, 404))
|
||||||
|
|
||||||
|
def test_create_group_success_wildcard(shared_zone_test_context):
|
||||||
|
"""
|
||||||
|
Tests that creating a group works
|
||||||
|
"""
|
||||||
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
|
result = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_group = {
|
||||||
|
"name": "test-create-group-success_wildcard{shared_zone_test_context.partition_id}",
|
||||||
|
"email": "test@ok.dummy.com",
|
||||||
|
"description": "this is a description",
|
||||||
|
"members": [{"id": "ok"}],
|
||||||
|
"admins": [{"id": "ok"}]
|
||||||
|
}
|
||||||
|
result = client.create_group(new_group, status=200)
|
||||||
|
|
||||||
|
assert_that(result["name"], is_(new_group["name"]))
|
||||||
|
assert_that(result["email"], is_(new_group["email"]))
|
||||||
|
assert_that(result["description"], is_(new_group["description"]))
|
||||||
|
assert_that(result["status"], is_("Active"))
|
||||||
|
assert_that(result["created"], not_none())
|
||||||
|
assert_that(result["id"], not_none())
|
||||||
|
assert_that(result["members"], has_length(1))
|
||||||
|
assert_that(result["members"][0]["id"], is_("ok"))
|
||||||
|
assert_that(result["admins"], has_length(1))
|
||||||
|
assert_that(result["admins"][0]["id"], is_("ok"))
|
||||||
|
finally:
|
||||||
|
if result:
|
||||||
|
client.delete_group(result["id"], status=(200, 404))
|
||||||
|
|
||||||
def test_creator_is_an_admin(shared_zone_test_context):
|
def test_creator_is_an_admin(shared_zone_test_context):
|
||||||
"""
|
"""
|
||||||
@@ -114,8 +144,36 @@ def test_create_group_without_name_or_email(shared_zone_test_context):
|
|||||||
"Missing Group.name",
|
"Missing Group.name",
|
||||||
"Missing Group.email"
|
"Missing Group.email"
|
||||||
))
|
))
|
||||||
|
def test_create_group_with_invalid_email_domain(shared_zone_test_context):
|
||||||
|
"""
|
||||||
|
Tests that creating a group With Invalid email fails
|
||||||
|
"""
|
||||||
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
|
|
||||||
|
new_group = {
|
||||||
|
"name": "invalid-email",
|
||||||
|
"email": "test@abc.com",
|
||||||
|
"description": "this is a description",
|
||||||
|
"members": [{"id": "ok"}],
|
||||||
|
"admins": [{"id": "ok"}]
|
||||||
|
}
|
||||||
|
error = client.create_group(new_group, status=400)
|
||||||
|
assert_that(error, is_("Please enter a valid Email ID. Valid domains should end with test.com,dummy.com"))
|
||||||
|
def test_create_group_with_invalid_email(shared_zone_test_context):
|
||||||
|
"""
|
||||||
|
Tests that creating a group With Invalid email fails
|
||||||
|
"""
|
||||||
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
|
|
||||||
|
new_group = {
|
||||||
|
"name": "invalid-email",
|
||||||
|
"email": "test.abc.com",
|
||||||
|
"description": "this is a description",
|
||||||
|
"members": [{"id": "ok"}],
|
||||||
|
"admins": [{"id": "ok"}]
|
||||||
|
}
|
||||||
|
error = client.create_group(new_group, status=400)
|
||||||
|
assert_that(error, is_("Please enter a valid Email ID."))
|
||||||
def test_create_group_without_members_or_admins(shared_zone_test_context):
|
def test_create_group_without_members_or_admins(shared_zone_test_context):
|
||||||
"""
|
"""
|
||||||
Tests that creating a group without members or admins fails
|
Tests that creating a group without members or admins fails
|
||||||
|
@@ -681,6 +681,24 @@ def test_create_dotted_cname_record_succeeds_if_all_dotted_hosts_config_satisfie
|
|||||||
client.wait_until_recordset_change_status(delete_result, "Complete")
|
client.wait_until_recordset_change_status(delete_result, "Complete")
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_IPv4_cname_record_fails(shared_zone_test_context):
|
||||||
|
"""
|
||||||
|
Test that creating a CNAME record set as IPv4 address records returns an error.
|
||||||
|
"""
|
||||||
|
client = shared_zone_test_context.ok_vinyldns_client
|
||||||
|
zone = shared_zone_test_context.parent_zone
|
||||||
|
apex_cname_rs = {
|
||||||
|
"zoneId": zone["id"],
|
||||||
|
"name": "test_create_cname_with_ipaddress",
|
||||||
|
"type": "CNAME",
|
||||||
|
"ttl": 500,
|
||||||
|
"records": [{"cname": "1.2.3.4"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
error = client.create_recordset(apex_cname_rs, status=400)
|
||||||
|
assert_that(error, is_(f'Invalid CNAME: 1.2.3.4, valid CNAME record data cannot be an IP address.'))
|
||||||
|
|
||||||
|
|
||||||
def test_create_cname_with_multiple_records(shared_zone_test_context):
|
def test_create_cname_with_multiple_records(shared_zone_test_context):
|
||||||
"""
|
"""
|
||||||
Test that creating a CNAME record set with multiple records returns an error
|
Test that creating a CNAME record set with multiple records returns an error
|
||||||
|
@@ -127,7 +127,9 @@ vinyldns {
|
|||||||
from = ${?EMAIL_FROM}
|
from = ${?EMAIL_FROM}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
valid-email-config{
|
||||||
|
email-domains = ["test.com","*dummy.com","*ok.com"]
|
||||||
|
}
|
||||||
sns {
|
sns {
|
||||||
class-name = "vinyldns.apadi.notifier.sns.SnsNotifierProvider"
|
class-name = "vinyldns.apadi.notifier.sns.SnsNotifierProvider"
|
||||||
class-name = ${?SNS_CLASS_NAME}
|
class-name = ${?SNS_CLASS_NAME}
|
||||||
|
@@ -22,7 +22,7 @@ import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
|
|||||||
import org.scalatest.propspec.AnyPropSpec
|
import org.scalatest.propspec.AnyPropSpec
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
import vinyldns.api.ValidationTestImprovements._
|
import vinyldns.api.ValidationTestImprovements._
|
||||||
import vinyldns.core.domain.{InvalidDomainName, InvalidCname, InvalidLength}
|
import vinyldns.core.domain.{InvalidDomainName, Fqdn, InvalidCname, InvalidLength}
|
||||||
|
|
||||||
class DomainValidationsSpec
|
class DomainValidationsSpec
|
||||||
extends AnyPropSpec
|
extends AnyPropSpec
|
||||||
@@ -77,6 +77,54 @@ class DomainValidationsSpec
|
|||||||
validateHostName("asterisk.domain*.name.") shouldBe invalid
|
validateHostName("asterisk.domain*.name.") shouldBe invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property("Shortest fqdn name should be valid") {
|
||||||
|
val fqdn = Fqdn("a.")
|
||||||
|
validateCname(fqdn, false) shouldBe valid
|
||||||
|
}
|
||||||
|
|
||||||
|
property("Ip address in cname should be invalid") {
|
||||||
|
val fqdn = Fqdn("1.2.3.4")
|
||||||
|
validateCname(fqdn, false) shouldBe invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
property("Longest fqdn name should be valid") {
|
||||||
|
val fqdn = Fqdn(("a" * 50 + ".") * 5)
|
||||||
|
validateCname(fqdn, false) shouldBe valid
|
||||||
|
}
|
||||||
|
|
||||||
|
property("fqdn name should pass property-based testing") {
|
||||||
|
forAll(domainGenerator) { domain: String =>
|
||||||
|
val domains= Fqdn(domain)
|
||||||
|
whenever(validateHostName(domains).isValid) {
|
||||||
|
domains.fqdn.length should be > 0
|
||||||
|
domains.fqdn.length should be < 256
|
||||||
|
(domains.fqdn should fullyMatch).regex(validFQDNRegex)
|
||||||
|
domains.fqdn should endWith(".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property("fqdn names beginning with invalid characters should fail with InvalidCname") {
|
||||||
|
validateCname(Fqdn("/slash.domain.name."), false).failWith[InvalidCname]
|
||||||
|
validateCname(Fqdn("-hyphen.domain.name."), false).failWith[InvalidCname]
|
||||||
|
}
|
||||||
|
|
||||||
|
property("fqdn names with underscores should pass property-based testing") {
|
||||||
|
validateCname(Fqdn("_underscore.domain.name."), false).isValid
|
||||||
|
validateCname(Fqdn("under_score.domain.name."), false).isValid
|
||||||
|
validateCname(Fqdn("underscore._domain.name."), false).isValid
|
||||||
|
}
|
||||||
|
|
||||||
|
// For wildcard records. '*' can only be in the beginning followed by '.' and domain name
|
||||||
|
property("fqdn names beginning with asterisk should pass property-based testing") {
|
||||||
|
validateCname(Fqdn("*.domain.name."), false) shouldBe valid
|
||||||
|
validateCname(Fqdn("aste*risk.domain.name."),false) shouldBe invalid
|
||||||
|
validateCname(Fqdn("*asterisk.domain.name."),false) shouldBe invalid
|
||||||
|
validateCname(Fqdn("asterisk*.domain.name."),false) shouldBe invalid
|
||||||
|
validateCname(Fqdn("asterisk.*domain.name."),false)shouldBe invalid
|
||||||
|
validateCname(Fqdn("asterisk.domain*.name."),false) shouldBe invalid
|
||||||
|
}
|
||||||
|
|
||||||
property("Valid Ipv4 addresses should pass property-based testing") {
|
property("Valid Ipv4 addresses should pass property-based testing") {
|
||||||
forAll(validIpv4Gen) { validIp: String =>
|
forAll(validIpv4Gen) { validIp: String =>
|
||||||
val res = validateIpv4Address(validIp)
|
val res = validateIpv4Address(validIp)
|
||||||
@@ -112,50 +160,50 @@ class DomainValidationsSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
property("Shortest cname should be valid") {
|
property("Shortest cname should be valid") {
|
||||||
validateCname("a.",true) shouldBe valid
|
validateIsReverseCname("a.",true) shouldBe valid
|
||||||
validateCname("a.",false) shouldBe valid
|
validateIsReverseCname("a.",false) shouldBe valid
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property("Longest cname should be valid") {
|
property("Longest cname should be valid") {
|
||||||
val name = ("a" * 50 + ".") * 5
|
val name = ("a" * 50 + ".") * 5
|
||||||
validateCname(name,true) shouldBe valid
|
validateIsReverseCname(name,true) shouldBe valid
|
||||||
validateCname(name,false) shouldBe valid
|
validateIsReverseCname(name,false) shouldBe valid
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property("Cnames with underscores should pass property-based testing") {
|
property("Cnames with underscores should pass property-based testing") {
|
||||||
validateCname("_underscore.domain.name.",true).isValid
|
validateIsReverseCname("_underscore.domain.name.",true).isValid
|
||||||
validateCname("under_score.domain.name.",true).isValid
|
validateIsReverseCname("under_score.domain.name.",true).isValid
|
||||||
validateCname("underscore._domain.name.",true).isValid
|
validateIsReverseCname("underscore._domain.name.",true).isValid
|
||||||
validateCname("_underscore.domain.name.",false).isValid
|
validateIsReverseCname("_underscore.domain.name.",false).isValid
|
||||||
validateCname("under_score.domain.name.",false).isValid
|
validateIsReverseCname("under_score.domain.name.",false).isValid
|
||||||
validateCname("underscore._domain.name.",false).isValid
|
validateIsReverseCname("underscore._domain.name.",false).isValid
|
||||||
}
|
}
|
||||||
|
|
||||||
// For wildcard records. '*' can only be in the beginning followed by '.' and domain name
|
// For wildcard records. '*' can only be in the beginning followed by '.' and domain name
|
||||||
property("Cnames beginning with asterisk should pass property-based testing") {
|
property("Cnames beginning with asterisk should pass property-based testing") {
|
||||||
validateCname("*.domain.name.",true) shouldBe valid
|
validateIsReverseCname("*.domain.name.",true) shouldBe valid
|
||||||
validateCname("aste*risk.domain.name.",true) shouldBe invalid
|
validateIsReverseCname("aste*risk.domain.name.",true) shouldBe invalid
|
||||||
validateCname("*asterisk.domain.name.",true) shouldBe invalid
|
validateIsReverseCname("*asterisk.domain.name.",true) shouldBe invalid
|
||||||
validateCname("asterisk*.domain.name.",true) shouldBe invalid
|
validateIsReverseCname("asterisk*.domain.name.",true) shouldBe invalid
|
||||||
validateCname("asterisk.*domain.name.",true) shouldBe invalid
|
validateIsReverseCname("asterisk.*domain.name.",true) shouldBe invalid
|
||||||
validateCname("asterisk.domain*.name.",true) shouldBe invalid
|
validateIsReverseCname("asterisk.domain*.name.",true) shouldBe invalid
|
||||||
validateCname("*.domain.name.",false) shouldBe valid
|
validateIsReverseCname("*.domain.name.",false) shouldBe valid
|
||||||
validateCname("aste*risk.domain.name.",false) shouldBe invalid
|
validateIsReverseCname("aste*risk.domain.name.",false) shouldBe invalid
|
||||||
validateCname("*asterisk.domain.name.",false) shouldBe invalid
|
validateIsReverseCname("*asterisk.domain.name.",false) shouldBe invalid
|
||||||
validateCname("asterisk*.domain.name.",false) shouldBe invalid
|
validateIsReverseCname("asterisk*.domain.name.",false) shouldBe invalid
|
||||||
validateCname("asterisk.*domain.name.",false) shouldBe invalid
|
validateIsReverseCname("asterisk.*domain.name.",false) shouldBe invalid
|
||||||
validateCname("asterisk.domain*.name.",false) shouldBe invalid
|
validateIsReverseCname("asterisk.domain*.name.",false) shouldBe invalid
|
||||||
}
|
}
|
||||||
property("Cname names with forward slash should pass with reverse zone") {
|
property("Cname names with forward slash should pass with reverse zone") {
|
||||||
validateCname("/slash.cname.name.",true).isValid
|
validateIsReverseCname("/slash.cname.name.",true).isValid
|
||||||
validateCname("slash./cname.name.",true).isValid
|
validateIsReverseCname("slash./cname.name.",true).isValid
|
||||||
validateCname("slash.cname./name.",true).isValid
|
validateIsReverseCname("slash.cname./name.",true).isValid
|
||||||
}
|
}
|
||||||
property("Cname names with forward slash should fail with forward zone") {
|
property("Cname names with forward slash should fail with forward zone") {
|
||||||
validateCname("/slash.cname.name.",false).failWith[InvalidCname]
|
validateIsReverseCname("/slash.cname.name.",false).failWith[InvalidCname]
|
||||||
validateCname("slash./cname.name.",false).failWith[InvalidCname]
|
validateIsReverseCname("slash./cname.name.",false).failWith[InvalidCname]
|
||||||
validateCname("slash.cname./name.",false).failWith[InvalidCname]
|
validateIsReverseCname("slash.cname./name.",false).failWith[InvalidCname]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -717,6 +717,21 @@ class BatchChangeValidationsSpec
|
|||||||
result should haveInvalid[DomainValidationError](InvalidCname(s"$invalidCNAMERecordData.",false))
|
result should haveInvalid[DomainValidationError](InvalidCname(s"$invalidCNAMERecordData.",false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property("""validateAddChangeInput: should fail with Invalid CNAME
|
||||||
|
|if validateRecordData fails for IPv4 Address in CNAME record data""".stripMargin) {
|
||||||
|
val invalidCNAMERecordData = "1.2.3.4"
|
||||||
|
val change =
|
||||||
|
AddChangeInput(
|
||||||
|
"test.comcast.com.",
|
||||||
|
RecordType.CNAME,
|
||||||
|
ttl,
|
||||||
|
CNAMEData(Fqdn(invalidCNAMERecordData))
|
||||||
|
)
|
||||||
|
val result = validateAddChangeInput(change, false)
|
||||||
|
|
||||||
|
result should haveInvalid[DomainValidationError](InvalidIPv4CName(s"Fqdn($invalidCNAMERecordData.)"))
|
||||||
|
}
|
||||||
|
|
||||||
property("""validateAddChangeInput: should fail with InvalidLength
|
property("""validateAddChangeInput: should fail with InvalidLength
|
||||||
|if validateRecordData fails for invalid CNAME record data""".stripMargin) {
|
|if validateRecordData fails for invalid CNAME record data""".stripMargin) {
|
||||||
val invalidCNAMERecordData = "s" * 256
|
val invalidCNAMERecordData = "s" * 256
|
||||||
|
@@ -29,6 +29,7 @@ import vinyldns.core.domain.auth.AuthPrincipal
|
|||||||
import vinyldns.core.domain.zone.ZoneRepository
|
import vinyldns.core.domain.zone.ZoneRepository
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import scalikejdbc.{ConnectionPool, DB}
|
import scalikejdbc.{ConnectionPool, DB}
|
||||||
|
import vinyldns.api.config.ValidEmailConfig
|
||||||
import vinyldns.api.domain.zone.NotAuthorizedError
|
import vinyldns.api.domain.zone.NotAuthorizedError
|
||||||
import vinyldns.core.TestMembershipData._
|
import vinyldns.core.TestMembershipData._
|
||||||
import vinyldns.core.TestZoneData._
|
import vinyldns.core.TestZoneData._
|
||||||
@@ -48,6 +49,8 @@ class MembershipServiceSpec
|
|||||||
private val mockZoneRepo = mock[ZoneRepository]
|
private val mockZoneRepo = mock[ZoneRepository]
|
||||||
private val mockGroupChangeRepo = mock[GroupChangeRepository]
|
private val mockGroupChangeRepo = mock[GroupChangeRepository]
|
||||||
private val mockRecordSetRepo = mock[RecordSetRepository]
|
private val mockRecordSetRepo = mock[RecordSetRepository]
|
||||||
|
private val mockValidEmailConfig = ValidEmailConfig(valid_domains = List("test.com","*dummy.com"))
|
||||||
|
private val mockValidEmailConfigNew = ValidEmailConfig(valid_domains = List())
|
||||||
|
|
||||||
private val backingService = new MembershipService(
|
private val backingService = new MembershipService(
|
||||||
mockGroupRepo,
|
mockGroupRepo,
|
||||||
@@ -55,9 +58,20 @@ class MembershipServiceSpec
|
|||||||
mockMembershipRepo,
|
mockMembershipRepo,
|
||||||
mockZoneRepo,
|
mockZoneRepo,
|
||||||
mockGroupChangeRepo,
|
mockGroupChangeRepo,
|
||||||
mockRecordSetRepo
|
mockRecordSetRepo,
|
||||||
|
mockValidEmailConfig
|
||||||
|
)
|
||||||
|
private val backingServiceNew = new MembershipService(
|
||||||
|
mockGroupRepo,
|
||||||
|
mockUserRepo,
|
||||||
|
mockMembershipRepo,
|
||||||
|
mockZoneRepo,
|
||||||
|
mockGroupChangeRepo,
|
||||||
|
mockRecordSetRepo,
|
||||||
|
mockValidEmailConfigNew
|
||||||
)
|
)
|
||||||
private val underTest = spy(backingService)
|
private val underTest = spy(backingService)
|
||||||
|
private val underTestNew = spy(backingServiceNew)
|
||||||
|
|
||||||
private val okUserInfo: UserInfo = UserInfo(okUser)
|
private val okUserInfo: UserInfo = UserInfo(okUser)
|
||||||
private val dummyUserInfo: UserInfo = UserInfo(dummyUser)
|
private val dummyUserInfo: UserInfo = UserInfo(dummyUser)
|
||||||
@@ -82,7 +96,7 @@ class MembershipServiceSpec
|
|||||||
// the update will remove users 3 and 4, add users 5 and 6, as well as a new admin user 7 and remove user2 as admin
|
// 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(
|
private val updatedInfo = Group(
|
||||||
name = "new.name",
|
name = "new.name",
|
||||||
email = "new.email",
|
email = "test@test.com",
|
||||||
description = Some("new desc"),
|
description = Some("new desc"),
|
||||||
id = "id",
|
id = "id",
|
||||||
memberIds = Set("user1", "user2", "user5", "user6", "user7"),
|
memberIds = Set("user1", "user2", "user5", "user6", "user7"),
|
||||||
@@ -282,8 +296,162 @@ class MembershipServiceSpec
|
|||||||
verify(mockMembershipRepo, never())
|
verify(mockMembershipRepo, never())
|
||||||
.saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean)
|
.saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"return an error if an invalid domain is entered" in {
|
||||||
|
val error = underTest.createGroup(groupInfo.copy(email = "test@ok.com"), okAuth).value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return an error if an invalid email is entered" in {
|
||||||
|
val error = underTest.createGroup(groupInfo.copy(email = "test.ok.com"), okAuth).value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return an error if an invalid email with * is entered" in {
|
||||||
|
val error = underTest.createGroup(groupInfo.copy(email = "test@*dummy.com"), okAuth).value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"return an error if an email is invalid test case 1" in {
|
||||||
|
val error = underTest.emailValidation(email = "test.ok.com").value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return an error if a domain is invalid test case 1" in {
|
||||||
|
val error = underTest.emailValidation(email = "test@ok.com").value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return an error if an email is invalid test case 2" in {
|
||||||
|
val error = underTest.emailValidation(email = "test@.@.test.com").value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return an error if an email is invalid test case 3" in {
|
||||||
|
val error = underTest.emailValidation(email = "test@.@@.test.com").value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return an error if an email is invalid test case 4" in {
|
||||||
|
val error = underTest.emailValidation(email = "@te@st@test.com").value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return an error if an email is invalid test case 5" in {
|
||||||
|
val error = underTest.emailValidation(email = ".test@test.com").value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return an error if an email is invalid test case 6" in {
|
||||||
|
val error = underTest.emailValidation(email = "te.....st@test.com").value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"return an error if an email is invalid test case 7" in {
|
||||||
|
val error = underTest.emailValidation(email = "test@test.com.").value.unsafeRunSync().swap.toOption.get
|
||||||
|
error shouldBe a[EmailValidationError]
|
||||||
|
}
|
||||||
|
|
||||||
|
"Check whether *dummy.com is a valid email" in {
|
||||||
|
val result = underTest.emailValidation(email = "test@ok.dummy.com").value.unsafeRunSync()
|
||||||
|
result shouldBe Right(())
|
||||||
|
}
|
||||||
|
|
||||||
|
"Check whether test.com is a valid email" in {
|
||||||
|
val result = underTest.emailValidation(email = "test@test.com").value.unsafeRunSync()
|
||||||
|
result shouldBe Right(())
|
||||||
|
}
|
||||||
|
|
||||||
|
"Check whether it is allowing any domain when the config is empty" in {
|
||||||
|
val result = underTestNew.emailValidation(email = "test@abc.com").value.unsafeRunSync()
|
||||||
|
result shouldBe Right(())
|
||||||
|
}
|
||||||
|
|
||||||
|
"Create Group when email has domain *dummy.com" in {
|
||||||
|
doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUser("ok")
|
||||||
|
doReturn(().toResult).when(underTest).groupValidation(groupInfo)
|
||||||
|
doReturn(().toResult).when(underTest).groupWithSameNameDoesNotExist(groupInfo.name)
|
||||||
|
doReturn(().toResult).when(underTest).usersExist(groupInfo.memberIds)
|
||||||
|
doReturn(IO.pure(okGroup)).when(mockGroupRepo).save(any[DB], any[Group])
|
||||||
|
doReturn(IO.pure(Set(okUser.id)))
|
||||||
|
.when(mockMembershipRepo)
|
||||||
|
.saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean)
|
||||||
|
doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange])
|
||||||
|
|
||||||
|
val result = underTest.createGroup(groupInfo.copy(email = "test@ok.dummy.com"), okAuth).value.unsafeRunSync().toOption.get
|
||||||
|
result shouldBe groupInfo.copy(email = "test@ok.dummy.com")
|
||||||
|
|
||||||
|
val groupCaptor = ArgumentCaptor.forClass(classOf[Group])
|
||||||
|
|
||||||
|
verify(mockMembershipRepo, times(2))
|
||||||
|
.saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean)
|
||||||
|
verify(mockGroupRepo).save(any[DB], groupCaptor.capture())
|
||||||
|
|
||||||
|
val savedGroup = groupCaptor.getValue
|
||||||
|
(savedGroup.memberIds should contain).only(okUser.id)
|
||||||
|
(savedGroup.adminUserIds should contain).only(okUser.id)
|
||||||
|
savedGroup.name shouldBe groupInfo.name
|
||||||
|
savedGroup.email shouldBe groupInfo.copy(email = "test@ok.dummy.com").email
|
||||||
|
savedGroup.description shouldBe groupInfo.description
|
||||||
|
}
|
||||||
|
|
||||||
|
"Create Group when email with any domain when config is empty" in {
|
||||||
|
doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUser("ok")
|
||||||
|
doReturn(().toResult).when(underTestNew).groupValidation(groupInfo)
|
||||||
|
doReturn(().toResult).when(underTestNew).groupWithSameNameDoesNotExist(groupInfo.name)
|
||||||
|
doReturn(().toResult).when(underTestNew).usersExist(groupInfo.memberIds)
|
||||||
|
doReturn(IO.pure(okGroup)).when(mockGroupRepo).save(any[DB], any[Group])
|
||||||
|
doReturn(IO.pure(Set(okUser.id)))
|
||||||
|
.when(mockMembershipRepo)
|
||||||
|
.saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean)
|
||||||
|
doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange])
|
||||||
|
|
||||||
|
val result = underTestNew.createGroup(groupInfo.copy(email = "test@abc.com"), okAuth).value.unsafeRunSync().toOption.get
|
||||||
|
result shouldBe groupInfo.copy(email = "test@abc.com")
|
||||||
|
|
||||||
|
val groupCaptor = ArgumentCaptor.forClass(classOf[Group])
|
||||||
|
|
||||||
|
verify(mockMembershipRepo, times(2))
|
||||||
|
.saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean)
|
||||||
|
verify(mockGroupRepo).save(any[DB], groupCaptor.capture())
|
||||||
|
|
||||||
|
val savedGroup = groupCaptor.getValue
|
||||||
|
(savedGroup.memberIds should contain).only(okUser.id)
|
||||||
|
(savedGroup.adminUserIds should contain).only(okUser.id)
|
||||||
|
savedGroup.name shouldBe groupInfo.name
|
||||||
|
savedGroup.email shouldBe groupInfo.copy(email = "test@abc.com").email
|
||||||
|
savedGroup.description shouldBe groupInfo.description
|
||||||
|
}
|
||||||
|
|
||||||
|
"Create Group when email has domain test.com" in {
|
||||||
|
doReturn(IO.pure(Some(okUser))).when(mockUserRepo).getUser("ok")
|
||||||
|
doReturn(().toResult).when(underTest).groupValidation(groupInfo)
|
||||||
|
doReturn(().toResult).when(underTest).groupWithSameNameDoesNotExist(groupInfo.name)
|
||||||
|
doReturn(().toResult).when(underTest).usersExist(groupInfo.memberIds)
|
||||||
|
doReturn(IO.pure(okGroup)).when(mockGroupRepo).save(any[DB], any[Group])
|
||||||
|
doReturn(IO.pure(Set(okUser.id)))
|
||||||
|
.when(mockMembershipRepo)
|
||||||
|
.saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean)
|
||||||
|
doReturn(IO.pure(okGroupChange)).when(mockGroupChangeRepo).save(any[DB], any[GroupChange])
|
||||||
|
|
||||||
|
val result = underTest.createGroup(groupInfo.copy(email = "test@test.com"), okAuth).value.unsafeRunSync().toOption.get
|
||||||
|
result shouldBe groupInfo.copy(email = "test@test.com")
|
||||||
|
|
||||||
|
val groupCaptor = ArgumentCaptor.forClass(classOf[Group])
|
||||||
|
|
||||||
|
verify(mockMembershipRepo, times(2))
|
||||||
|
.saveMembers(any[DB], anyString, any[Set[String]], isAdmin = anyBoolean)
|
||||||
|
verify(mockGroupRepo).save(any[DB], groupCaptor.capture())
|
||||||
|
|
||||||
|
val savedGroup = groupCaptor.getValue
|
||||||
|
(savedGroup.memberIds should contain).only(okUser.id)
|
||||||
|
(savedGroup.adminUserIds should contain).only(okUser.id)
|
||||||
|
savedGroup.name shouldBe groupInfo.name
|
||||||
|
savedGroup.email shouldBe groupInfo.copy(email = "test@test.com").email
|
||||||
|
savedGroup.description shouldBe groupInfo.description
|
||||||
|
}
|
||||||
|
|
||||||
"update an existing group" should {
|
"update an existing group" should {
|
||||||
"save the update and add new members and remove deleted members" in {
|
"save the update and add new members and remove deleted members" in {
|
||||||
doReturn(IO.pure(Some(existingGroup))).when(mockGroupRepo).getGroup(any[String])
|
doReturn(IO.pure(Some(existingGroup))).when(mockGroupRepo).getGroup(any[String])
|
||||||
|
@@ -1331,7 +1331,7 @@ class RecordSetServiceSpec
|
|||||||
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
doReturn(IO.pure(ListUsersResults(Seq(), None)))
|
||||||
.when(mockUserRepo)
|
.when(mockUserRepo)
|
||||||
.getUsers(Set.empty, None, None)
|
.getUsers(Set.empty, None, None)
|
||||||
|
|
||||||
val result =
|
val result =
|
||||||
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get
|
underTest.updateRecordSet(newRecord, auth).map(_.asInstanceOf[RecordSetChange]).value.unsafeRunSync().toOption.get
|
||||||
|
|
||||||
|
@@ -486,6 +486,10 @@ class RecordSetValidationsSpec
|
|||||||
val error = leftValue(cnameValidations(invalid, List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
val error = leftValue(cnameValidations(invalid, List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
}
|
}
|
||||||
|
"return an InvalidRequest if a cname record set fqdn is IPv4 address" in {
|
||||||
|
val error = leftValue(cnameValidations(cname.copy(records = List(CNAMEData(Fqdn("1.2.3.4")))), List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
|
error shouldBe an[RecordSetValidation]
|
||||||
|
}
|
||||||
"return an InvalidRequest if a cname record set name is dotted" in {
|
"return an InvalidRequest if a cname record set name is dotted" in {
|
||||||
val error = leftValue(cnameValidations(cname.copy(name = "dot.ted"), List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
val error = leftValue(cnameValidations(cname.copy(name = "dot.ted"), List(), okZone, None, true, dottedHostsConfigZonesAllowed.toSet, false))
|
||||||
error shouldBe an[InvalidRequest]
|
error shouldBe an[InvalidRequest]
|
||||||
|
@@ -81,4 +81,8 @@ object Messages {
|
|||||||
|
|
||||||
// Error displayed when group name or email is empty
|
// Error displayed when group name or email is empty
|
||||||
val GroupValidationErrorMsg = "Group name and email cannot be empty."
|
val GroupValidationErrorMsg = "Group name and email cannot be empty."
|
||||||
|
|
||||||
|
val EmailValidationErrorMsg = "Please enter a valid Email ID. Valid domains should end with"
|
||||||
|
|
||||||
|
val InvalidEmailValidationErrorMsg = "Please enter a valid Email ID."
|
||||||
}
|
}
|
||||||
|
@@ -52,6 +52,11 @@ final case class InvalidDomainName(param: String) extends DomainValidationError
|
|||||||
"joined by dots, and terminated with a dot."
|
"joined by dots, and terminated with a dot."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final case class InvalidIPv4CName(param: String) extends DomainValidationError {
|
||||||
|
def message: String =
|
||||||
|
s"""Invalid Cname: "$param", Valid CNAME record data should not be an IP address"""
|
||||||
|
}
|
||||||
|
|
||||||
final case class InvalidCname(param: String, isReverseZone: Boolean) extends DomainValidationError {
|
final case class InvalidCname(param: String, isReverseZone: Boolean) extends DomainValidationError {
|
||||||
def message: String =
|
def message: String =
|
||||||
isReverseZone match {
|
isReverseZone match {
|
||||||
|
@@ -36,7 +36,7 @@ object DomainValidationErrorType extends Enumeration {
|
|||||||
CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch,
|
CnameIsNotUniqueError, UserIsNotAuthorized, UserIsNotAuthorizedError, RecordNameNotUniqueInBatch,
|
||||||
RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError,
|
RecordInReverseZoneError, HighValueDomainError, MissingOwnerGroupId, ExistingMultiRecordError,
|
||||||
NewMultiRecordError, CnameAtZoneApexError, RecordRequiresManualReview, UnsupportedOperation,
|
NewMultiRecordError, CnameAtZoneApexError, RecordRequiresManualReview, UnsupportedOperation,
|
||||||
DeleteRecordDataDoesNotExist = Value
|
DeleteRecordDataDoesNotExist, InvalidIPv4CName = Value
|
||||||
|
|
||||||
// $COVERAGE-OFF$
|
// $COVERAGE-OFF$
|
||||||
def from(error: DomainValidationError): DomainValidationErrorType =
|
def from(error: DomainValidationError): DomainValidationErrorType =
|
||||||
@@ -74,6 +74,7 @@ object DomainValidationErrorType extends Enumeration {
|
|||||||
case _: RecordRequiresManualReview => RecordRequiresManualReview
|
case _: RecordRequiresManualReview => RecordRequiresManualReview
|
||||||
case _: UnsupportedOperation => UnsupportedOperation
|
case _: UnsupportedOperation => UnsupportedOperation
|
||||||
case _: DeleteRecordDataDoesNotExist => DeleteRecordDataDoesNotExist
|
case _: DeleteRecordDataDoesNotExist => DeleteRecordDataDoesNotExist
|
||||||
|
case _: InvalidIPv4CName => InvalidIPv4CName
|
||||||
}
|
}
|
||||||
// $COVERAGE-ON$
|
// $COVERAGE-ON$
|
||||||
}
|
}
|
||||||
|
@@ -483,6 +483,16 @@ sns {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
### Email Domain Configuration
|
||||||
|
This configuration setting determines the valid domains which are
|
||||||
|
allowed in the email fields. `*dummy.com` means it will allow any
|
||||||
|
subdomain within dummy.com like apac.dummy.com. If email-domains is
|
||||||
|
left empty then it will accept any domain name.
|
||||||
|
```yaml
|
||||||
|
valid-email-config {
|
||||||
|
email-domains = ["test.com","*dummy.com"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Batch Manual Review Enabled <a id="manual-review" />
|
### Batch Manual Review Enabled <a id="manual-review" />
|
||||||
|
|
||||||
@@ -760,6 +770,11 @@ dotted-hosts = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Valid Email Domains
|
||||||
|
valid-email-config {
|
||||||
|
email-domains = ["test.com","*dummy.com"]
|
||||||
|
}
|
||||||
|
|
||||||
sns {
|
sns {
|
||||||
# Path to notifier provider implementation
|
# Path to notifier provider implementation
|
||||||
class-name = "vinyldns.api.notifier.sns.SnsNotifierProvider"
|
class-name = "vinyldns.api.notifier.sns.SnsNotifierProvider"
|
||||||
|
Reference in New Issue
Block a user