Add abuse entity to registrar entities

From to the RDAP response profile:

2.4.5. Abuse Contact (email, phone) - an RDAP server MUST include an *entity*
with the *abuse* role within the registrar *entity* which MUST include *tel*
and *email*, and MAY include other members

Even though this is a MUST - this field will only be shown if the registrar has a *visible* abuse contact.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=242684303
This commit is contained in:
guyben 2019-04-09 09:46:21 -07:00 committed by jianglai
parent bf0d83585a
commit d1f833b9bc
3 changed files with 109 additions and 63 deletions

View file

@ -85,12 +85,15 @@ public class RdapJsonFormatter {
@Inject RdapJsonFormatter() {} @Inject RdapJsonFormatter() {}
/** /**
* What type of data to generate. Summary data includes only information about the object itself, * What type of data to generate.
* while full data includes associated items (e.g. for domains, full data includes the hosts, *
* contacts and history entries connected with the domain). Summary data is appropriate for search * <p>Summary data includes only information about the object itself, while full data includes
* queries which return many results, to avoid load on the system. According to the ICANN * associated items (e.g. for domains, full data includes the hosts, contacts and history entries
* operational profile, a remark must be attached to the returned object indicating that it * connected with the domain).
* includes only summary data. *
* <p>Summary data is appropriate for search queries which return many results, to avoid load on
* the system. According to the ICANN operational profile, a remark must be attached to the
* returned object indicating that it includes only summary data.
*/ */
public enum OutputDataType { public enum OutputDataType {
FULL, FULL,
@ -170,34 +173,33 @@ public class RdapJsonFormatter {
} }
/** Map of EPP status values to the RDAP equivalents. */ /** Map of EPP status values to the RDAP equivalents. */
private static final ImmutableMap<StatusValue, RdapStatus> statusToRdapStatusMap = private static final ImmutableMap<StatusValue, RdapStatus> STATUS_TO_RDAP_STATUS_MAP =
Maps.immutableEnumMap( new ImmutableMap.Builder<StatusValue, RdapStatus>()
new ImmutableMap.Builder<StatusValue, RdapStatus>() // RdapStatus.ADD_PERIOD not defined in our system
// RdapStatus.ADD_PERIOD not defined in our system // RdapStatus.AUTO_RENEW_PERIOD not defined in our system
// RdapStatus.AUTO_RENEW_PERIOD not defined in our system .put(StatusValue.CLIENT_DELETE_PROHIBITED, RdapStatus.CLIENT_DELETE_PROHIBITED)
.put(StatusValue.CLIENT_DELETE_PROHIBITED, RdapStatus.CLIENT_DELETE_PROHIBITED) .put(StatusValue.CLIENT_HOLD, RdapStatus.CLIENT_HOLD)
.put(StatusValue.CLIENT_HOLD, RdapStatus.CLIENT_HOLD) .put(StatusValue.CLIENT_RENEW_PROHIBITED, RdapStatus.CLIENT_RENEW_PROHIBITED)
.put(StatusValue.CLIENT_RENEW_PROHIBITED, RdapStatus.CLIENT_RENEW_PROHIBITED) .put(StatusValue.CLIENT_TRANSFER_PROHIBITED, RdapStatus.CLIENT_TRANSFER_PROHIBITED)
.put(StatusValue.CLIENT_TRANSFER_PROHIBITED, RdapStatus.CLIENT_TRANSFER_PROHIBITED) .put(StatusValue.CLIENT_UPDATE_PROHIBITED, RdapStatus.CLIENT_UPDATE_PROHIBITED)
.put(StatusValue.CLIENT_UPDATE_PROHIBITED, RdapStatus.CLIENT_UPDATE_PROHIBITED) .put(StatusValue.INACTIVE, RdapStatus.INACTIVE)
.put(StatusValue.INACTIVE, RdapStatus.INACTIVE) .put(StatusValue.LINKED, RdapStatus.ASSOCIATED)
.put(StatusValue.LINKED, RdapStatus.ASSOCIATED) .put(StatusValue.OK, RdapStatus.ACTIVE)
.put(StatusValue.OK, RdapStatus.ACTIVE) .put(StatusValue.PENDING_CREATE, RdapStatus.PENDING_CREATE)
.put(StatusValue.PENDING_CREATE, RdapStatus.PENDING_CREATE) .put(StatusValue.PENDING_DELETE, RdapStatus.PENDING_DELETE)
.put(StatusValue.PENDING_DELETE, RdapStatus.PENDING_DELETE) // RdapStatus.PENDING_RENEW not defined in our system
// RdapStatus.PENDING_RENEW not defined in our system // RdapStatus.PENDING_RESTORE not defined in our system
// RdapStatus.PENDING_RESTORE not defined in our system .put(StatusValue.PENDING_TRANSFER, RdapStatus.PENDING_TRANSFER)
.put(StatusValue.PENDING_TRANSFER, RdapStatus.PENDING_TRANSFER) .put(StatusValue.PENDING_UPDATE, RdapStatus.PENDING_UPDATE)
.put(StatusValue.PENDING_UPDATE, RdapStatus.PENDING_UPDATE) // RdapStatus.REDEMPTION_PERIOD not defined in our system
// RdapStatus.REDEMPTION_PERIOD not defined in our system // RdapStatus.RENEW_PERIOD not defined in our system
// RdapStatus.RENEW_PERIOD not defined in our system .put(StatusValue.SERVER_DELETE_PROHIBITED, RdapStatus.SERVER_DELETE_PROHIBITED)
.put(StatusValue.SERVER_DELETE_PROHIBITED, RdapStatus.SERVER_DELETE_PROHIBITED) .put(StatusValue.SERVER_HOLD, RdapStatus.SERVER_HOLD)
.put(StatusValue.SERVER_HOLD, RdapStatus.SERVER_HOLD) .put(StatusValue.SERVER_RENEW_PROHIBITED, RdapStatus.SERVER_RENEW_PROHIBITED)
.put(StatusValue.SERVER_RENEW_PROHIBITED, RdapStatus.SERVER_RENEW_PROHIBITED) .put(StatusValue.SERVER_TRANSFER_PROHIBITED, RdapStatus.SERVER_TRANSFER_PROHIBITED)
.put(StatusValue.SERVER_TRANSFER_PROHIBITED, RdapStatus.SERVER_TRANSFER_PROHIBITED) .put(StatusValue.SERVER_UPDATE_PROHIBITED, RdapStatus.SERVER_UPDATE_PROHIBITED)
.put(StatusValue.SERVER_UPDATE_PROHIBITED, RdapStatus.SERVER_UPDATE_PROHIBITED) // RdapStatus.TRANSFER_PERIOD not defined in our system
// RdapStatus.TRANSFER_PERIOD not defined in our system .build();
.build());
/** Role values specified in RFC 7483 § 10.2.4. */ /** Role values specified in RFC 7483 § 10.2.4. */
private enum RdapEntityRole { private enum RdapEntityRole {
@ -248,21 +250,20 @@ public class RdapJsonFormatter {
/** Map of EPP event values to the RDAP equivalents. */ /** Map of EPP event values to the RDAP equivalents. */
private static final ImmutableMap<HistoryEntry.Type, RdapEventAction> private static final ImmutableMap<HistoryEntry.Type, RdapEventAction>
historyEntryTypeToRdapEventActionMap = HISTORY_ENTRY_TYPE_TO_RDAP_EVENT_ACTION_MAP =
Maps.immutableEnumMap( new ImmutableMap.Builder<HistoryEntry.Type, RdapEventAction>()
new ImmutableMap.Builder<HistoryEntry.Type, RdapEventAction>() .put(HistoryEntry.Type.CONTACT_CREATE, RdapEventAction.REGISTRATION)
.put(HistoryEntry.Type.CONTACT_CREATE, RdapEventAction.REGISTRATION) .put(HistoryEntry.Type.CONTACT_DELETE, RdapEventAction.DELETION)
.put(HistoryEntry.Type.CONTACT_DELETE, RdapEventAction.DELETION) .put(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE, RdapEventAction.TRANSFER)
.put(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE, RdapEventAction.TRANSFER) .put(HistoryEntry.Type.DOMAIN_AUTORENEW, RdapEventAction.REREGISTRATION)
.put(HistoryEntry.Type.DOMAIN_AUTORENEW, RdapEventAction.REREGISTRATION) .put(HistoryEntry.Type.DOMAIN_CREATE, RdapEventAction.REGISTRATION)
.put(HistoryEntry.Type.DOMAIN_CREATE, RdapEventAction.REGISTRATION) .put(HistoryEntry.Type.DOMAIN_DELETE, RdapEventAction.DELETION)
.put(HistoryEntry.Type.DOMAIN_DELETE, RdapEventAction.DELETION) .put(HistoryEntry.Type.DOMAIN_RENEW, RdapEventAction.REREGISTRATION)
.put(HistoryEntry.Type.DOMAIN_RENEW, RdapEventAction.REREGISTRATION) .put(HistoryEntry.Type.DOMAIN_RESTORE, RdapEventAction.REINSTANTIATION)
.put(HistoryEntry.Type.DOMAIN_RESTORE, RdapEventAction.REINSTANTIATION) .put(HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE, RdapEventAction.TRANSFER)
.put(HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE, RdapEventAction.TRANSFER) .put(HistoryEntry.Type.HOST_CREATE, RdapEventAction.REGISTRATION)
.put(HistoryEntry.Type.HOST_CREATE, RdapEventAction.REGISTRATION) .put(HistoryEntry.Type.HOST_DELETE, RdapEventAction.DELETION)
.put(HistoryEntry.Type.HOST_DELETE, RdapEventAction.DELETION) .build();
.build());
private static final ImmutableList<String> CONFORMANCE_LIST = private static final ImmutableList<String> CONFORMANCE_LIST =
ImmutableList.of(RDAP_CONFORMANCE_LEVEL); ImmutableList.of(RDAP_CONFORMANCE_LEVEL);
@ -598,6 +599,8 @@ public class RdapJsonFormatter {
} }
ImmutableList.Builder<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>(); ImmutableList.Builder<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>();
builder.addAll(entities); builder.addAll(entities);
// TODO(b/130150723): we need to display the ABUSE contact for registrar object inside of Domain
// responses. Currently, we use summary for any "internal" registrar.
builder.add( builder.add(
makeRdapJsonForRegistrar( makeRdapJsonForRegistrar(
registrar.get(), registrar.get(),
@ -901,12 +904,12 @@ public class RdapJsonFormatter {
} }
// include the registrar contacts as subentities // include the registrar contacts as subentities
ImmutableList<ImmutableMap<String, Object>> registrarContacts = ImmutableList<ImmutableMap<String, Object>> registrarContacts =
registrar registrar.getContacts().stream()
.getContacts()
.stream()
.filter(RdapJsonFormatter::isVisible)
.map(registrarContact -> makeRdapJsonForRegistrarContact(registrarContact, null)) .map(registrarContact -> makeRdapJsonForRegistrarContact(registrarContact, null))
.filter(entity -> !entity.isEmpty())
.collect(toImmutableList()); .collect(toImmutableList());
// TODO(b/117242274): add a warning (severe?) log if registrar has no ABUSE contact, as having
// one is required by the RDAP response profile
if (!registrarContacts.isEmpty()) { if (!registrarContacts.isEmpty()) {
jsonBuilder.put("entities", registrarContacts); jsonBuilder.put("entities", registrarContacts);
} }
@ -930,12 +933,18 @@ public class RdapJsonFormatter {
/** /**
* Creates a JSON object for a {@link RegistrarContact}. * Creates a JSON object for a {@link RegistrarContact}.
* *
* <p>Returns an empty object if this contact shouldn't be visible (doesn't have a role).
*
* @param registrarContact the registrar contact for which the JSON object should be created * @param registrarContact the registrar contact for which the JSON object should be created
* @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the * @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the
* port43 field; if null, port43 is not added to the object * port43 field; if null, port43 is not added to the object
*/ */
static ImmutableMap<String, Object> makeRdapJsonForRegistrarContact( static ImmutableMap<String, Object> makeRdapJsonForRegistrarContact(
RegistrarContact registrarContact, @Nullable String whoisServer) { RegistrarContact registrarContact, @Nullable String whoisServer) {
ImmutableList<String> roles = makeRdapRoleList(registrarContact);
if (roles.isEmpty()) {
return ImmutableMap.of();
}
ImmutableMap.Builder<String, Object> jsonBuilder = new ImmutableMap.Builder<>(); ImmutableMap.Builder<String, Object> jsonBuilder = new ImmutableMap.Builder<>();
jsonBuilder.put("objectClassName", "entity"); jsonBuilder.put("objectClassName", "entity");
String gaeUserId = registrarContact.getGaeUserId(); String gaeUserId = registrarContact.getGaeUserId();
@ -943,7 +952,7 @@ public class RdapJsonFormatter {
jsonBuilder.put("handle", registrarContact.getGaeUserId()); jsonBuilder.put("handle", registrarContact.getGaeUserId());
} }
jsonBuilder.put("status", STATUS_LIST_ACTIVE); jsonBuilder.put("status", STATUS_LIST_ACTIVE);
jsonBuilder.put("roles", makeRdapRoleList(registrarContact)); jsonBuilder.put("roles", roles);
// Create the vCard. // Create the vCard.
ImmutableList.Builder<Object> vcardBuilder = new ImmutableList.Builder<>(); ImmutableList.Builder<Object> vcardBuilder = new ImmutableList.Builder<>();
vcardBuilder.add(VCARD_ENTRY_VERSION); vcardBuilder.add(VCARD_ENTRY_VERSION);
@ -988,6 +997,14 @@ public class RdapJsonFormatter {
/** /**
* Creates the list of RDAP roles for a registrar contact, using the visibleInWhoisAs* flags. * Creates the list of RDAP roles for a registrar contact, using the visibleInWhoisAs* flags.
*
* <p>Only contacts with a non-empty role list should be visible.
*
* <p>The RDAP response profile only mandates the "abuse" entity:
*
* <p>2.4.5. Abuse Contact (email, phone) - an RDAP server MUST include an *entity* with the
* *abuse* role within the registrar *entity* which MUST include *tel* and *email*, and MAY
* include other members
*/ */
private static ImmutableList<String> makeRdapRoleList(RegistrarContact registrarContact) { private static ImmutableList<String> makeRdapRoleList(RegistrarContact registrarContact) {
ImmutableList.Builder<String> rolesBuilder = new ImmutableList.Builder<>(); ImmutableList.Builder<String> rolesBuilder = new ImmutableList.Builder<>();
@ -997,15 +1014,12 @@ public class RdapJsonFormatter {
if (registrarContact.getVisibleInWhoisAsTech()) { if (registrarContact.getVisibleInWhoisAsTech()) {
rolesBuilder.add(RdapEntityRole.TECH.rfc7483String); rolesBuilder.add(RdapEntityRole.TECH.rfc7483String);
} }
if (registrarContact.getVisibleInDomainWhoisAsAbuse()) {
rolesBuilder.add(RdapEntityRole.ABUSE.rfc7483String);
}
return rolesBuilder.build(); return rolesBuilder.build();
} }
/** Checks whether the registrar contact should be visible (because it has visible roles). */
private static boolean isVisible(RegistrarContact registrarContact) {
return registrarContact.getVisibleInWhoisAsAdmin()
|| registrarContact.getVisibleInWhoisAsTech();
}
/** /**
* Creates an event list for a domain, host or contact resource. * Creates an event list for a domain, host or contact resource.
*/ */
@ -1025,7 +1039,7 @@ public class RdapJsonFormatter {
for (HistoryEntry historyEntry : for (HistoryEntry historyEntry :
ofy().load().type(HistoryEntry.class).ancestor(resource).order("modificationTime")) { ofy().load().type(HistoryEntry.class).ancestor(resource).order("modificationTime")) {
RdapEventAction rdapEventAction = RdapEventAction rdapEventAction =
historyEntryTypeToRdapEventActionMap.get(historyEntry.getType()); HISTORY_ENTRY_TYPE_TO_RDAP_EVENT_ACTION_MAP.get(historyEntry.getType());
// Only save the historyEntries if this is a type we care about. // Only save the historyEntries if this is a type we care about.
if (rdapEventAction == null) { if (rdapEventAction == null) {
continue; continue;
@ -1211,7 +1225,7 @@ public class RdapJsonFormatter {
Stream<RdapStatus> stream = Stream<RdapStatus> stream =
statusValues statusValues
.stream() .stream()
.map(status -> statusToRdapStatusMap.getOrDefault(status, RdapStatus.OBSCURED)); .map(status -> STATUS_TO_RDAP_STATUS_MAP.getOrDefault(status, RdapStatus.OBSCURED));
if (isRedacted) { if (isRedacted) {
stream = Streams.concat(stream, Stream.of(RdapStatus.REMOVED)); stream = Streams.concat(stream, Stream.of(RdapStatus.REMOVED));
} }

View file

@ -54,6 +54,22 @@
], ],
"entities" : "entities" :
[ [
{
"objectClassName" : "entity",
"status" : ["active"],
"roles" : ["abuse"],
"vcardArray" :
[
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", "Jake Doe"],
["tel", {"type" : ["voice"]}, "uri", "tel:+1.2125551216"],
["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551216"],
["email", {}, "text", "jakedoe@example.com"]
]
],
},
{ {
"objectClassName" : "entity", "objectClassName" : "entity",
"status" : ["active"], "status" : ["active"],

View file

@ -38,6 +38,22 @@
], ],
"entities" : "entities" :
[ [
{
"objectClassName" : "entity",
"status" : ["active"],
"roles" : ["abuse"],
"vcardArray" :
[
"vcard",
[
["version", {}, "text", "4.0"],
["fn", {}, "text", "Jake Doe"],
["tel", {"type" : ["voice"]}, "uri", "tel:+1.2125551216"],
["tel", {"type" : ["fax"]}, "uri", "tel:+1.2125551216"],
["email", {}, "text", "jakedoe@example.com"]
]
],
},
{ {
"objectClassName" : "entity", "objectClassName" : "entity",
"status" : ["active"], "status" : ["active"],