diff --git a/modules/api/src/main/scala/vinyldns/api/notifier/email/EmailNotifier.scala b/modules/api/src/main/scala/vinyldns/api/notifier/email/EmailNotifier.scala index 3abcd5ca8..955e88c4d 100644 --- a/modules/api/src/main/scala/vinyldns/api/notifier/email/EmailNotifier.scala +++ b/modules/api/src/main/scala/vinyldns/api/notifier/email/EmailNotifier.scala @@ -21,7 +21,7 @@ import cats.effect.IO import cats.implicits._ import cats.effect.IO import vinyldns.core.domain.batch.{BatchChange, BatchChangeApprovalStatus, SingleAddChange, SingleChange, SingleDeleteRRSetChange} -import vinyldns.core.domain.membership.{GroupRepository, User, UserRepository} +import vinyldns.core.domain.membership.{Group, GroupRepository, User, UserRepository} import org.slf4j.LoggerFactory import javax.mail.internet.{InternetAddress, MimeMessage} @@ -33,6 +33,7 @@ import vinyldns.core.domain.record.OwnerShipTransferStatus.OwnerShipTransferStat import java.time.format.{DateTimeFormatter, FormatStyle} import vinyldns.core.domain.batch.BatchChangeStatus._ import vinyldns.core.domain.batch.BatchChangeApprovalStatus._ +import vinyldns.core.domain.zone.Zone import java.time.ZoneId @@ -83,51 +84,41 @@ class EmailNotifier(config: EmailNotifierConfig, session: Session, userRepositor } } - def sendRecordSetOwnerTransferNotification(rsc: RecordSetChange): IO[Unit] = { + def sendRecordSetOwnerTransferNotification(rsc: RecordSetChange): IO[Unit] = for { - toGroup <- groupRepository.getGroup(rsc.recordSet.ownerGroupId.getOrElse("")) - ccGroup <- groupRepository.getGroup(rsc.recordSet.recordSetGroupChange.map(_.requestedOwnerGroupId.getOrElse("")).getOrElse("")) - _ <- toGroup match { + currentUser <- userRepository.getUser(rsc.userId) + currentGroup <- groupRepository.getGroup(rsc.recordSet.ownerGroupId.getOrElse("")) + ownerGroup <- groupRepository.getGroup(rsc.recordSet.recordSetGroupChange.map(_.requestedOwnerGroupId.getOrElse("")).getOrElse("")) + currentUsers <- currentGroup match { case Some(group) => - group.memberIds.toList.traverse { id => - userRepository.getUser(id).flatMap { - case Some(UserWithEmail(toEmail)) => - ccGroup match { - case Some(ccg) => - ccg.memberIds.toList.traverse { id => - userRepository.getUser(id).flatMap { - case Some(ccUser) => - val ccEmail = ccUser.email.getOrElse("") - send(toEmail)(new InternetAddress(ccEmail)) { message => - message.setSubject(s"VinylDNS RecordSet change ${rsc.id} results") - message.setContent(formatRecordSetChange(rsc), "text/html") - message - } - case None => - IO.unit - } - } - case None => IO.unit - } - case Some(user: User) if user.email.isDefined => - IO { - logger.warn( - s"Unable to properly parse email for ${user.id}: ${user.email.getOrElse("")}" - ) - } - case None => - IO { - logger.warn(s"Unable to find user: ${rsc.userId}") - } - case _ => - IO.unit + val users = group.memberIds.toList.map { id => + userRepository.getUser(id).map { + case Some(user) => user + case None => null } } - case None => IO.unit // Handle case where toGroup is None + users.traverse(identity) + case None => IO.pure(List.empty) + } + ownerUsers <- ownerGroup match { + case Some(group) => + val users = group.memberIds.toList.map { id => + userRepository.getUser(id).map { + case Some(user) => user + case None => null + } + } + users.traverse(identity) + case None => IO.pure(List.empty) + } + toEmails = currentUsers.collect { case UserWithEmail(address) => address } + ccEmails = ownerUsers.collect { case UserWithEmail(address) => address } + _ <- send(toEmails: _*)(ccEmails: _*) { message => + message.setSubject(s"VinylDNS RecordSet Ownership transfer") + message.setContent(formatRecordSetOwnerShipTransfer(rsc, currentUser, currentGroup, ownerGroup), "text/html") + message } } yield () - } - def formatBatchChange(bc: BatchChange): String = { val sb = new StringBuilder @@ -178,27 +169,52 @@ class EmailNotifier(config: EmailNotifierConfig, session: Session, userRepositor case (_, status) => status.toString } - def formatRecordSetChange(rsc: RecordSetChange): String = { - - val sb = new StringBuilder - sb.append(s"""

RecordSet Ownership Transfer

- | Submitter: ${ userRepository.getUser(rsc.userId).map(_.get.userName)} - | Id: ${rsc.id}
- | Submitted time: ${DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withZone(ZoneId.systemDefault()).format(rsc.created)}
- | OwnerShip Current Group: ${rsc.recordSet.ownerGroupId.getOrElse("none")}
- | OwnerShip Transfer Group: ${rsc.recordSet.recordSetGroupChange.map(_.requestedOwnerGroupId.getOrElse("none")).getOrElse("none")}
- | OwnerShip Transfer Status: ${formatOwnerShipStatus(rsc.recordSet.recordSetGroupChange.map(_.ownerShipTransferStatus).get)}
- """.stripMargin) - sb.toString + def formatRecordSetOwnerShipTransfer(rsc: RecordSetChange, + currentUser: Option[User], + currentGroup: Option[Group], + ownerGroup: Option[Group] + ): String = { + val portalHost = config.smtp.getProperty("mail.smtp.portal.url") + val sb = new StringBuilder + sb.append(s"""

RecordSet Ownership Transfer Alert:

+ |Submitted time: ${DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withZone(ZoneId.systemDefault()).format(rsc.created)}

+ | Submitter: ${currentUser.get.userName}

+ | Zone: ${if(portalHost != null) s"""${rsc.zone.name}
""" + else s"${rsc.zone.name}
"} + |
+ | + | + | + | + | + | + | + | + | + | + | + | + |
RecordSetRecord TypeTTLRecord Data
${rsc.recordSet.name}${rsc.recordSet.typ}${rsc.recordSet.ttl}${rsc.recordSet.records.map(id =>id)}

+ | Current Owner Group: ${currentGroup.get.name}

+ | Transfer Owner Group: ${ownerGroup.get.name}

+ | Status: ${formatOwnerShipStatus(rsc.recordSet.recordSetGroupChange.map(_.ownerShipTransferStatus).get,rsc.zone,portalHost)} + |

+ """.stripMargin) + sb.toString } - def formatOwnerShipStatus(status: OwnerShipTransferStatus): String = - status match { - case OwnerShipTransferStatus.ManuallyRejected => "Rejected" - case OwnerShipTransferStatus.PendingReview => "Pending Review" - case OwnerShipTransferStatus.ManuallyApproved => "Approved" - case OwnerShipTransferStatus.Cancelled => "Cancelled" - } + def formatOwnerShipStatus(status: OwnerShipTransferStatus, zone:Zone, portalHost: String): String = + status match { + case OwnerShipTransferStatus.ManuallyRejected => "Rejected" + case OwnerShipTransferStatus.PendingReview => s"""Pending Review

+ Requesting your review for the Ownership transfer.
+ ${if(portalHost != null) + s"""Go to Zones + >>> Search by RecordSet Name >>> Click Close Request
""" + else s""}""" + case OwnerShipTransferStatus.ManuallyApproved => "Approved" + case OwnerShipTransferStatus.Cancelled => "Cancelled" + } def formatSingleChange(sc: SingleChange, index: Int): String = sc match { case SingleAddChange( diff --git a/modules/api/src/test/scala/vinyldns/api/notifier/email/EmailNotifierSpec.scala b/modules/api/src/test/scala/vinyldns/api/notifier/email/EmailNotifierSpec.scala index 243ac3546..ff31c6cfd 100644 --- a/modules/api/src/test/scala/vinyldns/api/notifier/email/EmailNotifierSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/notifier/email/EmailNotifierSpec.scala @@ -189,13 +189,12 @@ class EmailNotifierSpec message.getFrom should be(Array(fromAddress)) message.getContentType should be("text/html; charset=us-ascii") message.getAllRecipients should be(expectedAddresses) - message.getSubject should be(s"VinylDNS RecordSet change ${rsc.id} results") + message.getSubject should be(s"VinylDNS RecordSet Ownership transfer") val content = message.getContent.asInstanceOf[String] - content.contains(rsc.id) should be(true) - content.contains(rsc.recordSet.ownerGroupId.get) should be(true) - content.contains(rsc.recordSet.recordSetGroupChange.map(_.requestedOwnerGroupId.get).get) should be(true) + content.contains(rsc.zone.name) should be(true) + content.contains(rsc.recordSet.name) should be(true) } "do nothing when user not found" in { diff --git a/modules/docs/src/main/mdoc/operator/config-api.md b/modules/docs/src/main/mdoc/operator/config-api.md index 276dee851..221639602 100644 --- a/modules/docs/src/main/mdoc/operator/config-api.md +++ b/modules/docs/src/main/mdoc/operator/config-api.md @@ -534,6 +534,9 @@ email = { smtp { # Host SMTP server + portal { + url= "http://127.0.0.1:9001" # portal uri for email link + } host = "example.host" # if smtp host requires authentication we can enable auth auth = true @@ -864,11 +867,20 @@ dotted-hosts = { settings = { # Sender address for e-mail notifications from = "Sender " - + smtp { - # Host SMTP server - host = "example.host" + # Host SMTP server + portal { + url= "http://127.0.0.1:9001" # portal uri for email link + } + host = "example.host" + # if smtp host requires authentication we can enable auth + auth = true + username = sampleUser + password = samplePassword + starttls.enable = true } + } } # Valid Email Domains