Allow setting the registry lock password in the UI (#241)

* Allow setting the lock password in the UI

* Add more screenshot tests

* Responses to CR and more screenshot tests

* Formatting

* Simplify lambda
This commit is contained in:
gbrodman 2019-09-03 16:39:02 -04:00 committed by GitHub
parent 0b2c65c59d
commit 45b960db1d
15 changed files with 266 additions and 38 deletions

View file

@ -619,6 +619,7 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
.putListOfStrings("allowedTlds", getAllowedTlds())
.putListOfStrings("ipAddressWhitelist", ipAddressWhitelist)
.putListOfJsonObjects("contacts", getContacts())
.put("registryLockAllowed", registryLockAllowed)
.build();
}

View file

@ -314,6 +314,7 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
.put("visibleInWhoisAsTech", visibleInWhoisAsTech)
.put("visibleInDomainWhoisAsAbuse", visibleInDomainWhoisAsAbuse)
.put("allowedToSetRegistryLockPassword", allowedToSetRegistryLockPassword)
.put("registryLockAllowed", isRegistryLockAllowed())
.put("gaeUserId", gaeUserId)
.build();
}

View file

@ -202,8 +202,13 @@ public final class RegistrarFormFields {
.build();
public static final FormField<String, String> CONTACT_GAE_USER_ID_FIELD =
FormFields.NAME.asBuilderNamed("gaeUserId")
.build();
FormFields.NAME.asBuilderNamed("gaeUserId").build();
public static final FormField<Boolean, Boolean> CONTACT_ALLOWED_TO_SET_REGISTRY_LOCK_PASSWORD =
FormField.named("allowedToSetRegistryLockPassword", Boolean.class).build();
public static final FormField<String, String> CONTACT_REGISTRY_LOCK_PASSWORD_FIELD =
FormFields.NAME.asBuilderNamed("registryLockPassword").build();
public static final FormField<String, Set<RegistrarContact.Type>> CONTACT_TYPES =
FormField.named("types")
@ -360,6 +365,12 @@ public final class RegistrarFormFields {
CONTACT_FAX_NUMBER_FIELD.extractUntyped(args).ifPresent(builder::setFaxNumber);
CONTACT_TYPES.extractUntyped(args).ifPresent(builder::setTypes);
CONTACT_GAE_USER_ID_FIELD.extractUntyped(args).ifPresent(builder::setGaeUserId);
CONTACT_ALLOWED_TO_SET_REGISTRY_LOCK_PASSWORD
.extractUntyped(args)
.ifPresent(builder::setAllowedToSetRegistryLockPassword);
CONTACT_REGISTRY_LOCK_PASSWORD_FIELD
.extractUntyped(args)
.ifPresent(builder::setRegistryLockPassword);
return builder;
}
}

View file

@ -438,6 +438,25 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
throw new ContactRequirementException(
"An abuse contact visible in domain WHOIS query must be designated");
}
// Any contact(s) with new passwords must be allowed to set them
for (RegistrarContact updatedContact : updatedContacts) {
if (updatedContact.isRegistryLockAllowed()
|| updatedContact.isAllowedToSetRegistryLockPassword()) {
RegistrarContact existingContact =
existingContacts.stream()
.filter(
contact -> contact.getEmailAddress().equals(updatedContact.getEmailAddress()))
.findFirst()
.orElseThrow(
() ->
new FormException(
"Not allowed to set registry lock password directly on new contact"));
if (!existingContact.isAllowedToSetRegistryLockPassword()) {
throw new FormException("Registrar contact not allowed to set registry lock password");
}
}
}
}
/**

View file

@ -117,7 +117,8 @@ registry.json.Response.prototype.results;
* localizedAddress: registry.json.RegistrarAddress,
* whoisServer: (string?|undefined),
* referralUrl: (string?|undefined),
* contacts: !Array.<registry.json.RegistrarContact>
* contacts: !Array.<registry.json.RegistrarContact>,
* registryLockAllowed: boolean
* }}
*/
registry.json.Registrar;
@ -144,7 +145,9 @@ registry.json.RegistrarAddress;
* visibleInDomainWhoisAsAbuse: boolean,
* phoneNumber: (string?|undefined),
* faxNumber: (string?|undefined),
* types: (string?|undefined)
* types: (string?|undefined),
* allowedToSetRegistryLockPassword: boolean,
* registryLockAllowed: boolean
* }}
*/
registry.json.RegistrarContact;

View file

