Avoid showing personal contact data in RDAP when not logged in

This CL changes the RDAP responses. When the requester asks for information about a domain, and is not logged in as the owning registrar, no contact information is shown. When the requester asks for information about a contact, and is not logged in as the owner registrar, the existence of the contact is shown, but not any personal data (the existence is shown to make things easier to test).

The login uses the same functionality as the registrar console.

For the most part, this CL does not include the necessary tests to make sure that data is not returned when not logged in. The CL is so large that I didn't want to burden it further. Those tests will be added in a follow-on CL.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=168022034
This commit is contained in:
mountford 2017-09-08 11:24:21 -07:00 committed by jianglai
parent e892a2f0fe
commit c85dc0c089
23 changed files with 853 additions and 266 deletions

View file

@ -12,6 +12,7 @@ java_library(
"//java/google/registry/model",
"//java/google/registry/request",
"//java/google/registry/request/auth",
"//java/google/registry/ui/server/registrar",
"//java/google/registry/util",
"//third_party/java/objectify:objectify-v4_1",
"@com_google_auto_value",

View file

@ -33,6 +33,7 @@ import com.google.re2j.Pattern;
import com.googlecode.objectify.cmd.Query;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.EppResource;
import google.registry.model.registrar.Registrar;
import google.registry.request.Action;
import google.registry.request.HttpException;
import google.registry.request.HttpException.BadRequestException;
@ -41,11 +42,15 @@ import google.registry.request.HttpException.UnprocessableEntityException;
import google.registry.request.RequestMethod;
import google.registry.request.RequestPath;
import google.registry.request.Response;
import google.registry.request.auth.AuthResult;
import google.registry.request.auth.UserAuthInfo;
import google.registry.ui.server.registrar.SessionUtils;
import google.registry.util.FormattingLogger;
import java.net.URI;
import java.net.URISyntaxException;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import org.json.simple.JSONValue;
/**
@ -66,9 +71,12 @@ public abstract class RdapActionBase implements Runnable {
private static final MediaType RESPONSE_MEDIA_TYPE = MediaType.create("application", "rdap+json");
@Inject HttpServletRequest request;
@Inject Response response;
@Inject @RequestMethod Action.Method requestMethod;
@Inject @RequestPath String requestPath;
@Inject AuthResult authResult;
@Inject SessionUtils sessionUtils;
@Inject RdapJsonFormatter rdapJsonFormatter;
@Inject @Config("rdapLinkBase") String rdapLinkBase;
@Inject @Config("rdapWhoisServer") @Nullable String rdapWhoisServer;
@ -142,6 +150,22 @@ public abstract class RdapActionBase implements Runnable {
}
}
Optional<String> getLoggedInClientId() {
if (!authResult.userAuthInfo().isPresent()) {
return Optional.<String>absent();
}
UserAuthInfo userAuthInfo = authResult.userAuthInfo().get();
if (!sessionUtils.checkRegistrarConsoleLogin(request, userAuthInfo)) {
return Optional.<String>absent();
}
String clientId = sessionUtils.getRegistrarClientId(request);
Optional<Registrar> registrar = Registrar.loadByClientIdCached(clientId);
if (!registrar.isPresent()) {
return Optional.<String>absent();
}
return Optional.of(clientId);
}
void validateDomainName(String name) {
try {
Optional<InternetDomainName> tld = findTldForName(InternetDomainName.from(name));

View file

@ -33,7 +33,7 @@ import org.joda.time.DateTime;
path = RdapDomainAction.PATH,
method = {GET, HEAD},
isPrefix = true,
auth = Auth.AUTH_PUBLIC_ANONYMOUS
auth = Auth.AUTH_PUBLIC
)
public class RdapDomainAction extends RdapActionBase {
@ -64,6 +64,12 @@ public class RdapDomainAction extends RdapActionBase {
throw new NotFoundException(pathSearchString + " not found");
}
return rdapJsonFormatter.makeRdapJsonForDomain(
domainResource, true, rdapLinkBase, rdapWhoisServer, now, OutputDataType.FULL);
domainResource,
true,
rdapLinkBase,
rdapWhoisServer,
now,
OutputDataType.FULL,
getLoggedInClientId());
}
}

View file

@ -65,7 +65,7 @@ import org.joda.time.DateTime;
@Action(
path = RdapDomainSearchAction.PATH,
method = {GET, HEAD},
auth = Auth.AUTH_PUBLIC_ANONYMOUS
auth = Auth.AUTH_PUBLIC
)
public class RdapDomainSearchAction extends RdapActionBase {
@ -375,11 +375,12 @@ public class RdapDomainSearchAction extends RdapActionBase {
List<DomainResource> domains, boolean isTruncated, DateTime now) {
OutputDataType outputDataType =
(domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL;
Optional<String> loggedInClientId = getLoggedInClientId();
ImmutableList.Builder<ImmutableMap<String, Object>> jsonBuilder = new ImmutableList.Builder<>();
for (DomainResource domain : domains) {
jsonBuilder.add(
rdapJsonFormatter.makeRdapJsonForDomain(
domain, false, rdapLinkBase, rdapWhoisServer, now, outputDataType));
domain, false, rdapLinkBase, rdapWhoisServer, now, outputDataType, loggedInClientId));
}
return RdapSearchResults.create(jsonBuilder.build(), isTruncated);
}

View file

@ -50,7 +50,7 @@ import org.joda.time.DateTime;
path = RdapEntityAction.PATH,
method = {GET, HEAD},
isPrefix = true,
auth = Auth.AUTH_PUBLIC_ANONYMOUS
auth = Auth.AUTH_PUBLIC
)
public class RdapEntityAction extends RdapActionBase {
@ -93,7 +93,8 @@ public class RdapEntityAction extends RdapActionBase {
rdapLinkBase,
rdapWhoisServer,
now,
OutputDataType.FULL);
OutputDataType.FULL,
getLoggedInClientId());
}
}
Long ianaIdentifier = Longs.tryParse(pathSearchString);

View file

@ -60,7 +60,7 @@ import org.joda.time.DateTime;
@Action(
path = RdapEntitySearchAction.PATH,
method = {GET, HEAD},
auth = Auth.AUTH_PUBLIC_ANONYMOUS
auth = Auth.AUTH_PUBLIC
)
public class RdapEntitySearchAction extends RdapActionBase {
@ -254,6 +254,7 @@ public class RdapEntitySearchAction extends RdapActionBase {
// There can be more results than our max size, partially because we have two pools to draw from
// (contacts and registrars), and partially because we try to fetch one more than the max size,
// so we can tell whether to display the truncation notification.
Optional<String> loggedInClientId = getLoggedInClientId();
List<ImmutableMap<String, Object>> jsonOutputList = new ArrayList<>();
for (ContactResource contact : contacts) {
if (jsonOutputList.size() >= rdapResultSetMaxSize) {
@ -268,7 +269,8 @@ public class RdapEntitySearchAction extends RdapActionBase {
rdapLinkBase,
rdapWhoisServer,
now,
outputDataType));
outputDataType,
loggedInClientId));
}
for (Registrar registrar : registrars) {
if (registrar.isActiveAndPubliclyVisible()) {

View file

@ -104,4 +104,24 @@ public class RdapIcannStandardInformation {
/** Truncation notice as a singleton list, for easy use. */
static final ImmutableList<ImmutableMap<String, Object>> TRUNCATION_NOTICES =
ImmutableList.of(TRUNCATED_RESULT_SET_NOTICE);
/** Included when the requester is not logged in as the owner of the domain being returned. */
static final ImmutableMap<String, Object> DOMAIN_CONTACTS_HIDDEN_DATA_REMARK =
ImmutableMap.<String, Object> of(
"title",
"Contacts Hidden",
"description",
ImmutableList.of("Domain contacts are visible only to the owning registrar."),
"type",
"object truncated due to unexplainable reasons");
/** Included when requester is not logged in as the owner of the contact being returned. */
static final ImmutableMap<String, Object> CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK =
ImmutableMap.<String, Object> of(
"title",
"Contact Personal Data Hidden",
"description",
ImmutableList.of("Contact personal data is visible only to the owning registrar."),
"type",
"object truncated due to unexplainable reasons");
}

