diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java new file mode 100644 index 00000000000..670f3abf9f2 --- /dev/null +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java @@ -0,0 +1,145 @@ +// Copyright 2024 The Nomulus Authors. All Rights Reserved. +// +// 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 google.registry.ui.server.console; + +import static com.google.common.base.Preconditions.checkArgument; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.request.Action.Method.POST; +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 google.registry.model.console.ConsolePermission; +import google.registry.model.console.User; +import google.registry.model.registrar.Registrar; +import google.registry.request.Action; +import google.registry.request.HttpException.BadRequestException; +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, + path = ConsoleEppPasswordAction.PATH, + method = {POST}, + 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; + + private final GmailClient gmailClient; + + @Inject + ConsoleUpdateRegistrarAction( + ConsoleApiParams consoleApiParams, + GmailClient gmailClient, + @Parameter("registrar") Optional registrar) { + super(consoleApiParams); + this.registrar = registrar; + this.gmailClient = gmailClient; + } + + @Override + protected void postHandler(User user) { + var errorMsg = "Missing param(s): %s"; + Registrar updatedRegistrar = + registrar.orElseThrow(() -> new BadRequestException(String.format(errorMsg, "registrar"))); + checkArgument( + !Strings.isNullOrEmpty(updatedRegistrar.getRegistrarId()), errorMsg, "registrarId"); + checkPermission( + user, updatedRegistrar.getRegistrarId(), ConsolePermission.EDIT_REGISTRAR_DETAILS); + + tm().transact( + () -> { + Optional existingRegistrar = + Registrar.loadByRegistrarId(updatedRegistrar.getRegistrarId()); + checkArgument( + !existingRegistrar.isEmpty(), + "Registrar with registrarId %s doesn't exists", + updatedRegistrar.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()) { + boolean isRealRegistrar = + Registrar.Type.REAL.equals(existingRegistrar.get().getType()); + if (RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get()) + && isRealRegistrar) { + checkArgumentPresent( + existingRegistrar.get().getWhoisAbuseContact(), + "Cannot modify allowed TLDs if there is no WHOIS abuse contact set. Please" + + " use the \"nomulus registrar_contact\" command on this registrar to" + + " set a WHOIS abuse contact."); + } + } + + tm().put( + existingRegistrar + .get() + .asBuilder() + .setAllowedTlds( + updatedRegistrar.getAllowedTlds().stream() + .map(DomainNameUtils::canonicalizeHostname) + .collect(Collectors.toSet())) + .setRegistryLockAllowed(updatedRegistrar.isRegistryLockAllowed()) + .build()); + + sendEmail(existingRegistrar.get(), updatedRegistrar); + }); + + 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))); + } + } +} diff --git a/core/src/test/java/google/registry/ui/server/console/ConsoleUpdateRegistrarActionTest.java b/core/src/test/java/google/registry/ui/server/console/ConsoleUpdateRegistrarActionTest.java new file mode 100644 index 00000000000..73b93dbc821 --- /dev/null +++ b/core/src/test/java/google/registry/ui/server/console/ConsoleUpdateRegistrarActionTest.java @@ -0,0 +1,179 @@ +// Copyright 2024 The Nomulus Authors. All Rights Reserved. +// +// 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 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.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; +import google.registry.model.registrar.Registrar; +import google.registry.model.registrar.RegistrarBase; +import google.registry.model.registrar.RegistrarPoc; +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.request.Action; +import google.registry.request.RequestModule; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.UserAuthInfo; +import google.registry.testing.FakeConsoleApiParams; +import google.registry.testing.FakeResponse; +import google.registry.testing.SystemPropertyExtension; +import google.registry.tools.GsonUtils; +import google.registry.ui.server.registrar.ConsoleApiParams; +import google.registry.ui.server.registrar.RegistrarConsoleModule; +import google.registry.util.EmailMessage; +import google.registry.util.RegistryEnvironment; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +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.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Tests for {@link google.registry.ui.server.console.ConsoleUpdateRegistrarAction}. */ +class ConsoleUpdateRegistrarActionTest { + private static final Gson GSON = GsonUtils.provideGson(); + + private ConsoleApiParams consoleApiParams; + private FakeResponse response; + + private Registrar registrar; + + private User user; + + 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(); + + @BeforeEach + void beforeEach() throws Exception { + createTlds("app", "dev"); + registrar = persistNewRegistrar("registrarId"); + persistResource( + registrar + .asBuilder() + .setType(RegistrarBase.Type.REAL) + .setEmailAddress("testEmail@google.com") + .build()); + user = + new User.Builder() + .setEmailAddress("user@registrarId.com") + .setRegistryLockEmailAddress("registryedit@registrarId.com") + .setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()) + .build(); + consoleApiParams = createParams(); + } + + @RegisterExtension + final JpaTestExtensions.JpaIntegrationTestExtension jpa = + new JpaTestExtensions.Builder().buildIntegrationTestExtension(); + + @Test + void testSuccess__updatesRegistrar() throws IOException { + var action = createAction(String.format(registrarPostData, "registrarId", "app, dev", false)); + action.run(); + Registrar newRegistrar = Registrar.loadByRegistrarId("registrarId").get(); + assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev"); + assertThat(newRegistrar.isRegistryLockAllowed()).isFalse(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); + } + + @Test + void testFails__missingWhoisContact() throws IOException { + RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension); + var action = createAction(String.format(registrarPostData, "registrarId", "app, dev", false)); + action.run(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST); + assertThat((String) ((FakeResponse) consoleApiParams.response()).getPayload()) + .contains("Cannot modify allowed TLDs if there is no WHOIS abuse contact set"); + } + + @Test + void testSuccess__presentWhoisContact() throws IOException { + RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension); + RegistrarPoc contact = + new RegistrarPoc.Builder() + .setRegistrar(registrar) + .setName("Test Registrar 1") + .setEmailAddress("test.registrar1@example.com") + .setPhoneNumber("+1.9999999999") + .setFaxNumber("+1.9999999991") + .setTypes(ImmutableSet.of(WHOIS)) + .setVisibleInWhoisAsAdmin(true) + .setVisibleInWhoisAsTech(true) + .setVisibleInDomainWhoisAsAbuse(true) + .build(); + persistResource(contact); + var action = createAction(String.format(registrarPostData, "registrarId", "app, dev", false)); + action.run(); + Registrar newRegistrar = Registrar.loadByRegistrarId("registrarId").get(); + assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev"); + assertThat(newRegistrar.isRegistryLockAllowed()).isFalse(); + assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_OK); + } + + @Test + void testSuccess__sendsEmail() throws AddressException, IOException { + var action = createAction(String.format(registrarPostData, "registrarId", "app, dev", false)); + action.run(); + verify(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"))); + } + + private ConsoleApiParams createParams() { + AuthResult authResult = AuthResult.createUser(UserAuthInfo.create(user)); + return FakeConsoleApiParams.get(Optional.of(authResult)); + } + + ConsoleUpdateRegistrarAction createAction(String requestData) throws IOException { + when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.POST.toString()); + doReturn(new BufferedReader(new StringReader(requestData))) + .when(consoleApiParams.request()) + .getReader(); + Optional maybeRegistrarUpdateData = + RegistrarConsoleModule.provideRegistrar( + GSON, RequestModule.provideJsonBody(consoleApiParams.request(), GSON)); + return new ConsoleUpdateRegistrarAction( + consoleApiParams, gmailClient, maybeRegistrarUpdateData); + } +}