diff --git a/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml b/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml index 80d6601e7..b2417bec1 100644 --- a/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml +++ b/core/src/main/java/google/registry/env/common/default/WEB-INF/web.xml @@ -61,6 +61,11 @@ /registry-lock-get + + frontend-servlet + /registry-lock-verify + + diff --git a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java index a9b529051..75b1632dc 100644 --- a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java +++ b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java @@ -31,6 +31,7 @@ import google.registry.ui.server.registrar.OteStatusAction; import google.registry.ui.server.registrar.RegistrarConsoleModule; import google.registry.ui.server.registrar.RegistrarSettingsAction; import google.registry.ui.server.registrar.RegistryLockGetAction; +import google.registry.ui.server.registrar.RegistryLockVerifyAction; /** Dagger component with per-request lifetime for "default" App Engine module. */ @RequestScope @@ -53,6 +54,8 @@ interface FrontendRequestComponent { RegistryLockGetAction registryLockGetAction(); + RegistryLockVerifyAction registryLockVerifyAction(); + @Subcomponent.Builder abstract class Builder implements RequestComponentBuilder { @Override public abstract Builder requestModule(RequestModule requestModule); diff --git a/core/src/main/java/google/registry/tools/LockOrUnlockDomainCommand.java b/core/src/main/java/google/registry/tools/LockOrUnlockDomainCommand.java index d66173cab..245dab7c8 100644 --- a/core/src/main/java/google/registry/tools/LockOrUnlockDomainCommand.java +++ b/core/src/main/java/google/registry/tools/LockOrUnlockDomainCommand.java @@ -38,7 +38,7 @@ public abstract class LockOrUnlockDomainCommand extends ConfirmingCommand private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - static final ImmutableSet REGISTRY_LOCK_STATUSES = + public static final ImmutableSet REGISTRY_LOCK_STATUSES = ImmutableSet.of( SERVER_DELETE_PROHIBITED, SERVER_TRANSFER_PROHIBITED, SERVER_UPDATE_PROHIBITED); diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleModule.java b/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleModule.java index 63b1a48e8..6c1ddc61b 100644 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleModule.java +++ b/core/src/main/java/google/registry/ui/server/registrar/RegistrarConsoleModule.java @@ -14,7 +14,7 @@ package google.registry.ui.server.registrar; - +import static google.registry.request.RequestParameters.extractBooleanParameter; import static google.registry.request.RequestParameters.extractOptionalIntParameter; import static google.registry.request.RequestParameters.extractOptionalParameter; import static google.registry.request.RequestParameters.extractRequiredParameter; @@ -144,4 +144,16 @@ public final class RegistrarConsoleModule { static Optional provideOptionalPasscode(HttpServletRequest req) { return extractOptionalParameter(req, "passcode"); } + + @Provides + @Parameter("lockVerificationCode") + static String provideLockVerificationCode(HttpServletRequest req) { + return extractRequiredParameter(req, "lockVerificationCode"); + } + + @Provides + @Parameter("isLock") + static Boolean provideIsLock(HttpServletRequest req) { + return extractBooleanParameter(req, "isLock"); + } } diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java new file mode 100644 index 000000000..6d77e4142 --- /dev/null +++ b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockVerifyAction.java @@ -0,0 +1,97 @@ +// Copyright 2020 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.registrar; + +import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER; + +import com.google.common.base.Supplier; +import com.google.common.base.Throwables; +import com.google.common.flogger.FluentLogger; +import com.google.template.soy.tofu.SoyTofu; +import google.registry.request.Action; +import google.registry.request.Parameter; +import google.registry.request.auth.Auth; +import google.registry.schema.domain.RegistryLock; +import google.registry.tools.DomainLockUtils; +import google.registry.ui.server.SoyTemplateUtils; +import google.registry.ui.soy.registrar.RegistryLockVerificationSoyInfo; +import google.registry.util.Clock; +import java.util.HashMap; +import javax.inject.Inject; + +/** Action that allows for verification of registry lock / unlock requests */ +@Action( + service = Action.Service.DEFAULT, + path = RegistryLockVerifyAction.PATH, + auth = Auth.AUTH_PUBLIC_LOGGED_IN) +public final class RegistryLockVerifyAction extends HtmlAction { + + public static final String PATH = "/registry-lock-verify"; + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private static final Supplier TOFU_SUPPLIER = + SoyTemplateUtils.createTofuSupplier( + google.registry.ui.soy.ConsoleSoyInfo.getInstance(), + google.registry.ui.soy.AnalyticsSoyInfo.getInstance(), + google.registry.ui.soy.registrar.RegistryLockVerificationSoyInfo.getInstance()); + + private final Clock clock; + private final String lockVerificationCode; + private final Boolean isLock; + + @Inject + public RegistryLockVerifyAction( + Clock clock, + @Parameter("lockVerificationCode") String lockVerificationCode, + @Parameter("isLock") Boolean isLock) { + this.clock = clock; + this.lockVerificationCode = lockVerificationCode; + this.isLock = isLock; + } + + @Override + public void runAfterLogin(HashMap data) { + try { + boolean isAdmin = authResult.userAuthInfo().get().isUserAdmin(); + final RegistryLock resultLock; + if (isLock) { + resultLock = DomainLockUtils.verifyAndApplyLock(lockVerificationCode, isAdmin, clock); + } else { + resultLock = DomainLockUtils.verifyAndApplyUnlock(lockVerificationCode, isAdmin, clock); + } + data.put("isLock", isLock); + data.put("success", true); + data.put("fullyQualifiedDomainName", resultLock.getDomainName()); + } catch (Throwable t) { + logger.atWarning().withCause(t).log( + "Error when verifying verification code %s", lockVerificationCode); + data.put("success", false); + data.put("errorMessage", Throwables.getRootCause(t).getMessage()); + } + response.setPayload( + TOFU_SUPPLIER + .get() + .newRenderer(RegistryLockVerificationSoyInfo.VERIFICATION_PAGE) + .setCssRenamingMap(CSS_RENAMING_MAP_SUPPLIER.get()) + .setData(data) + .render()); + } + + @Override + public String getPath() { + return PATH; + } +} diff --git a/core/src/main/resources/google/registry/ui/soy/registrar/RegistryLockVerification.soy b/core/src/main/resources/google/registry/ui/soy/registrar/RegistryLockVerification.soy new file mode 100644 index 000000000..d18d674be --- /dev/null +++ b/core/src/main/resources/google/registry/ui/soy/registrar/RegistryLockVerification.soy @@ -0,0 +1,71 @@ +// Copyright 2019 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. + +{namespace registry.soy.registrar.registrylock.verification} + + +/** + * Results page for a registry lock/unlock verification. + */ +{template .verificationPage} + {@param username: string} /** Arbitrary username to display. */ + {@param analyticsConfig: [googleAnalyticsId: string|null]} + {@param success: bool} + {@param? errorMessage: string} + {@param? isLock: bool} + {@param? fullyQualifiedDomainName: string} + {call registry.soy.console.header} + {param app: 'registrar' /} + {param subtitle: 'Verify Registry Lock' /} + {param analyticsConfig: $analyticsConfig /} + {/call} + {call registry.soy.console.googlebar data="all" /} + +{/template} + +/** + * Result page for failure, e.g. the UUID was invalid + */ +{template .failure} + {@param? errorMessage: string} +

Failed: {if isNonnull($errorMessage)} + {$errorMessage} + {else} + Undefined error message + {/if} +

+{/template} + +/** + * Result page for a successful lock / unlock. + */ +{template .success} + {@param? isLock: bool} + {@param? fullyQualifiedDomainName: string} +

+ Success: {if $isLock}lock{else}unlock{/if} has been applied to {$fullyQualifiedDomainName} +

+{/template} + diff --git a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java index ef5771cd3..dcfa32e1d 100644 --- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java +++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java @@ -30,6 +30,7 @@ import google.registry.tools.UpdateReservedListCommandTest; import google.registry.tools.server.CreatePremiumListActionTest; import google.registry.tools.server.UpdatePremiumListActionTest; import google.registry.ui.server.registrar.RegistryLockGetActionTest; +import google.registry.ui.server.registrar.RegistryLockVerifyActionTest; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -60,6 +61,7 @@ import org.junit.runners.Suite.SuiteClasses; PremiumListDaoTest.class, RegistryLockDaoTest.class, RegistryLockGetActionTest.class, + RegistryLockVerifyActionTest.class, ReservedListDaoTest.class, UnlockDomainCommandTest.class, UpdatePremiumListActionTest.class, diff --git a/core/src/test/java/google/registry/server/RegistryTestServer.java b/core/src/test/java/google/registry/server/RegistryTestServer.java index 0d36cfbbe..7214be456 100644 --- a/core/src/test/java/google/registry/server/RegistryTestServer.java +++ b/core/src/test/java/google/registry/server/RegistryTestServer.java @@ -83,7 +83,8 @@ public final class RegistryTestServer { route("/registrar-ote-setup", FrontendServlet.class), route("/registrar-ote-status", FrontendServlet.class), route("/registrar-settings", FrontendServlet.class), - route("/registry-lock-get", FrontendServlet.class)); + route("/registry-lock-get", FrontendServlet.class), + route("/registry-lock-verify", FrontendServlet.class)); private static final ImmutableList> FILTERS = ImmutableList.of( ObjectifyFilter.class, diff --git a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java new file mode 100644 index 000000000..c6dfd85af --- /dev/null +++ b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java @@ -0,0 +1,345 @@ +// Copyright 2020 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.registrar; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.createTlds; +import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType; +import static google.registry.testing.DatastoreHelper.newDomainBase; +import static google.registry.testing.DatastoreHelper.persistActiveHost; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES; +import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY; +import static javax.servlet.http.HttpServletResponse.SC_OK; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.users.User; +import com.google.appengine.api.users.UserService; +import com.google.appengine.api.users.UserServiceFactory; +import com.google.common.collect.ImmutableMap; +import google.registry.model.billing.BillingEvent; +import google.registry.model.billing.BillingEvent.Reason; +import google.registry.model.domain.DomainBase; +import google.registry.model.host.HostResource; +import google.registry.model.registry.Registry; +import google.registry.model.registry.RegistryLockDao; +import google.registry.model.reporting.HistoryEntry; +import google.registry.persistence.transaction.JpaTestRules; +import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule; +import google.registry.request.auth.AuthLevel; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.UserAuthInfo; +import google.registry.schema.domain.RegistryLock; +import google.registry.security.XsrfTokenManager; +import google.registry.testing.AppEngineRule; +import google.registry.testing.DatastoreHelper; +import google.registry.testing.FakeClock; +import google.registry.testing.FakeResponse; +import google.registry.testing.InjectRule; +import google.registry.testing.UserInfo; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import org.joda.time.Duration; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class RegistryLockVerifyActionTest { + + private final FakeClock fakeClock = new FakeClock(); + + @Rule + public final AppEngineRule appEngineRule = + AppEngineRule.builder() + .withDatastore() + .withUserService(UserInfo.create("marla.singer@example.com", "12345")) + .build(); + + @Rule + public final JpaIntegrationWithCoverageRule jpaRule = + new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageRule(); + + @Rule public final InjectRule inject = new InjectRule(); + + private final HttpServletRequest request = mock(HttpServletRequest.class); + private final UserService userService = UserServiceFactory.getUserService(); + private final User user = new User("marla.singer@example.com", "gmail.com", "12345"); + private final String lockId = "f1be78a2-2d61-458c-80f0-9dd8f2f8625f"; + + private FakeResponse response; + private DomainBase domain; + private AuthResult authResult; + private RegistryLockVerifyAction action; + + @Before + public void setup() { + createTlds("tld", "net"); + HostResource host = persistActiveHost("ns1.example.net"); + domain = persistResource(newDomainBase("example.tld", host)); + when(request.getRequestURI()).thenReturn("https://registry.example/registry-lock-verification"); + action = createAction(lockId, true); + } + + @Test + public void testSuccess_lockDomain() { + RegistryLockDao.save(createLock()); + action.run(); + assertThat(response.getStatus()).isEqualTo(SC_OK); + assertThat(reloadDomain().getStatusValues()).containsExactlyElementsIn(REGISTRY_LOCK_STATUSES); + assertThat(response.getPayload()).contains("Success: lock has been applied to example.tld"); + HistoryEntry historyEntry = getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE); + assertThat(historyEntry.getRequestedByRegistrar()).isTrue(); + assertThat(historyEntry.getBySuperuser()).isFalse(); + assertThat(historyEntry.getReason()) + .isEqualTo("Lock or unlock of a domain through a RegistryLock operation"); + assertBillingEvent(historyEntry); + } + + @Test + public void testSuccess_unlockDomain() { + action = createAction(lockId, false); + domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); + RegistryLockDao.save( + createLock().asBuilder().setUnlockRequestTimestamp(fakeClock.nowUtc()).build()); + action.run(); + assertThat(response.getStatus()).isEqualTo(SC_OK); + assertThat(response.getPayload()).contains("Success: unlock has been applied to example.tld"); + assertThat(reloadDomain().getStatusValues()).containsNoneIn(REGISTRY_LOCK_STATUSES); + HistoryEntry historyEntry = getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE); + assertThat(historyEntry.getRequestedByRegistrar()).isTrue(); + assertThat(historyEntry.getBySuperuser()).isFalse(); + assertThat(historyEntry.getReason()) + .isEqualTo("Lock or unlock of a domain through a RegistryLock operation"); + assertBillingEvent(historyEntry); + } + + @Test + public void testSuccess_adminLock_createsOnlyHistoryEntry() { + action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, true)); + RegistryLockDao.save(createLock().asBuilder().isSuperuser(true).build()); + + action.run(); + HistoryEntry historyEntry = getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_UPDATE); + assertThat(historyEntry.getRequestedByRegistrar()).isFalse(); + assertThat(historyEntry.getBySuperuser()).isTrue(); + DatastoreHelper.assertNoBillingEvents(); + } + + @Test + public void testFailure_badVerificationCode() { + RegistryLockDao.save( + createLock().asBuilder().setVerificationCode(UUID.randomUUID().toString()).build()); + action.run(); + assertThat(response.getPayload()).contains("Failed: Invalid verification code"); + assertNoDomainChanges(); + } + + @Test + public void testFailure_alreadyVerified() { + RegistryLockDao.save( + createLock().asBuilder().setLockCompletionTimestamp(fakeClock.nowUtc()).build()); + action.run(); + assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked"); + assertNoDomainChanges(); + } + + @Test + public void testFailure_expired() { + RegistryLockDao.save(createLock()); + fakeClock.advanceBy(Duration.standardHours(2)); + action.run(); + assertThat(response.getPayload()) + .contains("Failed: The pending lock has expired; please try again"); + assertNoDomainChanges(); + } + + @Test + public void testFailure_nonAdmin_verifyingAdminLock() { + RegistryLockDao.save(createLock().asBuilder().isSuperuser(true).build()); + action.run(); + assertThat(response.getPayload()).contains("Failed: Non-admin user cannot complete admin lock"); + assertNoDomainChanges(); + } + + @Test + public void testFailure_alreadyUnlocked() { + action = createAction(lockId, false); + RegistryLockDao.save( + createLock() + .asBuilder() + .setLockCompletionTimestamp(fakeClock.nowUtc()) + .setUnlockRequestTimestamp(fakeClock.nowUtc()) + .setUnlockCompletionTimestamp(fakeClock.nowUtc()) + .build()); + action.run(); + assertThat(response.getPayload()).contains("Failed: Domain example.tld is already unlocked"); + assertNoDomainChanges(); + } + + @Test + public void testFailure_alreadyLocked() { + RegistryLockDao.save(createLock()); + domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); + action.run(); + assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked"); + assertNoDomainChanges(); + } + + @Test + public void testFailure_notLoggedIn() { + action.authResult = AuthResult.NOT_AUTHENTICATED; + action.run(); + assertThat(response.getStatus()).isEqualTo(SC_MOVED_TEMPORARILY); + assertThat(response.getHeaders()).containsKey("Location"); + assertNoDomainChanges(); + } + + @Test + public void testFailure_doesNotChangeLockObject() { + // A failure when performing Datastore actions means that no actions should be taken in the + // Cloud SQL RegistryLock object + RegistryLock lock = createLock(); + RegistryLockDao.save(lock); + // reload the lock to pick up creation time + lock = RegistryLockDao.getByVerificationCode(lock.getVerificationCode()).get(); + fakeClock.advanceOneMilli(); + domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); + action.run(); + // we would have failed during the Datastore segment of the action + assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked"); + + // verify that the changes to the SQL object were rolled back + RegistryLock afterAction = + RegistryLockDao.getByVerificationCode(lock.getVerificationCode()).get(); + assertThat(afterAction).isEqualTo(lock); + } + + @Test + public void testFailure_isLockTrue_shouldBeFalse() { + domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); + RegistryLockDao.save( + createLock() + .asBuilder() + .setLockCompletionTimestamp(fakeClock.nowUtc()) + .setUnlockRequestTimestamp(fakeClock.nowUtc()) + .build()); + action.run(); + assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked"); + } + + @Test + public void testFailure_isLockFalse_shouldBeTrue() { + action = createAction(lockId, false); + RegistryLockDao.save(createLock()); + action.run(); + assertThat(response.getPayload()).contains("Failed: Domain example.tld is already unlocked"); + } + + @Test + public void testFailure_lock_unlock_lockAgain() { + RegistryLock lock = RegistryLockDao.save(createLock()); + action.run(); + assertThat(response.getPayload()).contains("Success: lock has been applied to example.tld"); + String unlockVerificationCode = "some-unlock-code"; + RegistryLockDao.save( + lock.asBuilder() + .setVerificationCode(unlockVerificationCode) + .setUnlockRequestTimestamp(fakeClock.nowUtc()) + .build()); + action = createAction(unlockVerificationCode, false); + action.run(); + assertThat(response.getPayload()).contains("Success: unlock has been applied to example.tld"); + action = createAction(lockId, true); + action.run(); + assertThat(response.getPayload()).contains("Failed: Invalid verification code"); + } + + @Test + public void testFailure_lock_lockAgain() { + RegistryLockDao.save(createLock()); + action.run(); + assertThat(response.getPayload()).contains("Success: lock has been applied to example.tld"); + action = createAction(lockId, true); + action.run(); + assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked"); + } + + @Test + public void testFailure_unlock_unlockAgain() { + action = createAction(lockId, false); + domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); + RegistryLockDao.save( + createLock().asBuilder().setUnlockRequestTimestamp(fakeClock.nowUtc()).build()); + action.run(); + assertThat(response.getStatus()).isEqualTo(SC_OK); + assertThat(response.getPayload()).contains("Success: unlock has been applied to example.tld"); + action = createAction(lockId, false); + action.run(); + assertThat(response.getPayload()).contains("Failed: Domain example.tld is already unlocked"); + } + + private RegistryLock createLock() { + return new RegistryLock.Builder() + .setDomainName("example.tld") + .setRegistrarId("TheRegistrar") + .setRepoId("repoId") + .setRegistrarPocId("marla.singer@example.com") + .isSuperuser(false) + .setVerificationCode(lockId) + .build(); + } + + private DomainBase reloadDomain() { + return ofy().load().entity(domain).now(); + } + + private void assertNoDomainChanges() { + assertThat(reloadDomain()).isEqualTo(domain); + } + + private void assertBillingEvent(HistoryEntry historyEntry) { + DatastoreHelper.assertBillingEvents( + new BillingEvent.OneTime.Builder() + .setReason(Reason.SERVER_STATUS) + .setTargetId(domain.getForeignKey()) + .setClientId(domain.getCurrentSponsorClientId()) + .setCost(Registry.get(domain.getTld()).getServerStatusChangeCost()) + .setEventTime(fakeClock.nowUtc()) + .setBillingTime(fakeClock.nowUtc()) + .setParent(historyEntry) + .build()); + } + + private RegistryLockVerifyAction createAction(String lockVerificationCode, Boolean isLock) { + response = new FakeResponse(); + RegistryLockVerifyAction action = + new RegistryLockVerifyAction(fakeClock, lockVerificationCode, isLock); + authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, false)); + action.req = request; + action.response = response; + action.authResult = authResult; + action.userService = userService; + action.logoFilename = "logo.png"; + action.productName = "Nomulus"; + action.analyticsConfig = ImmutableMap.of("googleAnalyticsId", "sampleId"); + action.xsrfTokenManager = new XsrfTokenManager(new FakeClock(), action.userService); + return action; + } +} diff --git a/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java b/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java index f7a1fd13a..e2430d974 100644 --- a/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java +++ b/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java @@ -18,7 +18,9 @@ import static google.registry.server.Fixture.BASIC; import static google.registry.server.Route.route; import static google.registry.testing.AppEngineRule.makeRegistrar2; import static google.registry.testing.AppEngineRule.makeRegistrarContact2; +import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.loadRegistrar; +import static google.registry.testing.DatastoreHelper.newDomainBase; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -26,7 +28,9 @@ import com.google.common.collect.ImmutableMap; import com.googlecode.objectify.ObjectifyFilter; import google.registry.model.ofy.OfyFilter; import google.registry.model.registrar.Registrar.State; +import google.registry.model.registry.RegistryLockDao; import google.registry.module.frontend.FrontendServlet; +import google.registry.schema.domain.RegistryLock; import google.registry.server.RegistryTestServer; import google.registry.testing.CertificateSamples; import org.junit.Rule; @@ -46,7 +50,8 @@ public class RegistrarConsoleScreenshotTest extends WebDriverTestCase { .setRoutes( route("/registrar", FrontendServlet.class), route("/registrar-ote-status", FrontendServlet.class), - route("/registrar-settings", FrontendServlet.class)) + route("/registrar-settings", FrontendServlet.class), + route("/registry-lock-verify", FrontendServlet.class)) .setFilters(ObjectifyFilter.class, OfyFilter.class) .setFixtures(BASIC) .setEmail("Marla.Singer@google.com") @@ -370,4 +375,36 @@ public class RegistrarConsoleScreenshotTest extends WebDriverTestCase { Thread.sleep(500); driver.diffPage("page"); } + + @Test + public void registryLockVerify_success() throws Throwable { + String lockVerificationCode = "f1be78a2-2d61-458c-80f0-9dd8f2f8625f"; + server.runInAppEngineEnvironment( + () -> { + createTld("tld"); + persistResource(newDomainBase("example.tld")); + RegistryLockDao.save( + new RegistryLock.Builder() + .setRegistrarPocId("johndoe@theregistrar.com") + .setRepoId("repoId") + .setRegistrarId("TheRegistrar") + .setVerificationCode("f1be78a2-2d61-458c-80f0-9dd8f2f8625f") + .isSuperuser(false) + .setDomainName("example.tld") + .build()); + return null; + }); + driver.get( + server.getUrl( + "/registry-lock-verify?isLock=true&lockVerificationCode=" + lockVerificationCode)); + driver.waitForElement(By.id("reg-content")); + driver.diffPage("page"); + } + + @Test + public void registryLockVerify_unknownLock() throws Throwable { + driver.get(server.getUrl("/registry-lock-verify?isLock=true&lockVerificationCode=asdfasdf")); + driver.waitForElement(By.id("reg-content")); + driver.diffPage("page"); + } } diff --git a/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt b/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt index 3f94daeeb..6a7f9066e 100644 --- a/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt +++ b/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt @@ -6,3 +6,4 @@ PATH CLASS METHODS OK AUTH_METHODS /registrar-ote-status OteStatusAction POST n API,LEGACY USER PUBLIC /registrar-settings RegistrarSettingsAction POST n API,LEGACY USER PUBLIC /registry-lock-get RegistryLockGetAction GET n API,LEGACY USER PUBLIC +/registry-lock-verify RegistryLockVerifyAction GET n API,LEGACY USER PUBLIC diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_success_page.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_success_page.png new file mode 100644 index 000000000..76acb814d Binary files /dev/null and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_success_page.png differ diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_unknownLock_page.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_unknownLock_page.png new file mode 100644 index 000000000..5b658d44a Binary files /dev/null and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_unknownLock_page.png differ