diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java index 1cf08f9be..df4aa2648 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java @@ -127,6 +127,14 @@ public abstract class ConsoleApiAction implements Runnable { } } + protected static void checkGlobalPermission(User user, ConsolePermission permission) { + if (!user.getUserRoles().hasGlobalPermission(permission)) { + throw new ConsolePermissionForbiddenException( + String.format( + "User %s does not have global permission %s", user.getEmailAddress(), permission)); + } + } + protected void postHandler(User user) { throw new UnsupportedOperationException("Console API POST handler not implemented"); } 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 index 1d9c461a8..45beb18f1 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleUpdateRegistrarAction.java @@ -15,6 +15,7 @@ package google.registry.ui.server.console; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.request.Action.Method.POST; import static google.registry.util.DateTimeUtils.START_INSTANT; @@ -38,7 +39,6 @@ import google.registry.util.RegistryEnvironment; import jakarta.inject.Inject; import java.time.Instant; import java.util.Optional; -import java.util.stream.Collectors; @Action( service = Service.CONSOLE, @@ -69,22 +69,23 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction { tm().transact( () -> { - Optional existingRegistrar = - Registrar.loadByRegistrarId(registrarParam.getRegistrarId()); - checkArgument( - !existingRegistrar.isEmpty(), - "Registrar with registrarId %s doesn't exists", - registrarParam.getRegistrarId()); + Registrar existingRegistrar = + Registrar.loadByRegistrarId(registrarParam.getRegistrarId()) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Registrar %s does not exist", + 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 RDAP abuse contact set. if (!registrarParam.getAllowedTlds().isEmpty()) { - boolean isRealRegistrar = - Registrar.Type.REAL.equals(existingRegistrar.get().getType()); + boolean isRealRegistrar = Registrar.Type.REAL.equals(existingRegistrar.getType()); if (RegistryEnvironment.PRODUCTION.equals(RegistryEnvironment.get()) && isRealRegistrar) { checkArgumentPresent( - existingRegistrar.get().getRdapAbuseContact(), + existingRegistrar.getRdapAbuseContact(), "Cannot modify allowed TLDs if there is no RDAP abuse contact set. Please" + " use the \"nomulus registrar_contact\" command on this registrar to" + " set a RDAP abuse contact."); @@ -103,20 +104,27 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction { var updatedRegistrarBuilder = existingRegistrar - .get() .asBuilder() .setLastPocVerificationDate(newLastPocVerificationDate); - if (user.getUserRoles() - .hasGlobalPermission(ConsolePermission.EDIT_REGISTRAR_DETAILS)) { - updatedRegistrarBuilder = - updatedRegistrarBuilder - .setAllowedTlds( - registrarParam.getAllowedTlds().stream() - .map(DomainNameUtils::canonicalizeHostname) - .collect(Collectors.toSet())) - .setRegistryLockAllowed(registrarParam.isRegistryLockAllowed()) - .setLastPocVerificationDate(newLastPocVerificationDate); + if (!registrarParam.getAllowedTlds().equals(existingRegistrar.getAllowedTlds())) { + // The global permission EDIT_REGISTRAR_DETAILS signifies a support agent who *does* + // have the ability to edit allowed TLDs. See support docs 2.19: "Enabling TLD + // Access in Production" + checkGlobalPermission(user, ConsolePermission.EDIT_REGISTRAR_DETAILS); + updatedRegistrarBuilder.setAllowedTlds( + registrarParam.getAllowedTlds().stream() + .map(DomainNameUtils::canonicalizeHostname) + .collect(toImmutableSet())); + } + + if (registrarParam.isRegistryLockAllowed() + != existingRegistrar.isRegistryLockAllowed()) { + // Enabling registry lock requires a support lead or FTE, which maps to + // MANAGE_REGISTRARS. See support docs 2.33: "Registry Lock Onboarding Process" + checkGlobalPermission(user, ConsolePermission.MANAGE_REGISTRARS); + updatedRegistrarBuilder.setRegistryLockAllowed( + registrarParam.isRegistryLockAllowed()); } var updatedRegistrar = updatedRegistrarBuilder.build(); @@ -126,14 +134,11 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction { .setType(ConsoleUpdateHistory.Type.REGISTRAR_UPDATE) .setDescription(updatedRegistrar.getRegistrarId())); - logConsoleChangesIfNecessary(updatedRegistrar, existingRegistrar.get()); + logConsoleChangesIfNecessary(updatedRegistrar, existingRegistrar); sendExternalUpdatesIfNecessary( EmailInfo.create( - existingRegistrar.get(), - updatedRegistrar, - ImmutableSet.of(), - ImmutableSet.of())); + existingRegistrar, updatedRegistrar, ImmutableSet.of(), ImmutableSet.of())); }); consoleApiParams.response().setStatus(SC_OK); 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 index 7bd6aa806..eb7d2ff70 100644 --- a/core/src/test/java/google/registry/ui/server/console/ConsoleUpdateRegistrarActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/ConsoleUpdateRegistrarActionTest.java @@ -20,6 +20,7 @@ import static google.registry.testing.DatabaseHelper.createTlds; import static google.registry.testing.DatabaseHelper.loadSingleton; 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.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -37,6 +38,7 @@ import google.registry.request.Action; import google.registry.request.RequestModule; import google.registry.request.auth.AuthResult; import google.registry.testing.ConsoleApiParamsUtils; +import google.registry.testing.FakeResponse; import google.registry.testing.SystemPropertyExtension; import google.registry.util.EmailMessage; import google.registry.util.RegistryEnvironment; @@ -55,8 +57,6 @@ class ConsoleUpdateRegistrarActionTest extends ConsoleActionBaseTestCase { private Registrar registrar; - private User user; - private static String registrarPostData = "{\"registrarId\":\"%s\",\"allowedTlds\":[%s],\"registryLockAllowed\":%s," + " \"lastPocVerificationDate\":%s }"; @@ -76,12 +76,6 @@ class ConsoleUpdateRegistrarActionTest extends ConsoleActionBaseTestCase { .setAllowedTlds(ImmutableSet.of()) .setRegistryLockAllowed(false) .build()); - user = - persistResource( - new User.Builder() - .setEmailAddress("user@registrarId.com") - .setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).build()) - .build()); } @Test @@ -209,15 +203,123 @@ class ConsoleUpdateRegistrarActionTest extends ConsoleActionBaseTestCase { .build()); } - private ConsoleApiParams createParams() { - AuthResult authResult = AuthResult.createUser(user); - return ConsoleApiParamsUtils.createFake(authResult); + @Test + void testFailure_registrarNotFound() throws IOException { + var action = + createAction( + String.format( + registrarPostData, + "nonexistent", + "app, dev", + false, + "\"2023-12-12T00:00:00.000Z\"")); + action.run(); + assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN); } - private ConsoleUpdateRegistrarAction createAction(String requestData) throws IOException { + @Test + void testSuccess_supportAgentCanUpdateAllowedTlds() throws IOException { + User supportAgentUser = + persistResource( + new User.Builder() + .setEmailAddress("agent@registrarId.com") + .setUserRoles( + new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build()) + .build()); + var action = + createAction( + supportAgentUser, + String.format( + registrarPostData, + "TheRegistrar", + "app, dev", + false, + "\"2023-12-12T00:00:00.000Z\"")); + action.run(); + Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get(); + assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev"); + assertThat(response.getStatus()).isEqualTo(SC_OK); + } + + @Test + void testFailure_supportAgentCannotUpdateRegistryLock() throws IOException { + User supportAgentUser = + persistResource( + new User.Builder() + .setEmailAddress("agent@registrarId.com") + .setUserRoles( + new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_AGENT).build()) + .build()); + var action = + createAction( + supportAgentUser, + String.format( + registrarPostData, + "TheRegistrar", + "app, dev", + true, + "\"2023-12-12T00:00:00.000Z\"")); + action.run(); + assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN); + Registrar unupdatedRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get(); + assertThat(unupdatedRegistrar.isRegistryLockAllowed()).isFalse(); + } + + @Test + void testFailure_userWithoutEditRegistrarDetailsCannotUpdateAllowedTlds() throws IOException { + User normalUser = + persistResource( + new User.Builder() + .setEmailAddress("normal@registrarId.com") + .setUserRoles(new UserRoles.Builder().setGlobalRole(GlobalRole.NONE).build()) + .build()); + var action = + createAction( + normalUser, + String.format( + registrarPostData, + "TheRegistrar", + "app, dev", + false, + "\"2023-12-12T00:00:00.000Z\"")); + action.run(); + assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN); + Registrar unupdatedRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get(); + assertThat(unupdatedRegistrar.getAllowedTlds()).isEmpty(); + } + + @Test + void testSuccess_supportLeadCanUpdateRegistryLock() throws IOException { + User supportLeadUser = + persistResource( + new User.Builder() + .setEmailAddress("lead@registrarId.com") + .setUserRoles( + new UserRoles.Builder().setGlobalRole(GlobalRole.SUPPORT_LEAD).build()) + .build()); + var action = + createAction( + supportLeadUser, + String.format( + registrarPostData, "TheRegistrar", "", true, "\"2023-12-12T00:00:00.000Z\"")); + action.run(); + Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get(); + assertThat(newRegistrar.isRegistryLockAllowed()).isTrue(); + assertThat(response.getStatus()).isEqualTo(SC_OK); + } + + private ConsoleUpdateRegistrarAction createAction(User actionUser, String requestData) + throws IOException { + AuthResult authResult = AuthResult.createUser(actionUser); + consoleApiParams = ConsoleApiParamsUtils.createFake(authResult); + response = (FakeResponse) consoleApiParams.response(); when(consoleApiParams.request().getMethod()).thenReturn(Action.Method.POST.toString()); Optional maybeRegistrarUpdateData = ConsoleModule.provideRegistrar(GSON, RequestModule.provideJsonBody(requestData, GSON)); return new ConsoleUpdateRegistrarAction(consoleApiParams, maybeRegistrarUpdateData); } + + private ConsoleUpdateRegistrarAction createAction(String requestData) throws IOException { + return createAction(fteUser, requestData); + } }