diff --git a/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java b/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java index 364949188..936cb0ef6 100644 --- a/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java +++ b/core/src/main/java/google/registry/rdap/RdapIcannStandardInformation.java @@ -149,14 +149,6 @@ public class RdapIcannStandardInformation { .build()) .build(); - /** - * String that replaces GDPR redacted values. - * - *
GTLD Registration Data Temp Spec 17may18, Appendix A, 2.2: Fields required to be "redacted" - * MUST privide in the value section text similar to "REDACTED FOR PRIVACY" - */ - static final String CONTACT_REDACTED_VALUE = "REDACTED FOR PRIVACY"; - /** * Included in ALL contact responses, even if the user is authorized. * diff --git a/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java b/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java index f61efb3ee..aa10270a3 100644 --- a/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java +++ b/core/src/main/java/google/registry/rdap/RdapJsonFormatter.java @@ -21,7 +21,6 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap; import static google.registry.model.EppResourceUtils.isLinked; import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm; -import static google.registry.rdap.RdapIcannStandardInformation.CONTACT_REDACTED_VALUE; import static google.registry.util.CollectionUtils.union; import com.google.common.annotations.VisibleForTesting; @@ -39,7 +38,6 @@ import com.google.gson.JsonArray; import google.registry.config.RegistryConfig.Config; import google.registry.model.EppResource; import google.registry.model.contact.Contact; -import google.registry.model.contact.ContactAddress; import google.registry.model.contact.ContactPhoneNumber; import google.registry.model.contact.PostalInfo; import google.registry.model.domain.DesignatedContact; @@ -519,69 +517,68 @@ public class RdapJsonFormatter { boolean isAuthorized = rdapAuthorization.isAuthorizedForRegistrar(contact.getCurrentSponsorRegistrarId()); - // ROID needs to be redacted if we aren't authorized, so we can't have a self-link for - // unauthorized users + VcardArray.Builder vcardBuilder = VcardArray.builder(); + if (isAuthorized) { - contactBuilder.linksBuilder().add(makeSelfLink("entity", contact.getRepoId())); - } - - // Only show the "summary data remark" if the user is authorized to see this data - because - // unauthorized users don't have a self link meaning they can't navigate to the full data. - if (outputDataType != OutputDataType.FULL && isAuthorized) { - contactBuilder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); - } - - // GTLD Registration Data Temp Spec 17may18, Appendix A, 2.3, 2.4 and RDAP Response Profile - // 2.7.4.1, 2.7.4.2 - the following fields must be redacted: - // for REGISTRANT: - // handle (ROID), FN (name), TEL (telephone/fax and extension), street, city, postal code - // for ADMIN, TECH: - // handle (ROID), FN (name), TEL (telephone/fax and extension), Organization, street, city, - // state/province, postal code, country - // - // Note that in theory we have to show the Organization and state/province and country for the - // REGISTRANT. For now, we won't do that until we make sure it's really OK for GDPR - // - if (!isAuthorized) { + fillRdapContactEntityWhenAuthorized(contactBuilder, vcardBuilder, contact, outputDataType); + } else { + // GTLD Registration Data Temp Spec 17may18, Appendix A, 2.3, 2.4 and RDAP Response Profile + // 2.7.4.1, 2.7.4.2 - the following fields must be redacted: + // for REGISTRANT: + // handle (ROID), FN (name), TEL (telephone/fax and extension), street, city, postal code + // for ADMIN, TECH: + // handle (ROID), FN (name), TEL (telephone/fax and extension), Organization, street, city, + // state/province, postal code, country + // + // Note that in theory we have to show the Organization and state/province and country for the + // REGISTRANT. For now, we won't do that until we make sure it's really OK for GDPR + // // RDAP Response Profile 2.7.4.3: if we redact values from the contact, we MUST include a // remark contactBuilder .remarksBuilder() .add(RdapIcannStandardInformation.CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK); - // to make sure we don't accidentally display data we shouldn't - we replace the - // contact with a safe resource. Then we can add any information we need (e.g. the - // Organization / state / country of the registrant), although we currently don't do that. - contact = - new Contact.Builder() - .setRepoId(CONTACT_REDACTED_VALUE) - .setVoiceNumber( - new ContactPhoneNumber.Builder().setPhoneNumber(CONTACT_REDACTED_VALUE).build()) - .setFaxNumber( - new ContactPhoneNumber.Builder().setPhoneNumber(CONTACT_REDACTED_VALUE).build()) - .setInternationalizedPostalInfo( - new PostalInfo.Builder() - .setName(CONTACT_REDACTED_VALUE) - .setOrg(CONTACT_REDACTED_VALUE) - .setType(PostalInfo.Type.INTERNATIONALIZED) - .setAddress( - new ContactAddress.Builder() - .setStreet(ImmutableList.of(CONTACT_REDACTED_VALUE)) - .setCity(CONTACT_REDACTED_VALUE) - .setState(CONTACT_REDACTED_VALUE) - .setZip(CONTACT_REDACTED_VALUE) - .setCountryCode("XX") - .build()) - .build()) - .build(); + contactBuilder.setHandle(""); + // The VCard format requires a "fn" entry even if it is empty (redacted) + vcardBuilder.add(Vcard.create("fn", "text", "")); } - // RDAP Response Profile 2.7.3 - we MUST provide a handle set with the ROID, subject to the - // redaction above. - contactBuilder.setHandle(contact.getRepoId()); + contactBuilder.setVcardArray(vcardBuilder.build()); + contactBuilder.rolesBuilder().addAll(roles); - // RDAP Response Profile doesn't mention status for contacts, so we only show it if we're both - // FULL and Authorized. - if (outputDataType == OutputDataType.FULL && isAuthorized) { + // RDAP Response Profile 2.7.5.1, 2.7.5.3: + // email MUST be omitted, and we MUST have a Remark saying so + contactBuilder + .remarksBuilder() + .add(RdapIcannStandardInformation.CONTACT_EMAIL_REDACTED_FOR_DOMAIN); + + if (outputDataType != OutputDataType.INTERNAL) { + // Rdap Response Profile 2.7.6 must have "last update of RDAP database" response. But this is + // only for direct query responses and not for internal objects. I'm not sure why it's in that + // section at all... + contactBuilder.setLastUpdateOfRdapDatabaseEvent( + Event.builder() + .setEventAction(EventAction.LAST_UPDATE_OF_RDAP_DATABASE) + .setEventDate(getRequestTime()) + .build()); + } + return contactBuilder.build(); + } + + private void fillRdapContactEntityWhenAuthorized( + RdapContactEntity.Builder contactBuilder, + VcardArray.Builder vcardBuilder, + Contact contact, + OutputDataType outputDataType) { + // ROID needs to be redacted if we aren't authorized, so we can't have a self-link for + // unauthorized users + contactBuilder.linksBuilder().add(makeSelfLink("entity", contact.getRepoId())); + // RDAP Response Profile 2.7.3 - we MUST provide a handle set with the ROID, subject to + // redaction. + contactBuilder.setHandle(contact.getRepoId()); + if (outputDataType.equals(OutputDataType.FULL)) { + // RDAP Response Profile doesn't mention status for contacts, so we only show it if we're both + // FULL and Authorized. contactBuilder .statusBuilder() .addAll( @@ -591,12 +588,18 @@ public class RdapJsonFormatter { : contact.getStatusValues(), false, contact.getDeletionTime().isBefore(getRequestTime()))); + // If we are outputting all data (not just summary data), also add events taken from the + // history entries. This isn't strictly required. + // + // We also only add it for authorized users because millisecond times can fingerprint a user + // just as much as the handle can. + contactBuilder.eventsBuilder().addAll(makeOptionalEvents(contact)); + } else { + // Only show the "summary data remark" if the user is authorized to see this data - because + // unauthorized users don't have a self link meaning they can't navigate to the full data. + contactBuilder.remarksBuilder().add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK); } - - contactBuilder.rolesBuilder().addAll(roles); - - VcardArray.Builder vcardBuilder = VcardArray.builder(); - // Adding the VCard members subject to the redaction above. + // Adding the VCard members when not redacted. // // RDAP Response Profile 2.7.3 - we MUST have FN, ADR, TEL, EMAIL. // @@ -622,33 +625,6 @@ public class RdapJsonFormatter { if (faxPhoneNumber != null) { vcardBuilder.add(makePhoneEntry(PHONE_TYPE_FAX, makePhoneString(faxPhoneNumber))); } - // RDAP Response Profile 2.7.5.1, 2.7.5.3: - // email MUST be omitted, and we MUST have a Remark saying so - contactBuilder - .remarksBuilder() - .add(RdapIcannStandardInformation.CONTACT_EMAIL_REDACTED_FOR_DOMAIN); - contactBuilder.setVcardArray(vcardBuilder.build()); - - if (outputDataType != OutputDataType.INTERNAL) { - // Rdap Response Profile 2.7.6 must have "last update of RDAP database" response. But this is - // only for direct query responses and not for internal objects. I'm not sure why it's in that - // section at all... - contactBuilder.setLastUpdateOfRdapDatabaseEvent( - Event.builder() - .setEventAction(EventAction.LAST_UPDATE_OF_RDAP_DATABASE) - .setEventDate(getRequestTime()) - .build()); - } - - // If we are outputting all data (not just summary data), also add events taken from the history - // entries. This isn't strictly required. - // - // We also only add it for authorized users because millisecond times can fingerprint a user - // just as much as the handle can. - if (outputDataType == OutputDataType.FULL && isAuthorized) { - contactBuilder.eventsBuilder().addAll(makeOptionalEvents(contact)); - } - return contactBuilder.build(); } /** diff --git a/core/src/test/resources/google/registry/rdap/rdap_associated_contact_no_personal_data.json b/core/src/test/resources/google/registry/rdap/rdap_associated_contact_no_personal_data.json index 6d79228b3..e50f65483 100644 --- a/core/src/test/resources/google/registry/rdap/rdap_associated_contact_no_personal_data.json +++ b/core/src/test/resources/google/registry/rdap/rdap_associated_contact_no_personal_data.json @@ -5,7 +5,7 @@ "icann_rdap_technical_implementation_guide_0" ], "objectClassName" : "entity", - "handle" : "REDACTED FOR PRIVACY", + "handle" : "", "events": [ { "eventAction": "last update of RDAP database", @@ -15,25 +15,8 @@ "vcardArray": [ "vcard", [ - ["version",{},"text","4.0"], - ["fn",{},"text","REDACTED FOR PRIVACY"], - ["org",{},"text","REDACTED FOR PRIVACY"], - [ - "adr", - {}, - "text", - [ - "", - "", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "XX" - ] - ], - ["tel",{"type":["voice"]},"uri","tel:REDACTED FOR PRIVACY"], - ["tel",{"type":["fax"]},"uri","tel:REDACTED FOR PRIVACY"] + ["version", {}, "text", "4.0"], + ["fn", {}, "text", ""] ] ], "remarks": [ diff --git a/core/src/test/resources/google/registry/rdap/rdap_contact_no_personal_data_with_remark.json b/core/src/test/resources/google/registry/rdap/rdap_contact_no_personal_data_with_remark.json index db0b27b92..d432adf60 100644 --- a/core/src/test/resources/google/registry/rdap/rdap_contact_no_personal_data_with_remark.json +++ b/core/src/test/resources/google/registry/rdap/rdap_contact_no_personal_data_with_remark.json @@ -5,7 +5,7 @@ "icann_rdap_technical_implementation_guide_0" ], "objectClassName" : "entity", - "handle" : "REDACTED FOR PRIVACY", + "handle" : "", "events": [ { "eventAction": "last update of RDAP database", @@ -15,25 +15,8 @@ "vcardArray": [ "vcard", [ - ["version",{},"text","4.0"], - ["fn",{},"text","REDACTED FOR PRIVACY"], - ["org",{},"text","REDACTED FOR PRIVACY"], - [ - "adr", - {}, - "text", - [ - "", - "", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "XX" - ] - ], - ["tel",{"type":["voice"]},"uri","tel:REDACTED FOR PRIVACY"], - ["tel",{"type":["fax"]},"uri","tel:REDACTED FOR PRIVACY"] + ["version", {}, "text", "4.0"], + ["fn", {}, "text", ""] ] ], "remarks": [ diff --git a/core/src/test/resources/google/registry/rdap/rdap_domain_no_contacts_with_remark.json b/core/src/test/resources/google/registry/rdap/rdap_domain_no_contacts_with_remark.json index ff257994f..02ec7399f 100644 --- a/core/src/test/resources/google/registry/rdap/rdap_domain_no_contacts_with_remark.json +++ b/core/src/test/resources/google/registry/rdap/rdap_domain_no_contacts_with_remark.json @@ -150,7 +150,7 @@ }, { "objectClassName": "entity", - "handle": "REDACTED FOR PRIVACY", + "handle": "", "roles":["administrative"], "remarks": [ { @@ -180,18 +180,14 @@ "vcard", [ ["version", {}, "text", "4.0"], - ["fn", {}, "text", "REDACTED FOR PRIVACY"], - ["org", {}, "text", "REDACTED FOR PRIVACY"], - ["adr", {}, "text", ["", "", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "XX"]], - ["tel", {"type":["voice"]}, "uri", "tel:REDACTED FOR PRIVACY"], - ["tel", {"type":["fax"]}, "uri", "tel:REDACTED FOR PRIVACY"] + ["fn", {}, "text", ""] ] ] }, { "objectClassName":"entity", - "handle":"REDACTED FOR PRIVACY", + "handle":"", "remarks":[ { "title":"REDACTED FOR PRIVACY", @@ -221,18 +217,14 @@ "vcard", [ ["version", {}, "text", "4.0"], - ["fn", {}, "text", "REDACTED FOR PRIVACY"], - ["org", {}, "text", "REDACTED FOR PRIVACY"], - ["adr", {}, "text", ["", "", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "XX"]], - ["tel", {"type":["voice"]}, "uri", "tel:REDACTED FOR PRIVACY"], - ["tel", {"type":["fax"]}, "uri", "tel:REDACTED FOR PRIVACY"] + ["fn", {}, "text", ""] ] ] }, { "objectClassName":"entity", - "handle":"REDACTED FOR PRIVACY", + "handle":"", "remarks":[ { "title":"REDACTED FOR PRIVACY", @@ -262,11 +254,7 @@ "vcard", [ ["version", {}, "text", "4.0"], - ["fn", {}, "text", "REDACTED FOR PRIVACY"], - ["org", {}, "text", "REDACTED FOR PRIVACY"], - ["adr", {}, "text", ["", "", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "XX"]], - ["tel", {"type":["voice"]}, "uri", "tel:REDACTED FOR PRIVACY"], - ["tel", {"type":["fax"]}, "uri", "tel:REDACTED FOR PRIVACY"] + ["fn", {}, "text", ""] ] ] } diff --git a/core/src/test/resources/google/registry/rdap/rdap_domain_unicode_no_contacts_with_remark.json b/core/src/test/resources/google/registry/rdap/rdap_domain_unicode_no_contacts_with_remark.json index 4a2fe2301..8d1bdaace 100644 --- a/core/src/test/resources/google/registry/rdap/rdap_domain_unicode_no_contacts_with_remark.json +++ b/core/src/test/resources/google/registry/rdap/rdap_domain_unicode_no_contacts_with_remark.json @@ -153,7 +153,7 @@ }, { "objectClassName": "entity", - "handle": "REDACTED FOR PRIVACY", + "handle": "", "roles":["administrative"], "remarks": [ { @@ -183,18 +183,14 @@ "vcard", [ ["version", {}, "text", "4.0"], - ["fn", {}, "text", "REDACTED FOR PRIVACY"], - ["org", {}, "text", "REDACTED FOR PRIVACY"], - ["adr", {}, "text", ["", "", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "XX"]], - ["tel", {"type":["voice"]}, "uri", "tel:REDACTED FOR PRIVACY"], - ["tel", {"type":["fax"]}, "uri", "tel:REDACTED FOR PRIVACY"] + ["fn", {}, "text", ""] ] ] }, { "objectClassName":"entity", - "handle":"REDACTED FOR PRIVACY", + "handle":"", "remarks":[ { "title":"REDACTED FOR PRIVACY", @@ -224,18 +220,13 @@ "vcard", [ ["version", {}, "text", "4.0"], - ["fn", {}, "text", "REDACTED FOR PRIVACY"], - ["org", {}, "text", "REDACTED FOR PRIVACY"], - ["adr", {}, "text", ["", "", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "XX"]], - ["tel", {"type":["voice"]}, "uri", "tel:REDACTED FOR PRIVACY"], - ["tel", {"type":["fax"]}, "uri", "tel:REDACTED FOR PRIVACY"] - ] + ["fn", {}, "text", ""] ] ] }, { "objectClassName":"entity", - "handle":"REDACTED FOR PRIVACY", + "handle":"", "remarks":[ { "title":"REDACTED FOR PRIVACY", @@ -265,11 +256,7 @@ "vcard", [ ["version", {}, "text", "4.0"], - ["fn", {}, "text", "REDACTED FOR PRIVACY"], - ["org", {}, "text", "REDACTED FOR PRIVACY"], - ["adr", {}, "text", ["", "", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "REDACTED FOR PRIVACY", "XX"]], - ["tel", {"type":["voice"]}, "uri", "tel:REDACTED FOR PRIVACY"], - ["tel", {"type":["fax"]}, "uri", "tel:REDACTED FOR PRIVACY"] + ["fn", {}, "text", ""] ] ] } diff --git a/core/src/test/resources/google/registry/rdap/rdapjson_domain_logged_out.json b/core/src/test/resources/google/registry/rdap/rdapjson_domain_logged_out.json index 18c3e2028..32c8f89f0 100644 --- a/core/src/test/resources/google/registry/rdap/rdapjson_domain_logged_out.json +++ b/core/src/test/resources/google/registry/rdap/rdapjson_domain_logged_out.json @@ -137,7 +137,7 @@ }, { "objectClassName": "entity", - "handle": "REDACTED FOR PRIVACY", + "handle": "", "roles": ["administrative"], "remarks": [ { @@ -166,31 +166,14 @@ "vcardArray": [ "vcard", [ - ["version",{},"text","4.0"], - ["fn",{},"text","REDACTED FOR PRIVACY"], - ["org",{},"text","REDACTED FOR PRIVACY"], - [ - "adr", - {}, - "text", - [ - "", - "", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "XX" - ] - ], - ["tel",{"type":["voice"]},"uri","tel:REDACTED FOR PRIVACY"], - ["tel",{"type":["fax"]},"uri","tel:REDACTED FOR PRIVACY"] + ["version", {}, "text", "4.0"], + ["fn", {}, "text", ""] ] ] }, { "objectClassName": "entity", - "handle": "REDACTED FOR PRIVACY", + "handle": "", "roles": ["technical"], "remarks": [ { @@ -219,31 +202,14 @@ "vcardArray": [ "vcard", [ - ["version",{},"text","4.0"], - ["fn",{},"text","REDACTED FOR PRIVACY"], - ["org",{},"text","REDACTED FOR PRIVACY"], - [ - "adr", - {}, - "text", - [ - "", - "", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "XX" - ] - ], - ["tel",{"type":["voice"]},"uri","tel:REDACTED FOR PRIVACY"], - ["tel",{"type":["fax"]},"uri","tel:REDACTED FOR PRIVACY"] + ["version", {}, "text", "4.0"], + ["fn", {}, "text", ""] ] ] }, { "objectClassName": "entity", - "handle": "REDACTED FOR PRIVACY", + "handle": "", "roles": ["registrant"], "remarks": [ { @@ -272,25 +238,8 @@ "vcardArray": [ "vcard", [ - ["version",{},"text","4.0"], - ["fn",{},"text","REDACTED FOR PRIVACY"], - ["org",{},"text","REDACTED FOR PRIVACY"], - [ - "adr", - {}, - "text", - [ - "", - "", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "XX" - ] - ], - ["tel",{"type":["voice"]},"uri","tel:REDACTED FOR PRIVACY"], - ["tel",{"type":["fax"]},"uri","tel:REDACTED FOR PRIVACY"] + ["version", {}, "text", "4.0"], + ["fn", {}, "text", ""] ] ] } diff --git a/core/src/test/resources/google/registry/rdap/rdapjson_registrant_logged_out.json b/core/src/test/resources/google/registry/rdap/rdapjson_registrant_logged_out.json index 0905bdae5..ca39ed198 100644 --- a/core/src/test/resources/google/registry/rdap/rdapjson_registrant_logged_out.json +++ b/core/src/test/resources/google/registry/rdap/rdapjson_registrant_logged_out.json @@ -1,6 +1,6 @@ { "objectClassName" : "entity", - "handle" : "REDACTED FOR PRIVACY", + "handle" : "", "roles" : ["registrant"], "events": [ { @@ -11,25 +11,8 @@ "vcardArray": [ "vcard", [ - ["version",{},"text","4.0"], - ["fn",{},"text","REDACTED FOR PRIVACY"], - ["org",{},"text","REDACTED FOR PRIVACY"], - [ - "adr", - {}, - "text", - [ - "", - "", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "REDACTED FOR PRIVACY", - "XX" - ] - ], - ["tel",{"type":["voice"]},"uri","tel:REDACTED FOR PRIVACY"], - ["tel",{"type":["fax"]},"uri","tel:REDACTED FOR PRIVACY"] + ["version", {}, "text", "4.0"], + ["fn", {}, "text", ""] ] ], "remarks": [