@ -102,7 +102,8 @@ registry.registrar.ContactSettings.prototype.renderItem = function(rspObj) {
item: targetContact,
namePrefix: 'contacts[' + targetContactNdx + '].',
actualTypesLookup: actualTypesLookup,
readonly: (rspObj.readonly || false)
readonly: (rspObj.readonly || false),
registryLockAllowedForRegistrar: rspObj.registryLockAllowed
});
this.setupAppbar();
} else {

View file

@ -53,6 +53,8 @@
{@param? description: ?} /** Input field description. */
{@param? placeholder: ?} /** Placeholder text. */
{@param? readonly: ?}
{@param? disabled: ?}
{@param? isPassword: ?}
<tr class="{css('kd-settings-pane-section')}">
<td>
<label for="{if isNonnull($namePrefix)}{$namePrefix + $name}{else}{$name}{/if}"
@ -82,6 +84,12 @@
{/if}
{if $readonly}
readonly
{/if}
{if $disabled}
disabled
{/if}
{if $isPassword}
type="password"
{/if}>
</td>
</tr>

View file

@ -105,6 +105,7 @@
{@param item: legacy_object_map<string, ?>}
{@param actualTypesLookup: legacy_object_map<string, bool>}
{@param readonly: bool}
{@param? registryLockAllowedForRegistrar: bool}
<form name="item" class="{css('item')} {css('registrar')}">
<h1>Contact Details</h1>
{call .contactInfo data="all"}
@ -112,6 +113,7 @@
{param item: $item /}
{param actualTypesLookup: $actualTypesLookup /}
{param readonly: $readonly /}
{param registryLockAllowedForRegistrar: $registryLockAllowedForRegistrar /}
{/call}
</form>
{/template}
@ -123,6 +125,7 @@
{@param item: legacy_object_map<string, ?>}
{@param actualTypesLookup: legacy_object_map<string, bool>}
{@param? readonly: bool}
{@param? registryLockAllowedForRegistrar: bool}
{let $possibleTypesLookup: [
['admin', 'Primary', 'Primary contact for general issues.'],
['billing', 'Billing', 'Contact for financial communications & invoices.'],
@ -165,6 +168,7 @@
{param namePrefix: $namePrefix /}
{param possibleTypesLookup: $possibleTypesLookup /}
{param actualTypesLookup: $actualTypesLookup /}
{param registryLockAllowedForRegistrar: $registryLockAllowedForRegistrar /}
{/call}
{/if}
</table>
@ -222,6 +226,7 @@
{@param namePrefix: string}
{@param possibleTypesLookup: list<list<string>>}
{@param actualTypesLookup: legacy_object_map<string, bool>}
{@param? registryLockAllowedForRegistrar: bool}
<tr class="{css('kd-settings-pane-section')}">
<td>
<label class="{css('setting-label')}">Contact type</label>
@ -236,7 +241,29 @@
{param actualTypesLookup: $actualTypesLookup /}
{/call}
</tr>
<tr><td colspan="2"><hr></tr>
{if $registryLockAllowedForRegistrar}
{let $placeholder: $item['registryLockAllowed'] ?
// If registry lock is allowed, there's already a password
'Contact support to reset password' :
// Otherwise, if not allowed, support must enable it
not $item['allowedToSetRegistryLockPassword'] ?
'Contact support to enable registry lock' : '' /}
{call registry.soy.forms.inputFieldRow data="all"}
{param label: 'Registry lock password:' /}
{param name: 'registryLockPassword' /}
{param disabled: not $item['allowedToSetRegistryLockPassword'] /}
{param isPassword: true /}
{param placeholder: $placeholder /}
{/call}
{/if}
{if isNonnull($item['allowedToSetRegistryLockPassword'])}
<input type="hidden" name="allowedToSetRegistryLockPassword"
value="{$item['allowedToSetRegistryLockPassword']}">
{/if}
<tr>
<td colspan="2">
<hr>
</tr>
{call .whoisVisibleRadios_}
{param description: 'Show in Registrar WHOIS record as Admin contact' /}
{param fieldName: $namePrefix + 'visibleInWhoisAsAdmin' /}
@ -250,12 +277,10 @@
{call .whoisVisibleRadios_}
{param description:
'Show Phone and Email in Domain WHOIS Record as Registrar Abuse Contact' +
' (Per CL&D Requirements)'
/}
' (Per CL&D Requirements)' /}
{param note:
'*Can only apply to one contact. Selecting Yes for this contact will' +
' force this setting for all other contacts to be No.'
/}
' force this setting for all other contacts to be No.' /}
{param fieldName: $namePrefix + 'visibleInDomainWhoisAsAbuse' /}
{param visible: $item['visibleInDomainWhoisAsAbuse'] == true /}
{/call}

View file

@ -21,6 +21,7 @@ import static google.registry.testing.DatastoreHelper.persistSimpleResource;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registrar.RegistrarContact.Type;
@ -184,4 +185,97 @@ public class ContactSettingsTest extends RegistrarSettingsActionTestCase {
"message", "The abuse contact visible in domain WHOIS query must have a phone number");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: ContactRequirementException");
}
@Test
public void testSuccess_setRegistryLockPassword() {
techContact =
persistResource(techContact.asBuilder().setAllowedToSetRegistryLockPassword(true).build());
Map<String, Object> contactMap = techContact.toJsonMap();
contactMap.put("registryLockPassword", "hi");
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(AppEngineRule.makeRegistrarContact2().toJsonMap(), contactMap));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response).containsAtLeastEntriesIn(ImmutableMap.of("status", "SUCCESS"));
techContact = Iterables.getOnlyElement(loadRegistrar(CLIENT_ID).getContactsOfType(Type.TECH));
assertThat(techContact.verifyRegistryLockPassword("hi")).isTrue();
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
}
@Test
public void testPost_failure_setRegistryLockPassword_newContact() {
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(
AppEngineRule.makeRegistrarContact2()
.asBuilder()
.setEmailAddress("someotheremail@example.com")
.setAllowedToSetRegistryLockPassword(true)
.build()
.toJsonMap(),
techContact.toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Not allowed to set registry lock password directly on new contact");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
@Test
public void testPost_failure_setRegistryLockPassword_notAllowed() {
// "allowedToSetRegistryLockPassword" must be set through the back end first
// before we can set a password through the UI
Map<String, Object> contactMap =
techContact.asBuilder().setAllowedToSetRegistryLockPassword(true).build().toJsonMap();
contactMap.put("registryLockPassword", "hi");
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(AppEngineRule.makeRegistrarContact2().toJsonMap(), contactMap));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Registrar contact not allowed to set registry lock password");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
@Test
public void testPost_failure_setRegistryLockAllowed() {
// One cannot set the "isAllowedToSetRegistryLockPassword" field through the UI
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
reqJson.put(
"contacts",
ImmutableList.of(
AppEngineRule.makeRegistrarContact2().toJsonMap(),
techContact.asBuilder().setAllowedToSetRegistryLockPassword(true).build().toJsonMap()));
Map<String, Object> response =
action.handleJsonRequest(ImmutableMap.of("op", "update", "id", CLIENT_ID, "args", reqJson));
assertThat(response)
.containsExactly(
"status",
"ERROR",
"results",
ImmutableList.of(),
"message",
"Registrar contact not allowed to set registry lock password");
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormException");
}
}

View file

@ -16,6 +16,8 @@ package google.registry.webdriver;
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.loadRegistrar;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@ -130,6 +132,69 @@ public class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
driver.diffPage("page");
}
@Test
public void settingsContactEdit_setRegistryLockPassword() throws Throwable {
server.runInAppEngineEnvironment(
() -> {
persistResource(
makeRegistrarContact2()
.asBuilder()
.setAllowedToSetRegistryLockPassword(true)
.build());
persistResource(makeRegistrar2().asBuilder().setRegistryLockAllowed(true).build());
return null;
});
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
Thread.sleep(1000);
driver.waitForElement(By.tagName("h1"));
driver.waitForElement(By.id("reg-app-btn-edit")).click();
driver.diffPage("page");
}
@Test
public void settingsContactEdit_setRegistryLockPassword_alreadySet() throws Throwable {
server.runInAppEngineEnvironment(
() -> {
persistResource(
makeRegistrarContact2()
.asBuilder()
.setAllowedToSetRegistryLockPassword(true)
.setRegistryLockPassword("hi")
.build());
persistResource(makeRegistrar2().asBuilder().setRegistryLockAllowed(true).build());
return null;
});
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
Thread.sleep(1000);
driver.waitForElement(By.tagName("h1"));
driver.waitForElement(By.id("reg-app-btn-edit")).click();
driver.diffPage("page");
}
@Test
public void settingsContactEdit_setRegistryLockPassword_notAllowedForContact() throws Throwable {
server.runInAppEngineEnvironment(
() -> persistResource(makeRegistrar2().asBuilder().setRegistryLockAllowed(true).build()));
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#contact-settings/johndoe@theregistrar.com"));
Thread.sleep(1000);
driver.waitForElement(By.tagName("h1"));
driver.waitForElement(By.id("reg-app-btn-edit")).click();
driver.diffPage("page");
}
@Test
public void settingsContactAdd() throws Throwable {
driver.manage().window().setSize(new Dimension(1050, 2000));
driver.get(server.getUrl("/registrar#contact-settings"));
Thread.sleep(1000);
driver.waitForElement(By.tagName("h1"));
driver.waitForElement(By.id("reg-app-btn-add")).click();
driver.diffPage("page");
}
@Test
public void settingsAdmin_whenAdmin() throws Throwable {
server.setIsAdmin(true);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Before After
Before After