From 8373b6335859b0b5f93526e8dfcdcf653e8f4563 Mon Sep 17 00:00:00 2001 From: dhfelix Date: Mon, 11 Mar 2019 16:17:08 -0600 Subject: [PATCH] Add several changes to comply with ICANN RDAP profile feb19 fix #84 --- .../configuration/RdapConfiguration.java | 53 +++++++++++++++++- .../rdap/server/notices/RequestNotices.java | 12 ++-- .../nic/rdap/server/notices/UserEvents.java | 2 +- .../nic/rdap/server/notices/UserNotices.java | 2 + .../server/privacy/EntityPrivacyFilter.java | 33 ++++++++++- .../nic/rdap/server/result/DomainResult.java | 9 ++- .../rdap/server/result/NameserverResult.java | 5 +- .../rdap/server/servlet/DomainServlet.java | 55 ++++++++++++++++++- .../server/servlet/NameserverServlet.java | 32 ++++++++++- .../nic/rdap/server/servlet/RdapServlet.java | 11 ++++ .../mx/nic/rdap/server/util/PrivacyUtil.java | 33 +++++++++++ .../META-INF/configuration.properties | 8 +++ .../webapp/WEB-INF/configuration.properties | 10 +++- 13 files changed, 245 insertions(+), 20 deletions(-) diff --git a/src/main/java/mx/nic/rdap/server/configuration/RdapConfiguration.java b/src/main/java/mx/nic/rdap/server/configuration/RdapConfiguration.java index 759e9f2..b19c254 100644 --- a/src/main/java/mx/nic/rdap/server/configuration/RdapConfiguration.java +++ b/src/main/java/mx/nic/rdap/server/configuration/RdapConfiguration.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -45,6 +46,8 @@ public class RdapConfiguration { private static final String NOTICES_TIMER_UPDATE_TIME_KEY = "notices_timer_update_time"; private static final String EVENTS_TIMER_UPDATE_TIME_KEY = "events_timer_update_time"; private static final String IS_DB_DATA_LIVE = "is_db_data_live"; + private static final String ADD_CUSTOM_CONFORMANCE_KEY = "add_custom_conformance"; + private static final String ADD_EMAIL_REMARK_KEY = "add_email_remark"; // Settings values @@ -61,6 +64,9 @@ public class RdapConfiguration { private static int noticesUpdateTime; private static int eventsUpdateTime; private static boolean isDbDataLive; + private static List customConformance; + private static boolean addEmailRemark; + private RdapConfiguration() { // no code. @@ -269,6 +275,26 @@ public static void loadRdapConfiguration() throws InitializationException { } } + if (isPropertyNullOrEmpty(ADD_CUSTOM_CONFORMANCE_KEY)) { + customConformance = Collections.emptyList(); + } else { + String customConformanceString = systemProperties.getProperty(ADD_CUSTOM_CONFORMANCE_KEY).trim(); + customConformance = Collections.unmodifiableList(parseConformances(customConformanceString)); + } + + if (isPropertyNullOrEmpty(ADD_EMAIL_REMARK_KEY)) { + invalidProperties.add(ADD_EMAIL_REMARK_KEY); + } else { + String addEmailRemarkString = systemProperties.getProperty(ADD_EMAIL_REMARK_KEY).trim(); + if (addEmailRemarkString.equalsIgnoreCase("true")) { + addEmailRemark = true; + } else if (addEmailRemarkString.equalsIgnoreCase("false")) { + addEmailRemark = false; + } else { + invalidProperties.add(ADD_EMAIL_REMARK_KEY); + } + } + if (!invalidProperties.isEmpty()) { InitializationException invalidValueException = new InitializationException( "The following required properties were not found or are invalid values in configuration file : " @@ -280,9 +306,22 @@ public static void loadRdapConfiguration() throws InitializationException { } isNSSharingNameConformance = false; - - - + } + + private static List parseConformances(String plainString) { + plainString = plainString.trim(); + List result = new ArrayList(); + String[] split = plainString.split(","); + + for (String conformance : split) { + conformance = conformance.trim(); + if (conformance.isEmpty()) + continue; + + result.add(conformance); + } + + return result; } /** @@ -486,4 +525,12 @@ public static int getNoticesUpdateTime() { public static boolean isDbDataLive() { return isDbDataLive; } + + public static List getCustomConformances() { + return customConformance; + } + + public static boolean addEmailRemark() { + return addEmailRemark; + } } diff --git a/src/main/java/mx/nic/rdap/server/notices/RequestNotices.java b/src/main/java/mx/nic/rdap/server/notices/RequestNotices.java index 53b0350..7d4c7f0 100644 --- a/src/main/java/mx/nic/rdap/server/notices/RequestNotices.java +++ b/src/main/java/mx/nic/rdap/server/notices/RequestNotices.java @@ -73,35 +73,37 @@ static void init(String userPath) throws SAXException, IOException, ParserConfig domainNotices = NoticesReader.parseNoticesXML(Paths.get(userPath, DOMAIN_FILE_NAME).toString()); } catch (FileNotFoundException | NoSuchFileException e) { // Nothing happens, continue - logger.log(Level.INFO, "Optional File '" + DOMAIN_FILE_NAME + "' not found, continue.", e); + logger.log(Level.INFO, + "Optional File '" + DOMAIN_FILE_NAME + "' not found, continue. \n\t" + e); } try { entityNotices = NoticesReader.parseNoticesXML(Paths.get(userPath, ENTITY_FILE_NAME).toString()); } catch (FileNotFoundException | NoSuchFileException e) { // Nothing happens, continue - logger.log(Level.INFO, "Optional File '" + ENTITY_FILE_NAME + "' not found, continue.", e); + logger.log(Level.INFO, "Optional File '" + ENTITY_FILE_NAME + "' not found, continue. \n\t" + e); } try { nsNotices = NoticesReader.parseNoticesXML(Paths.get(userPath, NS_FILE_NAME).toString()); } catch (FileNotFoundException | NoSuchFileException e) { // Nothing happens, continue - logger.log(Level.INFO, "Optional File '" + NS_FILE_NAME + "' not found, continue.", e); + logger.log(Level.INFO, "Optional File '" + NS_FILE_NAME + "' not found, continue. \n\t" + e); } try { autnumNotices = NoticesReader.parseNoticesXML(Paths.get(userPath, AUTNUM_FILE_NAME).toString()); } catch (FileNotFoundException | NoSuchFileException e) { // Nothing happens, continue - logger.log(Level.INFO, "Optional File '" + AUTNUM_FILE_NAME + "' not found, continue.", e); + logger.log(Level.INFO, "Optional File '" + AUTNUM_FILE_NAME + "' not found, continue. \n\t" + e); } try { ipNotices = NoticesReader.parseNoticesXML(Paths.get(userPath, IP_NETWORK_FILE_NAME).toString()); } catch (FileNotFoundException | NoSuchFileException e) { // Nothing happens, continue - logger.log(Level.INFO, "Optional File '" + IP_NETWORK_FILE_NAME + "' not found, continue.", e); + logger.log(Level.INFO, + "Optional File '" + IP_NETWORK_FILE_NAME + "' not found, continue. \n\t" + e); } if (RdapConfiguration.getNoticesUpdateTime() > 0) { diff --git a/src/main/java/mx/nic/rdap/server/notices/UserEvents.java b/src/main/java/mx/nic/rdap/server/notices/UserEvents.java index f02b73a..c40c636 100644 --- a/src/main/java/mx/nic/rdap/server/notices/UserEvents.java +++ b/src/main/java/mx/nic/rdap/server/notices/UserEvents.java @@ -45,7 +45,7 @@ static void init(String userPath) throws SAXException, IOException, ParserConfig userEvents = NoticesReader.parseEventsXML(Paths.get(userPath, EVENT_FILE_NAME).toString()); } catch (FileNotFoundException | NoSuchFileException e) { // Nothing happens, continue - logger.log(Level.INFO, "Optional File '" + EVENT_FILE_NAME + "' not found, continue.", e); + logger.log(Level.INFO, "Optional File '" + EVENT_FILE_NAME + "' not found, continue. \n\t" + e); } if (RdapConfiguration.getEventsUpdateTime() > 0) { diff --git a/src/main/java/mx/nic/rdap/server/notices/UserNotices.java b/src/main/java/mx/nic/rdap/server/notices/UserNotices.java index 8e35d92..bb9c722 100644 --- a/src/main/java/mx/nic/rdap/server/notices/UserNotices.java +++ b/src/main/java/mx/nic/rdap/server/notices/UserNotices.java @@ -60,6 +60,7 @@ public static void init(String userPath, boolean isDefaultPath) throws SAXExcept tos = NoticesReader.parseTOSXML(Paths.get(userPath, TOS_FILE_NAME).toString()); } catch (FileNotFoundException | NoSuchFileException e) { // Nothing happens, continue + logger.log(Level.INFO, "Optional File '" + TOS_FILE_NAME + "' not found, continue. \n\t" + e); } // The notices are optional. @@ -67,6 +68,7 @@ public static void init(String userPath, boolean isDefaultPath) throws SAXExcept notices = NoticesReader.parseNoticesXML(Paths.get(userPath, NOTICES_FILE_NAME).toString()); } catch (FileNotFoundException | NoSuchFileException e) { // Nothing happens, continue + logger.log(Level.INFO, "Optional File '" + NOTICES_FILE_NAME + "' not found, continue. \n\t" + e); } RequestNotices.init(userPath); diff --git a/src/main/java/mx/nic/rdap/server/privacy/EntityPrivacyFilter.java b/src/main/java/mx/nic/rdap/server/privacy/EntityPrivacyFilter.java index 2ea0f7f..f518ef6 100644 --- a/src/main/java/mx/nic/rdap/server/privacy/EntityPrivacyFilter.java +++ b/src/main/java/mx/nic/rdap/server/privacy/EntityPrivacyFilter.java @@ -1,5 +1,6 @@ package mx.nic.rdap.server.privacy; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -9,6 +10,8 @@ import mx.nic.rdap.core.catalog.Role; import mx.nic.rdap.core.db.Entity; +import mx.nic.rdap.core.db.Remark; +import mx.nic.rdap.core.db.RemarkDescription; import mx.nic.rdap.core.db.VCard; import mx.nic.rdap.core.db.VCardPostalInfo; import mx.nic.rdap.server.util.PrivacyUtil; @@ -190,12 +193,40 @@ public static boolean filterAnidatedEntities(List entities, UserInfo use } for (Entity e : entities) { - isPrivate |= filterEntity(e, userInfo); + boolean isRedacted = filterEntity(e, userInfo); + addRedactedForPrivacy(isRedacted, e); + isPrivate |= isRedacted; } return isPrivate; } + /* + * Rdap response profile feb-19 2.7.4.3 + * https://www.icann.org/en/system/files/files/rdap-response-profile-15feb19-en. + * pdf + */ + private static void addRedactedForPrivacy(boolean isRedacted, Entity e) { + if (!isRedacted) + return; + + if (e.getRemarks() == null) + e.setRemarks(new ArrayList<>()); + + Remark r = new Remark(); + r.setTitle("REDACTED FOR PRIVACY"); + r.setType("object redacted due to authorization."); + + RemarkDescription rd = new RemarkDescription(); + rd.setDescription("Some of the data in this object has been removed."); + rd.setOrder(1); + rd.setRemarkId(1L); + + r.getDescriptions().add(rd); + + e.getRemarks().add(r); + } + private static boolean filterVcard(VCard vcard, UserInfo userInfo, List entityRoles) { if (entityRoles == null || entityRoles.isEmpty()) { return filterVcard(vcard, userInfo, PrivacyUtil.getVCardPrivacySettings()); diff --git a/src/main/java/mx/nic/rdap/server/result/DomainResult.java b/src/main/java/mx/nic/rdap/server/result/DomainResult.java index dc1e865..8187bdb 100644 --- a/src/main/java/mx/nic/rdap/server/result/DomainResult.java +++ b/src/main/java/mx/nic/rdap/server/result/DomainResult.java @@ -55,11 +55,16 @@ public void validateResponse() { * Generates a link with the self information and add it to the domain */ public static void addSelfLinks(String header, String contextPath, Domain domain) { - Link self = new Link(header, contextPath, "domain", domain.getFQDN()); + String domainName = domain.getFQDN() != null ? domain.getFQDN() : domain.getUnicodeFQDN(); + + Link self = new Link(header, contextPath, "domain", domainName); domain.getLinks().add(self); for (Nameserver ns : domain.getNameServers()) { - self = new Link(header, contextPath, "nameserver", ns.getLdhName()); + String nsName = ns.getFqdnLdhName() != null ? ns.getFqdnLdhName() + : ns.getFqdnUnicodeName(); + + self = new Link(header, contextPath, "nameserver", nsName); ns.getLinks().add(self); } diff --git a/src/main/java/mx/nic/rdap/server/result/NameserverResult.java b/src/main/java/mx/nic/rdap/server/result/NameserverResult.java index 64a1f1e..101d48f 100644 --- a/src/main/java/mx/nic/rdap/server/result/NameserverResult.java +++ b/src/main/java/mx/nic/rdap/server/result/NameserverResult.java @@ -71,7 +71,10 @@ public void validateResponse() { * @param nameserver */ public static void addSelfLinks(String header, String contextPath, Nameserver nameserver) { - Link self = new Link(header, contextPath, "nameserver", nameserver.getLdhName()); + String nsName = nameserver.getFqdnLdhName() != null ? nameserver.getFqdnLdhName() + : nameserver.getFqdnUnicodeName(); + + Link self = new Link(header, contextPath, "nameserver", nsName); nameserver.getLinks().add(self); for (Entity ent : nameserver.getEntities()) { diff --git a/src/main/java/mx/nic/rdap/server/servlet/DomainServlet.java b/src/main/java/mx/nic/rdap/server/servlet/DomainServlet.java index ab9fb26..e590cb0 100644 --- a/src/main/java/mx/nic/rdap/server/servlet/DomainServlet.java +++ b/src/main/java/mx/nic/rdap/server/servlet/DomainServlet.java @@ -17,13 +17,14 @@ import mx.nic.rdap.server.result.RdapResult; import mx.nic.rdap.server.util.Util; -@WebServlet(name = "domain", urlPatterns = { "/domain/*" }) +@WebServlet(name = "domain", urlPatterns = {"/domain/*"}) public class DomainServlet extends DataAccessServlet { private static final long serialVersionUID = 1L; /** - * Constant value to set the maximum params expected in the URI, this servlet expects: domain + * Constant value to set the maximum params expected in the URI, this servlet + * expects: domain */ private static final int MAX_PARAMS_EXPECTED = 1; @@ -46,17 +47,65 @@ protected RdapResult doRdapDaGet(HttpServletRequest httpRequest, DomainDAO dao) try { label = new DomainLabel(request.getFullRequestValue()); } catch (DomainLabelException e) { - throw new BadRequestException(e); + if (e.getMessage() != null) { + throw new BadRequestException("Bad Request: " + e.getMessage(), e); + } else { + throw new BadRequestException(e); + } } Domain domain = dao.getByName(label); if (domain == null) { return null; } + checkResponse(domain, label); + return new DomainResult(Util.getServerUrl(httpRequest), httpRequest.getContextPath(), domain, Util.getUsername(SecurityUtils.getSubject())); } + private static void checkResponse(Domain domain, DomainLabel labelRequested) { + boolean isAlabel = labelRequested.isALabel(); + String zone = domain.getZone(); + String resultToAdd; + + if (isAlabel && (domain.getLdhName() == null || domain.getLdhName().isEmpty())) { + resultToAdd = labelRequested.getALabel().trim(); + if (zone != null && !zone.isEmpty()) { + resultToAdd = processResultToAdd(zone, resultToAdd); + } + + if (resultToAdd.endsWith(".")) + resultToAdd = resultToAdd.substring(0, resultToAdd.length() - 1); + + domain.setLdhName(resultToAdd); + } else if (!isAlabel && (domain.getUnicodeName() == null || domain.getUnicodeName().isEmpty())) { + resultToAdd = labelRequested.getULabel().trim(); + if (zone != null && !zone.isEmpty()) { + resultToAdd = processResultToAdd(zone, resultToAdd); + } + + if (resultToAdd.endsWith(".")) + resultToAdd = resultToAdd.substring(0, resultToAdd.length() - 1); + + domain.setUnicodeName(resultToAdd); + } + } + + private static String processResultToAdd(String zone, String resultToSanitize) { + if (resultToSanitize.endsWith(".")) + resultToSanitize = resultToSanitize.substring(0, resultToSanitize.length() - 1); + + if (zone.endsWith(".")) + zone = zone.substring(0, zone.length() - 1); + + int lastIndexOf = resultToSanitize.lastIndexOf(zone); + if (lastIndexOf > 0) + resultToSanitize = resultToSanitize.substring(0, lastIndexOf); + + return resultToSanitize; + } + private class DomainRequest { private String fullRequestValue; diff --git a/src/main/java/mx/nic/rdap/server/servlet/NameserverServlet.java b/src/main/java/mx/nic/rdap/server/servlet/NameserverServlet.java index fff9813..5eaa845 100644 --- a/src/main/java/mx/nic/rdap/server/servlet/NameserverServlet.java +++ b/src/main/java/mx/nic/rdap/server/servlet/NameserverServlet.java @@ -18,7 +18,7 @@ import mx.nic.rdap.server.result.RdapResult; import mx.nic.rdap.server.util.Util; -@WebServlet(name = "nameserver", urlPatterns = { "/nameserver/*" }) +@WebServlet(name = "nameserver", urlPatterns = {"/nameserver/*"}) public class NameserverServlet extends DataAccessServlet { private static final long serialVersionUID = 1L; @@ -54,7 +54,10 @@ protected RdapResult doRdapDaGet(HttpServletRequest httpRequest, NameserverDAO d try { label = new DomainLabel(request.getName()); } catch (DomainLabelException e) { - throw new BadRequestException(e); + if (e.getMessage() != null) + throw new BadRequestException("Bad Request: " + e.getMessage(), e); + else + throw new BadRequestException(e); } Nameserver nameserver = dao.getByName(label); @@ -68,15 +71,38 @@ protected RdapResult doRdapDaGet(HttpServletRequest httpRequest, NameserverDAO d try { nameserverCount = dao.getNameserverCount(label); } catch (NotImplementedException e) { - // throw the exception, if conformance is true the DAO must implement getNameserverCount function. + // throw the exception, if conformance is true the DAO must implement + // getNameserverCount function. throw e; } } + checkResponse(nameserver, label); + return new NameserverResult(Util.getServerUrl(httpRequest), httpRequest.getContextPath(), nameserver, Util.getUsername(SecurityUtils.getSubject()), nameserverCount); } + private static void checkResponse(Nameserver ns, DomainLabel labelRequested) { + boolean isAlabel = labelRequested.isALabel(); + String resultToAdd; + + if (isAlabel && (ns.getLdhName() == null || ns.getLdhName().isEmpty())) { + resultToAdd = labelRequested.getALabel().trim(); + if (resultToAdd.endsWith(".")) + resultToAdd = resultToAdd.substring(0, resultToAdd.length() - 1); + + ns.setLdhName(resultToAdd); + } else if (!isAlabel && (ns.getUnicodeName() == null || ns.getUnicodeName().isEmpty())) { + resultToAdd = labelRequested.getULabel().trim(); + + if (resultToAdd.endsWith(".")) + resultToAdd = resultToAdd.substring(0, resultToAdd.length() - 1); + + ns.setUnicodeName(resultToAdd); + } + } + private class NameserverRequest { private String name; diff --git a/src/main/java/mx/nic/rdap/server/servlet/RdapServlet.java b/src/main/java/mx/nic/rdap/server/servlet/RdapServlet.java index 548c0dc..57964c2 100644 --- a/src/main/java/mx/nic/rdap/server/servlet/RdapServlet.java +++ b/src/main/java/mx/nic/rdap/server/servlet/RdapServlet.java @@ -105,6 +105,17 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t result.getRdapResponse().getRdapConformance().add("rdap_nameservers_sharing_name"); } + // Add customConformance + if (!RdapConfiguration.getCustomConformances().isEmpty()) { + if (result.getRdapResponse().getRdapConformance() == null) { + result.getRdapResponse().setRdapConformance(new ArrayList<>()); + } + + for (String conformance : RdapConfiguration.getCustomConformances()) { + result.getRdapResponse().getRdapConformance().add(conformance); + } + } + // Add TOS notice if exists addNotices(result.getRdapResponse()); diff --git a/src/main/java/mx/nic/rdap/server/util/PrivacyUtil.java b/src/main/java/mx/nic/rdap/server/util/PrivacyUtil.java index a58a366..4855696 100644 --- a/src/main/java/mx/nic/rdap/server/util/PrivacyUtil.java +++ b/src/main/java/mx/nic/rdap/server/util/PrivacyUtil.java @@ -16,9 +16,11 @@ import mx.nic.rdap.core.catalog.RemarkType; import mx.nic.rdap.core.catalog.Role; import mx.nic.rdap.core.catalog.Status; +import mx.nic.rdap.core.db.Domain; import mx.nic.rdap.core.db.Entity; import mx.nic.rdap.core.db.RdapObject; import mx.nic.rdap.core.db.Remark; +import mx.nic.rdap.core.db.RemarkDescription; import mx.nic.rdap.server.catalog.PrivacyStatus; import mx.nic.rdap.server.configuration.RdapConfiguration; import mx.nic.rdap.server.listener.RdapInitializer; @@ -538,8 +540,39 @@ public static void addPrivacyRemarkAndStatus(RdapObject rdapObject) { rdapObject.setStatus(new ArrayList()); } rdapObject.getStatus().add(Status.PRIVATE); + } else if (rdapObject instanceof Domain) { + if (RdapConfiguration.addEmailRemark()) + addEmailRedactedForPrivacy(rdapObject); } } + + /* Rdap response profile feb-19 2.7.5.3 + * https://www.icann.org/en/system/files/files/rdap-response-profile-15feb19-en.pdf + */ + private static void addEmailRedactedForPrivacy(RdapObject rdapObject) { + if (rdapObject.getRemarks() == null) + rdapObject.setRemarks(new ArrayList<>()); + + Remark r = new Remark(); + r.setTitle("EMAIL REDACTED FOR PRIVACY"); + r.setType("object redacted due to authorization."); + + RemarkDescription rd = new RemarkDescription(); + rd.setDescription("Please query the RDDS service of the Registrar of Record identified in this output"); + rd.setOrder(1); + rd.setRemarkId(1L); + + RemarkDescription rd2 = new RemarkDescription(); + rd2.setDescription("for information on how to contact the Registrant of the queried domain name."); + rd2.setOrder(2); + rd2.setRemarkId(2L); + + r.getDescriptions().add(rd); + r.getDescriptions().add(rd2); + + rdapObject.getRemarks().add(r); + } + /** * @return true if the user is owner of the RdapObject */ diff --git a/src/main/resources/META-INF/configuration.properties b/src/main/resources/META-INF/configuration.properties index e64d7a1..f482681 100644 --- a/src/main/resources/META-INF/configuration.properties +++ b/src/main/resources/META-INF/configuration.properties @@ -49,3 +49,11 @@ events_timer_update_time = 0 #optional. boolean value to indicate the event rdap db last updated, is a live db or a copy db. is_db_data_live = false + +#optional. Rdap Conformances (separated by commas) to be displayed in the result response. Example: lunar_nic_conformance, custom_conformance. +#default: empty +add_custom_conformance = + +#optional. Boolean value, indicates if Email remark 2.7.5.3 (from ICANN RDAP response profile) needs to be added to the domain response +#default: false +add_email_remark = false \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/configuration.properties b/src/main/webapp/WEB-INF/configuration.properties index e149893..bf42b30 100644 --- a/src/main/webapp/WEB-INF/configuration.properties +++ b/src/main/webapp/WEB-INF/configuration.properties @@ -48,4 +48,12 @@ #events_timer_update_time = #optional. boolean value to indicate the event rdap db last updated, is a live db or a copy db. -#is_db_data_live = \ No newline at end of file +#is_db_data_live = + +#optional. Rdap Conformances (separated by commas) to be displayed in the result response. Example: lunar_nic_conformance, custom_conformance. +#default: empty +#add_custom_conformance = + +#optional. Boolean value, indicates if Email remark 2.7.5.3 (from ICANN RDAP response profile) needs to be added to the domain response +#default: false +#add_email_remark = false \ No newline at end of file