From d4837a9ef23348bdf9a1ff86d4a1595fad90145d Mon Sep 17 00:00:00 2001 From: Martin Polden Date: Thu, 15 Dec 2022 15:26:15 +0100 Subject: Move MailVerifier to application package --- .../yahoo/vespa/hosted/controller/Controller.java | 1 + .../vespa/hosted/controller/MailVerifier.java | 135 -------------------- .../controller/application/MailVerifier.java | 137 +++++++++++++++++++++ .../vespa/hosted/controller/MailVerifierTest.java | 3 +- 4 files changed, 140 insertions(+), 136 deletions(-) delete mode 100644 controller-server/src/main/java/com/yahoo/vespa/hosted/controller/MailVerifier.java create mode 100644 controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java (limited to 'controller-server') diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java index 9a7fffd2a9e..c8ec38ec73b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java @@ -17,6 +17,7 @@ import com.yahoo.vespa.flags.FlagSource; import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry; import com.yahoo.vespa.hosted.controller.api.integration.maven.MavenRepository; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; +import com.yahoo.vespa.hosted.controller.application.MailVerifier; import com.yahoo.vespa.hosted.controller.archive.CuratorArchiveBucketDb; import com.yahoo.vespa.hosted.controller.auditlog.AuditLogger; import com.yahoo.vespa.hosted.controller.config.ControllerConfig; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/MailVerifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/MailVerifier.java deleted file mode 100644 index 902343d5acf..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/MailVerifier.java +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller; - -import com.yahoo.config.provision.TenantName; -import com.yahoo.vespa.hosted.controller.api.integration.organization.Mail; -import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer; -import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; -import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; -import com.yahoo.vespa.hosted.controller.tenant.Tenant; -import com.yahoo.vespa.hosted.controller.tenant.TenantContacts; -import com.yahoo.vespa.hosted.controller.tenant.TenantInfo; -import com.yahoo.vespa.hosted.controller.tenant.PendingMailVerification; - -import java.net.URI; -import java.time.Clock; -import java.time.Duration; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static com.yahoo.yolean.Exceptions.uncheck; - - -/** - * @author olaa - */ -public class MailVerifier { - - private final TenantController tenantController; - private final Mailer mailer; - private final CuratorDb curatorDb; - private final Clock clock; - private final URI dashboardUri; - private static final Duration VERIFICATION_DEADLINE = Duration.ofDays(7); - - - public MailVerifier(URI dashboardUri, TenantController tenantController, Mailer mailer, CuratorDb curatorDb, Clock clock) { - this.tenantController = tenantController; - this.mailer = mailer; - this.curatorDb = curatorDb; - this.clock = clock; - this.dashboardUri = dashboardUri; - } - - public PendingMailVerification sendMailVerification(TenantName tenantName, String email, PendingMailVerification.MailType mailType) { - if (!email.contains("@")) { - throw new IllegalArgumentException("Invalid email address"); - } - - var verificationCode = UUID.randomUUID().toString(); - var verificationDeadline = clock.instant().plus(VERIFICATION_DEADLINE); - var pendingMailVerification = new PendingMailVerification(tenantName, email, verificationCode, verificationDeadline, mailType); - writePendingVerification(pendingMailVerification); - mailer.send(mailOf(pendingMailVerification)); - return pendingMailVerification; - } - - public Optional resendMailVerification(TenantName tenantName, String email, PendingMailVerification.MailType mailType) { - var oldPendingVerification = curatorDb.listPendingMailVerifications() - .stream() - .filter(pendingMailVerification -> - pendingMailVerification.getMailAddress().equals(email) && - pendingMailVerification.getMailType().equals(mailType) && - pendingMailVerification.getTenantName().equals(tenantName) - ).findFirst(); - - if (oldPendingVerification.isEmpty()) - return Optional.empty(); - - try (var lock = curatorDb.lockPendingMailVerification(oldPendingVerification.get().getVerificationCode())) { - curatorDb.deletePendingMailVerification(oldPendingVerification.get()); - } - - return Optional.of(sendMailVerification(tenantName, email, mailType)); - } - - public boolean verifyMail(String verificationCode) { - return curatorDb.getPendingMailVerification(verificationCode) - .filter(pendingMailVerification -> pendingMailVerification.getVerificationDeadline().isAfter(clock.instant())) - .map(pendingMailVerification -> { - var tenant = requireCloudTenant(pendingMailVerification.getTenantName()); - var oldTenantInfo = tenant.info(); - var updatedTenantInfo = switch (pendingMailVerification.getMailType()) { - case NOTIFICATIONS -> withTenantContacts(oldTenantInfo, pendingMailVerification); - case TENANT_CONTACT -> oldTenantInfo.withContact(oldTenantInfo.contact() - .withEmail(oldTenantInfo.contact().email().withVerification(true))); - }; - - tenantController.lockOrThrow(tenant.name(), LockedTenant.Cloud.class, lockedTenant -> { - lockedTenant = lockedTenant.withInfo(updatedTenantInfo); - tenantController.store(lockedTenant); - }); - - try (var lock = curatorDb.lockPendingMailVerification(pendingMailVerification.getVerificationCode())) { - curatorDb.deletePendingMailVerification(pendingMailVerification); - } - return true; - }).orElse(false); - } - - private TenantInfo withTenantContacts(TenantInfo oldInfo, PendingMailVerification pendingMailVerification) { - var newContacts = oldInfo.contacts().ofType(TenantContacts.EmailContact.class) - .stream() - .map(contact -> { - if (pendingMailVerification.getMailAddress().equals(contact.email().getEmailAddress())) - return contact.withEmail(contact.email().withVerification(true)); - return contact; - }).toList(); - return oldInfo.withContacts(new TenantContacts(newContacts)); - } - - private void writePendingVerification(PendingMailVerification pendingMailVerification) { - try (var lock = curatorDb.lockPendingMailVerification(pendingMailVerification.getVerificationCode())) { - curatorDb.writePendingMailVerification(pendingMailVerification); - } - } - - private CloudTenant requireCloudTenant(TenantName tenantName) { - return tenantController.get(tenantName) - .filter(tenant -> tenant.type() == Tenant.Type.cloud) - .map(CloudTenant.class::cast) - .orElseThrow(() -> new IllegalStateException("Mail verification is only applicable for cloud tenants")); - } - - private Mail mailOf(PendingMailVerification pendingMailVerification) { - var classLoader = this.getClass().getClassLoader(); - var template = uncheck(() -> classLoader.getResourceAsStream("mail/mail-verification.tmpl").readAllBytes()); - var message = new String(template) - .replaceAll("%\\{consoleUrl}", dashboardUri.getHost()) - .replaceAll("%\\{email}", pendingMailVerification.getMailAddress()) - .replaceAll("%\\{code}", pendingMailVerification.getVerificationCode()); - return new Mail(List.of(pendingMailVerification.getMailAddress()), "Please verify your email", "", message); - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java new file mode 100644 index 00000000000..afb0b61c23a --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java @@ -0,0 +1,137 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.application; + +import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.LockedTenant; +import com.yahoo.vespa.hosted.controller.TenantController; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Mail; +import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer; +import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; +import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; +import com.yahoo.vespa.hosted.controller.tenant.Tenant; +import com.yahoo.vespa.hosted.controller.tenant.TenantContacts; +import com.yahoo.vespa.hosted.controller.tenant.TenantInfo; +import com.yahoo.vespa.hosted.controller.tenant.PendingMailVerification; + +import java.net.URI; +import java.time.Clock; +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static com.yahoo.yolean.Exceptions.uncheck; + + +/** + * @author olaa + */ +public class MailVerifier { + + private static final Duration VERIFICATION_DEADLINE = Duration.ofDays(7); + + private final TenantController tenantController; + private final Mailer mailer; + private final CuratorDb curatorDb; + private final Clock clock; + private final URI dashboardUri; + + public MailVerifier(URI dashboardUri, TenantController tenantController, Mailer mailer, CuratorDb curatorDb, Clock clock) { + this.tenantController = tenantController; + this.mailer = mailer; + this.curatorDb = curatorDb; + this.clock = clock; + this.dashboardUri = dashboardUri; + } + + public PendingMailVerification sendMailVerification(TenantName tenantName, String email, PendingMailVerification.MailType mailType) { + if (!email.contains("@")) { + throw new IllegalArgumentException("Invalid email address"); + } + + var verificationCode = UUID.randomUUID().toString(); + var verificationDeadline = clock.instant().plus(VERIFICATION_DEADLINE); + var pendingMailVerification = new PendingMailVerification(tenantName, email, verificationCode, verificationDeadline, mailType); + writePendingVerification(pendingMailVerification); + mailer.send(mailOf(pendingMailVerification)); + return pendingMailVerification; + } + + public Optional resendMailVerification(TenantName tenantName, String email, PendingMailVerification.MailType mailType) { + var oldPendingVerification = curatorDb.listPendingMailVerifications() + .stream() + .filter(pendingMailVerification -> + pendingMailVerification.getMailAddress().equals(email) && + pendingMailVerification.getMailType().equals(mailType) && + pendingMailVerification.getTenantName().equals(tenantName) + ).findFirst(); + + if (oldPendingVerification.isEmpty()) + return Optional.empty(); + + try (var lock = curatorDb.lockPendingMailVerification(oldPendingVerification.get().getVerificationCode())) { + curatorDb.deletePendingMailVerification(oldPendingVerification.get()); + } + + return Optional.of(sendMailVerification(tenantName, email, mailType)); + } + + public boolean verifyMail(String verificationCode) { + return curatorDb.getPendingMailVerification(verificationCode) + .filter(pendingMailVerification -> pendingMailVerification.getVerificationDeadline().isAfter(clock.instant())) + .map(pendingMailVerification -> { + var tenant = requireCloudTenant(pendingMailVerification.getTenantName()); + var oldTenantInfo = tenant.info(); + var updatedTenantInfo = switch (pendingMailVerification.getMailType()) { + case NOTIFICATIONS -> withTenantContacts(oldTenantInfo, pendingMailVerification); + case TENANT_CONTACT -> oldTenantInfo.withContact(oldTenantInfo.contact() + .withEmail(oldTenantInfo.contact().email().withVerification(true))); + }; + + tenantController.lockOrThrow(tenant.name(), LockedTenant.Cloud.class, lockedTenant -> { + lockedTenant = lockedTenant.withInfo(updatedTenantInfo); + tenantController.store(lockedTenant); + }); + + try (var lock = curatorDb.lockPendingMailVerification(pendingMailVerification.getVerificationCode())) { + curatorDb.deletePendingMailVerification(pendingMailVerification); + } + return true; + }).orElse(false); + } + + private TenantInfo withTenantContacts(TenantInfo oldInfo, PendingMailVerification pendingMailVerification) { + var newContacts = oldInfo.contacts().ofType(TenantContacts.EmailContact.class) + .stream() + .map(contact -> { + if (pendingMailVerification.getMailAddress().equals(contact.email().getEmailAddress())) + return contact.withEmail(contact.email().withVerification(true)); + return contact; + }).toList(); + return oldInfo.withContacts(new TenantContacts(newContacts)); + } + + private void writePendingVerification(PendingMailVerification pendingMailVerification) { + try (var lock = curatorDb.lockPendingMailVerification(pendingMailVerification.getVerificationCode())) { + curatorDb.writePendingMailVerification(pendingMailVerification); + } + } + + private CloudTenant requireCloudTenant(TenantName tenantName) { + return tenantController.get(tenantName) + .filter(tenant -> tenant.type() == Tenant.Type.cloud) + .map(CloudTenant.class::cast) + .orElseThrow(() -> new IllegalStateException("Mail verification is only applicable for cloud tenants")); + } + + private Mail mailOf(PendingMailVerification pendingMailVerification) { + var classLoader = this.getClass().getClassLoader(); + var template = uncheck(() -> classLoader.getResourceAsStream("mail/mail-verification.tmpl").readAllBytes()); + var message = new String(template) + .replaceAll("%\\{consoleUrl}", dashboardUri.getHost()) + .replaceAll("%\\{email}", pendingMailVerification.getMailAddress()) + .replaceAll("%\\{code}", pendingMailVerification.getVerificationCode()); + return new Mail(List.of(pendingMailVerification.getMailAddress()), "Please verify your email", "", message); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java index edea07e205c..77145be4197 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer; +import com.yahoo.vespa.hosted.controller.application.MailVerifier; import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; import com.yahoo.vespa.hosted.controller.tenant.Email; import com.yahoo.vespa.hosted.controller.tenant.PendingMailVerification; @@ -99,4 +100,4 @@ class MailVerifierTest { assertTrue(tester.curator().getPendingMailVerification(resentVerification.get().getVerificationCode()).isPresent()); } -} \ No newline at end of file +} -- cgit v1.2.3