mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 00:17:20 +02:00
This CL include changes in the registrar console that makes it possible to designate an abuse contact in domain WHOIS record, per ICANN's CL&D requirement.
Frontend validation: ensures that only one WHOIS abuse contact exist per registrar. Any existing WHOIS abuse contact will be overridden when a new one is designated. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=155289097
This commit is contained in:
parent
275d6ddc10
commit
2846f9c6b9
6 changed files with 162 additions and 9 deletions
|
@ -108,6 +108,7 @@ registry.json.RegistrarAddress;
|
||||||
* emailAddress: string,
|
* emailAddress: string,
|
||||||
* visibleInWhoisAsAdmin: boolean,
|
* visibleInWhoisAsAdmin: boolean,
|
||||||
* visibleInWhoisAsTech: boolean,
|
* visibleInWhoisAsTech: boolean,
|
||||||
|
* visibleInDomainWhoisAsAbuse: boolean,
|
||||||
* phoneNumber: (string?|undefined),
|
* phoneNumber: (string?|undefined),
|
||||||
* faxNumber: (string?|undefined),
|
* faxNumber: (string?|undefined),
|
||||||
* types: (string?|undefined)
|
* types: (string?|undefined)
|
||||||
|
|
|
@ -196,6 +196,8 @@ registry.registrar.ContactSettings.prototype.prepareUpdate =
|
||||||
}
|
}
|
||||||
contact.visibleInWhoisAsAdmin = contact.visibleInWhoisAsAdmin == 'true';
|
contact.visibleInWhoisAsAdmin = contact.visibleInWhoisAsAdmin == 'true';
|
||||||
contact.visibleInWhoisAsTech = contact.visibleInWhoisAsTech == 'true';
|
contact.visibleInWhoisAsTech = contact.visibleInWhoisAsTech == 'true';
|
||||||
|
contact.visibleInDomainWhoisAsAbuse =
|
||||||
|
contact.visibleInDomainWhoisAsAbuse == 'true';
|
||||||
contact.types = '';
|
contact.types = '';
|
||||||
for (var tNdx in contact.type) {
|
for (var tNdx in contact.type) {
|
||||||
if (contact.type[tNdx]) {
|
if (contact.type[tNdx]) {
|
||||||
|
@ -206,6 +208,14 @@ registry.registrar.ContactSettings.prototype.prepareUpdate =
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete contact['type'];
|
delete contact['type'];
|
||||||
|
// Override previous domain WHOIS abuse contact.
|
||||||
|
if (contact.visibleInDomainWhoisAsAbuse) {
|
||||||
|
for (var c in modelCopy.contacts) {
|
||||||
|
if (modelCopy.contacts[c].emailAddress != contact.emailAddress) {
|
||||||
|
modelCopy.contacts[c].visibleInDomainWhoisAsAbuse = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
this.nextId = contact.emailAddress;
|
this.nextId = contact.emailAddress;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import com.google.common.collect.HashMultimap;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
import com.googlecode.objectify.Work;
|
import com.googlecode.objectify.Work;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
|
@ -52,6 +53,7 @@ import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
@ -270,11 +272,25 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ensurePhoneNumberNotRemovedForContactTypes(oldContactsByType, newContactsByType, Type.TECH);
|
ensurePhoneNumberNotRemovedForContactTypes(oldContactsByType, newContactsByType, Type.TECH);
|
||||||
|
Optional<RegistrarContact> domainWhoisAbuseContact =
|
||||||
|
getDomainWhoisVisibleAbuseContact(updatedContacts);
|
||||||
|
// If the new set has a domain WHOIS abuse contact, it must have a phone number.
|
||||||
|
if (domainWhoisAbuseContact.isPresent()
|
||||||
|
&& domainWhoisAbuseContact.get().getPhoneNumber() == null) {
|
||||||
|
throw new ContactRequirementException(
|
||||||
|
"The abuse contact visible in domain WHOIS query must have a phone number");
|
||||||
|
}
|
||||||
|
// If there was a domain WHOIS abuse contact in the old set, the new set must have one.
|
||||||
|
if (getDomainWhoisVisibleAbuseContact(existingContacts).isPresent()
|
||||||
|
&& !domainWhoisAbuseContact.isPresent()) {
|
||||||
|
throw new ContactRequirementException(
|
||||||
|
"An abuse contact visible in domain WHOIS query must be designated");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure that for each given registrar type, a phone number is present after update, if there
|
* Ensure that for each given registrar type, a phone number is present after update, if there was
|
||||||
* was one before.
|
* one before.
|
||||||
*/
|
*/
|
||||||
private static void ensurePhoneNumberNotRemovedForContactTypes(
|
private static void ensurePhoneNumberNotRemovedForContactTypes(
|
||||||
Multimap<RegistrarContact.Type, RegistrarContact> oldContactsByType,
|
Multimap<RegistrarContact.Type, RegistrarContact> oldContactsByType,
|
||||||
|
@ -291,6 +307,24 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the registrar contact whose phone number and email address is visible in domain WHOIS
|
||||||
|
* query as abuse contact (if any).
|
||||||
|
*
|
||||||
|
* <p>Frontend processing ensures that only one contact can be set as abuse contact in domain
|
||||||
|
* WHOIS record. Therefore it is possible to return inside the loop once one such contact is
|
||||||
|
* found.
|
||||||
|
*/
|
||||||
|
private static Optional<RegistrarContact> getDomainWhoisVisibleAbuseContact(
|
||||||
|
Set<RegistrarContact> contacts) {
|
||||||
|
return Iterables.tryFind(contacts, new Predicate<RegistrarContact>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(@Nullable RegistrarContact contact) {
|
||||||
|
return contact.getVisibleInDomainWhoisAsAbuse();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if any changes were made to the registrar besides the lastUpdateTime, and if so,
|
* Determines if any changes were made to the registrar besides the lastUpdateTime, and if so,
|
||||||
* sends an email with a diff of the changes to the configured notification email address and
|
* sends an email with a diff of the changes to the configured notification email address and
|
||||||
|
|
|
@ -46,7 +46,9 @@
|
||||||
{param name: $c['name'] /}
|
{param name: $c['name'] /}
|
||||||
{param emailAddress: $c['emailAddress'] /}
|
{param emailAddress: $c['emailAddress'] /}
|
||||||
{param visibleInWhois:
|
{param visibleInWhois:
|
||||||
($c['visibleInWhoisAsAdmin'] or $c['visibleInWhoisAsTech']) /}
|
($c['visibleInWhoisAsAdmin']
|
||||||
|
or $c['visibleInWhoisAsTech']
|
||||||
|
or $c['visibleInDomainWhoisAsAbuse']) /}
|
||||||
{param phoneNumber: $c['phoneNumber'] /}
|
{param phoneNumber: $c['phoneNumber'] /}
|
||||||
{param faxNumber: $c['faxNumber'] /}
|
{param faxNumber: $c['faxNumber'] /}
|
||||||
{/call}
|
{/call}
|
||||||
|
@ -199,12 +201,17 @@
|
||||||
<p class="{css setting-item-list}">
|
<p class="{css setting-item-list}">
|
||||||
{let $visibleAsAdmin: $item['visibleInWhoisAsAdmin'] == true /}
|
{let $visibleAsAdmin: $item['visibleInWhoisAsAdmin'] == true /}
|
||||||
{let $visibleAsTech: $item['visibleInWhoisAsTech'] == true /}
|
{let $visibleAsTech: $item['visibleInWhoisAsTech'] == true /}
|
||||||
{if (not $visibleAsAdmin) and (not $visibleAsTech)}
|
{let $visibleAsDomainAbuse: $item['visibleInDomainWhoisAsAbuse'] == true /}
|
||||||
|
{if (not $visibleAsAdmin) and (not $visibleAsTech) and (not $visibleAsDomainAbuse)}
|
||||||
<span class="{css whois-not-visible}">Not visible in WHOIS</span>
|
<span class="{css whois-not-visible}">Not visible in WHOIS</span>
|
||||||
{else}
|
{else}
|
||||||
{if $visibleAsAdmin}Admin{/if}
|
{if $visibleAsAdmin}Registrar Admin{/if}
|
||||||
{if $visibleAsAdmin and $visibleAsTech},{sp}{/if}
|
{if $visibleAsAdmin and $visibleAsTech},{sp}{/if}
|
||||||
{if $visibleAsTech}Technical{/if}
|
{if $visibleAsTech}Registrar Technical{/if}
|
||||||
|
{if $visibleAsTech}
|
||||||
|
{if $visibleAsDomainAbuse},{sp}{/if}
|
||||||
|
{elseif $visibleAsAdmin and $visibleAsDomainAbuse},{sp}{/if}
|
||||||
|
{if $visibleAsDomainAbuse}Domain Abuse{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/template}
|
{/template}
|
||||||
|
|
||||||
|
@ -231,31 +238,48 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr><td colspan="2"><hr></tr>
|
<tr><td colspan="2"><hr></tr>
|
||||||
{call .whoisVisibleRadios_}
|
{call .whoisVisibleRadios_}
|
||||||
{param description: 'Show in WHOIS as Admin contact' /}
|
{param description: 'Show in Registrar WHOIS record as Admin contact' /}
|
||||||
{param fieldName: $namePrefix + 'visibleInWhoisAsAdmin' /}
|
{param fieldName: $namePrefix + 'visibleInWhoisAsAdmin' /}
|
||||||
{param visible: $item['visibleInWhoisAsAdmin'] == true /}
|
{param visible: $item['visibleInWhoisAsAdmin'] == true /}
|
||||||
{/call}
|
{/call}
|
||||||
{call .whoisVisibleRadios_}
|
{call .whoisVisibleRadios_}
|
||||||
{param description: 'Show in WHOIS as Technical contact' /}
|
{param description: 'Show in Registrar WHOIS record as Technical contact' /}
|
||||||
{param fieldName: $namePrefix + 'visibleInWhoisAsTech' /}
|
{param fieldName: $namePrefix + 'visibleInWhoisAsTech' /}
|
||||||
{param visible: $item['visibleInWhoisAsTech'] == true /}
|
{param visible: $item['visibleInWhoisAsTech'] == true /}
|
||||||
{/call}
|
{/call}
|
||||||
|
{call .whoisVisibleRadios_}
|
||||||
|
{param description:
|
||||||
|
'Show Phone and Email in Domain WHOIS Record as Registrar Abuse Contact' +
|
||||||
|
' (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.'
|
||||||
|
/}
|
||||||
|
{param fieldName: $namePrefix + 'visibleInDomainWhoisAsAbuse' /}
|
||||||
|
{param visible: $item['visibleInDomainWhoisAsAbuse'] == true /}
|
||||||
|
{/call}
|
||||||
{/template}
|
{/template}
|
||||||
|
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
{template .whoisVisibleRadios_ private="true"}
|
{template .whoisVisibleRadios_ private="true"}
|
||||||
{@param description: string}
|
{@param description: string}
|
||||||
|
{@param? note: string}
|
||||||
{@param fieldName: string}
|
{@param fieldName: string}
|
||||||
{@param visible: bool}
|
{@param visible: bool}
|
||||||
<tr class="{css kd-settings-pane-section}">
|
<tr class="{css kd-settings-pane-section}">
|
||||||
<td>
|
<td>
|
||||||
<label for="{$fieldName}">{$description}</label>
|
<label for="{$fieldName}">{$description}</label>
|
||||||
|
{if $note}
|
||||||
|
<span class="{css description}">{$note}</span>
|
||||||
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td class="{css setting}">
|
<td class="{css setting}">
|
||||||
<label for="{$fieldName}">
|
<label for="{$fieldName}">
|
||||||
<input
|
<input
|
||||||
name="{$fieldName}"
|
name="{$fieldName}"
|
||||||
|
id="{$fieldName}.true"
|
||||||
type="radio"
|
type="radio"
|
||||||
value="true"
|
value="true"
|
||||||
{if $visible} checked{/if}>{sp}Yes
|
{if $visible} checked{/if}>{sp}Yes
|
||||||
|
@ -263,6 +287,7 @@
|
||||||
<label for="{$fieldName}">
|
<label for="{$fieldName}">
|
||||||
<input
|
<input
|
||||||
name="{$fieldName}"
|
name="{$fieldName}"
|
||||||
|
id="{$fieldName}.false"
|
||||||
type="radio"
|
type="radio"
|
||||||
value="false"
|
value="false"
|
||||||
{if not $visible} checked{/if}>{sp}No
|
{if not $visible} checked{/if}>{sp}No
|
||||||
|
|
|
@ -142,7 +142,7 @@ function testItemEditButtons() {
|
||||||
function testItemEdit() {
|
function testItemEdit() {
|
||||||
testItemView();
|
testItemView();
|
||||||
registry.testing.click($('reg-app-btn-edit'));
|
registry.testing.click($('reg-app-btn-edit'));
|
||||||
document.forms.namedItem('item').elements['contacts[0].name'].value = 'bob';
|
$('contacts[0].name').setAttribute('value', 'bob');
|
||||||
registry.testing.click($('reg-app-btn-save'));
|
registry.testing.click($('reg-app-btn-save'));
|
||||||
testContact.name = 'bob';
|
testContact.name = 'bob';
|
||||||
registry.testing.assertReqMockRsp(
|
registry.testing.assertReqMockRsp(
|
||||||
|
@ -248,6 +248,36 @@ function testOneOfManyUpdate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function testDomainWhoisAbuseContactOverride() {
|
||||||
|
registry.registrar.ConsoleTestUtil.visit(test, {
|
||||||
|
path: 'contact-settings/test@example.com',
|
||||||
|
xsrfToken: test.testXsrfToken,
|
||||||
|
testClientId: test.testClientId
|
||||||
|
});
|
||||||
|
var oldDomainWhoisAbuseContact = createTestContact('old@asdf.com');
|
||||||
|
oldDomainWhoisAbuseContact.visibleInDomainWhoisAsAbuse = true;
|
||||||
|
var testContacts = [oldDomainWhoisAbuseContact, testContact];
|
||||||
|
registry.testing.assertReqMockRsp(
|
||||||
|
test.testXsrfToken, '/registrar-settings', {op: 'read', args: {}},
|
||||||
|
{status: 'SUCCESS', message: 'OK', results: [{contacts: testContacts}]});
|
||||||
|
// Edit testContact.
|
||||||
|
registry.testing.click($('reg-app-btn-edit'));
|
||||||
|
$('contacts[1].visibleInDomainWhoisAsAbuse.true')
|
||||||
|
.setAttribute('checked', 'checked');
|
||||||
|
$('contacts[1].visibleInDomainWhoisAsAbuse.false').removeAttribute('checked');
|
||||||
|
registry.testing.click($('reg-app-btn-save'));
|
||||||
|
|
||||||
|
// Should save them all back, and flip the old abuse contact's visibility
|
||||||
|
// boolean.
|
||||||
|
testContact.visibleInDomainWhoisAsAbuse = true;
|
||||||
|
oldDomainWhoisAbuseContact.visibleInDomainWhoisAsAbuse = false;
|
||||||
|
registry.testing.assertReqMockRsp(
|
||||||
|
test.testXsrfToken, '/registrar-settings',
|
||||||
|
{op: 'update', args: {contacts: testContacts, readonly: false}},
|
||||||
|
{status: 'SUCCESS', message: 'OK', results: [{contacts: testContacts}]});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function testDelete() {
|
function testDelete() {
|
||||||
registry.registrar.ConsoleTestUtil.visit(test, {
|
registry.registrar.ConsoleTestUtil.visit(test, {
|
||||||
path: 'contact-settings/test@example.com',
|
path: 'contact-settings/test@example.com',
|
||||||
|
@ -305,6 +335,7 @@ function createTestContact(opt_email) {
|
||||||
faxNumber: '+1.2345551234',
|
faxNumber: '+1.2345551234',
|
||||||
visibleInWhoisAsAdmin: false,
|
visibleInWhoisAsAdmin: false,
|
||||||
visibleInWhoisAsTech: false,
|
visibleInWhoisAsTech: false,
|
||||||
|
visibleInDomainWhoisAsAbuse: false,
|
||||||
types: 'ADMIN'
|
types: 'ADMIN'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -318,6 +349,7 @@ function createTestContact(opt_email) {
|
||||||
function simulateJsonForContact(contact) {
|
function simulateJsonForContact(contact) {
|
||||||
contact.visibleInWhoisAsAdmin = contact.visibleInWhoisAsAdmin == 'true';
|
contact.visibleInWhoisAsAdmin = contact.visibleInWhoisAsAdmin == 'true';
|
||||||
contact.visibleInWhoisAsTech = contact.visibleInWhoisAsTech == 'true';
|
contact.visibleInWhoisAsTech = contact.visibleInWhoisAsTech == 'true';
|
||||||
|
contact.visibleInDomainWhoisAsAbuse = contact.visibleInDomainWhoisAsAbuse == 'true';
|
||||||
contact.types = '';
|
contact.types = '';
|
||||||
for (var tNdx in contact.type) {
|
for (var tNdx in contact.type) {
|
||||||
if (contact.type[tNdx]) {
|
if (contact.type[tNdx]) {
|
||||||
|
|
|
@ -128,4 +128,55 @@ public class ContactSettingsTest extends RegistrarSettingsActionTestCase {
|
||||||
assertThat(response).containsEntry("message", "Please provide a phone number for at least one "
|
assertThat(response).containsEntry("message", "Please provide a phone number for at least one "
|
||||||
+ RegistrarContact.Type.TECH.getDisplayName() + " contact");
|
+ RegistrarContact.Type.TECH.getDisplayName() + " contact");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPost_updateContacts_cannotRemoveWhoisAbuseContact_error() throws Exception {
|
||||||
|
// First make the contact's info visible in whois as abuse contact info.
|
||||||
|
Registrar registrar = Registrar.loadByClientId(CLIENT_ID);
|
||||||
|
RegistrarContact rc =
|
||||||
|
AppEngineRule.makeRegistrarContact2()
|
||||||
|
.asBuilder()
|
||||||
|
.setVisibleInDomainWhoisAsAbuse(true)
|
||||||
|
.build();
|
||||||
|
// Lest we anger the timestamp inversion bug.
|
||||||
|
persistResource(registrar);
|
||||||
|
persistSimpleResource(rc);
|
||||||
|
|
||||||
|
// Now try to remove the contact.
|
||||||
|
rc = rc.asBuilder().setVisibleInDomainWhoisAsAbuse(false).build();
|
||||||
|
Map<String, Object> reqJson = registrar.toJsonMap();
|
||||||
|
reqJson.put("contacts", ImmutableList.of(rc.toJsonMap()));
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(ImmutableMap.of("op", "update", "args", reqJson));
|
||||||
|
assertThat(response).containsEntry("status", "ERROR");
|
||||||
|
assertThat(response)
|
||||||
|
.containsEntry(
|
||||||
|
"message", "An abuse contact visible in domain WHOIS query must be designated");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPost_updateContacts_whoisAbuseContactMustHavePhoneNumber_error()
|
||||||
|
throws Exception {
|
||||||
|
// First make the contact's info visible in whois as abuse contact info.
|
||||||
|
Registrar registrar = Registrar.loadByClientId(CLIENT_ID);
|
||||||
|
RegistrarContact rc =
|
||||||
|
AppEngineRule.makeRegistrarContact2()
|
||||||
|
.asBuilder()
|
||||||
|
.setVisibleInDomainWhoisAsAbuse(true)
|
||||||
|
.build();
|
||||||
|
// Lest we anger the timestamp inversion bug.
|
||||||
|
persistResource(registrar);
|
||||||
|
persistSimpleResource(rc);
|
||||||
|
|
||||||
|
// Now try to set the phone number to null.
|
||||||
|
rc = rc.asBuilder().setPhoneNumber(null).build();
|
||||||
|
Map<String, Object> reqJson = registrar.toJsonMap();
|
||||||
|
reqJson.put("contacts", ImmutableList.of(rc.toJsonMap()));
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(ImmutableMap.of("op", "update", "args", reqJson));
|
||||||
|
assertThat(response).containsEntry("status", "ERROR");
|
||||||
|
assertThat(response)
|
||||||
|
.containsEntry(
|
||||||
|
"message", "The abuse contact visible in domain WHOIS query must have a phone number");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue