1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 06:15:42 +00:00

Unify email notifications for console updates (#2459)

This commit is contained in:
Pavlo Tkach
2024-05-31 15:20:56 -04:00
committed by GitHub
parent 81b239c6b3
commit 0c123e1676
14 changed files with 337 additions and 135 deletions

View File

@@ -35,7 +35,7 @@ import javax.mail.internet.InternetAddress;
public class SendEmailUtils {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final GmailClient gmailClient;
public final GmailClient gmailClient;
private final ImmutableList<String> registrarChangesNotificationEmailAddresses;
@Inject

View File

@@ -14,26 +14,44 @@
package google.registry.ui.server.console;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.request.Action.Method.GET;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.common.base.Ascii;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.CloudTasksUtils;
import google.registry.export.sheet.SyncRegistrarsSheetAction;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPocBase;
import google.registry.request.Action.Service;
import google.registry.request.HttpException;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.ConsoleUiAction;
import google.registry.util.DiffUtils;
import google.registry.util.RegistryEnvironment;
import jakarta.servlet.http.Cookie;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.inject.Inject;
/** Base class for handling Console API requests */
public abstract class ConsoleApiAction implements Runnable {
@@ -42,6 +60,8 @@ public abstract class ConsoleApiAction implements Runnable {
protected ConsoleApiParams consoleApiParams;
@Inject CloudTasksUtils cloudTasksUtils;
public ConsoleApiAction(ConsoleApiParams consoleApiParams) {
this.consoleApiParams = consoleApiParams;
}
@@ -124,10 +144,115 @@ public abstract class ConsoleApiAction implements Runnable {
return true;
}
private Map<String, Object> expandRegistrarWithContacts(
ImmutableSet<RegistrarPoc> contacts, Registrar registrar) {
ImmutableSet<Map<String, Object>> expandedContacts =
contacts.stream()
.map(RegistrarPoc::toDiffableFieldMap)
// Note: per the javadoc, toDiffableFieldMap includes sensitive data, but we don't want
// to display it here
.peek(
map -> {
map.remove("registryLockPasswordHash");
map.remove("registryLockPasswordSalt");
})
.collect(toImmutableSet());
Map<String, Object> registrarDiffMap = registrar.toDiffableFieldMap();
Stream.of("passwordHash", "salt") // fields to remove from final diff
.forEach(fieldToBeRemoved -> registrarDiffMap.remove(fieldToBeRemoved));
// Use LinkedHashMap here to preserve ordering; null values mean we can't use ImmutableMap.
LinkedHashMap<String, Object> result = new LinkedHashMap<>(registrarDiffMap);
result.put("contacts", expandedContacts);
return result;
}
protected void sendExternalUpdates(
Map<?, ?> diffs, Registrar registrar, ImmutableSet<RegistrarPoc> contacts) {
if (!consoleApiParams.sendEmailUtils().hasRecipients() && contacts.isEmpty()) {
return;
}
if (!RegistryEnvironment.UNITTEST.equals(RegistryEnvironment.get())
&& cloudTasksUtils != null) {
// Enqueues a sync registrar sheet task if enqueuing is not triggered by console tests and
// there's an update besides the lastUpdateTime
cloudTasksUtils.enqueue(
SyncRegistrarsSheetAction.QUEUE,
cloudTasksUtils.createGetTask(
SyncRegistrarsSheetAction.PATH, Service.BACKEND, ImmutableMultimap.of()));
}
String environment = Ascii.toLowerCase(String.valueOf(RegistryEnvironment.get()));
consoleApiParams
.sendEmailUtils()
.sendEmail(
String.format(
"Registrar %s (%s) updated in registry %s environment",
registrar.getRegistrarName(), registrar.getRegistrarId(), environment),
String.format(
"""
The following changes were made in registry %s environment to the registrar %s by\
%s:
%s""",
environment,
registrar.getRegistrarId(),
consoleApiParams.authResult().userIdForLogging(),
DiffUtils.prettyPrintDiffedMap(diffs, null)),
contacts.stream()
.filter(c -> c.getTypes().contains(RegistrarPocBase.Type.ADMIN))
.map(RegistrarPoc::getEmailAddress)
.collect(toImmutableList()));
}
/**
* Determines if any changes were made to the registrar besides the lastUpdateTime, and if so,
* sends an email with a diff of the changes to the configured notification email address and all
* contact addresses and enqueues a task to re-sync the registrar sheet.
*/
protected void sendExternalUpdatesIfNecessary(EmailInfo emailInfo) {
ImmutableSet<RegistrarPoc> existingContacts = emailInfo.contacts();
Registrar existingRegistrar = emailInfo.registrar();
Map<?, ?> diffs =
DiffUtils.deepDiff(
expandRegistrarWithContacts(existingContacts, existingRegistrar),
expandRegistrarWithContacts(emailInfo.updatedContacts(), emailInfo.updatedRegistrar()),
true);
@SuppressWarnings("unchecked")
Set<String> changedKeys = (Set<String>) diffs.keySet();
if (Sets.difference(changedKeys, ImmutableSet.of("lastUpdateTime")).isEmpty()) {
return;
}
sendExternalUpdates(diffs, existingRegistrar, existingContacts);
}
protected record EmailInfo(
Registrar registrar,
Registrar updatedRegistrar,
ImmutableSet<RegistrarPoc> contacts,
ImmutableSet<RegistrarPoc> updatedContacts) {
public static EmailInfo create(
Registrar registrar,
Registrar updatedRegistrar,
ImmutableSet<RegistrarPoc> contacts,
ImmutableSet<RegistrarPoc> updatedContacts) {
return new EmailInfo(registrar, updatedRegistrar, contacts, updatedContacts);
}
}
/** Specialized exception class used for failure when a user doesn't have the right permission. */
private static class ConsolePermissionForbiddenException extends RuntimeException {
private ConsolePermissionForbiddenException(String message) {
super(message);
}
}
}

View File

@@ -22,10 +22,11 @@ import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.annotations.Expose;
import google.registry.flows.EppException.AuthenticationErrorException;
import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.groups.GmailClient;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
@@ -34,10 +35,9 @@ import google.registry.request.auth.Auth;
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
import google.registry.request.auth.AuthenticatedRegistrarAccessor.RegistrarAccessDeniedException;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.EmailMessage;
import google.registry.util.DiffUtils;
import java.util.Optional;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
@Action(
service = Action.Service.DEFAULT,
@@ -45,16 +45,12 @@ import javax.mail.internet.InternetAddress;
method = {POST},
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleEppPasswordAction extends ConsoleApiAction {
protected static final String EMAIL_SUBJ = "EPP password update confirmation";
protected static final String EMAIL_BODY =
"Dear %s,\n" + "This is to confirm that your account password has been changed.";
public static final String PATH = "/console-api/eppPassword";
private final PasswordOnlyTransportCredentials credentials =
new PasswordOnlyTransportCredentials();
private final AuthenticatedRegistrarAccessor registrarAccessor;
private final GmailClient gmailClient;
private final Optional<EppPasswordData> eppPasswordChangeRequest;
@@ -62,11 +58,9 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
public ConsoleEppPasswordAction(
ConsoleApiParams consoleApiParams,
AuthenticatedRegistrarAccessor registrarAccessor,
GmailClient gmailClient,
@Parameter("eppPasswordChangeRequest") Optional<EppPasswordData> eppPasswordChangeRequest) {
super(consoleApiParams);
this.registrarAccessor = registrarAccessor;
this.gmailClient = gmailClient;
this.eppPasswordChangeRequest = eppPasswordChangeRequest;
}
@@ -107,12 +101,13 @@ public class ConsoleEppPasswordAction extends ConsoleApiAction {
tm().transact(
() -> {
tm().put(registrar.asBuilder().setPassword(eppRequestBody.newPassword()).build());
this.gmailClient.sendEmail(
EmailMessage.create(
EMAIL_SUBJ,
String.format(EMAIL_BODY, registrar.getRegistrarName()),
new InternetAddress(registrar.getEmailAddress(), true)));
Registrar updatedRegistrar =
registrar.asBuilder().setPassword(eppRequestBody.newPassword()).build();
tm().put(updatedRegistrar);
sendExternalUpdates(
ImmutableMap.of("password", new DiffUtils.DiffPair("********", "••••••••")),
registrar,
ImmutableSet.of());
});
consoleApiParams.response().setStatus(SC_OK);

View File

@@ -21,7 +21,7 @@ import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import static org.apache.http.HttpStatus.SC_OK;
import com.google.common.base.Strings;
import google.registry.groups.GmailClient;
import com.google.common.collect.ImmutableSet;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.User;
import google.registry.model.registrar.Registrar;
@@ -31,13 +31,10 @@ import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.util.DomainNameUtils;
import google.registry.util.EmailMessage;
import google.registry.util.RegistryEnvironment;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
@Action(
service = Action.Service.DEFAULT,
@@ -46,45 +43,37 @@ import javax.mail.internet.InternetAddress;
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
static final String PATH = "/console-api/registrar";
private static final String EMAIL_SUBJ = "Registrar %s has been updated";
private static final String EMAIL_BODY =
"The following changes were made in registry %s environment to the registrar %s:";
private final Optional<Registrar> registrar;
private final GmailClient gmailClient;
@Inject
ConsoleUpdateRegistrarAction(
ConsoleApiParams consoleApiParams,
GmailClient gmailClient,
@Parameter("registrar") Optional<Registrar> registrar) {
super(consoleApiParams);
this.registrar = registrar;
this.gmailClient = gmailClient;
}
@Override
protected void postHandler(User user) {
var errorMsg = "Missing param(s): %s";
Registrar updatedRegistrar =
Registrar registrarParam =
registrar.orElseThrow(() -> new BadRequestException(String.format(errorMsg, "registrar")));
checkArgument(
!Strings.isNullOrEmpty(updatedRegistrar.getRegistrarId()), errorMsg, "registrarId");
checkArgument(!Strings.isNullOrEmpty(registrarParam.getRegistrarId()), errorMsg, "registrarId");
checkPermission(
user, updatedRegistrar.getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS);
user, registrarParam.getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS);
tm().transact(
() -> {
Optional<Registrar> existingRegistrar =
Registrar.loadByRegistrarId(updatedRegistrar.getRegistrarId());
Registrar.loadByRegistrarId(registrarParam.getRegistrarId());
checkArgument(
!existingRegistrar.isEmpty(),
"Registrar with registrarId %s doesn't exists",
updatedRegistrar.getRegistrarId());
registrarParam.getRegistrarId());
// Only allow modifying allowed TLDs if we're in a non-PRODUCTION environment, if the
// registrar is not REAL, or the registrar has a WHOIS abuse contact set.
if (!updatedRegistrar.getAllowedTlds().isEmpty()) {
if (!registrarParam.getAllowedTlds().isEmpty()) {
boolean isRealRegistrar =
Registrar.Type.REAL.equals(existingRegistrar.get().getType());
if (RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get())
@@ -97,49 +86,27 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
}
}
tm().put(
Registrar updatedRegistrar =
existingRegistrar
.get()
.asBuilder()
.setAllowedTlds(
updatedRegistrar.getAllowedTlds().stream()
registrarParam.getAllowedTlds().stream()
.map(DomainNameUtils::canonicalizeHostname)
.collect(Collectors.toSet()))
.setRegistryLockAllowed(updatedRegistrar.isRegistryLockAllowed())
.build());
.setRegistryLockAllowed(registrarParam.isRegistryLockAllowed())
.build();
sendEmail(existingRegistrar.get(), updatedRegistrar);
tm().put(updatedRegistrar);
sendExternalUpdatesIfNecessary(
EmailInfo.create(
existingRegistrar.get(),
updatedRegistrar,
ImmutableSet.of(),
ImmutableSet.of()));
});
consoleApiParams.response().setStatus(SC_OK);
}
void sendEmail(Registrar oldRegistrar, Registrar updatedRegistrar) throws AddressException {
String emailBody =
String.format(EMAIL_BODY, RegistryEnvironment.get(), oldRegistrar.getRegistrarId());
StringBuilder diff = new StringBuilder();
if (oldRegistrar.isRegistryLockAllowed() != updatedRegistrar.isRegistryLockAllowed()) {
diff.append("/n");
diff.append(
String.format(
"Registry Lock Allowed: %s -> %s",
oldRegistrar.isRegistryLockAllowed(), updatedRegistrar.isRegistryLockAllowed()));
}
if (!oldRegistrar.getAllowedTlds().equals(updatedRegistrar.getAllowedTlds())) {
diff.append("/n");
diff.append(
String.format(
"Allowed TLDs: %s -> %s",
oldRegistrar.getAllowedTlds(), updatedRegistrar.getAllowedTlds()));
}
if (diff.length() > 0) {
this.gmailClient.sendEmail(
EmailMessage.create(
String.format(EMAIL_SUBJ, oldRegistrar.getRegistrarId()),
emailBody + diff,
new InternetAddress(oldRegistrar.getEmailAddress(), true)));
}
}
}

View File

@@ -103,6 +103,7 @@ public class ContactAction extends ConsoleApiAction {
Collections.singletonMap(
"contacts",
contacts.get().stream().map(RegistrarPoc::toJsonMap).collect(toImmutableList())));
try {
RegistrarSettingsAction.checkContactRequirements(oldContacts, updatedContacts);
} catch (FormException e) {
@@ -111,7 +112,16 @@ public class ContactAction extends ConsoleApiAction {
throw new IllegalArgumentException(e);
}
tm().transact(
() -> {
RegistrarPoc.updateContacts(registrar, updatedContacts);
Registrar updatedRegistrar =
registrar.asBuilder().setContactsRequireSyncing(true).build();
tm().put(updatedRegistrar);
sendExternalUpdatesIfNecessary(
EmailInfo.create(registrar, updatedRegistrar, oldContacts, updatedContacts));
});
consoleApiParams.response().setStatus(SC_OK);
}
}

View File

@@ -21,6 +21,7 @@ import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.collect.ImmutableSet;
import google.registry.flows.certs.CertificateChecker;
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
import google.registry.model.console.ConsolePermission;
@@ -81,7 +82,7 @@ public class SecurityAction extends ConsoleApiAction {
private void setResponse(Registrar savedRegistrar) {
Registrar registrarParameter = registrar.get();
Registrar.Builder updatedRegistrar =
Registrar.Builder updatedRegistrarBuilder =
savedRegistrar
.asBuilder()
.setIpAddressAllowList(registrarParameter.getIpAddressAllowList());
@@ -93,7 +94,7 @@ public class SecurityAction extends ConsoleApiAction {
if (registrarParameter.getClientCertificate().isPresent()) {
String newClientCert = registrarParameter.getClientCertificate().get();
certificateChecker.validateCertificate(newClientCert);
updatedRegistrar.setClientCertificate(newClientCert, tm().getTransactionTime());
updatedRegistrarBuilder.setClientCertificate(newClientCert, tm().getTransactionTime());
}
}
if (!savedRegistrar
@@ -102,7 +103,8 @@ public class SecurityAction extends ConsoleApiAction {
if (registrarParameter.getFailoverClientCertificate().isPresent()) {
String newFailoverCert = registrarParameter.getFailoverClientCertificate().get();
certificateChecker.validateCertificate(newFailoverCert);
updatedRegistrar.setFailoverClientCertificate(newFailoverCert, tm().getTransactionTime());
updatedRegistrarBuilder.setFailoverClientCertificate(
newFailoverCert, tm().getTransactionTime());
}
}
} catch (InsecureCertificateException e) {
@@ -110,7 +112,11 @@ public class SecurityAction extends ConsoleApiAction {
return;
}
tm().put(updatedRegistrar.build());
Registrar updatedRegistrar = updatedRegistrarBuilder.build();
tm().put(updatedRegistrar);
sendExternalUpdatesIfNecessary(
EmailInfo.create(savedRegistrar, updatedRegistrar, ImmutableSet.of(), ImmutableSet.of()));
consoleApiParams.response().setStatus(SC_OK);
}
}

View File

@@ -78,13 +78,22 @@ public class WhoisRegistrarFieldsAction extends ConsoleApiAction {
return;
}
Registrar.Builder newRegistrar = savedRegistrar.asBuilder();
newRegistrar.setWhoisServer(providedRegistrar.getWhoisServer());
newRegistrar.setUrl(providedRegistrar.getUrl());
newRegistrar.setLocalizedAddress(providedRegistrar.getLocalizedAddress());
newRegistrar.setPhoneNumber(providedRegistrar.getPhoneNumber());
newRegistrar.setFaxNumber(providedRegistrar.getFaxNumber());
tm().put(newRegistrar.build());
Registrar newRegistrar =
savedRegistrar
.asBuilder()
.setWhoisServer(providedRegistrar.getWhoisServer())
.setUrl(providedRegistrar.getUrl())
.setLocalizedAddress(providedRegistrar.getLocalizedAddress())
.setPhoneNumber(providedRegistrar.getPhoneNumber())
.setFaxNumber(providedRegistrar.getFaxNumber())
.build();
tm().put(newRegistrar);
sendExternalUpdatesIfNecessary(
EmailInfo.create(
savedRegistrar,
newRegistrar,
savedRegistrar.getContacts(),
savedRegistrar.getContacts()));
consoleApiParams.response().setStatus(SC_OK);
}
}

View File

@@ -17,6 +17,7 @@ package google.registry.ui.server.registrar;
import google.registry.request.Response;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.SendEmailUtils;
import jakarta.servlet.http.HttpServletRequest;
/** Groups necessary dependencies for Console API actions * */
@@ -24,12 +25,14 @@ public record ConsoleApiParams(
HttpServletRequest request,
Response response,
AuthResult authResult,
SendEmailUtils sendEmailUtils,
XsrfTokenManager xsrfTokenManager) {
public static ConsoleApiParams create(
HttpServletRequest request,
Response response,
AuthResult authResult,
SendEmailUtils sendEmailUtils,
XsrfTokenManager xsrfTokenManager) {
return new ConsoleApiParams(request, response, authResult, xsrfTokenManager);
return new ConsoleApiParams(request, response, authResult, sendEmailUtils, xsrfTokenManager);
}
}

View File

@@ -32,6 +32,7 @@ import google.registry.request.RequestScope;
import google.registry.request.Response;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
@@ -48,8 +49,9 @@ public final class RegistrarConsoleModule {
HttpServletRequest request,
Response response,
AuthResult authResult,
SendEmailUtils sendEmailUtils,
XsrfTokenManager xsrfTokenManager) {
return ConsoleApiParams.create(request, response, authResult, xsrfTokenManager);
return ConsoleApiParams.create(request, response, authResult, sendEmailUtils, xsrfTokenManager);
}
@Provides

View File

@@ -17,9 +17,12 @@ package google.registry.testing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import google.registry.groups.GmailClient;
import google.registry.model.console.User;
import google.registry.request.auth.AuthResult;
import google.registry.security.XsrfTokenManager;
import google.registry.ui.server.SendEmailUtils;
import google.registry.ui.server.registrar.ConsoleApiParams;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
@@ -29,6 +32,9 @@ public final class ConsoleApiParamsUtils {
public static ConsoleApiParams createFake(AuthResult authResult) {
HttpServletRequest request = mock(HttpServletRequest.class);
GmailClient gmailClient = mock(GmailClient.class);
SendEmailUtils sendEmailUtils =
new SendEmailUtils(ImmutableList.of("notification@test.example"), gmailClient);
XsrfTokenManager xsrfTokenManager =
new XsrfTokenManager(new FakeClock(DateTime.parse("2020-02-02T01:23:45Z")));
when(request.getCookies())
@@ -39,6 +45,7 @@ public final class ConsoleApiParamsUtils {
xsrfTokenManager.generateToken(
authResult.user().map(User::getEmailAddress).orElse("")))
});
return ConsoleApiParams.create(request, new FakeResponse(), authResult, xsrfTokenManager);
return ConsoleApiParams.create(
request, new FakeResponse(), authResult, sendEmailUtils, xsrfTokenManager);
}
}