View file

@ -444,6 +444,8 @@ public class RdapJsonFormatter {
* port43 field; if null, port43 is not added to the object
* @param now the as-date
* @param outputDataType whether to generate full or summary data
* @param loggedInClientId the logged-in client ID (or null if not logged in); if the requester is
* not logged in as the registrar owning the domain, no contact information is included
*/
ImmutableMap<String, Object> makeRdapJsonForDomain(
DomainResource domainResource,
@ -451,7 +453,8 @@ public class RdapJsonFormatter {
@Nullable String linkBase,
@Nullable String whoisServer,
DateTime now,
OutputDataType outputDataType) {
OutputDataType outputDataType,
Optional<String> loggedInClientId) {
// Start with the domain-level information.
ImmutableMap.Builder<String, Object> jsonBuilder = new ImmutableMap.Builder<>();
jsonBuilder.put("objectClassName", "domain");
@ -464,6 +467,8 @@ public class RdapJsonFormatter {
jsonBuilder.put("status", makeStatusValueList(domainResource.getStatusValues()));
jsonBuilder.put("links", ImmutableList.of(
makeLink("domain", domainResource.getFullyQualifiedDomainName(), linkBase)));
boolean displayContacts = loggedInClientId.isPresent()
&& loggedInClientId.get().equals(domainResource.getCurrentSponsorClientId());
// If we are outputting all data (not just summary data), also add information about hosts,
// contacts and events (history entries). If we are outputting summary data, instead add a
// remark indicating that fact.
@ -471,18 +476,43 @@ public class RdapJsonFormatter {
if (outputDataType == OutputDataType.SUMMARY) {
remarks = ImmutableList.of(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
} else {
remarks = ImmutableList.of();
remarks = displayContacts
? ImmutableList.<ImmutableMap<String, Object>>of()
: ImmutableList.of(RdapIcannStandardInformation.DOMAIN_CONTACTS_HIDDEN_DATA_REMARK);
ImmutableList<Object> events = makeEvents(domainResource, now);
if (!events.isEmpty()) {
jsonBuilder.put("events", events);
}
// Kick off the database loads of the nameservers that we will need.
// Kick off the database loads of the nameservers that we will need, so it can load
// asynchronously while we load and process the contacts.
Map<Key<HostResource>, HostResource> loadedHosts =
ofy().load().keys(domainResource.getNameservers());
// And the registrant and other contacts.
Map<Key<ContactResource>, ContactResource> loadedContacts =
ofy().load().keys(domainResource.getReferencedContacts());
// Nameservers
// Load the registrant and other contacts and add them to the data.
if (displayContacts) {
Map<Key<ContactResource>, ContactResource> loadedContacts =
ofy().load().keys(domainResource.getReferencedContacts());
ImmutableList.Builder<Object> entitiesBuilder = new ImmutableList.Builder<>();
for (DesignatedContact designatedContact :
FluentIterable.from(domainResource.getContacts())
.append(DesignatedContact.create(Type.REGISTRANT, domainResource.getRegistrant()))
.toSortedList(DESIGNATED_CONTACT_ORDERING)) {
ContactResource loadedContact = loadedContacts.get(designatedContact.getContactKey());
entitiesBuilder.add(makeRdapJsonForContact(
loadedContact,
false,
Optional.of(designatedContact.getType()),
linkBase,
null,
now,
outputDataType,
loggedInClientId));
}
ImmutableList<Object> entities = entitiesBuilder.build();
if (!entities.isEmpty()) {
jsonBuilder.put("entities", entities);
}
}
// Add the nameservers to the data; the load was kicked off above for efficiency.
ImmutableList.Builder<Object> nsBuilder = new ImmutableList.Builder<>();
for (HostResource hostResource
: HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts.values())) {
@ -493,25 +523,6 @@ public class RdapJsonFormatter {
if (!ns.isEmpty()) {
jsonBuilder.put("nameservers", ns);
}
// Contacts
ImmutableList.Builder<Object> entitiesBuilder = new ImmutableList.Builder<>();
for (DesignatedContact designatedContact : FluentIterable.from(domainResource.getContacts())
.append(DesignatedContact.create(Type.REGISTRANT, domainResource.getRegistrant()))
.toSortedList(DESIGNATED_CONTACT_ORDERING)) {
ContactResource loadedContact = loadedContacts.get(designatedContact.getContactKey());
entitiesBuilder.add(makeRdapJsonForContact(
loadedContact,
false,
Optional.of(designatedContact.getType()),
linkBase,
null,
now,
outputDataType));
}
ImmutableList<Object> entities = entitiesBuilder.build();
if (!entities.isEmpty()) {
jsonBuilder.put("entities", entities);
}
}
if (whoisServer != null) {
jsonBuilder.put("port43", whoisServer);
@ -633,6 +644,8 @@ public class RdapJsonFormatter {
* port43 field; if null, port43 is not added to the object
* @param now the as-date
* @param outputDataType whether to generate full or summary data
* @param loggedInClientId the logged-in client ID (or null if not logged in); personal contact
* data is only shown if the contact is owned by the logged-in client
*/
ImmutableMap<String, Object> makeRdapJsonForContact(
ContactResource contactResource,
@ -641,8 +654,11 @@ public class RdapJsonFormatter {
@Nullable String linkBase,
@Nullable String whoisServer,
DateTime now,
OutputDataType outputDataType) {
OutputDataType outputDataType,
Optional<String> loggedInClientId) {
ImmutableMap.Builder<String, Object> jsonBuilder = new ImmutableMap.Builder<>();
ImmutableList.Builder<ImmutableMap<String, Object>> remarksBuilder
= new ImmutableList.Builder<>();
jsonBuilder.put("objectClassName", "entity");
jsonBuilder.put("handle", contactResource.getRepoId());
jsonBuilder.put("status", makeStatusValueList(
@ -655,45 +671,48 @@ public class RdapJsonFormatter {
}
jsonBuilder.put("links",
ImmutableList.of(makeLink("entity", contactResource.getRepoId(), linkBase)));
// Create the vCard.
ImmutableList.Builder<Object> vcardBuilder = new ImmutableList.Builder<>();
vcardBuilder.add(VCARD_ENTRY_VERSION);
PostalInfo postalInfo = contactResource.getInternationalizedPostalInfo();
if (postalInfo == null) {
postalInfo = contactResource.getLocalizedPostalInfo();
}
if (postalInfo != null) {
if (postalInfo.getName() != null) {
vcardBuilder.add(ImmutableList.of("fn", ImmutableMap.of(), "text", postalInfo.getName()));
// If we are logged in as the owner of this contact, create the vCard.
if (loggedInClientId.isPresent()
&& loggedInClientId.get().equals(contactResource.getCurrentSponsorClientId())) {
ImmutableList.Builder<Object> vcardBuilder = new ImmutableList.Builder<>();
vcardBuilder.add(VCARD_ENTRY_VERSION);
PostalInfo postalInfo = contactResource.getInternationalizedPostalInfo();
if (postalInfo == null) {
postalInfo = contactResource.getLocalizedPostalInfo();
}
if (postalInfo.getOrg() != null) {
vcardBuilder.add(ImmutableList.of("org", ImmutableMap.of(), "text", postalInfo.getOrg()));
if (postalInfo != null) {
if (postalInfo.getName() != null) {
vcardBuilder.add(ImmutableList.of("fn", ImmutableMap.of(), "text", postalInfo.getName()));
}
if (postalInfo.getOrg() != null) {
vcardBuilder.add(ImmutableList.of("org", ImmutableMap.of(), "text", postalInfo.getOrg()));
}
ImmutableList<Object> addressEntry = makeVCardAddressEntry(postalInfo.getAddress());
if (addressEntry != null) {
vcardBuilder.add(addressEntry);
}
}
ImmutableList<Object> addressEntry = makeVCardAddressEntry(postalInfo.getAddress());
if (addressEntry != null) {
vcardBuilder.add(addressEntry);
ContactPhoneNumber voicePhoneNumber = contactResource.getVoiceNumber();
if (voicePhoneNumber != null) {
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_VOICE, makePhoneString(voicePhoneNumber)));
}
ContactPhoneNumber faxPhoneNumber = contactResource.getFaxNumber();
if (faxPhoneNumber != null) {
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_FAX, makePhoneString(faxPhoneNumber)));
}
String emailAddress = contactResource.getEmailAddress();
if (emailAddress != null) {
vcardBuilder.add(ImmutableList.of("email", ImmutableMap.of(), "text", emailAddress));
}
jsonBuilder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build()));
} else {
remarksBuilder.add(RdapIcannStandardInformation.CONTACT_PERSONAL_DATA_HIDDEN_DATA_REMARK);
}
ContactPhoneNumber voicePhoneNumber = contactResource.getVoiceNumber();
if (voicePhoneNumber != null) {
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_VOICE, makePhoneString(voicePhoneNumber)));
}
ContactPhoneNumber faxPhoneNumber = contactResource.getFaxNumber();
if (faxPhoneNumber != null) {
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_FAX, makePhoneString(faxPhoneNumber)));
}
String emailAddress = contactResource.getEmailAddress();
if (emailAddress != null) {
vcardBuilder.add(ImmutableList.of("email", ImmutableMap.of(), "text", emailAddress));
}
jsonBuilder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build()));
// If we are outputting all data (not just summary data), also add events taken from the history
// entries. If we are outputting summary data, instead add a remark indicating that fact.
List<ImmutableMap<String, Object>> remarks;
if (outputDataType == OutputDataType.SUMMARY) {
remarks = ImmutableList.of(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
remarksBuilder.add(RdapIcannStandardInformation.SUMMARY_DATA_REMARK);
} else {
remarks = ImmutableList.of();
ImmutableList<Object> events = makeEvents(contactResource, now);
if (!events.isEmpty()) {
jsonBuilder.put("events", events);
@ -706,11 +725,14 @@ public class RdapJsonFormatter {
addTopLevelEntries(
jsonBuilder,
BoilerplateType.ENTITY,
remarks,
remarksBuilder.build(),
ImmutableList.<ImmutableMap<String, Object>>of(),
linkBase);
} else if (!remarks.isEmpty()) {
jsonBuilder.put(REMARKS, remarks);
} else {
ImmutableList<ImmutableMap<String, Object>> remarks = remarksBuilder.build();
if (!remarks.isEmpty()) {
jsonBuilder.put(REMARKS, remarks);
}
}
return jsonBuilder.build();
}

View file

@ -32,7 +32,8 @@ public final class RdapUtils {
new Predicate<Registrar>() {
@Override
public boolean apply(Registrar registrar) {
return registrar.getIanaIdentifier() == ianaIdentifier;
Long registrarIanaIdentifier = registrar.getIanaIdentifier();
return (registrarIanaIdentifier != null) && (registrarIanaIdentifier == ianaIdentifier);
}});
}
}