Show pending locks in the locked-domains table (#495)

* Show pending locks in the locked-domains table

* asdf

* fix the tests

* including pending unlocks in the table

* fix the screenshot test
This commit is contained in:
gbrodman 2020-02-28 13:57:33 -05:00 committed by GitHub
parent 9573235ca7
commit e55f28b291
9 changed files with 152 additions and 54 deletions

View file

@ -25,11 +25,7 @@ import javax.persistence.EntityManager;
/** Data access object for {@link google.registry.schema.domain.RegistryLock}. */ /** Data access object for {@link google.registry.schema.domain.RegistryLock}. */
public final class RegistryLockDao { public final class RegistryLockDao {
/** /** Returns the most recent version of the {@link RegistryLock} referred to by the code. */
* 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.
*/
public static Optional<RegistryLock> getByVerificationCode(String verificationCode) { public static Optional<RegistryLock> getByVerificationCode(String verificationCode) {
jpaTm().assertInTransaction(); jpaTm().assertInTransaction();
EntityManager em = jpaTm().getEntityManager(); EntityManager em = jpaTm().getEntityManager();
@ -43,25 +39,24 @@ public final class RegistryLockDao {
return Optional.ofNullable(revisionId).map(revision -> em.find(RegistryLock.class, revision)); return Optional.ofNullable(revisionId).map(revision -> em.find(RegistryLock.class, revision));
} }
/** Returns all lock objects that this registrar has created. */ /** Returns all lock objects that this registrar has created, including pending locks. */
public static ImmutableList<RegistryLock> getLockedDomainsByRegistrarId(String registrarId) { public static ImmutableList<RegistryLock> getLocksByRegistrarId(String registrarId) {
jpaTm().assertInTransaction(); jpaTm().assertInTransaction();
return ImmutableList.copyOf( return ImmutableList.copyOf(
jpaTm() jpaTm()
.getEntityManager() .getEntityManager()
.createQuery( .createQuery(
"SELECT lock FROM RegistryLock lock WHERE" "SELECT lock FROM RegistryLock lock WHERE lock.registrarId = :registrarId"
+ " lock.registrarId = :registrarId " + " AND lock.unlockCompletionTimestamp IS NULL",
+ "AND lock.lockCompletionTimestamp IS NOT NULL "
+ "AND lock.unlockCompletionTimestamp IS NULL",
RegistryLock.class) RegistryLock.class)
.setParameter("registrarId", registrarId) .setParameter("registrarId", registrarId)
.getResultList()); .getResultList());
} }
/** /**
* Returns the most recent lock object for a given domain specified by repo ID, or empty if this * Returns the most recent lock object for a given domain specified by repo ID.
* domain hasn't been locked before. *
* <p>Returns empty if this domain hasn't been locked before.
*/ */
public static Optional<RegistryLock> getMostRecentByRepoId(String repoId) { public static Optional<RegistryLock> getMostRecentByRepoId(String repoId) {
jpaTm().assertInTransaction(); 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 * Returns the most recent verified lock object for a given domain specified by repo ID.
* if no lock has ever been finalized for this domain. This is different from {@link *
* #getMostRecentByRepoId(String)} in that it only returns verified locks. * <p>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<RegistryLock> getMostRecentVerifiedLockByRepoId(String repoId) { public static Optional<RegistryLock> getMostRecentVerifiedLockByRepoId(String repoId) {
jpaTm().assertInTransaction(); jpaTm().assertInTransaction();

View file

@ -68,6 +68,8 @@ public final class RegistryLockGetAction implements JsonGetAction {
private static final String FULLY_QUALIFIED_DOMAIN_NAME_PARAM = "fullyQualifiedDomainName"; private static final String FULLY_QUALIFIED_DOMAIN_NAME_PARAM = "fullyQualifiedDomainName";
private static final String LOCKED_TIME_PARAM = "lockedTime"; private static final String LOCKED_TIME_PARAM = "lockedTime";
private static final String LOCKED_BY_PARAM = "lockedBy"; 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 String USER_CAN_UNLOCK_PARAM = "userCanUnlock";
private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@ -155,20 +157,25 @@ public final class RegistryLockGetAction implements JsonGetAction {
return jpaTm() return jpaTm()
.transact( .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)) .map(lock -> lockToMap(lock, isAdmin))
.collect(toImmutableList())); .collect(toImmutableList()));
} }
private ImmutableMap<String, ?> lockToMap(RegistryLock lock, boolean isAdmin) { private ImmutableMap<String, ?> lockToMap(RegistryLock lock, boolean isAdmin) {
return ImmutableMap.of( return new ImmutableMap.Builder<String, Object>()
FULLY_QUALIFIED_DOMAIN_NAME_PARAM, .put(FULLY_QUALIFIED_DOMAIN_NAME_PARAM, lock.getDomainName())
lock.getDomainName(), .put(
LOCKED_TIME_PARAM, LOCKED_TIME_PARAM, lock.getLockCompletionTimestamp().map(DateTime::toString).orElse(""))
lock.getLockCompletionTimestamp().map(DateTime::toString).orElse(""), .put(LOCKED_BY_PARAM, lock.isSuperuser() ? "admin" : lock.getRegistrarPocId())
LOCKED_BY_PARAM, .put(IS_LOCK_PENDING_PARAM, !lock.getLockCompletionTimestamp().isPresent())
lock.isSuperuser() ? "admin" : lock.getRegistrarPocId(), .put(
USER_CAN_UNLOCK_PARAM, IS_UNLOCK_PENDING_PARAM,
isAdmin || !lock.isSuperuser()); lock.getUnlockRequestTimestamp().isPresent()
&& !lock.getUnlockCompletionTimestamp().isPresent())
.put(USER_CAN_UNLOCK_PARAM, isAdmin || !lock.isSuperuser())
.build();
} }
} }

View file

@ -37,7 +37,9 @@ registry.json.locks = {};
* fullyQualifiedDomainName: string, * fullyQualifiedDomainName: string,
* lockedTime: string, * lockedTime: string,
* lockedBy: string, * lockedBy: string,
* userCanUnlock: boolean * userCanUnlock: boolean,
* isLockPending: boolean,
* isUnlockPending: boolean
* }} * }}
*/ */
registry.json.locks.ExistingLock; registry.json.locks.ExistingLock;