View File

@@ -17,22 +17,20 @@ package google.registry.ui.server.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.auth.AuthenticatedRegistrarAccessor.Role.OWNER;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.gson.Gson;
import google.registry.flows.PasswordOnlyTransportCredentials;
import google.registry.groups.GmailClient;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
@@ -67,7 +65,6 @@ class ConsoleEppPasswordActionTest {
private ConsoleApiParams consoleApiParams;
protected PasswordOnlyTransportCredentials credentials = new PasswordOnlyTransportCredentials();
private FakeResponse response;
private GmailClient gmailClient = mock(GmailClient.class);
@RegisterExtension
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
@@ -75,14 +72,11 @@ class ConsoleEppPasswordActionTest {
@BeforeEach
void beforeEach() {
Registrar registrar = persistNewRegistrar("registrarId");
Registrar registrar = Registrar.loadByRegistrarId("TheRegistrar").get();
registrar =
registrar
.asBuilder()
.setType(Registrar.Type.TEST)
.setIanaIdentifier(null)
.setPassword("foobar")
.setEmailAddress("testEmail@google.com")
.build();
persistResource(registrar);
}
@@ -99,7 +93,7 @@ class ConsoleEppPasswordActionTest {
@Test
void testFailure_passwordsDontMatch() throws IOException {
ConsoleEppPasswordAction action =
createAction("registrarId", "oldPassword", "newPassword", "newPasswordRepeat");
createAction("TheRegistrar", "oldPassword", "newPassword", "newPasswordRepeat");
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
@@ -109,7 +103,7 @@ class ConsoleEppPasswordActionTest {
@Test
void testFailure_existingPasswordIncorrect() throws IOException {
ConsoleEppPasswordAction action =
createAction("registrarId", "oldPassword", "randomPasword", "randomPasword");
createAction("TheRegistrar", "oldPassword", "randomPasword", "randomPasword");
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_FORBIDDEN);
assertThat(((FakeResponse) consoleApiParams.response()).getPayload())
@@ -119,27 +113,33 @@ class ConsoleEppPasswordActionTest {
@Test
void testSuccess_sendsConfirmationEmail() throws IOException, AddressException {
ConsoleEppPasswordAction action =
createAction("registrarId", "foobar", "randomPassword", "randomPassword");
createAction("TheRegistrar", "foobar", "randomPassword", "randomPassword");
action.run();
verify(gmailClient, times(1))
verify(consoleApiParams.sendEmailUtils().gmailClient, times(1))
.sendEmail(
EmailMessage.create(
"EPP password update confirmation",
"Dear registrarId name,\n"
+ "This is to confirm that your account password has been changed.",
new InternetAddress("testEmail@google.com")));
EmailMessage.newBuilder()
.setSubject(
"Registrar The Registrar (TheRegistrar) updated in registry unittest"
+ " environment")
.setBody(
"The following changes were made in registry unittest environment to the"
+ " registrar TheRegistrar by user email@email.com:\n"
+ "\n"
+ "password: ******** -> ••••••••\n")
.setRecipients(ImmutableList.of(new InternetAddress("notification@test.example")))
.build());
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
}
@Test
void testSuccess_passwordUpdated() throws IOException {
ConsoleEppPasswordAction action =
createAction("registrarId", "foobar", "randomPassword", "randomPassword");
createAction("TheRegistrar", "foobar", "randomPassword", "randomPassword");
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
assertDoesNotThrow(
() -> {
credentials.validate(loadRegistrar("registrarId"), "randomPassword");
credentials.validate(loadRegistrar("TheRegistrar"), "randomPassword");
});
}
@@ -157,7 +157,7 @@ class ConsoleEppPasswordActionTest {
consoleApiParams = ConsoleApiParamsUtils.createFake(authResult);
AuthenticatedRegistrarAccessor authenticatedRegistrarAccessor =
AuthenticatedRegistrarAccessor.createForTesting(
ImmutableSetMultimap.of("registrarId", OWNER));
ImmutableSetMultimap.of("TheRegistrar", OWNER));
when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.POST.toString());
doReturn(
new BufferedReader(
@@ -171,6 +171,6 @@ class ConsoleEppPasswordActionTest {
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
return new ConsoleEppPasswordAction(
consoleApiParams, authenticatedRegistrarAccessor, gmailClient, maybePasswordChangeRequest);
consoleApiParams, authenticatedRegistrarAccessor, maybePasswordChangeRequest);
}
}

View File

@@ -17,19 +17,17 @@ package google.registry.ui.server.console;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registrar.RegistrarPocBase.Type.WHOIS;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson;
import google.registry.groups.GmailClient;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
@@ -73,8 +71,6 @@ class ConsoleUpdateRegistrarActionTest {
private static String registrarPostData =
"{\"registrarId\":\"%s\",\"allowedTlds\":[%s],\"registryLockAllowed\":%s}";
private GmailClient gmailClient = mock(GmailClient.class);
@RegisterExtension
@Order(Integer.MAX_VALUE)
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
@@ -82,17 +78,17 @@ class ConsoleUpdateRegistrarActionTest {
@BeforeEach
void beforeEach() throws Exception {
createTlds("app", "dev");
registrar = persistNewRegistrar("registrarId");
registrar = Registrar.loadByRegistrarId("TheRegistrar").get();
persistResource(
registrar
.asBuilder()
.setType(RegistrarBase.Type.REAL)
.setEmailAddress("testEmail@google.com")
.setAllowedTlds(ImmutableSet.of())
.setRegistryLockAllowed(false)
.build());
user =
new User.Builder()
.setEmailAddress("user@registrarId.com")
.setRegistryLockEmailAddress("registryedit@registrarId.com")
.setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build())
.build();
consoleApiParams = createParams();
@@ -104,9 +100,9 @@ class ConsoleUpdateRegistrarActionTest {
@Test
void testSuccess__updatesRegistrar() throws IOException {
var action = createAction(String.format(registrarPostData, "registrarId", "app, dev", false));
var action = createAction(String.format(registrarPostData, "TheRegistrar", "app, dev", false));
action.run();
Registrar newRegistrar = Registrar.loadByRegistrarId("registrarId").get();
Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev");
assertThat(newRegistrar.isRegistryLockAllowed()).isFalse();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
@@ -115,7 +111,7 @@ class ConsoleUpdateRegistrarActionTest {
@Test
void testFails__missingWhoisContact() throws IOException {
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
var action = createAction(String.format(registrarPostData, "registrarId", "app, dev", false));
var action = createAction(String.format(registrarPostData, "TheRegistrar", "app, dev", false));
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
assertThat((String) ((FakeResponse) consoleApiParams.response()).getPayload())
@@ -138,9 +134,9 @@ class ConsoleUpdateRegistrarActionTest {
.setVisibleInDomainWhoisAsAbuse(true)
.build();
persistResource(contact);
var action = createAction(String.format(registrarPostData, "registrarId", "app, dev", false));
var action = createAction(String.format(registrarPostData, "TheRegistrar", "app, dev", false));
action.run();
Registrar newRegistrar = Registrar.loadByRegistrarId("registrarId").get();
Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev");
assertThat(newRegistrar.isRegistryLockAllowed()).isFalse();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
@@ -148,15 +144,21 @@ class ConsoleUpdateRegistrarActionTest {
@Test
void testSuccess__sendsEmail() throws AddressException, IOException {
var action = createAction(String.format(registrarPostData, "registrarId", "app, dev", false));
var action = createAction(String.format(registrarPostData, "TheRegistrar", "app, dev", false));
action.run();
verify(gmailClient, times(1))
verify(consoleApiParams.sendEmailUtils().gmailClient, times(1))
.sendEmail(
EmailMessage.create(
"Registrar registrarId has been updated",
"The following changes were made in registry UNITTEST environment to the registrar"
+ " registrarId:/nAllowed TLDs: [] -> [app, dev]",
new InternetAddress("testEmail@google.com")));
EmailMessage.newBuilder()
.setSubject(
"Registrar The Registrar (TheRegistrar) updated in registry unittest"
+ " environment")
.setBody(
"The following changes were made in registry unittest environment to the"
+ " registrar TheRegistrar by user user@registrarId.com:\n"
+ "\n"
+ "allowedTlds: null -> [app, dev]\n")
.setRecipients(ImmutableList.of(new InternetAddress("notification@test.example")))
.build());
}
private ConsoleApiParams createParams() {
@@ -172,7 +174,6 @@ class ConsoleUpdateRegistrarActionTest {
Optional<Registrar> maybeRegistrarUpdateData =
RegistrarConsoleModule.provideRegistrar(
GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON));
return new ConsoleUpdateRegistrarAction(
consoleApiParams, gmailClient, maybeRegistrarUpdateData);
return new ConsoleUpdateRegistrarAction(consoleApiParams, maybeRegistrarUpdateData);
}
}

View File

@@ -16,6 +16,7 @@ package google.registry.ui.server.console.settings;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.registrar.RegistrarPocBase.Type.ADMIN;
import static google.registry.model.registrar.RegistrarPocBase.Type.WHOIS;
import static google.registry.testing.DatabaseHelper.createAdminUser;
import static google.registry.testing.DatabaseHelper.insertInDb;
@@ -23,9 +24,14 @@ import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson;
@@ -42,11 +48,14 @@ import google.registry.testing.ConsoleApiParamsUtils;
import google.registry.testing.FakeResponse;
import google.registry.ui.server.registrar.ConsoleApiParams;
import google.registry.ui.server.registrar.RegistrarConsoleModule;
import google.registry.util.EmailMessage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Optional;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -58,7 +67,7 @@ class ContactActionTest {
+ "\"emailAddress\":\"test.registrar1@example.com\","
+ "\"registrarId\":\"registrarId\","
+ "\"phoneNumber\":\"+1.9999999999\",\"faxNumber\":\"+1.9999999991\","
+ "\"types\":[\"WHOIS\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"types\":[\"WHOIS\",\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}";
private static String jsonRegistrar2 =
@@ -66,7 +75,7 @@ class ContactActionTest {
+ "\"emailAddress\":\"test.registrar2@example.com\","
+ "\"registrarId\":\"registrarId\","
+ "\"phoneNumber\":\"+1.1234567890\",\"faxNumber\":\"+1.1234567891\","
+ "\"types\":[\"WHOIS\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"types\":[\"WHOIS\",\"ADMIN\"],\"visibleInWhoisAsAdmin\":true,"
+ "\"visibleInWhoisAsTech\":false,\"visibleInDomainWhoisAsAbuse\":false}";
private Registrar testRegistrar;
@@ -88,7 +97,7 @@ class ContactActionTest {
.setEmailAddress("test.registrar1@example.com")
.setPhoneNumber("+1.9999999999")
.setFaxNumber("+1.9999999991")
.setTypes(ImmutableSet.of(WHOIS))
.setTypes(ImmutableSet.of(WHOIS, ADMIN))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInDomainWhoisAsAbuse(false)
@@ -110,6 +119,20 @@ class ContactActionTest {
.isEqualTo("[" + jsonRegistrar1 + "]");
}
@Test
void testSuccess_noOp() throws IOException {
insertInDb(testRegistrarPoc);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
"[" + jsonRegistrar1 + "]");
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
verify(consoleApiParams.sendEmailUtils().gmailClient, never()).sendEmail(any());
}
@Test
void testSuccess_onlyContactsWithNonEmptyType() throws IOException {
testRegistrarPoc = testRegistrarPoc.asBuilder().setTypes(ImmutableSet.of()).build();
@@ -127,6 +150,7 @@ class ContactActionTest {
@Test
void testSuccess_postCreateContactInfo() throws IOException {
insertInDb(testRegistrarPoc);
ContactAction action =
createAction(
Action.Method.POST,
@@ -167,6 +191,59 @@ class ContactActionTest {
"test.registrar2@example.com");
}
@Test
void testSuccess_sendsEmail() throws IOException, AddressException {
testRegistrarPoc = testRegistrarPoc.asBuilder().setEmailAddress("incorrect@email.com").build();
insertInDb(testRegistrarPoc);
ContactAction action =
createAction(
Action.Method.POST,
AuthResult.createUser(createAdminUser("email@email.com")),
testRegistrar.getRegistrarId(),
"[" + jsonRegistrar1 + "]");
action.run();
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK);
verify(consoleApiParams.sendEmailUtils().gmailClient, times(1))
.sendEmail(
EmailMessage.newBuilder()
.setSubject(
"Registrar New Registrar (registrarId) updated in registry unittest"
+ " environment")
.setBody(
"The following changes were made in registry unittest environment to the"
+ " registrar registrarId by admin email@email.com:\n"
+ "\n"
+ "contacts:\n"
+ " ADDED:\n"
+ " {name=Test Registrar 1,"
+ " emailAddress=test.registrar1@example.com, registrarId=registrarId,"
+ " registryLockEmailAddress=null, phoneNumber=+1.9999999999,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS], loginEmailAddress=null,"
+ " visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false,"
+ " visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n"
+ " REMOVED:\n"
+ " {name=Test Registrar 1, emailAddress=incorrect@email.com,"
+ " registrarId=registrarId, registryLockEmailAddress=null,"
+ " phoneNumber=+1.9999999999, faxNumber=+1.9999999991, types=[WHOIS,"
+ " ADMIN], loginEmailAddress=null, visibleInWhoisAsAdmin=true,"
+ " visibleInWhoisAsTech=false, visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n"
+ " FINAL CONTENTS:\n"
+ " {name=Test Registrar 1,"
+ " emailAddress=test.registrar1@example.com, registrarId=registrarId,"
+ " registryLockEmailAddress=null, phoneNumber=+1.9999999999,"
+ " faxNumber=+1.9999999991, types=[ADMIN, WHOIS], loginEmailAddress=null,"
+ " visibleInWhoisAsAdmin=true, visibleInWhoisAsTech=false,"
+ " visibleInDomainWhoisAsAbuse=false,"
+ " allowedToSetRegistryLockPassword=false}\n")
.setRecipients(
ImmutableList.of(
new InternetAddress("notification@test.example"),
new InternetAddress("incorrect@email.com")))
.build());
}
@Test
void testSuccess_postDeleteContactInfo() throws IOException {
insertInDb(testRegistrarPoc);

View File

@@ -36,7 +36,7 @@ public final class DiffUtils {
* A helper record to store the two sides of a diff. If both sides are Sets then they will be
* diffed, otherwise the two objects are toStringed in Collection format "[a, b]".
*/
private record DiffPair(@Nullable Object a, @Nullable Object b) {
public record DiffPair(@Nullable Object a, @Nullable Object b) {
@Override
public String toString() {