diff --git a/core/src/main/java/google/registry/model/registry/RegistryLockDao.java b/core/src/main/java/google/registry/model/registry/RegistryLockDao.java index 5d094c692..8568cce79 100644 --- a/core/src/main/java/google/registry/model/registry/RegistryLockDao.java +++ b/core/src/main/java/google/registry/model/registry/RegistryLockDao.java @@ -25,11 +25,7 @@ import javax.persistence.EntityManager; /** Data access object for {@link google.registry.schema.domain.RegistryLock}. */ public final class RegistryLockDao { - /** - * Returns the most recent version of the {@link RegistryLock} referred to by the verification - * code (there may be two instances of the same code in the database--one after lock object - * creation and one after verification. - */ + /** Returns the most recent version of the {@link RegistryLock} referred to by the code. */ public static Optional getByVerificationCode(String verificationCode) { jpaTm().assertInTransaction(); EntityManager em = jpaTm().getEntityManager(); @@ -43,25 +39,24 @@ public final class RegistryLockDao { return Optional.ofNullable(revisionId).map(revision -> em.find(RegistryLock.class, revision)); } - /** Returns all lock objects that this registrar has created. */ - public static ImmutableList getLockedDomainsByRegistrarId(String registrarId) { + /** Returns all lock objects that this registrar has created, including pending locks. */ + public static ImmutableList getLocksByRegistrarId(String registrarId) { jpaTm().assertInTransaction(); return ImmutableList.copyOf( jpaTm() .getEntityManager() .createQuery( - "SELECT lock FROM RegistryLock lock WHERE" - + " lock.registrarId = :registrarId " - + "AND lock.lockCompletionTimestamp IS NOT NULL " - + "AND lock.unlockCompletionTimestamp IS NULL", + "SELECT lock FROM RegistryLock lock WHERE lock.registrarId = :registrarId" + + " AND lock.unlockCompletionTimestamp IS NULL", RegistryLock.class) .setParameter("registrarId", registrarId) .getResultList()); } /** - * Returns the most recent lock object for a given domain specified by repo ID, or empty if this - * domain hasn't been locked before. + * Returns the most recent lock object for a given domain specified by repo ID. + * + *

Returns empty if this domain hasn't been locked before. */ public static Optional getMostRecentByRepoId(String repoId) { jpaTm().assertInTransaction(); @@ -78,9 +73,10 @@ public final class RegistryLockDao { } /** - * Returns the most recent verified lock object for a given domain specified by repo ID, or empty - * if no lock has ever been finalized for this domain. This is different from {@link - * #getMostRecentByRepoId(String)} in that it only returns verified locks. + * Returns the most recent verified lock object for a given domain specified by repo ID. + * + *

Returns empty if no lock has ever been finalized for this domain. This is different from + * {@link #getMostRecentByRepoId(String)} in that it only returns verified locks. */ public static Optional getMostRecentVerifiedLockByRepoId(String repoId) { jpaTm().assertInTransaction(); diff --git a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java index 644a7315b..656e7b591 100644 --- a/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java +++ b/core/src/main/java/google/registry/ui/server/registrar/RegistryLockGetAction.java @@ -68,6 +68,8 @@ public final class RegistryLockGetAction implements JsonGetAction { private static final String FULLY_QUALIFIED_DOMAIN_NAME_PARAM = "fullyQualifiedDomainName"; private static final String LOCKED_TIME_PARAM = "lockedTime"; private static final String LOCKED_BY_PARAM = "lockedBy"; + private static final String IS_LOCK_PENDING_PARAM = "isLockPending"; + private static final String IS_UNLOCK_PENDING_PARAM = "isUnlockPending"; private static final String USER_CAN_UNLOCK_PARAM = "userCanUnlock"; private static final FluentLogger logger = FluentLogger.forEnclosingClass(); @@ -155,20 +157,25 @@ public final class RegistryLockGetAction implements JsonGetAction { return jpaTm() .transact( () -> - RegistryLockDao.getLockedDomainsByRegistrarId(clientId).stream() + RegistryLockDao.getLocksByRegistrarId(clientId).stream() + .filter(lock -> !lock.isLockRequestExpired(jpaTm().getTransactionTime())) + .filter(lock -> !lock.isUnlockRequestExpired(jpaTm().getTransactionTime())) .map(lock -> lockToMap(lock, isAdmin)) .collect(toImmutableList())); } private ImmutableMap lockToMap(RegistryLock lock, boolean isAdmin) { - return ImmutableMap.of( - FULLY_QUALIFIED_DOMAIN_NAME_PARAM, - lock.getDomainName(), - LOCKED_TIME_PARAM, - lock.getLockCompletionTimestamp().map(DateTime::toString).orElse(""), - LOCKED_BY_PARAM, - lock.isSuperuser() ? "admin" : lock.getRegistrarPocId(), - USER_CAN_UNLOCK_PARAM, - isAdmin || !lock.isSuperuser()); + return new ImmutableMap.Builder() + .put(FULLY_QUALIFIED_DOMAIN_NAME_PARAM, lock.getDomainName()) + .put( + LOCKED_TIME_PARAM, lock.getLockCompletionTimestamp().map(DateTime::toString).orElse("")) + .put(LOCKED_BY_PARAM, lock.isSuperuser() ? "admin" : lock.getRegistrarPocId()) + .put(IS_LOCK_PENDING_PARAM, !lock.getLockCompletionTimestamp().isPresent()) + .put( + IS_UNLOCK_PENDING_PARAM, + lock.getUnlockRequestTimestamp().isPresent() + && !lock.getUnlockCompletionTimestamp().isPresent()) + .put(USER_CAN_UNLOCK_PARAM, isAdmin || !lock.isSuperuser()) + .build(); } } diff --git a/core/src/main/javascript/google/registry/ui/externs/json.js b/core/src/main/javascript/google/registry/ui/externs/json.js index d92f23231..8feeabaa0 100644 --- a/core/src/main/javascript/google/registry/ui/externs/json.js +++ b/core/src/main/javascript/google/registry/ui/externs/json.js @@ -37,7 +37,9 @@ registry.json.locks = {}; * fullyQualifiedDomainName: string, * lockedTime: string, * lockedBy: string, - * userCanUnlock: boolean + * userCanUnlock: boolean, + * isLockPending: boolean, + * isUnlockPending: boolean * }} */ registry.json.locks.ExistingLock; diff --git a/core/src/main/resources/google/registry/ui/soy/registrar/RegistryLock.soy b/core/src/main/resources/google/registry/ui/soy/registrar/RegistryLock.soy index 467c702e7..e6aa57f56 100644 --- a/core/src/main/resources/google/registry/ui/soy/registrar/RegistryLock.soy +++ b/core/src/main/resources/google/registry/ui/soy/registrar/RegistryLock.soy @@ -23,7 +23,8 @@ {template .locksContent} {@param email: string} - {@param locks: list<[fullyQualifiedDomainName: string, lockedTime: string, lockedBy: string, userCanUnlock: bool]>} + {@param locks: list<[fullyQualifiedDomainName: string, lockedTime: string, lockedBy: string, + userCanUnlock: bool, isLockPending: bool, isUnlockPending: bool]>} {@param lockEnabledForContact: bool} {call .newLock} @@ -63,7 +64,8 @@ /** Table that displays existing locks for this registrar. */ {template .existingLocksTable} - {@param locks: list<[fullyQualifiedDomainName: string, lockedTime: string, lockedBy: string, userCanUnlock: bool]>} + {@param locks: list<[fullyQualifiedDomainName: string, lockedTime: string, lockedBy: string, + userCanUnlock: bool, isLockPending: bool, isUnlockPending: bool]>} {@param lockEnabledForContact: bool}

Existing locks


@@ -76,19 +78,24 @@ {for $lock in $locks} - {$lock.fullyQualifiedDomainName} + {$lock.fullyQualifiedDomainName} + {if $lock.isLockPending} (pending) + {elseif $lock.isUnlockPending} (unlock pending) + {/if} {$lock.lockedTime} {$lock.lockedBy} - + {if not $lock.isLockPending and not $lock.isUnlockPending} + + {/if} {/for} diff --git a/core/src/test/java/google/registry/model/registry/RegistryLockDaoTest.java b/core/src/test/java/google/registry/model/registry/RegistryLockDaoTest.java index 34d1e5ec4..38f77ee31 100644 --- a/core/src/test/java/google/registry/model/registry/RegistryLockDaoTest.java +++ b/core/src/test/java/google/registry/model/registry/RegistryLockDaoTest.java @@ -127,8 +127,7 @@ public final class RegistryLockDaoTest { @Test public void testLoad_lockedDomains_byRegistrarId() { - RegistryLock lock = - createLock().asBuilder().setLockCompletionTimestamp(fakeClock.nowUtc()).build(); + RegistryLock lock = createLock(); RegistryLock secondLock = createLock() .asBuilder() diff --git a/core/src/test/java/google/registry/testing/SqlHelper.java b/core/src/test/java/google/registry/testing/SqlHelper.java index 5b69265a9..6fbef9be8 100644 --- a/core/src/test/java/google/registry/testing/SqlHelper.java +++ b/core/src/test/java/google/registry/testing/SqlHelper.java @@ -41,7 +41,7 @@ public class SqlHelper { } public static ImmutableList getRegistryLocksByRegistrarId(String registrarId) { - return jpaTm().transact(() -> RegistryLockDao.getLockedDomainsByRegistrarId(registrarId)); + return jpaTm().transact(() -> RegistryLockDao.getLocksByRegistrarId(registrarId)); } private SqlHelper() {} diff --git a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockGetActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockGetActionTest.java index 2523ea25c..3c522c386 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockGetActionTest.java +++ b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockGetActionTest.java @@ -45,6 +45,7 @@ import google.registry.testing.FakeResponse; import java.util.Map; import java.util.Optional; import org.joda.time.DateTime; +import org.joda.time.Duration; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -92,6 +93,28 @@ public final class RegistryLockGetActionTest { @Test public void testSuccess_retrievesLocks() { + RegistryLock expiredLock = + new RegistryLock.Builder() + .setRepoId("repoId") + .setDomainName("expired.test") + .setRegistrarId("TheRegistrar") + .setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY") + .setRegistrarPocId("johndoe@theregistrar.com") + .build(); + saveRegistryLock(expiredLock); + RegistryLock expiredUnlock = + new RegistryLock.Builder() + .setRepoId("repoId") + .setDomainName("expiredunlock.test") + .setRegistrarId("TheRegistrar") + .setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY") + .setRegistrarPocId("johndoe@theregistrar.com") + .setLockCompletionTimestamp(fakeClock.nowUtc()) + .setUnlockRequestTimestamp(fakeClock.nowUtc()) + .build(); + saveRegistryLock(expiredUnlock); + fakeClock.advanceBy(Duration.standardDays(1)); + RegistryLock regularLock = new RegistryLock.Builder() .setRepoId("repoId") @@ -114,12 +137,23 @@ public final class RegistryLockGetActionTest { RegistryLock incompleteLock = new RegistryLock.Builder() .setRepoId("repoId") - .setDomainName("incomplete.test") + .setDomainName("pending.test") .setRegistrarId("TheRegistrar") .setVerificationCode("111111111ABCDEFGHJKLMNPQRSTUVWXY") .setRegistrarPocId("johndoe@theregistrar.com") .build(); + RegistryLock incompleteUnlock = + new RegistryLock.Builder() + .setRepoId("repoId") + .setDomainName("incompleteunlock.test") + .setRegistrarId("TheRegistrar") + .setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY") + .setRegistrarPocId("johndoe@theregistrar.com") + .setLockCompletionTimestamp(fakeClock.nowUtc()) + .setUnlockRequestTimestamp(fakeClock.nowUtc()) + .build(); + RegistryLock unlockedLock = new RegistryLock.Builder() .setRepoId("repoId") @@ -135,6 +169,7 @@ public final class RegistryLockGetActionTest { saveRegistryLock(regularLock); saveRegistryLock(adminLock); saveRegistryLock(incompleteLock); + saveRegistryLock(incompleteUnlock); saveRegistryLock(unlockedLock); action.run(); @@ -154,16 +189,38 @@ public final class RegistryLockGetActionTest { "TheRegistrar", "locks", ImmutableList.of( - ImmutableMap.of( - "fullyQualifiedDomainName", "example.test", - "lockedTime", "2000-06-08T22:00:00.000Z", - "lockedBy", "johndoe@theregistrar.com", - "userCanUnlock", true), - ImmutableMap.of( - "fullyQualifiedDomainName", "adminexample.test", - "lockedTime", "2000-06-08T22:00:00.001Z", - "lockedBy", "admin", - "userCanUnlock", false))))); + new ImmutableMap.Builder<>() + .put("fullyQualifiedDomainName", "example.test") + .put("lockedTime", "2000-06-09T22:00:00.000Z") + .put("lockedBy", "johndoe@theregistrar.com") + .put("userCanUnlock", true) + .put("isLockPending", false) + .put("isUnlockPending", false) + .build(), + new ImmutableMap.Builder<>() + .put("fullyQualifiedDomainName", "adminexample.test") + .put("lockedTime", "2000-06-09T22:00:00.001Z") + .put("lockedBy", "admin") + .put("userCanUnlock", false) + .put("isLockPending", false) + .put("isUnlockPending", false) + .build(), + new ImmutableMap.Builder<>() + .put("fullyQualifiedDomainName", "pending.test") + .put("lockedTime", "") + .put("lockedBy", "johndoe@theregistrar.com") + .put("userCanUnlock", true) + .put("isLockPending", true) + .put("isUnlockPending", false) + .build(), + new ImmutableMap.Builder<>() + .put("fullyQualifiedDomainName", "incompleteunlock.test") + .put("lockedTime", "2000-06-09T22:00:00.001Z") + .put("lockedBy", "johndoe@theregistrar.com") + .put("userCanUnlock", true) + .put("isLockPending", false) + .put("isUnlockPending", true) + .build())))); } @Test diff --git a/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java b/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java index 1a3f1ea5f..25a5bcba4 100644 --- a/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java +++ b/core/src/test/java/google/registry/webdriver/RegistrarConsoleScreenshotTest.java @@ -24,6 +24,7 @@ import static google.registry.testing.DatastoreHelper.newDomainBase; import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.SqlHelper.saveRegistryLock; +import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES; import static google.registry.util.DateTimeUtils.START_OF_TIME; import com.google.common.collect.ImmutableMap; @@ -453,6 +454,35 @@ public class RegistrarConsoleScreenshotTest extends WebDriverTestCase { saveRegistryLock(createRegistryLock(domain).asBuilder().isSuperuser(true).build()); DomainBase otherDomain = persistActiveDomain("otherexample.tld"); saveRegistryLock(createRegistryLock(otherDomain)); + // include one pending-lock domain + DomainBase pendingDomain = persistActiveDomain("pending.tld"); + saveRegistryLock( + new RegistryLock.Builder() + .setVerificationCode(UUID.randomUUID().toString()) + .isSuperuser(false) + .setRegistrarId("TheRegistrar") + .setRegistrarPocId("Marla.Singer@crr.com") + .setDomainName("pending.tld") + .setRepoId(pendingDomain.getRepoId()) + .build()); + // and one pending-unlock domain + DomainBase pendingUnlockDomain = + persistResource( + newDomainBase("pendingunlock.tld") + .asBuilder() + .setStatusValues(REGISTRY_LOCK_STATUSES) + .build()); + saveRegistryLock( + new RegistryLock.Builder() + .setVerificationCode(UUID.randomUUID().toString()) + .isSuperuser(false) + .setRegistrarId("TheRegistrar") + .setRegistrarPocId("Marla.Singer@crr.com") + .setDomainName(pendingUnlockDomain.getFullyQualifiedDomainName()) + .setRepoId(pendingUnlockDomain.getRepoId()) + .setLockCompletionTimestamp(START_OF_TIME) + .setUnlockRequestTimestamp(START_OF_TIME) + .build()); return null; }); driver.get(server.getUrl("/registrar#registry-lock")); @@ -523,7 +553,7 @@ public class RegistrarConsoleScreenshotTest extends WebDriverTestCase { .setRegistrarId("TheRegistrar") .setRegistrarPocId("Marla.Singer@crr.com") .setLockCompletionTimestamp(START_OF_TIME) - .setDomainName("example.tld") + .setDomainName(domainBase.getFullyQualifiedDomainName()) .setRepoId(domainBase.getRepoId()) .build(); } diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_admin_page.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_admin_page.png index 26864434b..246484687 100644 Binary files a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_admin_page.png and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLock_nonEmpty_admin_page.png differ