View file

@ -23,7 +23,8 @@
{template .locksContent} {template .locksContent}
{@param email: string} {@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} {@param lockEnabledForContact: bool}
{call .newLock} {call .newLock}
@ -63,7 +64,8 @@
/** Table that displays existing locks for this registrar. */ /** Table that displays existing locks for this registrar. */
{template .existingLocksTable} {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} {@param lockEnabledForContact: bool}
<h2>Existing locks</h2> <h2>Existing locks</h2>
<br> <br>
@ -76,19 +78,24 @@
</tr> </tr>
{for $lock in $locks} {for $lock in $locks}
<tr class="{css('registry-locks-table-row')}"> <tr class="{css('registry-locks-table-row')}">
<td>{$lock.fullyQualifiedDomainName}</td> <td>{$lock.fullyQualifiedDomainName}
{if $lock.isLockPending}<i> (pending)</i>
{elseif $lock.isUnlockPending}<i> (unlock pending)</i>
{/if}</td>
<td>{$lock.lockedTime}</td> <td>{$lock.lockedTime}</td>
<td>{$lock.lockedBy}</td> <td>{$lock.lockedBy}</td>
<td> <td>
<button id="button-unlock-{$lock.fullyQualifiedDomainName}" {if not $lock.isLockPending and not $lock.isUnlockPending}
{if $lockEnabledForContact and $lock.userCanUnlock} <button id="button-unlock-{$lock.fullyQualifiedDomainName}"
class="domain-unlock-button {css('kd-button')} {css('kd-button-submit')}" {if $lockEnabledForContact and $lock.userCanUnlock}
{else} class="domain-unlock-button {css('kd-button')} {css('kd-button-submit')}"
class="{css('kd-button')}" {else}
disabled class="{css('kd-button')}"
{/if} disabled
>Unlock {/if}
</button> >Unlock
</button>
{/if}
</td> </td>
</tr> </tr>
{/for} {/for}

View file

@ -127,8 +127,7 @@ public final class RegistryLockDaoTest {
@Test @Test
public void testLoad_lockedDomains_byRegistrarId() { public void testLoad_lockedDomains_byRegistrarId() {
RegistryLock lock = RegistryLock lock = createLock();
createLock().asBuilder().setLockCompletionTimestamp(fakeClock.nowUtc()).build();
RegistryLock secondLock = RegistryLock secondLock =
createLock() createLock()
.asBuilder() .asBuilder()

View file

@ -41,7 +41,7 @@ public class SqlHelper {
} }
public static ImmutableList<RegistryLock> getRegistryLocksByRegistrarId(String registrarId) { public static ImmutableList<RegistryLock> getRegistryLocksByRegistrarId(String registrarId) {
return jpaTm().transact(() -> RegistryLockDao.getLockedDomainsByRegistrarId(registrarId)); return jpaTm().transact(() -> RegistryLockDao.getLocksByRegistrarId(registrarId));
} }
private SqlHelper() {} private SqlHelper() {}

View file

@ -45,6 +45,7 @@ import google.registry.testing.FakeResponse;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -92,6 +93,28 @@ public final class RegistryLockGetActionTest {
@Test @Test
public void testSuccess_retrievesLocks() { 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 = RegistryLock regularLock =
new RegistryLock.Builder() new RegistryLock.Builder()
.setRepoId("repoId") .setRepoId("repoId")
@ -114,12 +137,23 @@ public final class RegistryLockGetActionTest {
RegistryLock incompleteLock = RegistryLock incompleteLock =
new RegistryLock.Builder() new RegistryLock.Builder()
.setRepoId("repoId") .setRepoId("repoId")
.setDomainName("incomplete.test") .setDomainName("pending.test")
.setRegistrarId("TheRegistrar") .setRegistrarId("TheRegistrar")
.setVerificationCode("111111111ABCDEFGHJKLMNPQRSTUVWXY") .setVerificationCode("111111111ABCDEFGHJKLMNPQRSTUVWXY")
.setRegistrarPocId("johndoe@theregistrar.com") .setRegistrarPocId("johndoe@theregistrar.com")
.build(); .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 = RegistryLock unlockedLock =
new RegistryLock.Builder() new RegistryLock.Builder()
.setRepoId("repoId") .setRepoId("repoId")
@ -135,6 +169,7 @@ public final class RegistryLockGetActionTest {
saveRegistryLock(regularLock); saveRegistryLock(regularLock);
saveRegistryLock(adminLock); saveRegistryLock(adminLock);
saveRegistryLock(incompleteLock); saveRegistryLock(incompleteLock);
saveRegistryLock(incompleteUnlock);
saveRegistryLock(unlockedLock); saveRegistryLock(unlockedLock);
action.run(); action.run();
@ -154,16 +189,38 @@ public final class RegistryLockGetActionTest {
"TheRegistrar", "TheRegistrar",
"locks", "locks",
ImmutableList.of( ImmutableList.of(
ImmutableMap.of( new ImmutableMap.Builder<>()
"fullyQualifiedDomainName", "example.test", .put("fullyQualifiedDomainName", "example.test")
"lockedTime", "2000-06-08T22:00:00.000Z", .put("lockedTime", "2000-06-09T22:00:00.000Z")
"lockedBy", "johndoe@theregistrar.com", .put("lockedBy", "johndoe@theregistrar.com")
"userCanUnlock", true), .put("userCanUnlock", true)
ImmutableMap.of( .put("isLockPending", false)
"fullyQualifiedDomainName", "adminexample.test", .put("isUnlockPending", false)
"lockedTime", "2000-06-08T22:00:00.001Z", .build(),
"lockedBy", "admin", new ImmutableMap.Builder<>()
"userCanUnlock", false))))); .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 @Test

View file

@ -24,6 +24,7 @@ import static google.registry.testing.DatastoreHelper.newDomainBase;
import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.SqlHelper.saveRegistryLock; 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 static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -453,6 +454,35 @@ public class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
saveRegistryLock(createRegistryLock(domain).asBuilder().isSuperuser(true).build()); saveRegistryLock(createRegistryLock(domain).asBuilder().isSuperuser(true).build());
DomainBase otherDomain = persistActiveDomain("otherexample.tld"); DomainBase otherDomain = persistActiveDomain("otherexample.tld");
saveRegistryLock(createRegistryLock(otherDomain)); 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; return null;
}); });
driver.get(server.getUrl("/registrar#registry-lock")); driver.get(server.getUrl("/registrar#registry-lock"));
@ -523,7 +553,7 @@ public class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
.setRegistrarId("TheRegistrar") .setRegistrarId("TheRegistrar")
.setRegistrarPocId("Marla.Singer@crr.com") .setRegistrarPocId("Marla.Singer@crr.com")
.setLockCompletionTimestamp(START_OF_TIME) .setLockCompletionTimestamp(START_OF_TIME)
.setDomainName("example.tld") .setDomainName(domainBase.getFullyQualifiedDomainName())
.setRepoId(domainBase.getRepoId()) .setRepoId(domainBase.getRepoId())
.build(); .build();
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 60 KiB